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.

GenericRecordJsonWriter.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. /*
  2. * ====================================================================
  3. * Licensed to the Apache Software Foundation (ASF) under one or more
  4. * contributor license agreements. See the NOTICE file distributed with
  5. * this work for additional information regarding copyright ownership.
  6. * The ASF licenses this file to You under the Apache License, Version 2.0
  7. * (the "License"); you may not use this file except in compliance with
  8. * 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, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. * ====================================================================
  18. */
  19. package org.apache.poi.util;
  20. import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM;
  21. import java.awt.Color;
  22. import java.awt.geom.AffineTransform;
  23. import java.awt.geom.Dimension2D;
  24. import java.awt.geom.Path2D;
  25. import java.awt.geom.PathIterator;
  26. import java.awt.geom.Point2D;
  27. import java.awt.geom.Rectangle2D;
  28. import java.awt.image.BufferedImage;
  29. import java.awt.image.ColorModel;
  30. import java.awt.image.ComponentColorModel;
  31. import java.awt.image.DirectColorModel;
  32. import java.awt.image.IndexColorModel;
  33. import java.awt.image.PackedColorModel;
  34. import java.io.Closeable;
  35. import java.io.File;
  36. import java.io.FileOutputStream;
  37. import java.io.Flushable;
  38. import java.io.IOException;
  39. import java.io.OutputStream;
  40. import java.io.OutputStreamWriter;
  41. import java.io.PrintWriter;
  42. import java.io.Writer;
  43. import java.lang.reflect.Array;
  44. import java.nio.charset.StandardCharsets;
  45. import java.util.AbstractMap;
  46. import java.util.ArrayList;
  47. import java.util.Arrays;
  48. import java.util.Base64;
  49. import java.util.List;
  50. import java.util.Map;
  51. import java.util.function.Supplier;
  52. import java.util.regex.Matcher;
  53. import java.util.regex.Pattern;
  54. import org.apache.poi.common.usermodel.GenericRecord;
  55. import org.apache.poi.util.GenericRecordUtil.AnnotatedFlag;
  56. @SuppressWarnings({"UnusedReturnValue", "WeakerAccess"})
  57. @Beta
  58. public class GenericRecordJsonWriter implements Closeable {
  59. private static final String TABS;
  60. private static final String ZEROS = "0000000000000000";
  61. private static final Pattern ESC_CHARS = Pattern.compile("[\"\\p{Cntrl}\\\\]");
  62. private static final String NL = System.getProperty("line.separator");
  63. @FunctionalInterface
  64. protected interface GenericRecordHandler {
  65. /**
  66. * Handler method
  67. *
  68. * @param record the parent record, applied via instance method reference
  69. * @param name the name of the property
  70. * @param object the value of the property
  71. * @return {@code true}, if the element was handled and output produced,
  72. * The provided methods can be overridden and a implementation can return {@code false},
  73. * if the element hasn't been written to the stream
  74. */
  75. boolean print(GenericRecordJsonWriter record, String name, Object object);
  76. }
  77. private static final List<Map.Entry<Class<?>,GenericRecordHandler>> handler = new ArrayList<>();
  78. static {
  79. char[] t = new char[255];
  80. Arrays.fill(t, '\t');
  81. TABS = new String(t);
  82. handler(String.class, GenericRecordJsonWriter::printObject);
  83. handler(Number.class, GenericRecordJsonWriter::printNumber);
  84. handler(Boolean.class, GenericRecordJsonWriter::printBoolean);
  85. handler(List.class, GenericRecordJsonWriter::printList);
  86. handler(GenericRecord.class, GenericRecordJsonWriter::printGenericRecord);
  87. handler(AnnotatedFlag.class, GenericRecordJsonWriter::printAnnotatedFlag);
  88. handler(byte[].class, GenericRecordJsonWriter::printBytes);
  89. handler(Point2D.class, GenericRecordJsonWriter::printPoint);
  90. handler(Dimension2D.class, GenericRecordJsonWriter::printDimension);
  91. handler(Rectangle2D.class, GenericRecordJsonWriter::printRectangle);
  92. handler(Path2D.class, GenericRecordJsonWriter::printPath);
  93. handler(AffineTransform.class, GenericRecordJsonWriter::printAffineTransform);
  94. handler(Color.class, GenericRecordJsonWriter::printColor);
  95. handler(BufferedImage.class, GenericRecordJsonWriter::printImage);
  96. handler(Array.class, GenericRecordJsonWriter::printArray);
  97. handler(Object.class, GenericRecordJsonWriter::printObject);
  98. }
  99. private static void handler(Class<?> c, GenericRecordHandler printer) {
  100. handler.add(new AbstractMap.SimpleEntry<>(c,printer));
  101. }
  102. protected final AppendableWriter aw;
  103. protected final PrintWriter fw;
  104. protected int indent = 0;
  105. protected boolean withComments = true;
  106. protected int childIndex = 0;
  107. public GenericRecordJsonWriter(File fileName) throws IOException {
  108. OutputStream os = ("null".equals(fileName.getName())) ? NULL_OUTPUT_STREAM : new FileOutputStream(fileName);
  109. aw = new AppendableWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));
  110. fw = new PrintWriter(aw);
  111. }
  112. public GenericRecordJsonWriter(Appendable buffer) {
  113. aw = new AppendableWriter(buffer);
  114. fw = new PrintWriter(aw);
  115. }
  116. public static String marshal(GenericRecord record) {
  117. return marshal(record, true);
  118. }
  119. public static String marshal(GenericRecord record, boolean withComments) {
  120. final StringBuilder sb = new StringBuilder();
  121. try (GenericRecordJsonWriter w = new GenericRecordJsonWriter(sb)) {
  122. w.setWithComments(withComments);
  123. w.write(record);
  124. return sb.toString();
  125. } catch (IOException e) {
  126. return "{}";
  127. }
  128. }
  129. public void setWithComments(boolean withComments) {
  130. this.withComments = withComments;
  131. }
  132. @Override
  133. public void close() throws IOException {
  134. fw.close();
  135. }
  136. protected String tabs() {
  137. return TABS.substring(0, Math.min(indent, TABS.length()));
  138. }
  139. public void write(GenericRecord record) {
  140. final String tabs = tabs();
  141. Enum<?> type = record.getGenericRecordType();
  142. String recordName = (type != null) ? type.name() : record.getClass().getSimpleName();
  143. fw.append(tabs);
  144. fw.append("{");
  145. if (withComments) {
  146. fw.append(" /* ");
  147. fw.append(recordName);
  148. if (childIndex > 0) {
  149. fw.append(" - index: ");
  150. fw.print(childIndex);
  151. }
  152. fw.append(" */");
  153. }
  154. fw.println();
  155. boolean hasProperties = writeProperties(record);
  156. fw.println();
  157. writeChildren(record, hasProperties);
  158. fw.append(tabs);
  159. fw.append("}");
  160. }
  161. protected boolean writeProperties(GenericRecord record) {
  162. Map<String, Supplier<?>> prop = record.getGenericProperties();
  163. if (prop == null || prop.isEmpty()) {
  164. return false;
  165. }
  166. final int oldChildIndex = childIndex;
  167. childIndex = 0;
  168. long cnt = prop.entrySet().stream().filter(e -> writeProp(e.getKey(),e.getValue())).count();
  169. childIndex = oldChildIndex;
  170. return cnt > 0;
  171. }
  172. protected boolean writeChildren(GenericRecord record, boolean hasProperties) {
  173. List<? extends GenericRecord> list = record.getGenericChildren();
  174. if (list == null || list.isEmpty()) {
  175. return false;
  176. }
  177. indent++;
  178. aw.setHoldBack(tabs() + (hasProperties ? ", " : "") + "\"children\": [" + NL);
  179. final int oldChildIndex = childIndex;
  180. childIndex = 0;
  181. long cnt = list.stream().filter(l -> writeValue(null, l) && ++childIndex > 0).count();
  182. childIndex = oldChildIndex;
  183. aw.setHoldBack(null);
  184. if (cnt > 0) {
  185. fw.println();
  186. fw.println(tabs() + "]");
  187. }
  188. indent--;
  189. return cnt > 0;
  190. }
  191. public void writeError(String errorMsg) {
  192. fw.append("{ error: ");
  193. printObject("error", errorMsg);
  194. fw.append(" }");
  195. }
  196. protected boolean writeProp(String name, Supplier<?> value) {
  197. final boolean isNext = (childIndex>0);
  198. aw.setHoldBack(isNext ? NL + tabs() + "\t, " : tabs() + "\t ");
  199. final int oldChildIndex = childIndex;
  200. childIndex = 0;
  201. boolean written = writeValue(name, value.get());
  202. childIndex = oldChildIndex + (written ? 1 : 0);
  203. aw.setHoldBack(null);
  204. return written;
  205. }
  206. protected boolean writeValue(String name, Object o) {
  207. if (childIndex > 0) {
  208. aw.setHoldBack(",");
  209. }
  210. GenericRecordHandler grh = (o == null)
  211. ? GenericRecordJsonWriter::printNull
  212. : handler.stream().filter(h -> matchInstanceOrArray(h.getKey(), o)).
  213. findFirst().map(Map.Entry::getValue).orElse(null);
  214. boolean result = grh != null && grh.print(this, name, o);
  215. aw.setHoldBack(null);
  216. return result;
  217. }
  218. protected static boolean matchInstanceOrArray(Class<?> key, Object instance) {
  219. return key.isInstance(instance) || (Array.class.equals(key) && instance.getClass().isArray());
  220. }
  221. protected void printName(String name) {
  222. fw.print(name != null ? "\""+name+"\": " : "");
  223. }
  224. protected boolean printNull(String name, Object o) {
  225. printName(name);
  226. fw.write("null");
  227. return true;
  228. }
  229. @SuppressWarnings("java:S3516")
  230. protected boolean printNumber(String name, Object o) {
  231. Number n = (Number)o;
  232. printName(name);
  233. if (o instanceof Float) {
  234. fw.print(n.floatValue());
  235. return true;
  236. } else if (o instanceof Double) {
  237. fw.print(n.doubleValue());
  238. return true;
  239. }
  240. fw.print(n.longValue());
  241. final int size;
  242. if (n instanceof Byte) {
  243. size = 2;
  244. } else if (n instanceof Short) {
  245. size = 4;
  246. } else if (n instanceof Integer) {
  247. size = 8;
  248. } else if (n instanceof Long) {
  249. size = 16;
  250. } else {
  251. size = -1;
  252. }
  253. long l = n.longValue();
  254. if (withComments && size > 0 && (l < 0 || l > 9)) {
  255. fw.write(" /* 0x");
  256. fw.write(trimHex(l, size));
  257. fw.write(" */");
  258. }
  259. return true;
  260. }
  261. protected boolean printBoolean(String name, Object o) {
  262. printName(name);
  263. fw.write(((Boolean)o).toString());
  264. return true;
  265. }
  266. protected boolean printList(String name, Object o) {
  267. printName(name);
  268. fw.println("[");
  269. int oldChildIndex = childIndex;
  270. childIndex = 0;
  271. ((List<?>)o).forEach(e -> { writeValue(null, e); childIndex++; });
  272. childIndex = oldChildIndex;
  273. fw.write(tabs() + "\t]");
  274. return true;
  275. }
  276. protected boolean printGenericRecord(String name, Object o) {
  277. printName(name);
  278. this.indent++;
  279. write((GenericRecord) o);
  280. this.indent--;
  281. return true;
  282. }
  283. protected boolean printAnnotatedFlag(String name, Object o) {
  284. printName(name);
  285. AnnotatedFlag af = (AnnotatedFlag) o;
  286. fw.print(af.getValue().get().longValue());
  287. if (withComments) {
  288. fw.write(" /* ");
  289. fw.write(af.getDescription());
  290. fw.write(" */ ");
  291. }
  292. return true;
  293. }
  294. protected boolean printBytes(String name, Object o) {
  295. printName(name);
  296. fw.write('"');
  297. fw.write(Base64.getEncoder().encodeToString((byte[]) o));
  298. fw.write('"');
  299. return true;
  300. }
  301. protected boolean printPoint(String name, Object o) {
  302. printName(name);
  303. Point2D p = (Point2D)o;
  304. fw.write("{ \"x\": "+p.getX()+", \"y\": "+p.getY()+" }");
  305. return true;
  306. }
  307. protected boolean printDimension(String name, Object o) {
  308. printName(name);
  309. Dimension2D p = (Dimension2D)o;
  310. fw.write("{ \"width\": "+p.getWidth()+", \"height\": "+p.getHeight()+" }");
  311. return true;
  312. }
  313. protected boolean printRectangle(String name, Object o) {
  314. printName(name);
  315. Rectangle2D p = (Rectangle2D)o;
  316. fw.write("{ \"x\": "+p.getX()+", \"y\": "+p.getY()+", \"width\": "+p.getWidth()+", \"height\": "+p.getHeight()+" }");
  317. return true;
  318. }
  319. protected boolean printPath(String name, Object o) {
  320. printName(name);
  321. final PathIterator iter = ((Path2D)o).getPathIterator(null);
  322. final double[] pnts = new double[6];
  323. fw.write("[");
  324. indent += 2;
  325. String t = tabs();
  326. indent -= 2;
  327. boolean isNext = false;
  328. while (!iter.isDone()) {
  329. fw.println(isNext ? ", " : "");
  330. fw.print(t);
  331. isNext = true;
  332. final int segType = iter.currentSegment(pnts);
  333. fw.append("{ \"type\": ");
  334. switch (segType) {
  335. case PathIterator.SEG_MOVETO:
  336. fw.write("\"move\", \"x\": "+pnts[0]+", \"y\": "+pnts[1]);
  337. break;
  338. case PathIterator.SEG_LINETO:
  339. fw.write("\"lineto\", \"x\": "+pnts[0]+", \"y\": "+pnts[1]);
  340. break;
  341. case PathIterator.SEG_QUADTO:
  342. fw.write("\"quad\", \"x1\": "+pnts[0]+", \"y1\": "+pnts[1]+", \"x2\": "+pnts[2]+", \"y2\": "+pnts[3]);
  343. break;
  344. case PathIterator.SEG_CUBICTO:
  345. fw.write("\"cubic\", \"x1\": "+pnts[0]+", \"y1\": "+pnts[1]+", \"x2\": "+pnts[2]+", \"y2\": "+pnts[3]+", \"x3\": "+pnts[4]+", \"y3\": "+pnts[5]);
  346. break;
  347. case PathIterator.SEG_CLOSE:
  348. fw.write("\"close\"");
  349. break;
  350. }
  351. fw.append(" }");
  352. iter.next();
  353. }
  354. fw.write("]");
  355. return true;
  356. }
  357. protected boolean printObject(String name, Object o) {
  358. printName(name);
  359. fw.write('"');
  360. final String str = o.toString();
  361. final Matcher m = ESC_CHARS.matcher(str);
  362. int pos = 0;
  363. while (m.find()) {
  364. fw.append(str, pos, m.start());
  365. String match = m.group();
  366. switch (match) {
  367. case "\n":
  368. fw.write("\\\\n");
  369. break;
  370. case "\r":
  371. fw.write("\\\\r");
  372. break;
  373. case "\t":
  374. fw.write("\\\\t");
  375. break;
  376. case "\b":
  377. fw.write("\\\\b");
  378. break;
  379. case "\f":
  380. fw.write("\\\\f");
  381. break;
  382. case "\\":
  383. fw.write("\\\\\\\\");
  384. break;
  385. case "\"":
  386. fw.write("\\\\\"");
  387. break;
  388. default:
  389. fw.write("\\\\u");
  390. fw.write(trimHex(match.charAt(0), 4));
  391. break;
  392. }
  393. pos = m.end();
  394. }
  395. fw.append(str, pos, str.length());
  396. fw.write('"');
  397. return true;
  398. }
  399. protected boolean printAffineTransform(String name, Object o) {
  400. printName(name);
  401. AffineTransform xForm = (AffineTransform)o;
  402. fw.write(
  403. "{ \"scaleX\": "+xForm.getScaleX()+
  404. ", \"shearX\": "+xForm.getShearX()+
  405. ", \"transX\": "+xForm.getTranslateX()+
  406. ", \"scaleY\": "+xForm.getScaleY()+
  407. ", \"shearY\": "+xForm.getShearY()+
  408. ", \"transY\": "+xForm.getTranslateY()+" }");
  409. return true;
  410. }
  411. protected boolean printColor(String name, Object o) {
  412. printName(name);
  413. final int rgb = ((Color)o).getRGB();
  414. fw.print(rgb);
  415. if (withComments) {
  416. fw.write(" /* 0x");
  417. fw.write(trimHex(rgb, 8));
  418. fw.write(" */");
  419. }
  420. return true;
  421. }
  422. protected boolean printArray(String name, Object o) {
  423. printName(name);
  424. fw.write("[");
  425. int length = Array.getLength(o);
  426. final int oldChildIndex = childIndex;
  427. for (childIndex=0; childIndex<length; childIndex++) {
  428. writeValue(null, Array.get(o, childIndex));
  429. }
  430. childIndex = oldChildIndex;
  431. fw.write(tabs() + "\t]");
  432. return true;
  433. }
  434. protected boolean printImage(String name, Object o) {
  435. BufferedImage img = (BufferedImage)o;
  436. final String[] COLOR_SPACES = {
  437. "XYZ","Lab","Luv","YCbCr","Yxy","RGB","GRAY","HSV","HLS","CMYK","Unknown","CMY","Unknown"
  438. };
  439. final String[] IMAGE_TYPES = {
  440. "CUSTOM","INT_RGB","INT_ARGB","INT_ARGB_PRE","INT_BGR","3BYTE_BGR","4BYTE_ABGR","4BYTE_ABGR_PRE",
  441. "USHORT_565_RGB","USHORT_555_RGB","BYTE_GRAY","USHORT_GRAY","BYTE_BINARY","BYTE_INDEXED"
  442. };
  443. printName(name);
  444. ColorModel cm = img.getColorModel();
  445. String colorType =
  446. (cm instanceof IndexColorModel) ? "indexed" :
  447. (cm instanceof ComponentColorModel) ? "component" :
  448. (cm instanceof DirectColorModel) ? "direct" :
  449. (cm instanceof PackedColorModel) ? "packed" : "unknown";
  450. fw.write(
  451. "{ \"width\": "+img.getWidth()+
  452. ", \"height\": "+img.getHeight()+
  453. ", \"type\": \""+IMAGE_TYPES[img.getType()]+"\""+
  454. ", \"colormodel\": \""+colorType+"\""+
  455. ", \"pixelBits\": "+cm.getPixelSize()+
  456. ", \"numComponents\": "+cm.getNumComponents()+
  457. ", \"colorSpace\": \""+COLOR_SPACES[Math.min(cm.getColorSpace().getType(),12)]+"\""+
  458. ", \"transparency\": "+cm.getTransparency()+
  459. ", \"alpha\": "+cm.hasAlpha()+
  460. "}"
  461. );
  462. return true;
  463. }
  464. static String trimHex(final long l, final int size) {
  465. final String b = Long.toHexString(l);
  466. int len = b.length();
  467. return ZEROS.substring(0, Math.max(0,size-len)) + b.substring(Math.max(0,len-size), len);
  468. }
  469. static class AppendableWriter extends Writer {
  470. private final Appendable appender;
  471. private final Writer writer;
  472. private String holdBack;
  473. AppendableWriter(Appendable buffer) {
  474. super(buffer);
  475. this.appender = buffer;
  476. this.writer = null;
  477. }
  478. AppendableWriter(Writer writer) {
  479. super(writer);
  480. this.appender = null;
  481. this.writer = writer;
  482. }
  483. void setHoldBack(String holdBack) {
  484. this.holdBack = holdBack;
  485. }
  486. @Override
  487. public void write(char[] cbuf, int off, int len) throws IOException {
  488. if (holdBack != null) {
  489. if (appender != null) {
  490. appender.append(holdBack);
  491. } else if (writer != null) {
  492. writer.write(holdBack);
  493. }
  494. holdBack = null;
  495. }
  496. if (appender != null) {
  497. appender.append(String.valueOf(cbuf), off, len);
  498. } else if (writer != null) {
  499. writer.write(cbuf, off, len);
  500. }
  501. }
  502. @Override
  503. public void flush() throws IOException {
  504. Object o = (appender != null) ? appender : writer;
  505. if (o instanceof Flushable) {
  506. ((Flushable)o).flush();
  507. }
  508. }
  509. @Override
  510. public void close() throws IOException {
  511. flush();
  512. Object o = (appender != null) ? appender : writer;
  513. if (o instanceof Closeable) {
  514. ((Closeable)o).close();
  515. }
  516. }
  517. }
  518. }