1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package io.github.tonywasher.joceanus.metis.viewer;
18
19 import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20 import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
21 import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogManager;
22 import io.github.tonywasher.joceanus.oceanus.logger.OceanusLogger;
23 import io.github.tonywasher.joceanus.metis.data.MetisDataDelta;
24 import io.github.tonywasher.joceanus.metis.data.MetisDataDifference;
25 import io.github.tonywasher.joceanus.metis.exc.MetisIOException;
26 import org.w3c.dom.Document;
27 import org.w3c.dom.Element;
28
29 import javax.xml.XMLConstants;
30 import javax.xml.parsers.DocumentBuilder;
31 import javax.xml.parsers.DocumentBuilderFactory;
32 import javax.xml.transform.Transformer;
33 import javax.xml.transform.TransformerException;
34 import javax.xml.transform.TransformerFactory;
35 import javax.xml.transform.dom.DOMSource;
36 import javax.xml.transform.stream.StreamResult;
37 import java.io.StringWriter;
38
39
40
41
42 public class MetisViewerBuilder {
43
44
45
46 private static final OceanusLogger LOGGER = OceanusLogManager.getLogger(MetisViewerBuilder.class);
47
48
49
50
51 private static final int WRAP_HEXSTRING = 60;
52
53
54
55
56 private static final String ELEMENT_HTML = "html";
57
58
59
60
61 private static final String ELEMENT_BODY = "body";
62
63
64
65
66 private static final String ELEMENT_TITLE = "h2";
67
68
69
70
71 private static final String ELEMENT_LINK = "a";
72
73
74
75
76 private static final String ELEMENT_TABLE = "table";
77
78
79
80
81 private static final String ELEMENT_THEAD = "thead";
82
83
84
85
86 private static final String ELEMENT_TBODY = "tbody";
87
88
89
90
91 private static final String ELEMENT_TROW = "tr";
92
93
94
95
96 private static final String ELEMENT_THDR = "th";
97
98
99
100
101 private static final String ELEMENT_TCELL = "td";
102
103
104
105
106 private static final String ATTR_CLASS = "class";
107
108
109
110
111 private static final String ATTR_HREF = "href";
112
113
114
115
116 private static final String CLASS_VIEWER = "-metis-viewer";
117
118
119
120
121 private static final String CLASS_ODDROW = "-metis-oddrow";
122
123
124
125
126 private static final String CLASS_EVENROW = "-metis-evenrow";
127
128
129
130
131 private static final String CLASS_CHANGED = "-metis-changed";
132
133
134
135
136 private static final String CLASS_SECCHANGED = "-metis-security";
137
138
139
140
141 private final OceanusDataFormatter theFormatter;
142
143
144
145
146 private final Transformer theXformer;
147
148
149
150
151 private final Document theDocument;
152
153
154
155
156 private final Element theBody;
157
158
159
160
161 private MetisViewerPage thePage;
162
163
164
165
166 private Element theTblHdr;
167
168
169
170
171 private Element theTblBody;
172
173
174
175
176 private Element theTblRow;
177
178
179
180
181 private int theNumRows;
182
183
184
185
186
187
188
189 protected MetisViewerBuilder(final OceanusDataFormatter pFormatter) throws OceanusException {
190
191 try {
192
193 theFormatter = pFormatter;
194
195
196 final DocumentBuilderFactory myDocFactory = DocumentBuilderFactory.newInstance();
197 myDocFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
198 myDocFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
199 myDocFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
200
201
202 final DocumentBuilder myBuilder = myDocFactory.newDocumentBuilder();
203
204
205 final TransformerFactory myXformFactory = TransformerFactory.newInstance();
206 myXformFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
207 myXformFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
208 myXformFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
209 theXformer = myXformFactory.newTransformer();
210
211
212 theDocument = myBuilder.newDocument();
213
214
215 final Element myHtml = theDocument.createElement(ELEMENT_HTML);
216 theDocument.appendChild(myHtml);
217 theBody = theDocument.createElement(ELEMENT_BODY);
218 myHtml.appendChild(theBody);
219
220 } catch (Exception e) {
221 throw new MetisIOException("Failed to create", e);
222 }
223 }
224
225
226
227
228 protected void formatDocument() {
229
230 try {
231
232 final StringWriter myWriter = new StringWriter();
233 theXformer.transform(new DOMSource(theDocument), new StreamResult(myWriter));
234
235
236 thePage.setHtml(myWriter.toString());
237
238 } catch (TransformerException e) {
239 LOGGER.error("Failed to format document", e);
240 }
241 }
242
243
244
245
246
247
248 private static void clearElement(final Element pElement) {
249
250 while (pElement.hasChildNodes()) {
251 pElement.removeChild(pElement.getFirstChild());
252 }
253 }
254
255
256
257
258
259
260 protected void resetDocument(final MetisViewerPage pPage) {
261
262 thePage = pPage;
263 thePage.resetPage();
264
265
266 clearElement(theBody);
267 }
268
269
270
271
272
273
274 protected void newTitle(final String pTitle) {
275
276 final Element myTitle = theDocument.createElement(ELEMENT_TITLE);
277 theBody.appendChild(myTitle);
278 myTitle.setTextContent(pTitle);
279 }
280
281
282
283
284 protected void newTable() {
285
286 final Element myTable = theDocument.createElement(ELEMENT_TABLE);
287 myTable.setAttribute(ATTR_CLASS, CLASS_VIEWER);
288 theBody.appendChild(myTable);
289 final Element myHead = theDocument.createElement(ELEMENT_THEAD);
290 myTable.appendChild(myHead);
291 theTblHdr = theDocument.createElement(ELEMENT_TROW);
292 myHead.appendChild(theTblHdr);
293 theTblBody = theDocument.createElement(ELEMENT_TBODY);
294 myTable.appendChild(theTblBody);
295
296
297 theTblRow = null;
298 theNumRows = 0;
299 }
300
301
302
303
304
305
306 protected void newTitleCell(final String pTitle) {
307
308 final Element myCell = theDocument.createElement(ELEMENT_THDR);
309 theTblHdr.appendChild(myCell);
310 myCell.setTextContent(pTitle);
311 }
312
313
314
315
316 protected void newTableRow() {
317
318 theTblRow = theDocument.createElement(ELEMENT_TROW);
319 theTblBody.appendChild(theTblRow);
320
321
322 theTblRow.setAttribute(ATTR_CLASS, (theNumRows % 2 == 0)
323 ? CLASS_EVENROW
324 : CLASS_ODDROW);
325 theNumRows++;
326 }
327
328
329
330
331
332
333 protected void newDataCell(final Object pData) {
334 newDataCell(pData, false);
335 }
336
337
338
339
340
341
342
343 protected void newDataCell(final Object pData,
344 final boolean pChanged) {
345
346 final Element myCell = theDocument.createElement(ELEMENT_TCELL);
347 theTblRow.appendChild(myCell);
348
349
350 final String myText = formatValue(pData);
351
352
353 if (pData instanceof MetisDataDelta myDelta) {
354
355 final MetisDataDifference myDiff = myDelta.getDifference();
356
357
358 if (!myDiff.isIdentical()) {
359 myCell.setAttribute(ATTR_CLASS, myDiff.isValueChanged()
360 ? CLASS_CHANGED
361 : CLASS_SECCHANGED);
362 }
363 } else if (pChanged) {
364 myCell.setAttribute(ATTR_CLASS, CLASS_CHANGED);
365 }
366
367
368 if (MetisViewerPage.isLinkable(pData)) {
369
370 final Element myLink = theDocument.createElement(ELEMENT_LINK);
371 myCell.appendChild(myLink);
372 myLink.setAttribute(ATTR_HREF, thePage.newLink(pData));
373 myLink.setTextContent(myText);
374
375
376 } else {
377 myCell.setTextContent(myText);
378 }
379 }
380
381
382
383
384
385
386
387 private String formatValue(final Object pValue) {
388
389 String myFormat = theFormatter.formatObject(pValue);
390
391
392 if (needsWrapping(pValue)
393 && (myFormat.length() > WRAP_HEXSTRING)) {
394 final StringBuilder myBuffer = new StringBuilder(myFormat.length() << 1);
395
396
397 myBuffer.append(myFormat);
398
399
400 int iCount = myFormat.length()
401 / WRAP_HEXSTRING;
402 while (iCount > 0) {
403 myBuffer.insert(WRAP_HEXSTRING
404 * iCount--, '\n');
405 }
406
407
408 myFormat = myBuffer.toString();
409 }
410
411
412 return myFormat;
413 }
414
415
416
417
418
419
420
421 private static boolean needsWrapping(final Object pObject) {
422
423 Object myObject = pObject;
424 if (myObject instanceof MetisDataDelta) {
425 myObject = ((MetisDataDelta) pObject).getObject();
426 }
427 return myObject instanceof byte[];
428 }
429 }