View Javadoc
1   package net.sumaris.core.util;
2   
3   /*-
4    * #%L
5    * SUMARiS :: Sumaris Core Shared
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2018 SUMARiS Consortium
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU General Public License as
13   * published by the Free Software Foundation, either version 3 of the
14   * License, or (at your option) any later version.
15   * 
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   * 
21   * You should have received a copy of the GNU General Public
22   * License along with this program.  If not, see
23   * <http://www.gnu.org/licenses/gpl-3.0.html>.
24   * #L%
25   */
26  
27  import com.google.common.base.Preconditions;
28  import com.google.common.collect.*;
29  import net.sumaris.core.dao.technical.SortDirection;
30  import net.sumaris.core.dao.technical.model.IEntity;
31  import net.sumaris.core.exception.SumarisTechnicalException;
32  import org.apache.commons.beanutils.PropertyUtils;
33  import org.apache.commons.collections4.CollectionUtils;
34  import org.apache.commons.collections4.ComparatorUtils;
35  import org.apache.commons.collections4.MapUtils;
36  import org.apache.commons.lang3.ArrayUtils;
37  import org.apache.commons.lang3.StringUtils;
38  import org.springframework.beans.BeanUtils;
39  
40  import java.beans.PropertyDescriptor;
41  import java.io.Serializable;
42  import java.lang.reflect.InvocationTargetException;
43  import java.util.*;
44  import java.util.function.Function;
45  import java.util.function.Predicate;
46  import java.util.stream.Collectors;
47  import java.util.stream.Stream;
48  
49  /**
50   * helper class for beans (split by property, make sure list exists, ...)
51   * Created by blavenie on 13/10/15.
52   */
53  public class Beans {
54  
55      protected Beans() {
56          // helper class does not instantiate
57      }
58  
59      /**
60       * <p>getList.</p>
61       *
62       * @param list a {@link Collection} object.
63       * @param <E> a E object.
64       * @return a {@link List} object.
65       */
66      public static <E> List<E> getList(Collection<E> list) {
67          if (CollectionUtils.isEmpty(list)) {
68              return Lists.newArrayList();
69          } else if (list instanceof List<?>){
70              return (List<E>) list;
71          } else {
72              return Lists.newArrayList(list);
73          }
74      }
75  
76      /**
77       * <p>getList.</p>
78       *
79       * @param list a {@link Collection} object.
80       * @param <E> a E object.
81       * @return a {@link List} object.
82       */
83      public static <E> Stream<E> getStream(Collection<E> list) {
84          if (list == null) {
85              return Stream.empty();
86          }
87          return list.stream();
88      }
89  
90      /**
91       * <p>getListWithoutNull.</p>
92       *
93       * @param list a {@link Collection} object.
94       * @param <E> a E object.
95       * @return a {@link List} object.
96       */
97      public static <E> List<E> getListWithoutNull(Collection<E> list) {
98          List<E> result = getList(list);
99          result.removeAll(Collections.singleton((E) null));
100         return result;
101     }
102 
103     /**
104      * <p>getSet.</p>
105      *
106      * @param list a {@link Collection} object.
107      * @param <E> a E object.
108      * @return a {@link Set} object.
109      */
110     public static <E> Set<E> getSet(Collection<E> list) {
111         if (CollectionUtils.isEmpty(list)) {
112             return Sets.newHashSet();
113         } else {
114             return Sets.newHashSet(list);
115         }
116     }
117 
118     /**
119      * <p>getSetWithoutNull.</p>
120      *
121      * @param list a {@link Collection} object.
122      * @param <E> a E object.
123      * @return a {@link Set} object.
124      */
125     public static <E> Set<E> getSetWithoutNull(Collection<E> list) {
126         Set<E> result = getSet(list);
127         result.removeAll(Collections.singleton((E) null));
128         return result;
129     }
130 
131     /**
132      * <p>getMap.</p>
133      *
134      * @param map a {@link Map} object.
135      * @param <K> a K object.
136      * @param <V> a V object.
137      * @return a {@link Map} object.
138      */
139     public static <K, V> Map<K, V> getMap(Map<K, V> map) {
140         if (MapUtils.isEmpty(map)) {
141             return Maps.newHashMap();
142         } else {
143             return Maps.newHashMap(map);
144         }
145     }
146 
147     /**
148      * <p>splitByProperty.</p>
149      *
150      * @param list a {@link Iterable} object.
151      * @param propertyName a {@link String} object.
152      * @param <K> a K object.
153      * @param <V> a V object.
154      * @return a {@link Map} object.
155      */
156     public static <K, V> Map<K, V> splitByProperty(Iterable<V> list, String propertyName) {
157         Preconditions.checkArgument(StringUtils.isNotBlank(propertyName));
158         return getMap(Maps.uniqueIndex(list, input -> getProperty(input, propertyName)));
159     }
160 
161     /**
162      * <p>splitByProperty.</p>
163      *
164      * @param list a {@link Iterable} object.
165      * @param propertyName a {@link String} object.
166      * @param <K> a K object.
167      * @param <V> a V object.
168      * @return a {@link Map} object.
169      */
170     public static <K, V> Multimap<K, V> splitByNotUniqueProperty(Iterable<V> list, String propertyName) {
171         Preconditions.checkArgument(StringUtils.isNotBlank(propertyName));
172         return Multimaps.index(list, input -> getProperty(input, propertyName));
173     }
174 
175     /**
176      * <p>splitByProperty.</p>
177      *
178      * @param list a {@link Iterable} object.
179      * @param <V> a V object.
180      * @return a {@link Map} object.
181      */
182     public static <V> Multimap<Integer, V> splitByNotUniqueHashcode(Iterable<V> list) {
183         return Multimaps.index(list, Object::hashCode);
184     }
185 
186     /**
187      * <p>splitByProperty.</p>
188      *
189      * @param list a {@link Iterable} object.
190      * @param <K> a K object.
191      * @param <V> a V object.
192      * @return a {@link Map} object.
193      */
194     public static <K extends Serializable, V extends IEntity<K>> Map<K, V> splitById(Iterable<V> list) {
195         return getMap(Maps.uniqueIndex(list, IEntity::getId));
196     }
197 
198     /**
199      * <p>splitByProperty.</p>
200      *
201      * @param list a {@link Iterable} object.
202      * @param <K> a K object.
203      * @param <V> a V object.
204      * @return a {@link Map} object.
205      */
206     public static <K extends Serializable, V extends IEntity<K>> List<K> collectIds(Collection<V> list) {
207         return transformCollection(list, IEntity::getId);
208     }
209 
210     /**
211      * <p>collectProperties.</p>
212      *
213      * @param collection a {@link Collection} object.
214      * @param propertyName a {@link String} object.
215      * @param <K> a K object.
216      * @param <V> a V object.
217      * @return a {@link List} object.
218      */
219     public static <K, V> List<K> collectProperties(Collection<V> collection, String propertyName) {
220         if (CollectionUtils.isEmpty(collection)) return new ArrayList<>();
221         Preconditions.checkArgument(StringUtils.isNotBlank(propertyName));
222         return collection.stream().map((Function<V, K>) v -> getProperty(v, propertyName)).collect(Collectors.toList());
223 
224     }
225 
226     private static <K, V> Function<V, K> newPropertyFunction(final String propertyName) {
227         return input -> getProperty(input, propertyName);
228     }
229 
230     /**
231      * <p>getProperty.</p>
232      *
233      * @param object       a K object.
234      * @param propertyName a {@link String} object.
235      * @param <K>          a K object.
236      * @param <V>          a V object.
237      * @return a V object.
238      */
239     @SuppressWarnings("unchecked")
240     public static <K, V> V getProperty(K object, String propertyName) {
241         try {
242             return (V) PropertyUtils.getProperty(object, propertyName);
243         } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
244             throw new SumarisTechnicalException( String.format("Could not get property %1s on object of type %2s", propertyName, object.getClass().getName()), e);
245         }
246     }
247 
248     /**
249      * <p>setProperty.</p>
250      *
251      * @param object       a K object.
252      * @param propertyName a {@link String} object.
253      * @param value        a V object.
254      * @param <K>          a K object.
255      * @param <V>          a V object.
256      */
257     public static <K, V> void setProperty(K object, String propertyName, V value) {
258         try {
259             PropertyUtils.setProperty(object, propertyName, value);
260         } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
261             throw new SumarisTechnicalException( String.format("Could not set property %1s not found on object of type %2s", propertyName, object.getClass().getName()), e);
262         }
263     }
264 
265     public static Integer[] asIntegerArray(Collection<Integer> values) {
266         if (CollectionUtils.isEmpty(values)) {
267             return null;
268         }
269         return values.toArray(new Integer[0]);
270     }
271 
272     public static String[] asStringArray(Collection<String> values) {
273         if (CollectionUtils.isEmpty(values)) {
274             return null;
275         }
276         return values.toArray(new String[0]);
277     }
278 
279     public static String[] asStringArray(String value, String delimiter) {
280         if (StringUtils.isBlank(value)) return new String[0];
281         StringTokenizer tokenizer = new StringTokenizer(value, delimiter);
282         String[] values = new String[tokenizer.countTokens()];
283         int i=0;
284         while (tokenizer.hasMoreTokens()) {
285             values[i] = tokenizer.nextToken();
286             i++;
287         }
288         return values;
289     }
290 
291     public static <E> List<E> filterCollection(Collection<E> collection, Predicate<E> predicate) {
292         return collection.stream().filter(predicate).collect(Collectors.toList());
293     }
294 
295     public static <O, E> List<O> transformCollection(Collection<? extends E> collection, Function<E, O> funtion) {
296         return collection.stream().map(funtion).collect(Collectors.toList());
297     }
298 
299     public static <T> Comparator<T> naturalComparator(final String sortAttribute, final SortDirection sortDirection) {
300         if (sortAttribute == null) {
301             return naturalComparator("id", sortDirection);
302         }
303 
304         final Comparator<String> propertyComparator = ComparatorUtils.naturalComparator();
305 
306         if (SortDirection.ASC.equals(sortDirection)) {
307             return (o1, o2) -> propertyComparator.compare(
308                         getProperty(o1, sortAttribute),
309                         getProperty(o2, sortAttribute)
310                 );
311         }
312         else {
313             return (o1, o2) -> propertyComparator.compare(
314                     getProperty(o2, sortAttribute),
315                     getProperty(o1, sortAttribute)
316             );
317         }
318     }
319 
320     //public static Map<String, String[]> cacheCopyPropertiesIgnored;
321     public static Map<Class<?>, Map<Class<?>, String[]>> cacheCopyPropertiesIgnored = Maps.newConcurrentMap();
322 
323     /**
324      * Usefull method that ignore complex type, as list
325      * @param source
326      * @param target
327      */
328     public static <S, T> void copyProperties(S source, T target) {
329         copyProperties(source, target, (String) null);
330     }
331 
332     /**
333      * Usefull method that ignore complex type, as list
334      * @param source
335      * @param target
336      */
337     public static <S, T> void copyProperties(S source, T target, String... exceptProperties) {
338 
339         Map<Class<?>, String[]> cache = cacheCopyPropertiesIgnored.computeIfAbsent(source.getClass(), k -> Maps.newConcurrentMap());
340         String[] ignoredProperties = cache.get(target.getClass());
341 
342         // Fill the cache
343         if (ignoredProperties == null) {
344 
345             PropertyDescriptor[] targetDescriptors = BeanUtils.getPropertyDescriptors(target.getClass());
346             Map<String, PropertyDescriptor> targetProperties = Maps.uniqueIndex(ImmutableList.copyOf(targetDescriptors), PropertyDescriptor::getName);
347 
348             ignoredProperties = Stream.of(BeanUtils.getPropertyDescriptors(source.getClass()))
349                 // Keep invalid properties
350                 .filter(pd -> {
351                     PropertyDescriptor targetDescriptor = targetProperties.get(pd.getName());
352                     return targetDescriptor == null
353                         || !targetDescriptor.getPropertyType().isAssignableFrom(pd.getPropertyType())
354                         || Collection.class.isAssignableFrom(pd.getPropertyType())
355                         || targetDescriptor.getWriteMethod() == null;
356                 })
357                 .map(PropertyDescriptor::getName)
358                 .toArray(String[]::new);
359 
360             // Add to cache
361             cache.put(target.getClass(), ignoredProperties);
362         }
363 
364         BeanUtils.copyProperties(source, target, ArrayUtils.addAll(ignoredProperties, exceptProperties));
365     }
366 }