OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Blame | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.utils.doml;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;

public class Document {

    private Element root;
    private byte[] buffer = new byte[64];

    public Document() {
    }

    public Document(String rootName) {
        this.root = new Element(rootName);
    }

    public Document(Element root) {
        this.root = root;
    }

    public void loadFrom(BufferedReader br) throws IOException {
        this.root = readElement(new DOMLStreamReader(br), 0);
    }

    private Element readElement(DOMLStreamReader r, int depth) throws IOException {
        int t = r.readNextToken();
        if (t == DOMLStreamReader.EOF || t == DOMLStreamReader.NO_TAG_CHARACTER) {
            return null;
        }
        if (t != DOMLStreamReader.TAG_START) {
            throw new IOException("not element start found (" + t + ")");
        }
        final String tagName = r.readTagName();
        final Element current = new Element(tagName);
        t = r.readNextToken();
        if (t == DOMLStreamReader.TAG_END_COMPACT) {
            return current;
        } else if (t == DOMLStreamReader.TAG_END) {
            readChildren(r, depth, tagName, current);
            return current;
        } else if (t == DOMLStreamReader.NO_TAG_CHARACTER) {
            while (t == DOMLStreamReader.NO_TAG_CHARACTER) {
                final String name = r.readAttributeName();
                final String value = r.readAttributeValue();
                current.setAttribute(name, value);
                t = r.readNextToken();
            }
            if (t == DOMLStreamReader.TAG_END) {
                readChildren(r, depth, tagName, current);
            }
            return current;

        }
        return null;
    }

    private void readChildren(DOMLStreamReader r, int depth, String tagName, Element current) throws IOException {
        int t;
        boolean tryReadNextChild = false;
        do {
            char n1 = r.readNextChar();
            while (n1 < 33) {
                // Optimization for : n1 == ' ' || n1 == '\n' || n1 == '\r' || n1 == '\t'
                n1 = r.readNextChar();
            }
            if (n1 == 65535) {
                // EOF
                return;
            }

            tryReadNextChild = false;
            if (n1 == '<') {
                char n2 = r.readNextChar();
                if (n2 != '/') {
                    tryReadNextChild = true;
                    r.unread(n2);
                    r.unread(n1);
                    Element e = readElement(r, depth + 1);
                    if (e != null) {
                        current.addChild(e);
                    }
                } else {
                    r.unread(n2);
                    r.unread(n1);
                }
            }

        } while (tryReadNextChild);

        // end tag
        t = r.readNextToken();

        if (t != DOMLStreamReader.TAG_START) {
            throw new IOException("not closing element start found for " + tagName + " (" + t + ") : '" + r.readNextChar());
        }

        final String ctagName = r.readTagName();

        t = r.readNextToken();
        if (t != DOMLStreamReader.TAG_END) {
            throw new IOException("closing element end tag for '" + ctagName + "' is missing " + t);
        }
    }

    public void writeTo(BufferedWriter writer) throws IOException {
        if (this.root == null)
            return;
        writeTo(writer, 0, this.root);
        writer.flush();
    }

    private Element readElement(BufferedInputStream in) throws IOException {

        final String name = readUTF(in);
        final int attributeCount = readInt(in);
        final int childrenCount = readInt(in);
        final Element e = new Element(name, attributeCount, childrenCount);
        for (int i = 0; i < attributeCount; i++) {
            final String n = readUTF(in);
            final String v = readUTF(in);
            e.addAttributeNoCheck(n, v);
        }
        for (int i = 0; i < childrenCount; i++) {
            e.addChildNoCheck(readElement(in));
        }
        return e;
    }

    public void loadFromBinary(BufferedInputStream in) throws IOException {
        this.root = readElement(in);
    }

    public void loadFromBinary(File file) throws IOException {
        try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(file))) {
            this.root = readElement(in);
        }
    }

    public void loadFromBinary(byte[] bytes) throws IOException {
        try (BufferedInputStream in = new BufferedInputStream(new ByteArrayInputStream(bytes))) {
            this.root = readElement(in);
        }
    }

    public void writeToBinary(BufferedOutputStream out) throws IOException {
        if (this.root == null) {
            return;
        }
        writeTo(out, this.root);
        out.flush();
    }

    public void writeToBinary(File file) throws IOException {
        try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
            this.writeToBinary(out);
        }
    }

    private void writeTo(BufferedOutputStream out, Element e) throws IOException {
        // ObjectStream are not used because readUTF allocates too much char[]
        writeUTF(out, e.getName());
        final int attributeCount = e.getAttributeCount();
        writeInt(out, attributeCount);
        writeInt(out, e.getChildrenCount());
        // write attributes
        if (attributeCount > 0) {
            e.writeAttributes(out);
        }
        // write children
        for (Element c : e.getChildren()) {
            writeTo(out, c);
        }
    }

    public byte[] toByteArray() throws IOException {
        if (this.root == null) {
            return new byte[0];
        }
        final ByteArrayOutputStream out2 = new ByteArrayOutputStream(1024 + this.root.getChildrenCount() * 32);
        try (BufferedOutputStream out = new BufferedOutputStream(out2)) {
            this.writeToBinary(out);
        }
        return out2.toByteArray();
    }

    private static final void writeInt(BufferedOutputStream out, int v) throws IOException {
        out.write((v >>> 24) & 0xFF);
        out.write((v >>> 16) & 0xFF);
        out.write((v >>> 8) & 0xFF);
        out.write((v) & 0xFF);
    }

    static final void writeUTF(BufferedOutputStream out, String str) throws IOException {
        final byte[] buf = str.getBytes(StandardCharsets.UTF_8);
        writeInt(out, buf.length);
        out.write(buf);
    }

    private final int readInt(BufferedInputStream in) throws IOException {
        final int ch1 = in.read();
        final int ch2 = in.read();
        final int ch3 = in.read();
        final int ch4 = in.read();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
    }

    private final String readUTF(BufferedInputStream in) throws IOException {
        final int size = readInt(in);
        if (this.buffer.length < size) {
            this.buffer = new byte[size + 32];
        }
        final int s = in.read(this.buffer, 0, size);
        return new String(this.buffer, 0, s, StandardCharsets.UTF_8);
    }

    @Override
    public String toString() {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            writeTo(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)));
            return out.toString(StandardCharsets.UTF_8.name());
        } catch (IOException e) {
            return e.getMessage();
        }
    }

    private void writeTo(final BufferedWriter writer, int depth, Element e) throws IOException {
        if (e.getChildrenCount() == 0) {
            for (int i = 0; i < depth; i++) {
                writer.append(' ');
            }
            writer.append('<');
            writer.append(e.getName());
            e.appendAttributes(writer);
            writer.append(" />\n");
        } else {
            for (int i = 0; i < depth; i++) {
                writer.append(' ');
            }
            writer.append('<');
            writer.append(e.getName());
            e.appendAttributes(writer);
            writer.append(">\n");

            int d = depth + 1;
            for (Element child : e.getChildren()) {
                writeTo(writer, d, child);
            }
            for (int i = 0; i < depth; i++) {
                writer.append(' ');
            }
            writer.append("</");
            writer.append(e.getName());
            writer.append(">\n");
        }
    }

    public Element getRoot() {
        return this.root;
    }

}