View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.util;
21  
22  import org.apache.myfaces.tobago.component.ClientBehaviors;
23  import org.apache.myfaces.tobago.context.Markup;
24  import org.apache.myfaces.tobago.internal.context.DateTimeI18n;
25  import org.apache.myfaces.tobago.internal.renderkit.Collapse;
26  import org.apache.myfaces.tobago.internal.renderkit.Command;
27  import org.apache.myfaces.tobago.internal.renderkit.CommandMap;
28  import org.apache.myfaces.tobago.layout.Measure;
29  import org.apache.myfaces.tobago.layout.MeasureList;
30  import org.slf4j.Logger;
31  import org.slf4j.LoggerFactory;
32  
33  import java.lang.invoke.MethodHandles;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.StringTokenizer;
39  
40  public class JsonUtils {
41  
42    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
43  
44    private JsonUtils() {
45    }
46  
47    private static void encode(final StringBuilder builder, final String name, final String[] value) {
48      builder.append("\"");
49      builder.append(name);
50      builder.append("\":");
51      encode(builder, value);
52      builder.append(",");
53    }
54  
55    private static void encode(final StringBuilder builder, final String[] value) {
56      builder.append("[");
57      boolean colon = false;
58      for (final String item : value) {
59        if (colon) {
60          builder.append(",");
61        }
62        builder.append("\"");
63        for (int i = 0; i < item.length(); i++) {
64          final char c = item.charAt(i);
65          if (c == '\"') {
66            builder.append("\\\"");
67          } else {
68            builder.append(c);
69          }
70        }
71        builder.append("\"");
72        colon = true;
73      }
74      builder.append("]");
75    }
76  
77    private static void encode(final StringBuilder builder, final Boolean[] value) {
78      builder.append("[");
79      boolean colon = false;
80      for (final boolean item : value) {
81        if (colon) {
82          builder.append(",");
83        }
84        builder.append(item);
85        colon = true;
86      }
87      builder.append("]");
88    }
89  
90    private static void encode(final StringBuilder builder, final List<Integer> value) {
91      builder.append("[");
92      boolean colon = false;
93      for (final Integer item : value) {
94        if (colon) {
95          builder.append(",");
96        }
97        builder.append(item);
98        colon = true;
99      }
100     builder.append("]");
101   }
102 
103   private static void encode(final StringBuilder builder, final String name, final Boolean value) {
104     builder.append("\"");
105     builder.append(name);
106     builder.append("\":");
107     builder.append(value);
108     builder.append(",");
109   }
110 
111   private static void encode(final StringBuilder builder, final String name, final Integer value) {
112     builder.append("\"");
113     builder.append(name);
114     builder.append("\":");
115     builder.append(value);
116     builder.append(",");
117   }
118 
119   private static void encode(final StringBuilder builder, final String name, final String value) {
120     builder.append("\"");
121     builder.append(name);
122     builder.append("\":\"");
123     int length = value.length();
124     for (int i = 0; i < length; i++) {
125       final char c = value.charAt(i);
126       if (c == '\"') {
127         builder.append("\\\"");
128       } else {
129         builder.append(c);
130       }
131     }
132     builder.append("\",");
133   }
134 
135   public static String encode(final CommandMap commandMap) {
136 
137     if (commandMap == null) {
138       return null;
139     }
140 
141     final StringBuilder builder = new StringBuilder();
142     builder.append("{");
143     final int initialLength = builder.length();
144 
145     final Command click = commandMap.getClick();
146     if (click != null) {
147       encode(builder, ClientBehaviors.click, click);
148     }
149 
150     final Map<ClientBehaviors, Command> other = commandMap.getOther();
151     if (other != null) {
152       for (final Map.Entry<ClientBehaviors, Command> entry : other.entrySet()) {
153         encode(builder, entry.getKey(), entry.getValue());
154       }
155     }
156 
157     if (builder.length() - initialLength > 0) {
158       assert builder.charAt(builder.length() - 1) == ',';
159       builder.deleteCharAt(builder.length() - 1);
160     }
161 
162     builder.append("}");
163     return builder.toString();
164   }
165 
166   private static void encode(final StringBuilder builder, final ClientBehaviors name, final Command command) {
167     builder.append("\"");
168     builder.append(name);
169     builder.append("\":{");
170     final int initialLength = builder.length();
171 
172     final String action = command.getAction();
173     if (action != null) {
174       encode(builder, "action", action);
175     }
176     final Boolean transition = command.getTransition();
177     if (transition != null && !transition) { // true is the default, so encoding is needed.
178       encode(builder, "transition", transition);
179     }
180     final String target = command.getTarget();
181     if (target != null) {
182       encode(builder, "target", target);
183     }
184     final String execute = command.getExecute();
185     if (execute != null) {
186       encode(builder, "execute", execute);
187     }
188     final String render = command.getRender();
189     if (render != null) {
190       encode(builder, "render", render);
191     }
192     final Collapse collapse = command.getCollapse();
193     if (collapse != null) {
194       encode(builder, "collapse", collapse);
195     }
196     final String focus = command.getFocus();
197     if (focus != null) {
198       encode(builder, "focus", focus);
199     }
200     final String confirmation = command.getConfirmation();
201     if (confirmation != null) {
202       encode(builder, "confirmation", confirmation);
203     }
204     final Integer delay = command.getDelay();
205     if (delay != null) {
206       encode(builder, "delay", delay);
207     }
208     final Boolean omit = command.getOmit();
209     if (omit != null && omit) { // false is the default, so encoding is needed.
210       encode(builder, "omit", true);
211     }
212 
213     if (builder.length() - initialLength > 0) {
214       assert builder.charAt(builder.length() - 1) == ',';
215       builder.deleteCharAt(builder.length() - 1);
216     }
217 
218     builder.append("},");
219   }
220 
221   private static void encode(final StringBuilder builder, final String name, final Collapse collapse) {
222     builder.append("\"");
223     builder.append(name);
224     builder.append("\":{");
225     final int initialLength = builder.length();
226 
227     final Collapse.Action action = collapse.getAction();
228     if (action != null) {
229       encode(builder, "transition", action.name());
230     }
231     final String forId = collapse.getFor();
232     if (forId != null) {
233       encode(builder, "forId", forId);
234     }
235     if (builder.length() - initialLength > 0) {
236       assert builder.charAt(builder.length() - 1) == ',';
237       builder.deleteCharAt(builder.length() - 1);
238     }
239 
240     builder.append("},");
241   }
242 
243   public static String encode(final DateTimeI18n dateTimeI18n) {
244     final StringBuilder builder = new StringBuilder();
245     builder.append("{");
246     final int initialLength = builder.length();
247 
248     encode(builder, "monthNames", dateTimeI18n.getMonthNames());
249     encode(builder, "monthNamesShort", dateTimeI18n.getMonthNamesShort());
250     encode(builder, "dayNames", dateTimeI18n.getDayNames());
251     encode(builder, "dayNamesShort", dateTimeI18n.getDayNamesShort());
252     encode(builder, "dayNamesMin", dateTimeI18n.getDayNamesMin());
253     encode(builder, "firstDay", dateTimeI18n.getFirstDay());
254     encode(builder, "minDays", dateTimeI18n.getMinDays());
255     encode(builder, "today", dateTimeI18n.getToday());
256     encode(builder, "cancel", dateTimeI18n.getCancel());
257     encode(builder, "clear", dateTimeI18n.getClear());
258     encode(builder, "week", dateTimeI18n.getWeek());
259 
260     if (builder.length() - initialLength > 0) {
261       assert builder.charAt(builder.length() - 1) == ',';
262       builder.deleteCharAt(builder.length() - 1);
263     }
264     builder.append("}");
265     return builder.toString();
266   }
267 
268   public static String encode(final String[] strings) {
269     if (strings == null) {
270       return null;
271     }
272     final StringBuilder builder = new StringBuilder();
273     encode(builder, strings);
274     return builder.toString();
275   }
276 
277   public static String encode(final Integer[] integers) {
278     if (integers == null) {
279       return null;
280     }
281     final StringBuilder builder = new StringBuilder();
282     encode(builder, Arrays.asList(integers));
283     return builder.toString();
284   }
285 
286   public static String encode(final Boolean[] booleans) {
287     if (booleans == null) {
288       return null;
289     }
290     final StringBuilder builder = new StringBuilder();
291     encode(builder, booleans);
292     return builder.toString();
293   }
294 
295   public static String encode(final List<Integer> integers) {
296     if (integers == null) {
297       return null;
298     }
299     final StringBuilder builder = new StringBuilder();
300     encode(builder, integers);
301     return builder.toString();
302   }
303 
304   private static void encode(final MeasureList layout, final StringBuilder builder) {
305     builder.append("[");
306     for (final Measure measure : layout) {
307       final Measure.Unit unit = measure.getUnit();
308       if (unit == Measure.Unit.FR) {
309         final float factor = measure.getValue(); // todo: might be better with "fr" suffix, but needs a JS change
310         builder.append(factor);
311       } else if (unit == Measure.Unit.AUTO) {
312         builder.append("\"auto\"");
313       } else {
314         builder.append("{\"measure\":\"");
315         builder.append(measure.serialize());
316         builder.append("\"}");
317       }
318       builder.append(',');
319     }
320     if (builder.charAt(builder.length() - 1) == ',') {
321       builder.deleteCharAt(builder.length() - 1);
322     }
323     builder.append("]");
324   }
325 
326   public static List<Integer> decodeIntegerArray(final String json) {
327     if (json == null) {
328       return null;
329     }
330     String string = json.trim();
331     final List<Integer> result = new ArrayList<>();
332     if (string.length() < 2 || string.charAt(0) != '[' || string.charAt(string.length() - 1) != ']') {
333       LOG.warn("Can't parse JSON array: no surrounding square brackets []: '{}'", string);
334     } else {
335       string = string.substring(1, string.length() - 1);
336       final StringTokenizer tokenizer = new StringTokenizer(string, ",");
337       while (tokenizer.hasMoreTokens()) {
338         final String token = tokenizer.nextToken().trim();
339         try {
340           result.add(Integer.parseInt(token));
341         } catch (final NumberFormatException e) {
342           LOG.warn("Can't parse JSON array: not an integer token: '{}'", token);
343           // ignoring so far
344         }
345       }
346     }
347     return result;
348   }
349 
350   public static String encode(final Markup markups) {
351     if (markups == null || markups.isEmpty()) {
352       return null;
353     }
354     final StringBuilder builder = new StringBuilder(20);
355     builder.append('[');
356     for (final String markup : markups) {
357       builder.append('"');
358       builder.append(markup);
359       builder.append('"');
360       builder.append(',');
361     }
362     if (builder.length() > 1) {
363       builder.deleteCharAt(builder.length() - 1);
364     }
365     builder.append(']');
366     return builder.toString();
367   }
368 
369   public static String encode(final MeasureList measures, final String name) {
370     final StringBuilder builder = new StringBuilder();
371     builder.append("{\"");
372     builder.append(name);
373     builder.append("\":");
374     JsonUtils.encode(measures, builder);
375     builder.append("}");
376     return builder.toString();
377   }
378 
379   public static String encodeEmptyArray() {
380     return "[]";
381   }
382 }