DOMによるXML読み込みテストアプリ

package com.example.myapplication;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

class XmlReader {
    @NonNull private static final String TAG = XmlReader.class.getSimpleName();
    /**
     * 各要素のタグ名称
     */
    @NonNull private final String TARGET = "target";
    @NonNull private final String TARGET_LIST = "targetList";
    @NonNull private final String TARGET_NUM = "targetNum";
    @NonNull private final String TARGET_NAME = "targetName";
    @NonNull private final String TARGET_ID = "targetId";
    @NonNull private final String EXPIRATION_DATA = "expirationDate";
    /**
     * targetListの要素数
     */
    private final int TARGET_LIST_NUM = 1;
    /**
     * targetIdの要素数
     */
    private final int TARGET_ID_NUM = 1;
    /**
     * expirationDateの要素数
     */
    private final int TARGET_EXPIRATION_DATE_NUM = 1;
    /**
     * targetNumの要素数の最大値
     */
    private final int TARGET_NUM_MAX = 2;
    /**
     * targetNumの要素数の最小値
     */
    private final int TARGET_NUM_MIN = 1;

    /**
     * ファイル検索に使用するnameのリスト
     */
    @Nullable
    private List<Node> mNameElementList;

    /**
     * name要素取得
     *
     * @return name要素のリスト
     */
    @Nullable
    public List<Node> getNameElementList() {
        Log.i(TAG, "getNameElementList()");
        return mNameElementList;
    }

    /**
     * DOMによるxml読み込み処理
     *
     * @param inputStream 読み込み対象XML
     * @return true:正常なXML, false:不正なXML
     * @throws SAXException
     * @throws IOException
     * @throws ParserConfigurationException
     */
    public boolean domRead(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException {
        Log.i(TAG, "domRead()");
        //　初期化
        mNameElementList = null;
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder documentBuilder = factory.newDocumentBuilder();
        Document document = documentBuilder.parse(inputStream);
        boolean result = false;
        // ルート要素取得
        Element root = document.getDocumentElement();
        if (null != root) {
            if (TARGET.equals(root.getTagName())) {
                Log.i(TAG, "root.getTagName() : " + root.getTagName());
                //ルートからexpirationDateを取得
                NodeList expirationDate = root.getElementsByTagName(EXPIRATION_DATA);
                if (checkExpirationDate(expirationDate)) {
                    //ルートからtargetIdを取得
                    NodeList targetId = root.getElementsByTagName(TARGET_ID);
                    if (checkTargetId(targetId)) {
                        //ルートからtargetListを取得
                        NodeList targetList = root.getElementsByTagName(TARGET_LIST);
                        if (checkTargetList(targetList)) {
                            result = true;
                        } else {
                            Log.i(TAG, "Error : targetList is invalid");
                        }
                    } else {
                        Log.i(TAG, "Error : targetId is invalid");
                    }
                } else {
                    Log.i(TAG, "Error : expirationDate is invalid");
                }
            } else {
                Log.i(TAG, "Error : target tag name is invalid");
            }
        } else {
            Log.i(TAG, "Error : Root is null");
        }
        return result;
    }

    /**
     * targetListのチェック
     *
     * @param targetList チェック対象のNodeList
     * @return true:正常なtargetList, false:不正なtargetList
     */
    private boolean checkTargetList(@Nullable NodeList targetList) {
        Log.i(TAG, "checkTargetList()");
        boolean result = false;
        if (null != targetList) {
            if (TARGET_LIST_NUM == targetList.getLength()) {
                Element targetListElement = (Element) targetList.item(0);
                if (null != targetListElement) {
                    NodeList targetListChildren = targetListElement.getChildNodes();
                    if (null != targetListChildren) {
                        result = checkTargetListChildren(targetListChildren);
                    } else {
                        Log.i(TAG, "Error : targetList children is null");
                    }
                } else {
                    Log.i(TAG, "Error : targetList is null");
                }
            } else {
                Log.i(TAG, "Error : targetList is invalid configuration");
            }
        } else {
            Log.i(TAG, "Error : targetList is null");
        }
        Log.i(TAG, "checkTargetList() result : " + result);
        return result;
    }

    /**
     * targetListの子要素のチェック
     *
     * @param targetListChildren チェック対象のNodeList
     * @return true:正常なtargetListの子要素, false:不正なtargetListの子要素
     */
    private boolean checkTargetListChildren(@NonNull NodeList targetListChildren) {
        Log.i(TAG, "checkTargetListChildren()");
        List<Node> targetNumList = new ArrayList<>();
        List<Node> targetNameList = new ArrayList<>();
        boolean result = false;
        for (int i = 0; i < targetListChildren.getLength(); i++) {
            Node node = targetListChildren.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                String nodeName = node.getNodeName();
                Log.i(TAG, "nodeName : " + nodeName);
                if (TARGET_NUM.equals(nodeName)) {
                    targetNumList.add(node);
                } else if (TARGET_NAME.equals(nodeName)) {
                    targetNameList.add(node);
                } else {
                    Log.i(TAG, "Error : element tag name is invalid");
                    break;
                }
            }
        }
        result = targetNumWithTargetNameVerification(targetNumList, targetNameList);
        Log.i(TAG, "checkTargetListChildren() result : " + result);
        return result;
    }

    /**
     * targetNumとtargetNameの整合性確認
     *
     * @param targetNumList  確認対象のtargetNumList
     * @param targetNameList 確認対象のtargetNameList
     * @return true:整合OK, false:整合NG
     */
    private boolean targetNumWithTargetNameVerification(@NonNull List<Node> targetNumList, @NonNull List<Node> targetNameList) {
        Log.i(TAG, "targetNumWithTargetNameVerification()");
        boolean result = false;
        // targetNumの要素数確認
        if (TARGET_ID_NUM == targetNumList.size()) {
            int targetNum = 0;
            try {
                // targetNumの要素取得
                targetNum = Integer.parseInt(targetNumList.get(0).getTextContent());
            } catch (@NonNull NumberFormatException | DOMException | IndexOutOfBoundsException e) {
                e.printStackTrace();
            }
            // targetNumの整合性を確認
            if (TARGET_NUM_MIN == targetNum || TARGET_NUM_MAX == targetNum) {
                // targetNumの値とtargetNameの数が一致しているか確認
                if (targetNum == targetNameList.size()) {
                    Log.i(TAG, "Success : targetElement is valid");
                    result = true;
                    mNameElementList = targetNameList;
                } else {
                    Log.i(TAG, "Error : The number of targetNum does not match the targetName");
                }
            } else {
                Log.i(TAG, "Error : targetNum is invalid");
            }
        } else {
            Log.i(TAG, "Error : targetNum is invalid");
        }
        Log.i(TAG, "targetNumWithTargetNameVerification() result : " + result);
        return result;
    }

    /**
     * expirationDateのチェック
     *
     * @param expirationDate チェック対象のNodeList
     * @return true:正常なexpirationDate, false:不正なexpirationDate
     */
    private boolean checkExpirationDate(@Nullable NodeList expirationDate) {
        Log.i(TAG, "checkExpirationDate()");
        String expirationDateString = null;
        boolean result = false;
        if (null != expirationDate) {
            if (TARGET_EXPIRATION_DATE_NUM == expirationDate.getLength()) {
                Node node = expirationDate.item(0);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element element = (Element) node;
                    if (EXPIRATION_DATA.equals(element.getNodeName())) {
                        try {
                            expirationDateString = element.getTextContent();
                        } catch (DOMException e) {
                            e.printStackTrace();
                        }
                        if (null != expirationDateString) {
                            result = expirationDateVerification(expirationDateString);
                        } else {
                            Log.i(TAG, "Error : expirationDateString is null");
                        }
                    } else {
                        Log.i(TAG, "Error : element tag name is invalid");
                    }
                } else {
                    Log.i(TAG, "Error : expirationDate element is invalid");
                }
            } else {
                Log.i(TAG, "Error : expirationDate is invalid configuration");
            }
        } else {
            Log.i(TAG, "Error : expirationDate is null");
        }
        Log.i(TAG, "checkExpirationDate() result : " + result);
        return result;
    }

    /**
     * expirationDateの整合性確認
     *
     * @param expirationDateString チェック対象のexpirationDate
     * @return true:正常な要素, false:不正な要素
     */
    private boolean expirationDateVerification(@NonNull String expirationDateString) {
        Log.i(TAG, "expirationDateVerification()");
        // 現在時刻取得
        LocalDateTime nowDate = LocalDateTime.now();
        LocalDateTime expirationDate = null;
        try {
            // expirationDateをLocalDateTimeに変換
            expirationDate = expirationDateConversion(expirationDateString);
        } catch (@NonNull DateTimeParseException | IllegalArgumentException e) {
            e.printStackTrace();
        }
        // 現在時刻とexpirationDateの比較
        Log.i(TAG, "nowDate : " + nowDate + ", expirationDate : " + expirationDate);
        boolean result = false;
        if (null != nowDate && null != expirationDate) {
            // 有効期限内
            if (nowDate.isBefore(expirationDate)) {
                Log.i(TAG, "Success : Within the expiration date");
                result = true;
            // 有効期限と同時刻
            } else if (nowDate.isEqual(expirationDate)) {
                Log.i(TAG, "Success : Expiration date is just");
                result = true;
            // 有効期限切れ
            } else {
                Log.i(TAG, "Error : Expired");
            }
        } else {
            Log.i(TAG, "Error : Verification failure");
        }
        Log.i(TAG, "expirationDateVerification() result : " + result);
        return result;
    }

    /**
     * String(yyyyMMddHHmmss)からLocalDateTimeに変換
     *
     * @return LocalDateTime型の日付(変換対象がnullの場合 、 null)
     */
    @Nullable
    private LocalDateTime expirationDateConversion(@NonNull String expirationDateString) {
        Log.i(TAG, "expirationDateConversion() expirationDateString : " + expirationDateString);
        LocalDateTime dateTime = null;
        if (expirationDateString.length() == 14) {
            dateTime = LocalDateTime.of(
                    Integer.parseInt(expirationDateString.substring(0, 4)),    // 年
                    Integer.parseInt(expirationDateString.substring(4, 6)),    // 月
                    Integer.parseInt(expirationDateString.substring(6, 8)),    // 日
                    Integer.parseInt(expirationDateString.substring(8, 10)),   // 時
                    Integer.parseInt(expirationDateString.substring(10, 12)),  // 分
                    Integer.parseInt(expirationDateString.substring(12, 14))); // 秒
        } else {
            Log.i(TAG, "Error : ExpirationDate is invalid");
        }
        return dateTime;
    }

    /**
     * targetIdのチェック
     *
     * @param targetId チェック対象のNodeList
     * @return true:正常なtargetId, false:不正なtargetId
     */
    private boolean checkTargetId(@Nullable NodeList targetId) {
        Log.i(TAG, "checkTargetId()");
        String targetIdString = null;
        boolean result = false;
        if (null != targetId) {
            if (TARGET_ID_NUM == targetId.getLength()) {
                Node node = targetId.item(0);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element element = (Element) node;
                    if (TARGET_ID.equals(element.getNodeName())) {
                        try {
                            targetIdString = element.getTextContent();
                        } catch (DOMException e) {
                            e.printStackTrace();
                        }
                        if (null != targetIdString) {
                            Log.i(TAG, "Success : checkTargetId() targetIdString : " + targetIdString);
                            result = true;
                        } else {
                            Log.i(TAG, "Error : targetIdString is null");
                        }
                    } else {
                        Log.i(TAG, "Error : element tag name is invalid");
                    }
                } else {
                    Log.i(TAG, "Error : targetId element is invalid");
                }
            } else {
                Log.i(TAG, "Error : targetId is invalid configuration");
            }
        } else {
            Log.i(TAG, "Error : targetId is null");
        }
        Log.i(TAG, "checkTargetId() result : " + result);
        return result;
    }
}