You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DesignFormatter.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui.declarative;
  17. import java.io.Serializable;
  18. import java.math.BigDecimal;
  19. import java.text.DecimalFormat;
  20. import java.text.DecimalFormatSymbols;
  21. import java.text.NumberFormat;
  22. import java.util.Collections;
  23. import java.util.Date;
  24. import java.util.Locale;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.TimeZone;
  28. import java.util.concurrent.ConcurrentHashMap;
  29. import org.jsoup.parser.Parser;
  30. import com.vaadin.data.Result;
  31. import com.vaadin.data.util.converter.Converter;
  32. import com.vaadin.data.util.converter.StringToBigDecimalConverter;
  33. import com.vaadin.data.util.converter.StringToDoubleConverter;
  34. import com.vaadin.data.util.converter.StringToFloatConverter;
  35. import com.vaadin.event.ShortcutAction;
  36. import com.vaadin.server.Resource;
  37. import com.vaadin.ui.declarative.converters.DesignDateConverter;
  38. import com.vaadin.ui.declarative.converters.DesignEnumConverter;
  39. import com.vaadin.ui.declarative.converters.DesignObjectConverter;
  40. import com.vaadin.ui.declarative.converters.DesignResourceConverter;
  41. import com.vaadin.ui.declarative.converters.DesignShortcutActionConverter;
  42. import com.vaadin.ui.declarative.converters.DesignTimeZoneConverter;
  43. import com.vaadin.ui.declarative.converters.DesignToStringConverter;
  44. /**
  45. * Class focused on flexible and consistent formatting and parsing of different
  46. * values throughout reading and writing {@link Design}. An instance of this
  47. * class is used by {@link DesignAttributeHandler}.
  48. *
  49. * @since 7.4
  50. * @author Vaadin Ltd
  51. */
  52. public class DesignFormatter implements Serializable {
  53. private final Map<Class<?>, Converter<String, ?>> converterMap = new ConcurrentHashMap<Class<?>, Converter<String, ?>>();
  54. private final Converter<String, Object> stringObjectConverter = new DesignObjectConverter();
  55. /**
  56. * Creates the formatter with default types already mapped.
  57. */
  58. public DesignFormatter() {
  59. mapDefaultTypes();
  60. }
  61. /**
  62. * Maps default types to their converters.
  63. *
  64. */
  65. protected void mapDefaultTypes() {
  66. // numbers use standard toString/valueOf approach
  67. for (Class<?> c : new Class<?>[] { Byte.class, Short.class,
  68. Integer.class, Long.class }) {
  69. DesignToStringConverter<?> conv = new DesignToStringConverter(c);
  70. converterMap.put(c, conv);
  71. try {
  72. converterMap.put((Class<?>) c.getField("TYPE").get(null), conv);
  73. } catch (Exception e) {
  74. ; // this will never happen
  75. }
  76. }
  77. // booleans use a bit different converter than the standard one
  78. // "false" is boolean false, everything else is boolean true
  79. Converter<String, Boolean> booleanConverter = new Converter<String, Boolean>() {
  80. @Override
  81. public Result<Boolean> convertToModel(String value, Locale locale) {
  82. return Result.ok(!value.equalsIgnoreCase("false"));
  83. }
  84. @Override
  85. public String convertToPresentation(Boolean value, Locale locale) {
  86. if (value.booleanValue()) {
  87. return "";
  88. } else {
  89. return "false";
  90. }
  91. }
  92. };
  93. converterMap.put(Boolean.class, booleanConverter);
  94. converterMap.put(boolean.class, booleanConverter);
  95. // floats and doubles use formatters
  96. final DecimalFormatSymbols symbols = new DecimalFormatSymbols(
  97. new Locale("en_US"));
  98. final DecimalFormat fmt = new DecimalFormat("0.###", symbols);
  99. fmt.setGroupingUsed(false);
  100. Converter<String, ?> floatConverter = new StringToFloatConverter() {
  101. @Override
  102. protected NumberFormat getFormat(Locale locale) {
  103. return fmt;
  104. };
  105. };
  106. converterMap.put(Float.class, floatConverter);
  107. converterMap.put(float.class, floatConverter);
  108. Converter<String, ?> doubleConverter = new StringToDoubleConverter() {
  109. @Override
  110. protected NumberFormat getFormat(Locale locale) {
  111. return fmt;
  112. };
  113. };
  114. converterMap.put(Double.class, doubleConverter);
  115. converterMap.put(double.class, doubleConverter);
  116. final DecimalFormat bigDecimalFmt = new DecimalFormat("0.###", symbols);
  117. bigDecimalFmt.setGroupingUsed(false);
  118. bigDecimalFmt.setParseBigDecimal(true);
  119. converterMap.put(BigDecimal.class, new StringToBigDecimalConverter() {
  120. @Override
  121. protected NumberFormat getFormat(Locale locale) {
  122. return bigDecimalFmt;
  123. };
  124. });
  125. // strings do nothing
  126. converterMap.put(String.class, new Converter<String, String>() {
  127. @Override
  128. public Result<String> convertToModel(String value, Locale locale) {
  129. return Result.ok(value);
  130. }
  131. @Override
  132. public String convertToPresentation(String value, Locale locale) {
  133. return value;
  134. }
  135. });
  136. // char takes the first character from the string
  137. Converter<String, Character> charConverter = new DesignToStringConverter<Character>(
  138. Character.class) {
  139. @Override
  140. public Result<Character> convertToModel(String value,
  141. Locale locale) {
  142. return Result.ok(value.charAt(0));
  143. }
  144. };
  145. converterMap.put(Character.class, charConverter);
  146. converterMap.put(char.class, charConverter);
  147. converterMap.put(Date.class, new DesignDateConverter());
  148. converterMap.put(ShortcutAction.class,
  149. new DesignShortcutActionConverter());
  150. converterMap.put(Resource.class, new DesignResourceConverter());
  151. converterMap.put(TimeZone.class, new DesignTimeZoneConverter());
  152. }
  153. /**
  154. * Adds a converter for a given type.
  155. *
  156. * @param type
  157. * Type to convert to/from.
  158. * @param converter
  159. * Converter.
  160. */
  161. protected <T> void addConverter(Class<?> type,
  162. Converter<String, ?> converter) {
  163. converterMap.put(type, converter);
  164. }
  165. /**
  166. * Removes the converter for given type, if it was present.
  167. *
  168. * @param type
  169. * Type to remove converter for.
  170. */
  171. protected void removeConverter(Class<?> type) {
  172. converterMap.remove(type);
  173. }
  174. /**
  175. * Returns a set of classes that have a converter registered. This is <b>not
  176. * the same</b> as the list of supported classes - subclasses of classes in
  177. * this set are also supported.
  178. *
  179. * @return An unmodifiable set of classes that have a converter registered.
  180. */
  181. protected Set<Class<?>> getRegisteredClasses() {
  182. return Collections.unmodifiableSet(converterMap.keySet());
  183. }
  184. /**
  185. * Parses a given string as a value of given type.
  186. *
  187. * @param value
  188. * String value to convert.
  189. * @param type
  190. * Expected result type.
  191. * @return String converted to the expected result type using a registered
  192. * converter for that type.
  193. */
  194. public <T> T parse(String value, Class<? extends T> type) {
  195. Converter<String, T> converter = findConverterFor(type);
  196. if (converter != null) {
  197. Result<T> result = converter.convertToModel(value, null);
  198. return result.getOrThrow(msg -> new IllegalArgumentException(msg));
  199. } else {
  200. return null;
  201. }
  202. }
  203. /**
  204. * Finds a formatter for a given object and attempts to format it.
  205. *
  206. * @param object
  207. * Object to format.
  208. * @return String representation of the object, as returned by the
  209. * registered converter.
  210. */
  211. public String format(Object object) {
  212. return format(object,
  213. object == null ? Object.class : object.getClass());
  214. }
  215. /**
  216. * Formats an object according to a converter suitable for a given type.
  217. *
  218. * @param object
  219. * Object to format.
  220. * @param type
  221. * Type of the object.
  222. * @return String representation of the object, as returned by the
  223. * registered converter.
  224. */
  225. public <T> String format(T object, Class<? extends T> type) {
  226. if (object == null) {
  227. return null;
  228. } else {
  229. Converter<String, Object> converter = findConverterFor(
  230. object.getClass());
  231. return converter.convertToPresentation(object, null);
  232. }
  233. }
  234. /**
  235. * Checks whether or not a value of a given type can be converted. If a
  236. * converter for a superclass is found, this will return true.
  237. *
  238. * @param type
  239. * Type to check.
  240. * @return <b>true</b> when either a given type or its supertype has a
  241. * converter, <b>false</b> otherwise.
  242. */
  243. public boolean canConvert(Class<?> type) {
  244. return findConverterFor(type) != null;
  245. }
  246. /**
  247. * Finds a converter for a given type. May return a converter for a
  248. * superclass instead, if one is found and {@code strict} is false.
  249. *
  250. * @param sourceType
  251. * Type to find a converter for.
  252. * @param strict
  253. * Whether or not search should be strict. When this is
  254. * <b>false</b>, a converter for a superclass of given type may
  255. * be returned.
  256. * @return A valid converter for a given type or its supertype, <b>null</b>
  257. * if it was not found.
  258. */
  259. @SuppressWarnings({ "unchecked", "rawtypes" })
  260. protected <T> Converter<String, T> findConverterFor(
  261. Class<? extends T> sourceType, boolean strict) {
  262. if (sourceType == Object.class) {
  263. // Use for propertyIds, itemIds and such. Only string type objects
  264. // are really supported if no special logic is implemented in the
  265. // component.
  266. return (Converter<String, T>) stringObjectConverter;
  267. }
  268. if (converterMap.containsKey(sourceType)) {
  269. return ((Converter<String, T>) converterMap.get(sourceType));
  270. } else if (!strict) {
  271. for (Class<?> supported : converterMap.keySet()) {
  272. if (supported.isAssignableFrom(sourceType)) {
  273. return ((Converter<String, T>) converterMap.get(supported));
  274. }
  275. }
  276. }
  277. if (sourceType.isEnum()) {
  278. return new DesignEnumConverter(sourceType);
  279. }
  280. return null;
  281. }
  282. /**
  283. * Finds a converter for a given type. May return a converter for a
  284. * superclass instead, if one is found.
  285. *
  286. * @param sourceType
  287. * Type to find a converter for.
  288. * @return A valid converter for a given type or its subtype, <b>null</b> if
  289. * it was not found.
  290. */
  291. protected <T> Converter<String, T> findConverterFor(
  292. Class<? extends T> sourceType) {
  293. return findConverterFor(sourceType, false);
  294. }
  295. /**
  296. * <p>
  297. * Encodes <em>some</em> special characters in a given input String to make
  298. * it ready to be written as contents of a text node. WARNING: this will
  299. * e.g. encode "&lt;someTag&gt;" to "&amp;lt;someTag&amp;gt;" as this method
  300. * doesn't do any parsing and assumes that there are no intended HTML
  301. * elements in the input. Only some entities are actually encoded:
  302. * &amp;,&lt;, &gt; It's assumed that other entities are taken care of by
  303. * Jsoup.
  304. * </p>
  305. * <p>
  306. * Typically, this method will be used by components to encode data (like
  307. * option items in {@code AbstractSelect}) when dumping to HTML format
  308. * </p>
  309. *
  310. * @since 7.5.7
  311. * @param input
  312. * String to be encoded
  313. * @return String with &amp;,&lt; and &gt; replaced with their HTML entities
  314. */
  315. public static String encodeForTextNode(String input) {
  316. if (input == null) {
  317. return null;
  318. }
  319. return input.replace("&", "&amp;").replace(">", "&gt;").replace("<",
  320. "&lt;");
  321. }
  322. /**
  323. * <p>
  324. * Decodes HTML entities in a text from text node and replaces them with
  325. * actual characters.
  326. * </p>
  327. *
  328. * <p>
  329. * Typically this method will be used by components to read back data (like
  330. * option items in {@code AbstractSelect}) from HTML. Note that this method
  331. * unencodes more characters than {@link #encodeForTextNode(String)} encodes
  332. * </p>
  333. *
  334. * @since 7.6
  335. * @param input
  336. * @return
  337. */
  338. public static String decodeFromTextNode(String input) {
  339. return Parser.unescapeEntities(input, false);
  340. }
  341. }