org.asqatasun.rules.elementchecker.contrast.ContrastChecker.java Source code

Java tutorial

Introduction

Here is the source code for org.asqatasun.rules.elementchecker.contrast.ContrastChecker.java

Source

/*
 *  Asqatasun - Automated webpage assessment
 *  Copyright (C) 2008-2015  Asqatasun.org
 * 
 *  This file is part of Asqatasun.
 * 
 *  Asqatasun is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) 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 Affero General Public License for more details.
 * 
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *  Contact us by mail: asqatasun AT asqatasun DOT org
 */

package org.asqatasun.rules.elementchecker.contrast;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;
import javax.annotation.Nonnull;
import javax.persistence.NoResultException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.log4j.Logger;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.asqatasun.entity.audit.EvidenceElement;
import org.asqatasun.entity.audit.TestSolution;
import org.asqatasun.entity.parameterization.Parameter;
import org.asqatasun.processor.SSPHandler;
import org.asqatasun.ruleimplementation.TestSolutionHandler;
import org.asqatasun.rules.domelement.DomElement;
import org.asqatasun.rules.domelement.extractor.DomElementExtractor;
import org.asqatasun.rules.elementchecker.ElementCheckerImpl;
import org.asqatasun.rules.elementchecker.contrast.exception.ContrastCheckerParseResultException;
import static org.asqatasun.rules.keystore.CssLikeQueryStore.IMG_CSS_LIKE_QUERY;
import static org.asqatasun.rules.keystore.EvidenceStore.*;
import static org.asqatasun.rules.keystore.RemarkMessageStore.*;
import org.asqatasun.rules.elementchecker.contrast.helper.ContrastHelper;
import static org.asqatasun.service.ProcessRemarkService.DEFAULT_EVIDENCE;

/**
 * This checker tests whether the contrast between the colour of each textual 
 * element and their background colour is correct regarding a settable ratio
 * value. 
 * 
 */
public class ContrastChecker extends ElementCheckerImpl {

    private static final Logger LOGGER = Logger.getLogger(ContrastChecker.class);

    private static final String ALT_CONTRAST_MECHA_PARAM_KEY = "ALTERNATIVE_CONTRAST_MECHANISM";

    private static final int NORMAL_FONT_SIZE_THRESHOLD = 18;
    private static final int BOLD_FONT_SIZE_THRESHOLD = 14;

    /** The contrast ratio */
    private final Float contrastRatio;

    /** is lower test */
    private boolean isLowerTest = true;

    /** check bold text */
    private boolean checkBoldText = true;

    /** check normal text */
    private boolean checkNormalText = true;

    /** */
    private final boolean createSourceCodeRemarkForNmi = false;

    /** */
    private final Set<String> notTreatedBackgroundColorValue = new HashSet<>();

    /** */
    private boolean alternativeContrastMechanismPresent = false;

    /** The element counter */
    int elementCounter = 0;

    public int getElementCounter() {
        return elementCounter;
    }

    /**
     * Constructor 
     * 
     * @param contrastRatio
     * @param checkNormalText
     * @param checkBoldText
     * @param isLowerTest 
     */
    public ContrastChecker(@Nonnull Float contrastRatio, @Nonnull boolean checkNormalText,
            @Nonnull boolean checkBoldText, @Nonnull boolean isLowerTest) {
        super();
        this.contrastRatio = contrastRatio;
        this.checkNormalText = checkNormalText;
        this.checkBoldText = checkBoldText;
        this.isLowerTest = isLowerTest;
    }

    @Override
    protected void doCheck(SSPHandler sspHandler, Elements elements, TestSolutionHandler testSolutionHandler) {
        alternativeContrastMechanismPresent = isAlternativeContrastMechanismPresentFromParam(sspHandler);

        Collection<DomElement> domElements;
        try {
            domElements = DomElementExtractor.extractDomElements(sspHandler);
            if (CollectionUtils.isEmpty(domElements)) {
                return;
            }
        } catch (NoResultException nre) {
            // if the getPreProcessResult returns a noResultException, that 
            // means a problem occured when executing js. Nothing cannot be done.
            // the testResult is NOT_TESTED
            resetCollectedDataOnException(testSolutionHandler, nre);
            return;
        }
        try {
            // at this step, if the NoResultException hasn't be caught, that 
            // means that some elements are present. We initialise the solution
            // as passed, if some elements are invalid, we override it with a 
            // a failed solution
            checkDomElements(sspHandler, domElements, testSolutionHandler);
            if (elementCounter > 0) {
                // means that no element is on error or NMI
                if (testSolutionHandler.getTestSolution().equals(TestSolution.NOT_APPLICABLE)) {
                    LOGGER.debug("nothing to say about contrast");
                    if (sspHandler.domCssLikeSelectNodeSet(IMG_CSS_LIKE_QUERY).getSelectedElementNumber() == 0) {
                        testSolutionHandler.addTestSolution(TestSolution.PASSED);
                        LOGGER.debug("The page has no image, the result is Passed");
                    } else {
                        testSolutionHandler.addTestSolution(TestSolution.NEED_MORE_INFO);
                        addProcessRemark(TestSolution.NEED_MORE_INFO, CHECK_CONTRAST_OF_IMAGE_MSG);
                        LOGGER.debug("The page has images, the result is NMI");
                    }
                } else {
                    LOGGER.debug("some Constrast remarks have been thrown");
                }
            } else {
                LOGGER.debug("test is not applicable");
            }
        } catch (ContrastCheckerParseResultException ccpre) {
            // if any problem is encountered while analysing elements, the 
            // test result is set to NOT_TESTED and the processRemarkService is
            // reset
            resetCollectedDataOnException(testSolutionHandler, ccpre);
        }
    }

    /**
     * 
     * @param sspHandler
     * @param domElements
     * @param testSolutionHandler
     * 
     * @throws ContrastCheckerParseResultException
     */
    private void checkDomElements(SSPHandler sspHandler, Collection<DomElement> domElements,
            TestSolutionHandler testSolutionHandler) throws ContrastCheckerParseResultException {
        for (DomElement domElement : domElements) {

            // if the luminosity couldn't have been computed, its value is set 
            // to "-1"
            if (isElementPartOfTheScope(domElement)) {

                String bgColor = domElement.getBgColor();
                String fgColor = domElement.getFgColor();

                if (ContrastHelper.isColorTestable(fgColor) && ContrastHelper.isColorTestable(bgColor)) {

                    elementCounter++;

                    Double contrast = ContrastHelper.getConstrastRatio(fgColor, bgColor);
                    if (contrast < contrastRatio) {
                        LOGGER.debug(" cssPath " + domElement.getPath() + " " + +contrast);
                        DecimalFormatSymbols decimalSymbol = new DecimalFormatSymbols(Locale.getDefault());
                        decimalSymbol.setDecimalSeparator('.');
                        TestSolution elementSolution = createRemarkOnBadContrastElement(sspHandler, domElement,
                                new DecimalFormat("#.00", decimalSymbol).format(contrast));
                        testSolutionHandler.addTestSolution(elementSolution);
                    } else {
                        LOGGER.debug(" good luminosity " + domElement.getPath() + " " + +contrast);
                    }
                } else {
                    elementCounter++;
                    createNmiRemarkForManualCheckElement(sspHandler, domElement);
                    testSolutionHandler.addTestSolution(TestSolution.NEED_MORE_INFO);
                    LOGGER.debug(" nmi " + domElement.getPath());
                }
            }
        }
    }

    /**
     * 
     * @param sspHandler
     * @param domElement
     * @param contrast
     * @return the TestSolution
     */
    private TestSolution createRemarkOnBadContrastElement(SSPHandler sspHandler, DomElement domElement,
            String contrast) throws ContrastCheckerParseResultException {

        TestSolution testSolution;
        String msgCode;

        if (domElement.isHidden()) {
            // if the result is hidden, the result is NEED_MORE_INFO
            testSolution = TestSolution.NEED_MORE_INFO;
            msgCode = BAD_CONTRAST_HIDDEN_ELEMENT_MSG;
        } else if (alternativeContrastMechanismPresent) {
            // An alternative contrast mechanism is provided
            testSolution = TestSolution.NEED_MORE_INFO;
            msgCode = BAD_CONTRAST_BUT_ALT_MECHANISM_MSG;
        } else {
            // By default the result is failed
            testSolution = TestSolution.FAILED;
            msgCode = BAD_CONTRAST_MSG;
        }

        Element element = DomElementExtractor.getElementFromDomElement(domElement, sspHandler);
        if (element != null) {
            Collection<EvidenceElement> eeList = new LinkedList<>();
            eeList.add(getEvidenceElement(FG_COLOR_EE, domElement.getFgColor()));
            eeList.add(getEvidenceElement(BG_COLOR_EE, domElement.getBgColor()));
            eeList.add(getEvidenceElement(CONTRAST_EE, contrast));
            addSourceCodeRemark(testSolution, element, msgCode, eeList);
        } else {
            // the element can't be retrieved by jsoup, that means that
            // something is weird with the dom. The check is stopped, and
            // the test returns not_tested to avoid false positive results
            throw new ContrastCheckerParseResultException();
        }

        return testSolution;
    }

    /**
     * 
     * @param sspHandler
     * @param element
     * @return 
     */
    private void createNmiRemarkForManualCheckElement(SSPHandler sspHandler, DomElement element) {

        if (createSourceCodeRemarkForNmi) {
            Element el = DomElementExtractor.getElementFromDomElement(element, sspHandler);
            if (el != null) {
                Collection<EvidenceElement> eeList = new ArrayList<>();
                eeList.add(getEvidenceElement(FG_COLOR_EE, element.getFgColor()));
                eeList.add(getEvidenceElement(BG_COLOR_EE, element.getBgColor()));
                addSourceCodeRemark(TestSolution.NEED_MORE_INFO, el, CHECK_CONTRAST_MANUALLY_MSG, eeList);
            }
        } else if (!notTreatedBackgroundColorValue.contains(element.getBgColor())) {
            List<EvidenceElement> eeList = new ArrayList<>();
            eeList.add(getEvidenceElement(DEFAULT_EVIDENCE, element.getDisplayableBgColor()));
            addSourceCodeRemark(TestSolution.NEED_MORE_INFO, null, NOT_TREATED_BACKGROUND_COLOR_MSG, eeList);
            notTreatedBackgroundColorValue.add(element.getBgColor());
        }
    }

    /**
     * 
     * @param fontSize
     * @param fontWeight
     * @return 
     */
    private boolean isElementPartOfTheScope(DomElement element) {
        if (!element.isTextNode()) {
            return false;
        }
        boolean isBold = element.isBold();
        float fontSize = element.getFontSizeInPt();
        if (isLowerTest) {
            return (checkBoldText && element.isBold() && fontSize < BOLD_FONT_SIZE_THRESHOLD)
                    || (checkNormalText && !isBold && fontSize < NORMAL_FONT_SIZE_THRESHOLD);
        } else {
            return (checkBoldText && isBold && fontSize >= BOLD_FONT_SIZE_THRESHOLD)
                    || (checkNormalText && !isBold && fontSize >= NORMAL_FONT_SIZE_THRESHOLD);
        }
    }

    /**
     * Reset collected data when an exception occurred
     * @param testSolutionHandler 
     */
    private void resetCollectedDataOnException(TestSolutionHandler testSolutionHandler, Exception exception) {
        LOGGER.info(exception);
        testSolutionHandler.cleanTestSolutions();
        testSolutionHandler.addTestSolution(TestSolution.NOT_TESTED);
        elementCounter = 0;
    }

    private boolean isAlternativeContrastMechanismPresentFromParam(SSPHandler sspHandler) {
        for (Parameter parameter : sspHandler.getSSP().getAudit().getParameterSet()) {
            if (parameter.getParameterElement().getParameterElementCode()
                    .equalsIgnoreCase(ALT_CONTRAST_MECHA_PARAM_KEY)) {
                if (Boolean.valueOf(parameter.getValue())) {
                    return true;
                }
                break;
            }
        }
        return false;
    }

}