net.owl_black.vmgparser.VmgParser.java Source code

Java tutorial

Introduction

Here is the source code for net.owl_black.vmgparser.VmgParser.java

Source

package net.owl_black.vmgparser;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.logging.Logger;

import ezvcard.Ezvcard;
import ezvcard.VCard;
import ezvcard.util.org.apache.commons.codec.DecoderException;
import ezvcard.util.org.apache.commons.codec.net.QuotedPrintableCodec;
import net.owl_black.vmgparser.VmgObj.XIrmcBox;
import net.owl_black.vmgparser.VmgObj.XIrmcStatus;

/* Copyright (c) 2012-2015, Louis-Paul CORDIER
 * All rights reserved.
 * 
 * This file is part of vmgparser library.
 * Vmgparser library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Vmgparser library 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with vmgparser library.  If not, see <http://www.gnu.org/licenses/>. */

public class VmgParser {

    VmgLexer lexer;
    VmgToken c_tok; //current token
    VmgToken p_tok; //previous token
    Stack<String> env_stack; //Stack for storing environnement.
    Stack<String> opt_stack; //Stack for storing options
    VCard vcard;
    boolean parseVBody;

    DateTimeFormatter[] date_patterns = { DateTimeFormatter.ofPattern("yyyy"),
            DateTimeFormatter.ofPattern("d.M.yyyy H:m:s"), DateTimeFormatter.ofPattern("M.d.yyyy H:m:s"),

            DateTimeFormatter.ofPattern("yyyy.d.M.H:m:s"), DateTimeFormatter.ofPattern("yyyy.d.M.H:m:s"),

            DateTimeFormatter.ofPattern("yyyy.M.d.H.m.s"), DateTimeFormatter.ofPattern("yyyy.M.d.H.m.s'Z'"),

            DateTimeFormatter.ofPattern("yyyy.d.M.H.m.s"), DateTimeFormatter.ofPattern("yyyy.d.M.H.m.s'Z'"), };

    private static Logger log = Logger.getLogger(VmgParser.class.getName());

    public VmgParser(File f, String encoding) throws UnsupportedEncodingException, FileNotFoundException {
        if (f == null)
            throw new NullPointerException("Given file is null");

        if (!f.exists())
            throw new FileNotFoundException("Provided VMG file does not exist.");

        lexer = new VmgLexer(f, encoding);
        env_stack = new Stack<String>();
        opt_stack = new Stack<String>();

        //Initialize the list of Date parser.

    }

    private void expect(VmgTokenType toktype) {
        if (found(toktype)) {
            return;
        } else {
            log.severe("Line: " + c_tok.getLineNb() + " - I expected " + toktype._name + " but got "
                    + c_tok.type._name);
            System.err.flush(); //TODO: fix that shit
        }

    }

    private boolean found(VmgTokenType toktype) {
        if (c_tok.type == toktype) {
            getToken();
            return true;
        }
        return false;
    }

    private boolean found_nostep(VmgTokenType toktype) { //Same as found, but without any token step.
        if (c_tok.type == toktype) {
            return true;
        }
        return false;
    }

    private void vmg_linefeed() {

        boolean already_warn = false;

        if (!found(VmgTokenType.CRLF))
            expect(VmgTokenType.LINEFEED);

        while (found(VmgTokenType.LINEFEED) || found(VmgTokenType.CRLF)) {
            if (!already_warn) {
                log.warning("Line: " + p_tok.getLineNb()
                        + " - Extra linefeed detected. This VMG does not respect the RFC norm!");
                already_warn = true;
            }
        }

    }

    private VmgToken vmg_begin() {

        VmgToken retTok;

        if (!found(VmgTokenType.ID_BEGIN))
            return null;

        //We found the "BEGIN" keyword, start to process the beacon.
        expect(VmgTokenType.SYM_COLON);
        expect(VmgTokenType.IDENTIFIER);

        //Store the token that we will return
        retTok = p_tok;

        //TODO: handle errors
        vmg_linefeed();

        return retTok;
    }

    private VmgToken vmg_end() {

        VmgToken retTok;

        if (!found(VmgTokenType.ID_END))
            return null;

        //We found the "BEGIN" keyword, start to process the beacon.
        expect(VmgTokenType.SYM_COLON);
        expect(VmgTokenType.IDENTIFIER);

        //Store the token that we will return
        retTok = p_tok;

        //TODO: handle errors
        vmg_linefeed();

        return retTok;
    }

    private VmgProperty vmg_property(boolean isInVbody) {

        if (!found(VmgTokenType.IDENTIFIER))
            return null;

        VmgProperty vProp;
        String params = null;

        //contentline = [group "."] name *(";" param) ":" value CRLF
        vProp = new VmgProperty(p_tok.content);

        //Check for parameters
        if (found(VmgTokenType.SYM_SCOLON)) {
            params = c_tok.content;
            getToken();

            //Get the param list
            while (!found(VmgTokenType.SYM_COLON)) { //Retrieve data until cariage return
                params += c_tok.content;
                getToken();
            }

            //Process params:
            vProp.params = process_params(params);

        } else if (found(VmgTokenType.SYM_COLON)) {
            //DO nothing
        } else {
            //We have full text. This can happen only when we are inside the vbody beacon.
            //If it is not the case, simply return an error.
            if (isInVbody) {
                vProp.value = vProp.name + vProp.value;

                //Change the property to text
                vProp.name = "TEXT";

            } else //TODO: add line number in the backlog.
                log.severe("Non standard property. VMG is not well formatted.");
        }

        //READ value
        while (true) {
            //Check previous character
            if (found_nostep(VmgTokenType.CRLF)) { //Retrieve data until cariage return
                if (p_tok.type != VmgTokenType.SYM_EQUAL)
                    break;
                else if (!vProp.params.quoted_printable)
                    break;
            }

            if (found_nostep(VmgTokenType.LINEFEED))
                break;

            getToken();
            vProp.value += p_tok.content;
        }

        getToken();

        return vProp;

    }

    private VCard vmg_vcard() {

        VmgToken tok;
        String vcard_txt;

        //TODO: add check for the number of originator.
        //Get the entire vcard:
        vcard_txt = "BEGIN:VCARD\n";

        while (!found_nostep(VmgTokenType.ID_END)) {
            vcard_txt += c_tok.content;
            getToken();
        }

        if ((tok = vmg_end()) == null) {
            log.severe("End of the vcard is mis-formated.");
            //TODO: stop algorithm.
        }

        if (!tok.content.equals("VCARD")) {
            log.severe(" Vcard is not closed properly (" + p_tok.content + ")");
        }

        //Parse vcard:
        vcard_txt += "END:VCARD";
        return Ezvcard.parse(vcard_txt).first();
    }

    private VmgEnvelope vmg_envelope() {

        /*"BEGIN:VENV"<CRLF>
         * {
         *       [<vmessage-recipient>]*
         *      <vmessage-envelope> * | <vmessage-content>
         * }
         * "END:VENV"<CRLF> */

        VmgEnvelope vE = new VmgEnvelope();
        VmgToken tok;
        List<VCard> vOriginator = null;

        while (true) {
            //Check the next environment:
            if ((tok = vmg_begin()) == null) {
                log.severe("Bad VMG formatting: I expected \"BEGIN\"");
                return null;
            }

            if (!tok.content.equals("VCARD"))
                break;

            //[<vmessage-recipient>]*
            VCard vC = vmg_vcard();

            if (vC != null) {

                if (vOriginator == null) {
                    vOriginator = new ArrayList<VCard>();
                }

                vOriginator.add(vC);

            } else {
                log.severe("EzVcard library wasn't able to parse correctly the vcard.");
                return null;
            }

            //TODO: handle correctly linefeed
            if (found(VmgTokenType.LINEFEED))
                System.out.println("Linefeed detected after the vcard. This VMG does not respect the RFC norm!");
        }

        //Fullfill the vEnv with the new list of originator.
        vE.setvOriginator(vOriginator);

        //<vmessage-envelope>*
        if (tok.content.equals("VENV")) {
            vE.setvEnv(vmg_envelope()); //recursively enter into the venveloppe.
        } else if (tok.content.equals("VBODY")) { // <vmessage-content>
            //Handle vbody
            VmgBody vB;

            if (parseVBody)
                vB = vmg_body_extended();
            else
                vB = vmg_body();

            if (vB != null) {
                vE.setvBody(vB);
                //            System.out.println("[VBody]");
                //            System.out.print(vB.toString());
                //            System.out.println("end [VBody]");
            }
        } else {
            //error
            log.severe("TO HANDLE");
        }

        return vE;
    }

    private VmgBody vmg_body() {

        String bodyContent = "";
        VmgToken tok;

        while (!found_nostep(VmgTokenType.ID_END)) {
            bodyContent += c_tok.content;
            getToken();
            //TODO: handle correctly the \n that can occurs at the end of the string.
            //MUST be handled in the extended version of the VmgBody
        }

        if ((tok = vmg_end()) == null) {
            log.severe("End of the vbody is mis-formated.");
            return null;
            //TODO: stop algorithm.
        }

        if (!tok.content.equals("VBODY")) {
            log.severe(" Vbody is not closed properly (" + p_tok.content + ")");
            return null;
        }

        //Create a new vbody object
        return new VmgBody(bodyContent);
    }

    /*
    private String streamToString(InputStream in) throws IOException {
       StringBuilder out = new StringBuilder();
       BufferedReader br = new BufferedReader(new InputStreamReader(in));
       for(String line = br.readLine(); line != null; line = br.readLine())
     out.append(line);
       br.close();
       return out.toString();
    }*/

    private VmgBodyExtended vmg_body_extended() {
        VmgProperty vP = null;

        VmgBodyExtended vBe = new VmgBodyExtended();
        List<VmgProperty> properties = vBe.getvProp();
        QuotedPrintableCodec decoder = new QuotedPrintableCodec();

        while ((vP = vmg_property(true)) != null) {

            if ((vP.params != null) && (vP.params.quoted_printable)) {
                //Process quoted printable:
                try {
                    vP.value = decoder.decode(vP.value);
                } catch (DecoderException e) {
                    // TODO Auto-generated catch block
                    log.warning("Impossible to decode the property containing quoted printable text.");
                }
            }

            if (vP.name.equalsIgnoreCase("TEXT")) //Retrieve text from the VMG
            {
                vBe.setContent(vP.value);
            } else if (vP.name.equalsIgnoreCase("DATE")) //Retrieve date from the VMG.
            {
                boolean date_parse_suceed = false;
                for (DateTimeFormatter dtf : date_patterns) {
                    try {
                        LocalDateTime lDate = LocalDateTime.parse(vP.value, dtf);
                        vBe.setDate(lDate);
                        date_parse_suceed = true;
                        break;
                    } catch (DateTimeParseException e) {
                    }
                }

                if (!date_parse_suceed) {
                    log.warning("Can't parse date.");
                }

            }

            properties.add(vP);
            //System.out.println(vP.toString());
        }

        return vBe;
    }

    public VmgObj vmg_object(boolean parseBody) {

        VmgObj vmg;
        VmgProperty vP;
        List<VmgProperty> vObjPro = null;
        VCard vC;
        List<VCard> vObjVcards = null;
        VmgEnvelope vE;
        VmgToken tok;

        this.parseVBody = parseBody;

        //Instantiate a new VmgObject:
        vmg = new VmgObj();

        /*
         * <vmessage-object> ::= {
         *    "BEGIN:VMSG" <CRLF>
         *    <vmessage-property>*
         *    [<vmessage-originator>]*
         *    <vmessage-envelope>
         *    "END:VMSG" <CRLF>
         * }
         */

        //Read the first token:
        getToken();

        // "BEGIN:VMSG" : check the version of the vMessage 
        if ((tok = vmg_begin()) == null) {
            //TODO: handle error
            System.out.println("Error!");
        }

        if (!tok.content.equals("VMSG")) {
            //TODO: handle error
            System.out.println("Error!");
        }

        //Check that we have the correct version of VMG, that is VERSION:1.1
        expect(VmgTokenType.ID_VERSION);
        expect(VmgTokenType.SYM_COLON);
        expect(VmgTokenType.NUMBER);
        if (!p_tok.content.equals("1.1")) {
            log.severe(" VMG version is invalid. Only version 1.1 is supported.");
            return null;
        }

        //The version is fine, let's instantiate the first property.
        vP = new VmgProperty("VERSION");
        vP.value = "1.1";
        vObjPro = vmg.getvProp();
        vObjPro.add(vP);

        //Prevent bad linefeed formatting.
        vmg_linefeed();

        //If the VMSG respect the vCard syntax starting from version 4.0, \r\n is replaced
        //by a single \n
        log.fine("[VMSG]");

        // <vmessage-property>*
        //Get the list of properties in order to modify it. There is at least one property: the version.

        while ((vP = vmg_property(false)) != null) {

            if (vP.name.equalsIgnoreCase("X-IRMC-BOX")) {
                //Parse Inbox/Outbox values.
                if (vP.value.equalsIgnoreCase("SENTBOX")) {
                    vmg.setXIBox(XIrmcBox.SENTBOX);
                } else if (vP.value.equalsIgnoreCase("INBOX")) {
                    vmg.setXIBox(XIrmcBox.INBOX);
                } else if (vP.value.equalsIgnoreCase("OUTBOX")) {
                    vmg.setXIBox(XIrmcBox.OUTBOX);
                }
            } else if (vP.name.equalsIgnoreCase("X-IRMC-STATUS")) {
                if (vP.value.equalsIgnoreCase("READ")) {
                    vmg.setXIStatus(XIrmcStatus.READ);
                } else if (vP.value.equalsIgnoreCase("UNREAD")) {
                    vmg.setXIStatus(XIrmcStatus.UNREAD);
                }
            }

            vObjPro.add(vP);
            //System.out.println(vP.toString());
        }

        //Fullfill the VMG with the new list of originator. TODO: is it useful?
        vmg.setvProp(vObjPro);

        // [<vmessage-originator>]* (make it optional because of brackets. [])
        while (true) {
            if ((tok = vmg_begin()) == null) {
                log.severe("Bad VMG formatting: I expected \"BEGIN\"");
                return null;
            }
            if (!tok.content.equals("VCARD"))
                break;

            vC = vmg_vcard();

            if (vC != null) {
                //We have successfully retrieved a vCard. If it's the first vcard we encounter, initialize it.
                if (vObjVcards == null) {
                    vObjVcards = new ArrayList<VCard>();
                }

                vObjVcards.add(vC);

                //System.out.println("[VCard]");
                //for (Telephone phone : vC.getTelephoneNumbers()) {
                //   System.out.println(phone.getText());
                //}
                //System.out.println("end [VCard]");
            } else {
                log.severe("EzVcard library wasn't able to parse correctly the vcard.");
                return null;
            }

            if (found(VmgTokenType.LINEFEED))
                System.out.println("Linefeed detected after the vcard. This VMG does not respect the RFC norm!");
        }

        //Fullfill the VMG with the new list of originator.
        vmg.setvOriginator(vObjVcards);

        //<vmessage-envelope>
        if (!tok.content.equals("VENV")) {
            log.severe("Bad VMG formatting: I expected an enveloppe, and got \"" + tok.content + "\" instead.");
            return null;
        }

        if ((vE = vmg_envelope()) == null) {
            log.severe("Bad VMG formatting: the returned enveloppe is not valid.");
            return null;
        }

        vmg.setvEnv(vE);

        return vmg;
    }

    private ParamType process_params(String param_string) {
        ParamType param = new ParamType();

        //Split string into params (separated by ; scolon)
        String[] params = param_string.split(";");

        for (String string : params) {
            switch (string) { //TODO: make this proper.
            case "ENCODING=QUOTED-PRINTABLE":
                param.quoted_printable = true;
                break;

            case "CHARSET=UTF-8":
                param.charset_utf8 = true;
                break;

            case "CHARSET=ISO-8859-1":
                param.charset_iso8859_1 = true;
                break;

            default:
                System.out.println("UNKNOWN PARAMETER");
            }
        }

        return param;
    }

    private void getToken() {
        p_tok = c_tok;
        c_tok = lexer.get();
    }
}