com.dgtlrepublic.anitomyj.ParserHelper.java Source code

Java tutorial

Introduction

Here is the source code for com.dgtlrepublic.anitomyj.ParserHelper.java

Source

/*
 * Copyright (c) 2014-2016, Eren Okka
 * Copyright (c) 2016, Paul Miller
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

package com.dgtlrepublic.anitomyj;

import static com.dgtlrepublic.anitomyj.Element.ElementCategory.kElementAnimeSeason;
import static com.dgtlrepublic.anitomyj.Token.TokenCategory.kBracket;
import static com.dgtlrepublic.anitomyj.Token.TokenCategory.kIdentifier;
import static com.dgtlrepublic.anitomyj.Token.TokenCategory.kUnknown;
import static com.dgtlrepublic.anitomyj.Token.TokenFlag.kFlagNotDelimiter;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import com.dgtlrepublic.anitomyj.Element.ElementCategory;
import com.dgtlrepublic.anitomyj.Token.Result;
import com.dgtlrepublic.anitomyj.Token.TokenCategory;

/**
 * Utility class to assist in the parsing.
 *
 * @author Paul Miller
 * @author Eren Okka
 */
public class ParserHelper {
    public static final String kDashes = "-\u2010\u2011\u2012\u2013\u2014\u2015";
    public static final String kDashesWithSpace = " -\u2010\u2011\u2012\u2013\u2014\u2015";
    public static final Map<String, String> ordinals = Collections.unmodifiableMap(new HashMap<String, String>() {
        {
            //@formatter:off
            put("1st", "1");
            put("First", "1");
            put("2nd", "2");
            put("Second", "2");
            put("3rd", "3");
            put("Third", "3");
            put("4th", "4");
            put("Fourth", "4");
            put("5th", "5");
            put("Fifth", "5");
            put("6th", "6");
            put("Sixth", "6");
            put("7th", "7");
            put("Seventh", "7");
            put("8th", "8");
            put("Eighth", "8");
            put("9th", "9");
            put("Ninth", "9");
            //@formatter:on
        }
    });

    private final Parser parser;

    public ParserHelper(Parser parser) {
        this.parser = parser;
    }

    /** Returns whether or not the {@code result} matches the {@code category}. */
    public static boolean isTokenCategory(Result result, TokenCategory category) {
        return result != null && result.token != null && result.token.getCategory() == category;
    }

    /*** Returns whether or not the {@code token} matches the {@code category}. */
    public static boolean isTokenCategory(Token token, TokenCategory category) {
        return token != null && token.getCategory() == category;
    }

    /** Returns whether or not the {@code string} is a CRC string. */
    public static boolean isCrc32(String string) {
        return string != null && string.length() == 8 && StringHelper.isHexadecimalString(string);
    }

    /** Returns whether or not the {@code character} is a dash character. */
    public static boolean isDashCharacter(char c) {
        return StringUtils.containsAny(String.valueOf(c), kDashes);
    }

    /** Returns a number from an original(e.g 2nd). */
    public static String getNumberFromOrdinal(String string) {
        if (StringUtils.isEmpty(string))
            return "";
        String foundString = ordinals.get(string);
        return foundString != null ? foundString : "";
    }

    /** Returns the index of the first digit in the {@code string}; -1 otherwise. */
    public static int indexOfFirstDigit(String string) {
        if (StringUtils.isEmpty(string))
            return -1;
        for (int i = 0; i < string.length(); i++) {
            if (Character.isDigit(string.charAt(i))) {
                return i;
            }
        }

        return -1;
    }

    /** Returns whether or not the {@code string} is a resolution. */
    public static boolean isResolution(String string) {
        if (StringUtils.isEmpty(string))
            return false;
        int minWidthSize = 3;
        int minHeightSize = 3;

        // *###x###*
        if (string.length() >= minWidthSize + 1 + minHeightSize) {
            int pos = StringUtils.indexOfAny(string, "xX\u00D7");
            if (pos != -1 && pos >= minWidthSize && pos <= string.length() - (minHeightSize + 1)) {
                for (int i = 0; i < string.length(); i++) {
                    if (i != pos && !Character.isDigit(string.charAt(i)))
                        return false;
                }

                return true;
            }

            // *###p
        } else if (string.length() >= minHeightSize + 1) {
            if (Character.toLowerCase(string.charAt(string.length() - 1)) == 'p') {
                for (int i = 0; i < string.length() - 1; i++) {
                    if (!Character.isDigit(string.charAt(i)))
                        return false;
                }

                return true;
            }
        }

        return false;
    }

    /** Returns whether or not the {@code category} is searchable. */
    public static boolean isElementCategorySearchable(ElementCategory category) {
        switch (category) {
        case kElementAnimeSeasonPrefix:
        case kElementAnimeType:
        case kElementAudioTerm:
        case kElementDeviceCompatibility:
        case kElementEpisodePrefix:
        case kElementFileChecksum:
        case kElementLanguage:
        case kElementOther:
        case kElementReleaseGroup:
        case kElementReleaseInformation:
        case kElementReleaseVersion:
        case kElementSource:
        case kElementSubtitles:
        case kElementVideoResolution:
        case kElementVideoTerm:
        case kElementVolumePrefix:
            return true;
        }

        return false;
    }

    /** Returns whether the {@code category} is singular. */
    public static boolean isElementCategorySingular(ElementCategory category) {
        switch (category) {
        case kElementAnimeSeason:
        case kElementAnimeType:
        case kElementAudioTerm:
        case kElementDeviceCompatibility:
        case kElementEpisodeNumber:
        case kElementLanguage:
        case kElementOther:
        case kElementReleaseInformation:
        case kElementSource:
        case kElementVideoTerm:
            return false;
        }

        return true;
    }

    /** Returns whether or not a token at the current {@code pos} is isolated(surrounded by braces). */
    public boolean isTokenIsolated(int pos) {
        Result prevToken = Token.findPrevToken(parser.getTokens(), pos, kFlagNotDelimiter);
        if (!isTokenCategory(prevToken, kBracket))
            return false;
        Result nextToken = Token.findNextToken(parser.getTokens(), pos, kFlagNotDelimiter);
        return isTokenCategory(nextToken, kBracket);
    }

    /** Finds ands sets the anime season keyword. */
    public boolean checkAndSetAnimeSeasonKeyword(Token token, int currentTokenPos) {
        TriConsumer<Token, Token, String> setAnimeSeason = (first, second, content) -> {
            parser.getElements().add(new Element(kElementAnimeSeason, content));
            first.setCategory(kIdentifier);
            second.setCategory(kIdentifier);
        };

        Result previousToken = Token.findPrevToken(parser.getTokens(), currentTokenPos, kFlagNotDelimiter);
        if (previousToken.token != null) {
            String number = getNumberFromOrdinal(previousToken.token.getContent());
            if (!number.isEmpty()) {
                setAnimeSeason.accept(previousToken.token, token, number);
                return true;
            }
        }

        Result nextToken = Token.findNextToken(parser.getTokens(), currentTokenPos, kFlagNotDelimiter);
        if (nextToken.token != null && StringHelper.isNumericString(nextToken.token.getContent())) {
            setAnimeSeason.accept(token, nextToken.token, nextToken.token.getContent());
            return true;
        }

        return false;
    }

    /**
     * A method to find the correct volume/episode number when prefixed(i.e Vol.4).
     *
     * @param category        the category we're searching for.
     * @param currentTokenPos the current token position
     * @param token           the token
     * @return true if we found the volume/episode number
     */
    public boolean checkExtentKeyword(ElementCategory category, int currentTokenPos, Token token) {
        Result nToken = Token.findNextToken(parser.getTokens(), currentTokenPos, kFlagNotDelimiter);
        if (isTokenCategory(nToken.token, kUnknown)) {
            if (indexOfFirstDigit(nToken.token.getContent()) == 0) {
                switch (category) {
                case kElementEpisodeNumber:
                    if (!parser.getParserNumber().matchEpisodePatterns(nToken.token.getContent(), nToken.token)) {
                        parser.getParserNumber().setEpisodeNumber(nToken.token.getContent(), nToken.token, false);
                    }
                    break;
                case kElementVolumeNumber:
                    if (!parser.getParserNumber().matchVolumePatterns(nToken.token.getContent(), nToken.token)) {
                        parser.getParserNumber().setVolumeNumber(nToken.token.getContent(), nToken.token, false);
                    }
                    break;
                default:
                    return false;
                }

                token.setCategory(kIdentifier);
                return true;
            }
        }

        return false;
    }

    /**
     * Builds an element an adds it to the internal element list.
     *
     * @param category       the element category
     * @param keepDelimiters delimiters to keep in the element content.
     * @param tokens         the tokens used to create the element content value.
     */
    public void buildElement(ElementCategory category, boolean keepDelimiters, List<Token> tokens) {
        StringBuilder element = new StringBuilder();

        for (ListIterator<Token> iter = tokens.listIterator(); iter.hasNext();) {
            Token token = iter.next();
            switch (token.getCategory()) {
            case kUnknown:
                element.append(token.getContent());
                token.setCategory(kIdentifier);
                break;
            case kBracket:
                element.append(token.getContent());
                break;
            case kDelimiter: {
                String delimiter = "";
                if (StringUtils.isNotEmpty(token.getContent())) {
                    delimiter = StringUtils.substring(token.getContent(), 0, 1);
                }

                if (keepDelimiters) {
                    element.append(delimiter);
                } else if (iter.hasPrevious() && iter.hasNext()) {
                    switch (delimiter) {
                    case ",":
                    case "&":
                        element.append(delimiter);
                        break;
                    default:
                        element.append(' ');
                        break;
                    }
                }
                break;
            }
            }
        }

        if (!keepDelimiters) {
            element = new StringBuilder(StringHelper.trimAny(element.toString(), kDashesWithSpace));
        }

        if (!element.toString().isEmpty()) {
            parser.getElements().add(new Element(category, element.toString()));
        }
    }
}