PageRenderTime 29ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/afirma-simple/src/main/java/xmlwise/Plist.java

https://gitlab.com/edgardo001/clienteafirma
Java | 338 lines | 221 code | 21 blank | 96 comment | 22 complexity | 2df469fc4b3e9487ef1887e16f041ded MD5 | raw file
  1. package xmlwise;
  2. import java.io.Closeable;
  3. import java.io.IOException;
  4. import java.text.DateFormat;
  5. import java.text.SimpleDateFormat;
  6. import java.util.ArrayList;
  7. import java.util.Date;
  8. import java.util.HashMap;
  9. import java.util.Iterator;
  10. import java.util.List;
  11. import java.util.Map;
  12. import java.util.TimeZone;
  13. /** Plist xml handling (serialization and deserialization).
  14. * The xml plist dtd can be found at <a href="http://www.apple.com/DTDs/PropertyList-1.0.dtd">http://www.apple.com/DTDs/PropertyList-1.0.dtd</a>.
  15. * @author Christoffer Lerno. */
  16. public final class Plist
  17. {
  18. /**
  19. * Singleton instance.
  20. */
  21. private final static Plist PLIST = new Plist();
  22. /**
  23. * All element types possible for a plist.
  24. */
  25. private static enum ElementType
  26. {
  27. INTEGER,
  28. STRING,
  29. REAL,
  30. DATA,
  31. DATE,
  32. DICT,
  33. ARRAY,
  34. TRUE,
  35. FALSE,
  36. }
  37. private static final String BASE64_STRING
  38. = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; //$NON-NLS-1$
  39. private static final char[] BASE64_CHARS = BASE64_STRING.toCharArray();
  40. private final DateFormat m_dateFormat;
  41. private final Map<Class<?>, ElementType> m_simpleTypes;
  42. /**
  43. * Utility method to close a closeable.
  44. *
  45. * @param closeable or null.
  46. */
  47. static void silentlyClose(final Closeable closeable)
  48. {
  49. try
  50. {
  51. if (closeable != null) {
  52. closeable.close();
  53. }
  54. }
  55. catch (final IOException e)
  56. {
  57. // Ignore
  58. }
  59. }
  60. /**
  61. * Create a nested {@code map<String, Object>} from a plist xml string using the default mapping.
  62. *
  63. * @param xml the plist xml data as a string.
  64. * @return the resulting map as read from the plist data.
  65. * @throws XmlParseException if the plist could not be properly parsed.
  66. */
  67. public static Map<String, Object> fromXml(final String xml) throws XmlParseException
  68. {
  69. return PLIST.parse(Xmlwise.createXml(xml));
  70. }
  71. /**
  72. * Create a plist handler.
  73. */
  74. Plist()
  75. {
  76. this.m_dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); //$NON-NLS-1$
  77. this.m_dateFormat.setTimeZone(TimeZone.getTimeZone("Z")); //$NON-NLS-1$
  78. this.m_simpleTypes = new HashMap<>();
  79. this.m_simpleTypes.put(Integer.class, ElementType.INTEGER);
  80. this.m_simpleTypes.put(Byte.class, ElementType.INTEGER);
  81. this.m_simpleTypes.put(Short.class, ElementType.INTEGER);
  82. this.m_simpleTypes.put(Short.class, ElementType.INTEGER);
  83. this.m_simpleTypes.put(Long.class, ElementType.INTEGER);
  84. this.m_simpleTypes.put(String.class, ElementType.STRING);
  85. this.m_simpleTypes.put(Float.class, ElementType.REAL);
  86. this.m_simpleTypes.put(Double.class, ElementType.REAL);
  87. this.m_simpleTypes.put(byte[].class, ElementType.DATA);
  88. this.m_simpleTypes.put(Boolean.class, ElementType.TRUE);
  89. this.m_simpleTypes.put(Date.class, ElementType.DATE);
  90. }
  91. /**
  92. * Convert an object to its plist representation.
  93. *
  94. * @param o the object to convert, must be Integer, Double, String, Date, Boolean, byte[],
  95. * Map or List.
  96. * @return an <tt>XmlElement</tt> containing the serialized version of the object.
  97. */
  98. XmlElement objectToXml(final Object o)
  99. {
  100. final ElementType type = this.m_simpleTypes.get(o.getClass());
  101. if (type != null)
  102. {
  103. switch (type) {
  104. case REAL:
  105. return new XmlElement("real", o.toString()); //$NON-NLS-1$
  106. case INTEGER:
  107. return new XmlElement("integer", o.toString()); //$NON-NLS-1$
  108. case TRUE:
  109. return new XmlElement(((Boolean) o).booleanValue() ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
  110. case DATE:
  111. return new XmlElement("date", this.m_dateFormat.format((Date) o)); //$NON-NLS-1$
  112. case STRING:
  113. return new XmlElement("string", (String) o); //$NON-NLS-1$
  114. case DATA:
  115. return new XmlElement("data", base64encode((byte[]) o)); //$NON-NLS-1$
  116. }
  117. }
  118. if (o instanceof Map)
  119. {
  120. return toXmlDict((Map) o);
  121. }
  122. else if (o instanceof List)
  123. {
  124. return toXmlArray((List) o);
  125. }
  126. else {
  127. throw new RuntimeException("Cannot use " + o.getClass() + " in plist."); //$NON-NLS-1$ //$NON-NLS-2$
  128. }
  129. }
  130. /**
  131. * Convert a list to its plist representation.
  132. *
  133. * @param list the list to convert.
  134. * @return an <tt>XmlElement</tt> representing the list.
  135. */
  136. private XmlElement toXmlArray(final List<Object> list) {
  137. final XmlElement array = new XmlElement("array"); //$NON-NLS-1$
  138. for (final Object o : list) {
  139. array.add(objectToXml(o));
  140. }
  141. return array;
  142. }
  143. /**
  144. * Convert a map to its plist representation.
  145. *
  146. * @param map the map to convert, assumed to have string keys.
  147. * @return an <tt>XmlElement</tt> representing the map.
  148. */
  149. private XmlElement toXmlDict(final Map<String, Object> map) {
  150. final XmlElement dict = new XmlElement("dict"); //$NON-NLS-1$
  151. for (final Map.Entry<String, Object> entry : map.entrySet()) {
  152. dict.add(new XmlElement("key", entry.getKey())); //$NON-NLS-1$
  153. dict.add(objectToXml(entry.getValue()));
  154. }
  155. return dict;
  156. }
  157. /**
  158. * Parses a plist top element into a map dictionary containing all the data
  159. * in the plist.
  160. *
  161. * @param element the top plist element.
  162. * @return the resulting data tree structure.
  163. * @throws XmlParseException if there was any error parsing the xml.
  164. */
  165. Map<String, Object> parse(final XmlElement element) throws XmlParseException {
  166. if (!"plist".equalsIgnoreCase(element.getName())) { //$NON-NLS-1$
  167. throw new XmlParseException("Expected plist top element, was: " + element.getName()); //$NON-NLS-1$
  168. }
  169. return (Map<String, Object>)parseElement(element.get("dict").getFirst()); //$NON-NLS-1$
  170. }
  171. /**
  172. * Parses a (non-top) xml element.
  173. *
  174. * @param element the element to parse.
  175. * @return the resulting object.
  176. * @throws XmlParseException if there was some error in the xml.
  177. */
  178. private Object parseElement(final XmlElement element) throws XmlParseException {
  179. try {
  180. return parseElementRaw(element);
  181. }
  182. catch (final Exception e) {
  183. throw new XmlParseException("Failed to parse: " + element.toXml(), e); //$NON-NLS-1$
  184. }
  185. }
  186. /**
  187. * Parses a (non-top) xml element.
  188. *
  189. * @param element the element to parse.
  190. * @return the resulting object.
  191. * @throws Exception if there was some error parsing the xml.
  192. */
  193. private Object parseElementRaw(final XmlElement element) throws Exception {
  194. final ElementType type = ElementType.valueOf(element.getName().toUpperCase());
  195. switch (type) {
  196. case INTEGER:
  197. return parseInt(element.getValue());
  198. case REAL:
  199. return Double.valueOf(element.getValue());
  200. case STRING:
  201. return element.getValue();
  202. case DATE:
  203. return this.m_dateFormat.parse(element.getValue());
  204. case DATA:
  205. return base64decode(element.getValue());
  206. case ARRAY:
  207. return parseArray(element);
  208. case TRUE:
  209. return Boolean.TRUE;
  210. case FALSE:
  211. return Boolean.FALSE;
  212. case DICT:
  213. return parseDict(element);
  214. default:
  215. throw new RuntimeException("Unexpected type: " + element.getName()); //$NON-NLS-1$
  216. }
  217. }
  218. /** Parses a string into a Long or Integer depending on size.
  219. * @param value the value as a string.
  220. * @return the long value of this string is the value doesn't fit in an integer,
  221. * otherwise the int value of the string. */
  222. private static Number parseInt(final String value)
  223. {
  224. final Long l = Long.valueOf(value);
  225. if (l.intValue() == l.longValue()) {
  226. return Integer.valueOf(l.intValue());
  227. }
  228. return l;
  229. }
  230. /**
  231. * Parse a list of xml elements as a plist dict.
  232. *
  233. * @param elements the elements to parse.
  234. * @return the dict deserialized as a map.
  235. * @throws Exception if there are any problems deserializing the map.
  236. */
  237. private Map<String, Object> parseDict(final List<XmlElement> elements) throws Exception {
  238. final Iterator<XmlElement> element = elements.iterator();
  239. final HashMap<String, Object> dict = new HashMap<>();
  240. while (element.hasNext()) {
  241. final XmlElement key = element.next();
  242. if (!"key".equals(key.getName())) { //$NON-NLS-1$
  243. throw new Exception("Expected key but was " + key.getName()); //$NON-NLS-1$
  244. }
  245. final Object o = parseElementRaw(element.next());
  246. dict.put(key.getValue(), o);
  247. }
  248. return dict;
  249. }
  250. /**
  251. * Parse a list of xml elements as a plist array.
  252. *
  253. * @param elements the elements to parse.
  254. * @return the array deserialized as a list.
  255. * @throws Exception if there are any problems deserializing the list.
  256. */
  257. private List<Object> parseArray(final List<XmlElement> elements) throws Exception
  258. {
  259. final ArrayList<Object> list = new ArrayList<>(elements.size());
  260. for (final XmlElement element : elements)
  261. {
  262. list.add(parseElementRaw(element));
  263. }
  264. return list;
  265. }
  266. /**
  267. * Encode an array of bytes to a string using base64 encoding.
  268. *
  269. * @param bytes the bytes to convert.
  270. * @return the base64 representation of the bytes.
  271. */
  272. static String base64encode(final byte[] bytes) {
  273. final StringBuilder builder = new StringBuilder((bytes.length + 2)/ 3 * 4);
  274. for (int i = 0; i < bytes.length; i += 3) {
  275. final byte b0 = bytes[i];
  276. final byte b1 = i < bytes.length - 1 ? bytes[i + 1] : 0;
  277. final byte b2 = i < bytes.length - 2 ? bytes[i + 2] : 0;
  278. builder.append(BASE64_CHARS[(b0 & 0xFF) >> 2]);
  279. builder.append(BASE64_CHARS[(b0 & 0x03) << 4 | (b1 & 0xF0) >> 4]);
  280. builder.append(i < bytes.length - 1 ? Character.valueOf(BASE64_CHARS[(b1 & 0x0F) << 2 | (b2 & 0xC0) >> 6]) : "="); //$NON-NLS-1$
  281. builder.append(i < bytes.length - 2 ? Character.valueOf(BASE64_CHARS[b2 & 0x3F]) : "="); //$NON-NLS-1$
  282. }
  283. return builder.toString();
  284. }
  285. /**
  286. * Converts a string to a byte array assuming the string uses base64-encoding.
  287. *
  288. * @param b64 the string to convert.
  289. * @return the resulting byte array.
  290. */
  291. static byte[] base64decode(final String b64) {
  292. String base64 = b64.trim();
  293. final int endTrim = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0; //$NON-NLS-1$ //$NON-NLS-2$
  294. final int length = base64.length() / 4 * 3 - endTrim;
  295. base64 = base64.replace('=', 'A');
  296. final byte[] result = new byte[length];
  297. final int stringLength = base64.length();
  298. int index = 0;
  299. for (int i = 0; i < stringLength; i += 4) {
  300. final int i0 = BASE64_STRING.indexOf(base64.charAt(i));
  301. final int i1 = BASE64_STRING.indexOf(base64.charAt(i + 1));
  302. final int i2 = BASE64_STRING.indexOf(base64.charAt(i + 2));
  303. final int i3 = BASE64_STRING.indexOf(base64.charAt(i + 3));
  304. final byte b0 = (byte) (i0 << 2 | i1 >> 4);
  305. final byte b1 = (byte) (i1 << 4 | i2 >> 2);
  306. final byte b2 = (byte) (i2 << 6 | i3);
  307. result[index++] = b0;
  308. if (index < length) {
  309. result[index++] = b1;
  310. if (index < length) {
  311. result[index++] = b2;
  312. }
  313. }
  314. }
  315. return result;
  316. }
  317. }