View Javadoc
1   /**
2    * This file Copyright (c) 2014-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.ui.admincentral.shellapp.pulse.item;
35  
36  import java.lang.reflect.InvocationTargetException;
37  import java.lang.reflect.Method;
38  import java.util.Map;
39  
40  import com.vaadin.v7.data.Property;
41  import com.vaadin.v7.data.util.AbstractProperty;
42  
43  /**
44   * Nested Property for a map on a bean. Allows accessing map values in a dotted notation,
45   * e.g. "address.street".
46   *
47   * Multiple level of nesting is not supported.
48   *
49   * @param <T> Type of the nested Property. Resolved in {@link #initialize(Class, String)}.
50   */
51  public class NestedMapProperty<T> extends AbstractProperty<T> {
52  
53      private final Object instance;
54      private String propertyName;
55      private Class<? extends T> type;
56      private Map map;
57  
58      public NestedMapProperty(Object instance, String propertyName) {
59          this.instance = instance;
60          this.propertyName = propertyName;
61  
62          initialize(instance.getClass(), propertyName);
63      }
64  
65      /**
66       * Gets the value stored in the Property. The value is read from the map .
67       *
68       * @return the value of the Property
69       */
70      @Override
71      public T getValue() {
72          return (T) map.get(propertyName);
73      }
74  
75      /**
76       * Sets the value of the property. The new value must be assignable to the
77       * type of this property.
78       *
79       * @param newValue
80       *            the New value of the property.
81       * @throws <code>Property.ReadOnlyException</code> if the object is in
82       *         read-only mode.
83       */
84      @Override
85      public void setValue(T newValue) throws ReadOnlyException {
86          // Checks the mode
87          if (isReadOnly()) {
88              throw new Property.ReadOnlyException();
89          }
90          map.put(propertyName, newValue);
91          fireValueChange();
92      }
93  
94      @Override
95      public Class<? extends T> getType() {
96          return type;
97      }
98  
99      private void initialize(Class<?> beanClass, String propertyName)
100             throws IllegalArgumentException {
101 
102         Class<?> propertyClass = beanClass;
103         String[] simplePropertyNames = propertyName.split("\\.");
104 
105         if (propertyName.endsWith(".") || simplePropertyNames.length != 2) {
106             throw new IllegalArgumentException("Invalid property name '"
107                     + propertyName + "'");
108         }
109 
110         String simplePropertyName = simplePropertyNames[0].trim();
111         this.propertyName = simplePropertyNames[1].trim();
112 
113         try {
114             Method getMethod = initGetterMethod(
115                     simplePropertyName, propertyClass);
116             propertyClass = getMethod.getReturnType();
117 
118             if (propertyClass.isAssignableFrom(Map.class)) {
119                 this.map = ((Map) getMethod.invoke(instance));
120 
121                 Object object = map.get(this.propertyName);
122                 if (object == null) {
123                     throw new IllegalArgumentException("Value '"
124                             + propertyName + "' is null.");
125                 }
126                 this.type = (Class<? extends T>) convertPrimitiveType(object.getClass());
127             }
128             else {
129                 throw new IllegalArgumentException("Field '"
130                         + simplePropertyName + "' is not a Map.");
131             }
132 
133         } catch (NoSuchMethodException e) {
134             throw new IllegalArgumentException("No getter defined for '"
135                     + simplePropertyName + "'.");
136         } catch (InvocationTargetException e) {
137             throw new IllegalArgumentException("Could not invoke getter defined for '"
138                     + simplePropertyName + "'.");
139         } catch (IllegalAccessException e) {
140             throw new IllegalArgumentException("Could not access getter defined for '"
141                     + simplePropertyName + "'.");
142         }
143 
144 
145     }
146 
147     /**
148      * Find a getter method for a property (getXyz(), isXyz() or areXyz()).
149      *
150      * @param propertyName
151      *            name of the property
152      * @param beanClass
153      *            class in which to look for the getter methods
154      * @return Method
155      * @throws NoSuchMethodException
156      *             if no getter found
157      */
158     static Method initGetterMethod(String propertyName, final Class<?> beanClass)
159             throws NoSuchMethodException {
160         propertyName = propertyName.substring(0, 1).toUpperCase()
161                 + propertyName.substring(1);
162 
163         Method getMethod = null;
164         try {
165             getMethod = beanClass.getMethod("get" + propertyName,
166                     new Class[] {});
167         } catch (final java.lang.NoSuchMethodException ignored) {
168             try {
169                 getMethod = beanClass.getMethod("is" + propertyName,
170                         new Class[] {});
171             } catch (final java.lang.NoSuchMethodException ignoredAsWell) {
172                 getMethod = beanClass.getMethod("are" + propertyName,
173                         new Class[] {});
174             }
175         }
176         return getMethod;
177     }
178 
179     static Class<?> convertPrimitiveType(Class<?> type) {
180         // Gets the return type from get method
181         if (type.isPrimitive()) {
182             if (type.equals(Boolean.TYPE)) {
183                 type = Boolean.class;
184             } else if (type.equals(Integer.TYPE)) {
185                 type = Integer.class;
186             } else if (type.equals(Float.TYPE)) {
187                 type = Float.class;
188             } else if (type.equals(Double.TYPE)) {
189                 type = Double.class;
190             } else if (type.equals(Byte.TYPE)) {
191                 type = Byte.class;
192             } else if (type.equals(Character.TYPE)) {
193                 type = Character.class;
194             } else if (type.equals(Short.TYPE)) {
195                 type = Short.class;
196             } else if (type.equals(Long.TYPE)) {
197                 type = Long.class;
198             }
199         }
200         return type;
201     }
202 
203 
204 }