Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

DesignFormatter.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. /*
  2. * Copyright 2000-2014 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.util.converter.Converter;
  31. import com.vaadin.data.util.converter.StringToBigDecimalConverter;
  32. import com.vaadin.data.util.converter.StringToDoubleConverter;
  33. import com.vaadin.data.util.converter.StringToFloatConverter;
  34. import com.vaadin.event.ShortcutAction;
  35. import com.vaadin.server.Resource;
  36. import com.vaadin.ui.AbstractSelect;
  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, Enum> stringEnumConverter = new DesignEnumConverter();
  55. private final Converter<String, Object> stringObjectConverter = new DesignObjectConverter();
  56. /**
  57. * Creates the formatter with default types already mapped.
  58. */
  59. public DesignFormatter() {
  60. mapDefaultTypes();
  61. }
  62. /**
  63. * Maps default types to their converters.
  64. *
  65. */
  66. protected void mapDefaultTypes() {
  67. // numbers use standard toString/valueOf approach
  68. for (Class<?> c : new Class<?>[] { Byte.class, Short.class,
  69. Integer.class, Long.class }) {
  70. DesignToStringConverter<?> conv = new DesignToStringConverter(c);
  71. converterMap.put(c, conv);
  72. try {
  73. converterMap.put((Class<?>) c.getField("TYPE").get(null), conv);
  74. } catch (Exception e) {
  75. ; // this will never happen
  76. }
  77. }
  78. // booleans use a bit different converter than the standard one
  79. // "false" is boolean false, everything else is boolean true
  80. Converter<String, Boolean> booleanConverter = new Converter<String, Boolean>() {
  81. @Override
  82. public Boolean convertToModel(String value,
  83. Class<? extends Boolean> targetType, Locale locale)
  84. throws Converter.ConversionException {
  85. return !value.equalsIgnoreCase("false");
  86. }
  87. @Override
  88. public String convertToPresentation(Boolean value,
  89. Class<? extends String> targetType, Locale locale)
  90. throws Converter.ConversionException {
  91. if (value.booleanValue()) {
  92. return "";
  93. } else {
  94. return "false";
  95. }
  96. }
  97. @Override
  98. public Class<Boolean> getModelType() {
  99. return Boolean.class;
  100. }
  101. @Override
  102. public Class<String> getPresentationType() {
  103. return String.class;
  104. }
  105. };
  106. converterMap.put(Boolean.class, booleanConverter);
  107. converterMap.put(boolean.class, booleanConverter);
  108. // floats and doubles use formatters
  109. final DecimalFormatSymbols symbols = new DecimalFormatSymbols(
  110. new Locale("en_US"));
  111. final DecimalFormat fmt = new DecimalFormat("0.###", symbols);
  112. fmt.setGroupingUsed(false);
  113. Converter<String, ?> floatConverter = new StringToFloatConverter() {
  114. @Override
  115. protected NumberFormat getFormat(Locale locale) {
  116. return fmt;
  117. };
  118. };
  119. converterMap.put(Float.class, floatConverter);
  120. converterMap.put(float.class, floatConverter);
  121. Converter<String, ?> doubleConverter = new StringToDoubleConverter() {
  122. @Override
  123. protected NumberFormat getFormat(Locale locale) {
  124. return fmt;
  125. };
  126. };
  127. converterMap.put(Double.class, doubleConverter);
  128. converterMap.put(double.class, doubleConverter);
  129. final DecimalFormat bigDecimalFmt = new DecimalFormat("0.###", symbols);
  130. bigDecimalFmt.setGroupingUsed(false);
  131. bigDecimalFmt.setParseBigDecimal(true);
  132. converterMap.put(BigDecimal.class, new StringToBigDecimalConverter() {
  133. @Override
  134. protected NumberFormat getFormat(Locale locale) {
  135. return bigDecimalFmt;
  136. };
  137. });
  138. // strings do nothing
  139. converterMap.put(String.class, new Converter<String, String>() {
  140. @Override
  141. public String convertToModel(String value,
  142. Class<? extends String> targetType, Locale locale)
  143. throws Converter.ConversionException {
  144. return value;
  145. }
  146. @Override
  147. public String convertToPresentation(String value,
  148. Class<? extends String> targetType, Locale locale)
  149. throws Converter.ConversionException {
  150. return value;
  151. }
  152. @Override
  153. public Class<String> getModelType() {
  154. return String.class;
  155. }
  156. @Override
  157. public Class<String> getPresentationType() {
  158. return String.class;
  159. }
  160. });
  161. // char takes the first character from the string
  162. Converter<String, Character> charConverter = new DesignToStringConverter<Character>(
  163. Character.class) {
  164. @Override
  165. public Character convertToModel(String value,
  166. Class<? extends Character> targetType, Locale locale)
  167. throws Converter.ConversionException {
  168. return value.charAt(0);
  169. }
  170. };
  171. converterMap.put(Character.class, charConverter);
  172. converterMap.put(char.class, charConverter);
  173. converterMap.put(Date.class, new DesignDateConverter());
  174. converterMap.put(ShortcutAction.class,
  175. new DesignShortcutActionConverter());
  176. converterMap.put(Resource.class, new DesignResourceConverter());
  177. converterMap.put(TimeZone.class, new DesignTimeZoneConverter());
  178. }
  179. /**
  180. * Adds a converter for a new type.
  181. *
  182. * @param converter
  183. * Converter to add.
  184. */
  185. protected <T> void addConverter(Converter<String, T> converter) {
  186. converterMap.put(converter.getModelType(), converter);
  187. }
  188. /**
  189. * Adds a converter for a given type.
  190. *
  191. * @param type
  192. * Type to convert to/from.
  193. * @param converter
  194. * Converter.
  195. */
  196. protected <T> void addConverter(Class<?> type,
  197. Converter<String, ?> converter) {
  198. converterMap.put(type, converter);
  199. }
  200. /**
  201. * Removes the converter for given type, if it was present.
  202. *
  203. * @param type
  204. * Type to remove converter for.
  205. */
  206. protected void removeConverter(Class<?> type) {
  207. converterMap.remove(type);
  208. }
  209. /**
  210. * Returns a set of classes that have a converter registered. This is <b>not
  211. * the same</b> as the list of supported classes - subclasses of classes in
  212. * this set are also supported.
  213. *
  214. * @return An unmodifiable set of classes that have a converter registered.
  215. */
  216. protected Set<Class<?>> getRegisteredClasses() {
  217. return Collections.unmodifiableSet(converterMap.keySet());
  218. }
  219. /**
  220. * Parses a given string as a value of given type
  221. *
  222. * @param value
  223. * String value to convert.
  224. * @param type
  225. * Expected result type.
  226. * @return String converted to the expected result type using a registered
  227. * converter for that type.
  228. */
  229. public <T> T parse(String value, Class<? extends T> type) {
  230. Converter<String, T> converter = findConverterFor(type);
  231. if (converter != null) {
  232. return converter.convertToModel(value, type, null);
  233. } else {
  234. return null;
  235. }
  236. }
  237. /**
  238. * Finds a formatter for a given object and attempts to format it.
  239. *
  240. * @param object
  241. * Object to format.
  242. * @return String representation of the object, as returned by the
  243. * registered converter.
  244. */
  245. public String format(Object object) {
  246. return format(object, object == null ? Object.class : object.getClass());
  247. }
  248. /**
  249. * Formats an object according to a converter suitable for a given type.
  250. *
  251. * @param object
  252. * Object to format.
  253. * @param type
  254. * Type of the object.
  255. * @return String representation of the object, as returned by the
  256. * registered converter.
  257. */
  258. public <T> String format(T object, Class<? extends T> type) {
  259. if (object == null) {
  260. return null;
  261. } else {
  262. return findConverterFor(object.getClass()).convertToPresentation(
  263. object, String.class, null);
  264. }
  265. }
  266. /**
  267. * Checks whether or not a value of a given type can be converted. If a
  268. * converter for a superclass is found, this will return true.
  269. *
  270. * @param type
  271. * Type to check.
  272. * @return <b>true</b> when either a given type or its supertype has a
  273. * converter, <b>false</b> otherwise.
  274. */
  275. public boolean canConvert(Class<?> type) {
  276. return findConverterFor(type) != null;
  277. }
  278. /**
  279. * Finds a converter for a given type. May return a converter for a
  280. * superclass instead, if one is found and {@code strict} is false.
  281. *
  282. * @param sourceType
  283. * Type to find a converter for.
  284. * @param strict
  285. * Whether or not search should be strict. When this is
  286. * <b>false</b>, a converter for a superclass of given type may
  287. * be returned.
  288. * @return A valid converter for a given type or its supertype, <b>null</b>
  289. * if it was not found.
  290. */
  291. @SuppressWarnings("unchecked")
  292. protected <T> Converter<String, T> findConverterFor(
  293. Class<? extends T> sourceType, boolean strict) {
  294. if (sourceType == Object.class) {
  295. // Use for propertyIds, itemIds and such. Only string type objects
  296. // are really supported if no special logic is implemented in the
  297. // component.
  298. return (Converter<String, T>) stringObjectConverter;
  299. }
  300. if (converterMap.containsKey(sourceType)) {
  301. return ((Converter<String, T>) converterMap.get(sourceType));
  302. } else if (!strict) {
  303. for (Class<?> supported : converterMap.keySet()) {
  304. if (supported.isAssignableFrom(sourceType)) {
  305. return ((Converter<String, T>) converterMap.get(supported));
  306. }
  307. }
  308. }
  309. if (sourceType.isEnum()) {
  310. return (Converter<String, T>) stringEnumConverter;
  311. }
  312. return null;
  313. }
  314. /**
  315. * Finds a converter for a given type. May return a converter for a
  316. * superclass instead, if one is found.
  317. *
  318. * @param sourceType
  319. * Type to find a converter for.
  320. * @return A valid converter for a given type or its subtype, <b>null</b> if
  321. * it was not found.
  322. */
  323. protected <T> Converter<String, T> findConverterFor(
  324. Class<? extends T> sourceType) {
  325. return findConverterFor(sourceType, false);
  326. }
  327. /**
  328. * <p>
  329. * Encodes <em>some</em> special characters in a given input String to make
  330. * it ready to be written as contents of a text node. WARNING: this will
  331. * e.g. encode "&lt;someTag&gt;" to "&amp;lt;someTag&amp;gt;" as this method
  332. * doesn't do any parsing and assumes that there are no intended HTML
  333. * elements in the input. Only some entities are actually encoded:
  334. * &amp;,&lt;, &gt; It's assumed that other entities are taken care of by
  335. * Jsoup.
  336. * </p>
  337. * <p>
  338. * Typically, this method will be used by components to encode data (like
  339. * option items in {@link AbstractSelect}) when dumping to HTML format
  340. * </p>
  341. *
  342. * @since 7.5.7
  343. * @param input
  344. * String to be encoded
  345. * @return String with &amp;,&lt; and &gt; replaced with their HTML entities
  346. */
  347. public static String encodeForTextNode(String input) {
  348. if (input == null) {
  349. return null;
  350. }
  351. return input.replace("&", "&amp;").replace(">", "&gt;")
  352. .replace("<", "&lt;");
  353. }
  354. /**
  355. * <p>
  356. * Decodes HTML entities in a text from text node and replaces them with
  357. * actual characters.
  358. * </p>
  359. *
  360. * <p>
  361. * Typically this method will be used by components to read back data (like
  362. * option items in {@link AbstractSelect}) from HTML. Note that this method
  363. * unencodes more characters than {@link #encodeForTextNode(String)} encodes
  364. * </p>
  365. *
  366. * @since 7.6
  367. * @param input
  368. * @return
  369. */
  370. public static String decodeFromTextNode(String input) {
  371. return Parser.unescapeEntities(input, false);
  372. }
  373. }