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

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