View Javadoc
1   /*
2    * Copyright (c) 2011-2024 PrimeFaces Extensions
3    *
4    *  Permission is hereby granted, free of charge, to any person obtaining a copy
5    *  of this software and associated documentation files (the "Software"), to deal
6    *  in the Software without restriction, including without limitation the rights
7    *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    *  copies of the Software, and to permit persons to whom the Software is
9    *  furnished to do so, subject to the following conditions:
10   *
11   *  The above copyright notice and this permission notice shall be included in
12   *  all copies or substantial portions of the Software.
13   *
14   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20   *  THE SOFTWARE.
21   */
22  package org.primefaces.extensions.converter;
23  
24  import java.io.Serializable;
25  import java.lang.reflect.Array;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import javax.el.ValueExpression;
32  import javax.faces.application.FacesMessage;
33  import javax.faces.component.UIComponent;
34  import javax.faces.context.FacesContext;
35  import javax.faces.convert.Converter;
36  import javax.faces.convert.ConverterException;
37  import javax.faces.convert.FacesConverter;
38  
39  import org.primefaces.extensions.util.ExtLangUtils;
40  import org.primefaces.extensions.util.json.GsonConverter;
41  import org.primefaces.extensions.util.json.ParameterizedTypeImpl;
42  import org.primefaces.util.Constants;
43  
44  /**
45   * {@link Converter} which converts a JSON string to an object an vice-versa.
46   *
47   * @author Thomas Andraschko / last modified by $Author$
48   * @version $Revision$
49   * @since 0.2
50   */
51  @FacesConverter(value = "org.primefaces.extensions.converter.JsonConverter")
52  public class JsonConverter implements Converter, Serializable {
53  
54      private static final long serialVersionUID = 20121214L;
55  
56      private static final Map<String, Class<?>> PRIMITIVE_CLASSES = new HashMap<>();
57      private static final Map<String, Class<?>> PRIMITIVE_ARRAY_CLASSES = new HashMap<>();
58  
59      static {
60          PRIMITIVE_CLASSES.put("boolean", boolean.class);
61          PRIMITIVE_CLASSES.put("byte", byte.class);
62          PRIMITIVE_CLASSES.put("short", short.class);
63          PRIMITIVE_CLASSES.put("char", char.class);
64          PRIMITIVE_CLASSES.put("int", int.class);
65          PRIMITIVE_CLASSES.put("long", long.class);
66          PRIMITIVE_CLASSES.put("float", float.class);
67          PRIMITIVE_CLASSES.put("double", double.class);
68  
69          PRIMITIVE_ARRAY_CLASSES.put("boolean[]", boolean[].class);
70          PRIMITIVE_ARRAY_CLASSES.put("byte[]", byte[].class);
71          PRIMITIVE_ARRAY_CLASSES.put("short[]", short[].class);
72          PRIMITIVE_ARRAY_CLASSES.put("char[]", char[].class);
73          PRIMITIVE_ARRAY_CLASSES.put("int[]", int[].class);
74          PRIMITIVE_ARRAY_CLASSES.put("long[]", long[].class);
75          PRIMITIVE_ARRAY_CLASSES.put("float[]", float[].class);
76          PRIMITIVE_ARRAY_CLASSES.put("double[]", double[].class);
77      }
78  
79      private String type;
80  
81      @Override
82      public Object getAsObject(FacesContext context, UIComponent component, String value) {
83          java.lang.reflect.Type objType;
84  
85          if (getType() == null) {
86              final ValueExpression expression = component.getValueExpression("value");
87              objType = expression.getType(context.getELContext());
88          }
89          else {
90              objType = getObjectType(getType().trim(), false);
91          }
92  
93          return GsonConverter.getGson().fromJson(value, objType);
94      }
95  
96      @Override
97      public String getAsString(FacesContext context, UIComponent component, Object value) {
98          if (getType() == null) {
99              return GsonConverter.getGson().toJson(value);
100         }
101         else {
102             return GsonConverter.getGson().toJson(value, getObjectType(getType().trim(), false));
103         }
104     }
105 
106     protected java.lang.reflect.Type getObjectType(String type, boolean isTypeArg) {
107         Class clazz = PRIMITIVE_CLASSES.get(type);
108         if (clazz != null) {
109             if (!isTypeArg) {
110                 return clazz;
111             }
112             else {
113                 throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
114                             "Type argument can not be a primitive type, but it was " + type
115                                         + ".",
116                             Constants.EMPTY_STRING));
117             }
118         }
119 
120         clazz = PRIMITIVE_ARRAY_CLASSES.get(type);
121         if (clazz != null) {
122             return clazz;
123         }
124 
125         final int arrayBracketIdx = type.indexOf('[');
126         final int leftBracketIdx = type.indexOf('<');
127         if (arrayBracketIdx >= 0 && (leftBracketIdx < 0 || arrayBracketIdx < leftBracketIdx)) {
128             // array
129             try {
130                 clazz = Class.forName(type.substring(0, arrayBracketIdx));
131 
132                 return Array.newInstance(clazz, 0).getClass();
133             }
134             catch (final ClassNotFoundException e) {
135                 throw notFoundException(type.substring(0, arrayBracketIdx));
136             }
137         }
138 
139         if (leftBracketIdx < 0) {
140             try {
141                 return Class.forName(type);
142             }
143             catch (final ClassNotFoundException e) {
144                 throw notFoundException(type);
145             }
146         }
147 
148         final int rightBracketIdx = type.lastIndexOf('>');
149         if (rightBracketIdx < 0) {
150             throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, type + " is not a valid generic type.",
151                         Constants.EMPTY_STRING));
152         }
153 
154         Class rawType;
155         try {
156             rawType = Class.forName(type.substring(0, leftBracketIdx));
157         }
158         catch (final ClassNotFoundException e) {
159             throw notFoundException(type.substring(0, leftBracketIdx));
160         }
161 
162         final String strTypeArgs = type.substring(leftBracketIdx + 1, rightBracketIdx);
163         final List<String> listTypeArgs = new ArrayList<>();
164         int startPos = 0;
165         int seekPos = 0;
166 
167         while (true) {
168             final int commaPos = strTypeArgs.indexOf(',', seekPos);
169             if (commaPos >= 0) {
170                 final String term = strTypeArgs.substring(startPos, commaPos);
171                 final int countLeftBrackets = ExtLangUtils.countMatches(term, '<');
172                 final int countRightBrackets = ExtLangUtils.countMatches(term, '>');
173                 if (countLeftBrackets == countRightBrackets) {
174                     listTypeArgs.add(term.trim());
175                     startPos = commaPos + 1;
176                 }
177 
178                 seekPos = commaPos + 1;
179             }
180             else {
181                 listTypeArgs.add(strTypeArgs.substring(startPos).trim());
182 
183                 break;
184             }
185         }
186 
187         if (listTypeArgs.isEmpty()) {
188             throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, type + " is not a valid generic type.",
189                         Constants.EMPTY_STRING));
190         }
191 
192         final int size = listTypeArgs.size();
193         final java.lang.reflect.Type[] objectTypes = new java.lang.reflect.Type[size];
194         for (int i = 0; i < size; i++) {
195             // recursive call for each type argument
196             objectTypes[i] = getObjectType(listTypeArgs.get(i), true);
197         }
198 
199         return new ParameterizedTypeImpl(rawType, objectTypes, null);
200     }
201 
202     public String getType() {
203         return type;
204     }
205 
206     public void setType(String type) {
207         this.type = type;
208     }
209 
210     private ConverterException notFoundException(String classType) {
211         return new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
212                     "Class " + classType + " not found", Constants.EMPTY_STRING));
213     }
214 }