diff --git a/datablock/src/main/java/tv/hd3g/datablock/ser/DatablockObjectSerializer.java b/datablock/src/main/java/tv/hd3g/datablock/ser/DatablockObjectSerializer.java new file mode 100644 index 00000000..9949e185 --- /dev/null +++ b/datablock/src/main/java/tv/hd3g/datablock/ser/DatablockObjectSerializer.java @@ -0,0 +1,194 @@ +/* + * This file is part of datablock. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * Copyright (C) hdsdi3g for hd3g.tv 2024 + * + */ +package tv.hd3g.datablock.ser; + +import static com.fasterxml.jackson.core.JsonEncoding.UTF8; + +import java.io.IOException; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.helpers.DefaultHandler; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +import lombok.extern.slf4j.Slf4j; +import tv.hd3g.datablock.DataBlockChunkIndexItem; +import tv.hd3g.datablock.DatablockChunkPayloadExtractor; +import tv.hd3g.datablock.DatablockDocument; + +/** + * Stateless, reusable + */ +@Slf4j +public class DatablockObjectSerializer {// TODO test + + private final SAXParserFactory saxParserFactory; + private final XMLEventFactory xmlEventFactory; + private final XMLOutputFactory xmlOutputFactory; + private final JsonFactory jsonFactory; + + public DatablockObjectSerializer() { + saxParserFactory = SAXParserFactory.newInstance(); + try { + saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); + saxParserFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + } catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) { + throw new InternalError("Can't load SAX parser", e); + } + + xmlEventFactory = XMLEventFactory.newInstance(); + xmlOutputFactory = XMLOutputFactory.newInstance(); + jsonFactory = new JsonFactory(); + } + + public T xmlDeserialize(final DatablockChunkPayloadExtractor payloadSource, + final H handler, + final Function processor) throws IOException { + return payloadSource.createInputStream(inputStream -> { + try { + saxParserFactory.newSAXParser().parse(new InputSource(inputStream), handler); + } catch (ParserConfigurationException | SAXException e) { + throw new ImportExportException("Can't process XML", e); + } + return processor.apply(handler); + }); + } + + public DataBlockChunkIndexItem xmlSerialize(final DatablockDocument document, + final byte[] fourCC, + final short version, + final boolean archived, + final T object, + final BiConsumer serializer) throws IOException { + + return document.appendChunk(fourCC, version, archived, + outputStream -> { + try { + final var writer = xmlOutputFactory.createXMLEventWriter(outputStream); + final var xmlEventWriterWrapper = new XMLEventWriterWrapper(writer, xmlEventFactory); + serializer.accept(object, xmlEventWriterWrapper); + writer.close(); + } catch (final XMLStreamException e) { + throw new ImportExportException("Can't make XML document", e); + } + }); + } + + public T jsonDeserialize(final DatablockChunkPayloadExtractor payloadSource, + final H handler, + final Function processor) throws IOException { + return payloadSource.createInputStream(inputStream -> { + try (final var parser = jsonFactory.createParser(inputStream)) { + jsonCrawnReader(handler, parser); + } + return processor.apply(handler); + }); + } + + void jsonCrawnReader(final JsonParserEventHandler handler, final JsonParser parser) throws IOException { // NOSONAR 3776 + String fieldName = null; + while (!parser.isClosed()) { + final var jsonToken = parser.nextToken(); + + if (jsonToken == JsonToken.START_OBJECT) { + handler.startObject(); + fieldName = null; + } else if (jsonToken == JsonToken.END_OBJECT) { + handler.endObject(); + fieldName = null; + } else if (jsonToken == JsonToken.START_ARRAY) { + handler.startArray(); + fieldName = null; + } else if (jsonToken == JsonToken.END_ARRAY) { + handler.endArray(); + fieldName = null; + } else if (jsonToken == JsonToken.FIELD_NAME) { + if (fieldName != null) { + log.warn("Json fieldName on fieldName ? {} on {} ({}) ?", + fieldName, parser.currentName(), parser); + } + fieldName = parser.currentName(); + } else if (jsonToken == JsonToken.VALUE_STRING) { + if (fieldName != null) { + handler.field(fieldName, parser.getValueAsString()); + } else { + handler.value(parser.getValueAsString()); + } + fieldName = null; + } else if (jsonToken == JsonToken.VALUE_NUMBER_INT) { + if (fieldName != null) { + handler.field(fieldName, parser.getValueAsLong()); + } else { + handler.value(parser.getValueAsLong()); + } + fieldName = null; + } else if (jsonToken == JsonToken.VALUE_NUMBER_FLOAT) { + if (fieldName != null) { + handler.field(fieldName, parser.getValueAsDouble()); + } else { + handler.value(parser.getValueAsDouble()); + } + fieldName = null; + } else if (jsonToken == JsonToken.VALUE_TRUE || jsonToken == JsonToken.VALUE_FALSE) { + if (fieldName != null) { + handler.field(fieldName, jsonToken == JsonToken.VALUE_TRUE); + } else { + handler.value(jsonToken == JsonToken.VALUE_TRUE); + } + fieldName = null; + } else if (jsonToken == JsonToken.VALUE_NULL) { + if (fieldName != null) { + handler.nullField(fieldName); + } else { + handler.nullValue(); + } + fieldName = null; + } else { + log.warn("Non managed Json: {} on {}", jsonToken, parser); + } + } + } + + public DataBlockChunkIndexItem jsonSerialize(final DatablockDocument document, + final byte[] fourCC, + final short version, + final boolean archived, + final T object, + final JsonGeneratorConsumer serializer) throws IOException { + return document.appendChunk(fourCC, version, archived, + outputStream -> { + try (final var jsonGenerator = jsonFactory.createGenerator(outputStream, UTF8)) { + serializer.accept(object, jsonGenerator); + } + }); + } + +} diff --git a/datablock/src/main/java/tv/hd3g/datablock/ser/ImportExportException.java b/datablock/src/main/java/tv/hd3g/datablock/ser/ImportExportException.java new file mode 100644 index 00000000..922982f7 --- /dev/null +++ b/datablock/src/main/java/tv/hd3g/datablock/ser/ImportExportException.java @@ -0,0 +1,25 @@ +/* + * This file is part of datablock. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * Copyright (C) hdsdi3g for hd3g.tv 2024 + * + */ +package tv.hd3g.datablock.ser; + +public class ImportExportException extends RuntimeException {// TODO test + + public ImportExportException(final String message, final Exception source) { + super(message, source); + } + +} diff --git a/datablock/src/main/java/tv/hd3g/datablock/ser/JsonGeneratorConsumer.java b/datablock/src/main/java/tv/hd3g/datablock/ser/JsonGeneratorConsumer.java new file mode 100644 index 00000000..752eb879 --- /dev/null +++ b/datablock/src/main/java/tv/hd3g/datablock/ser/JsonGeneratorConsumer.java @@ -0,0 +1,28 @@ +/* + * This file is part of datablock. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * Copyright (C) hdsdi3g for hd3g.tv 2024 + * + */ +package tv.hd3g.datablock.ser; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; + +@FunctionalInterface +public interface JsonGeneratorConsumer { + + void accept(T t, JsonGenerator g) throws IOException; + +} diff --git a/datablock/src/main/java/tv/hd3g/datablock/ser/JsonParserEventHandler.java b/datablock/src/main/java/tv/hd3g/datablock/ser/JsonParserEventHandler.java new file mode 100644 index 00000000..2006f9ef --- /dev/null +++ b/datablock/src/main/java/tv/hd3g/datablock/ser/JsonParserEventHandler.java @@ -0,0 +1,49 @@ +/* + * This file is part of datablock. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * Copyright (C) hdsdi3g for hd3g.tv 2024 + * + */ +package tv.hd3g.datablock.ser; + +public interface JsonParserEventHandler { + + void startObject(); + + void endObject(); + + void startArray(); + + void endArray(); + + void field(String name, String v); + + void field(String name, long v); + + void field(String name, double v); + + void field(String name, boolean v); + + void nullField(String name); + + void value(String v); + + void value(long v); + + void value(double v); + + void value(boolean v); + + void nullValue(); + +} diff --git a/datablock/src/main/java/tv/hd3g/datablock/ser/Ser.java b/datablock/src/main/java/tv/hd3g/datablock/ser/Ser.java deleted file mode 100644 index c20700c0..00000000 --- a/datablock/src/main/java/tv/hd3g/datablock/ser/Ser.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * This file is part of datablock. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * Copyright (C) hdsdi3g for hd3g.tv 2024 - * - */ -package tv.hd3g.datablock.ser; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParserFactory; -import javax.xml.stream.XMLEventFactory; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; - -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import com.fasterxml.jackson.core.JsonEncoding; -import com.fasterxml.jackson.core.JsonFactory; - -public class Ser { - - public Ser() throws IOException { - // TODO create serializers - // TODO check if all jackson deps are needed - // TODO merge API - - final var jsonFactory = new JsonFactory(); - - final var baos = new ByteArrayOutputStream(); - try (final var json = jsonFactory.createGenerator(baos, JsonEncoding.UTF8)) { - json.writeStartObject(); - json.writeFieldName("report"); - json.writeStartObject(); - json.writeEndObject(); - json.writeEndObject(); - } - - var bais = new ByteArrayInputStream(baos.toByteArray()); - try (final var json = jsonFactory.createParser(bais)) { - // https://jenkov.com/tutorials/java-json/jackson-jsonparser.html - } - - baos.reset(); - final var xml = XMLEventFactory.newInstance(); - try { - final var writer = XMLOutputFactory.newInstance() - .createXMLEventWriter(baos); - writer.add(xml.createStartDocument()); - writer.add(xml.createStartElement("", null, "REPORT")); - writer.add(xml.createEndElement("", null, "REPORT")); - writer.add(xml.createEndDocument()); - writer.close(); - } catch (final XMLStreamException e) { - throw new IllegalStateException("Can't write XML file", e); - } - - bais = new ByteArrayInputStream(baos.toByteArray()); - try { - final var factory = SAXParserFactory.newInstance(); - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - factory.setFeature("http://xml.org/sax/features/external-general-entities", false); - factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - factory.newSAXParser().parse(new InputSource(bais), new SAXP()); - } catch (SAXException | ParserConfigurationException e) { - throw new InternalError("Can't load SAX parser", e); - } - } - - class SAXP extends DefaultHandler { - - @Override - public void startDocument() throws SAXException { - } - - @Override - public void endDocument() throws SAXException { - } - } - -} diff --git a/datablock/src/main/java/tv/hd3g/datablock/ser/XMLEventWriterWrapper.java b/datablock/src/main/java/tv/hd3g/datablock/ser/XMLEventWriterWrapper.java new file mode 100644 index 00000000..af6ccb18 --- /dev/null +++ b/datablock/src/main/java/tv/hd3g/datablock/ser/XMLEventWriterWrapper.java @@ -0,0 +1,170 @@ +/* + * This file is part of datablock. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * Copyright (C) hdsdi3g for hd3g.tv 2024 + * + */ +package tv.hd3g.datablock.ser; + +import static java.util.Objects.requireNonNull; + +import java.util.Iterator; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Attribute; +import javax.xml.stream.events.EntityDeclaration; +import javax.xml.stream.events.Namespace; +import javax.xml.stream.events.XMLEvent; + +public class XMLEventWriterWrapper {// TODO test + + private final XMLEventWriter writer; + private final XMLEventFactory xml; + + XMLEventWriterWrapper(final XMLEventWriter writer, final XMLEventFactory xml) { + this.writer = requireNonNull(writer); + this.xml = requireNonNull(xml); + } + + private void add(final XMLEvent event) { + try { + writer.add(event); + } catch (final XMLStreamException e) { + throw new IllegalStateException("Can't push an write event to XML stream", e); + } + } + + public void createAttribute(final String prefix, + final String namespaceURI, + final String localName, + final String value) { + add(xml.createAttribute(prefix, namespaceURI, localName, value)); + } + + public void createAttribute(final String localName, final String value) { + add(xml.createAttribute(localName, value)); + } + + public void createAttribute(final QName name, final String value) { + add(xml.createAttribute(name, value)); + } + + public void createNamespace(final String namespaceURI) { + add(xml.createNamespace(namespaceURI)); + } + + public void createNamespace(final String prefix, final String namespaceUri) { + add(xml.createNamespace(prefix, namespaceUri)); + } + + public void createStartElement(final QName name, + final Iterator attributes, + final Iterator namespaces) { + add(xml.createStartElement(name, attributes, namespaces)); + } + + public void createStartElement(final String prefix, final String namespaceUri, final String localName) { + add(xml.createStartElement(prefix, namespaceUri, localName)); + } + + public void createStartElement(final String prefix, + final String namespaceUri, + final String localName, + final Iterator attributes, + final Iterator namespaces) { + add(xml.createStartElement(prefix, namespaceUri, localName, attributes, namespaces)); + } + + public void createStartElement(final String prefix, + final String namespaceUri, + final String localName, + final Iterator attributes, + final Iterator namespaces, + final NamespaceContext context) { + add(xml.createStartElement(prefix, namespaceUri, localName, attributes, namespaces, context)); + } + + public void createEndElement(final QName name, final Iterator namespaces) { + add(xml.createEndElement(name, namespaces)); + } + + public void createEndElement(final String prefix, final String namespaceUri, final String localName) { + add(xml.createEndElement(prefix, namespaceUri, localName)); + } + + public void createEndElement(final String prefix, + final String namespaceUri, + final String localName, + final Iterator namespaces) { + add(xml.createEndElement(prefix, namespaceUri, localName, namespaces)); + } + + public void createCharacters(final String content) { + add(xml.createCharacters(content)); + } + + public void createCData(final String content) { + add(xml.createCData(content)); + } + + public void createSpace(final String content) { + add(xml.createSpace(content)); + } + + public void createIgnorableSpace(final String content) { + add(xml.createIgnorableSpace(content)); + } + + public void createStartDocument() { + add(xml.createStartDocument()); + } + + public void createStartDocument(final String encoding, + final String version, + final boolean standalone) { + add(xml.createStartDocument(encoding, version, standalone)); + } + + public void createStartDocument(final String encoding, final String version) { + add(xml.createStartDocument(encoding, version)); + } + + public void createStartDocument(final String encoding) { + add(xml.createStartDocument(encoding)); + } + + public void createEndDocument() { + add(xml.createEndDocument()); + } + + public void createEntityReference(final String name, final EntityDeclaration declaration) { + add(xml.createEntityReference(name, declaration)); + } + + public void createComment(final String text) { + add(xml.createComment(text)); + } + + public void createProcessingInstruction(final String target, final String data) { + add(xml.createProcessingInstruction(target, data)); + } + + public void createDTD(final String dtd) { + add(xml.createDTD(dtd)); + } + +}