View Javadoc
1   /**
2    * This file Copyright (c) 2016-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.map2bean;
35  
36  import static java.util.stream.Collectors.toList;
37  
38  import info.magnolia.jcr.node2bean.PropertyTypeDescriptor;
39  import info.magnolia.jcr.node2bean.TypeDescriptor;
40  import info.magnolia.transformer.TransformationProblem;
41  
42  import java.util.ArrayDeque;
43  import java.util.ArrayList;
44  import java.util.Deque;
45  import java.util.Iterator;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.Optional;
49  import java.util.StringJoiner;
50  import java.util.function.Function;
51  
52  import org.apache.commons.lang3.StringUtils;
53  
54  /**
55   * Carries contextual information about the state of map-to-bean transformation run. Used by {@link Map2BeanTransformer}.
56   * Aggregates the following info:
57   * <ul>
58   * <li>names of the map nodes currently stacked for transformation and {@link #currentPath()} method which allows to present these keys in a form of the path</li>
59   * <li>stack of corresponding un-transformed values</li>
60   * <li>stack of corresponding target type descriptors (and optionally their generic parameters)</li>
61   * <li>a list of problems which occurred during transformation, accessible via {@link #getProblems()}</li>
62   * </ul>
63   */
64  public class TransformationState {
65  
66      /**
67       * A tuple containing information required for transformation of a single value.
68       */
69      class Entry {
70          String name;
71          Object value;
72          TypeDescriptor typeDescriptor;
73          TypeDescriptor genericTypeDescriptor;
74  
75          Entry(String name, Object value, TypeDescriptor typeDescriptor, TypeDescriptor genericType) {
76              this.name = name;
77              this.value = value;
78              this.typeDescriptor = typeDescriptor;
79              this.genericTypeDescriptor = genericType;
80          }
81      }
82  
83      private final List<TransformationProblem> problems = new ArrayList<>();
84      private final Deque<Entry> entries = new ArrayDeque<>();
85  
86      void pushEntry(String name, Object value, TypeDescriptor typeDescriptor) {
87          this.pushEntry(name, value, typeDescriptor, null);
88      }
89  
90      public void pushEntry(String name, Object value, PropertyTypeDescriptor typeDescriptor) {
91          this.pushEntry(name, value, typeDescriptor.getType(), typeDescriptor.getCollectionEntryType());
92      }
93  
94      void pushEntry(String name, Object value, TypeDescriptor typeDescriptor, TypeDescriptor genericTypeDescriptor) {
95          entries.push(new Entry(name, value, typeDescriptor, genericTypeDescriptor));
96      }
97  
98      public void popCurrentEntry() {
99          entries.pop();
100     }
101 
102     Entry peek() {
103         return entries.peek();
104     }
105 
106     String peekName() {
107         return entries.peek().name;
108     }
109 
110     TypeDescriptor peekTypeDescriptor() {
111         return entries.peek().typeDescriptor;
112     }
113 
114     Object peekValue() {
115         return entries.peek().value;
116     }
117 
118     List<?> peekList() {
119         return getListValue(entries.peek().value);
120     }
121 
122     Map<String, Object> peekMap() {
123         if (entries.peek().value instanceof Map) {
124             return (Map<String, Object>) entries.peek().value;
125         }
126         return null;
127     }
128 
129     List<TransformationProblem> getProblems() {
130         return problems;
131     }
132 
133     String currentPath() {
134         final StringJoiner joiner = new StringJoiner("/");
135         for (final Iterator<Entry> entryIterator = entries.descendingIterator(); entryIterator.hasNext(); ) {
136             Entry next = entryIterator.next();
137             joiner.add(Optional.ofNullable(next.value)
138                     .filter(Map.class::isInstance)
139                     .map(Map.class::cast)
140                     .map(map -> map.get("name"))
141                     .map(Object::toString)
142                     .orElse(next.name)
143             );
144         }
145         return StringUtils.defaultIfBlank(String.join("/", joiner.toString()), "/");
146     }
147 
148     public void trackProblem(TransformationProblem.Builder problemBuilder) {
149         problems.add(problemBuilder.withLocation(currentPath()).build());
150     }
151 
152     private static List<Object> getListValue(Object value) {
153         if (value instanceof List) {
154             return (List<Object>) value;
155         }
156 
157         if (value instanceof Map) {
158             final Map<String, Object> map = (Map) value;
159             final Function<Map.Entry<String, Object>, Object> entry2Value = new Function<Map.Entry<String, Object>, Object>() {
160                 @Override
161                 public Object apply(Map.Entry<String, Object> input) {
162                     final Object returnValue = input.getValue();
163                     if (returnValue instanceof Map) {
164                         final Map mapValue = (Map) returnValue;
165                         if (!mapValue.containsKey("name")) {
166                             mapValue.put("name", input.getKey());
167                         }
168                     }
169                     return returnValue;
170                 }
171             };
172             return map.entrySet().stream().map(entry2Value).collect(toList());
173         }
174 
175         return null;
176     }
177 }