com.googlecode.fightinglayoutbugs.FightingLayoutBugs.java Source code

Java tutorial

Introduction

Here is the source code for com.googlecode.fightinglayoutbugs.FightingLayoutBugs.java

Source

/*
 * Copyright 2009-2012 Michael Tamm
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.fightinglayoutbugs;

import com.googlecode.fightinglayoutbugs.helpers.DebugHelper;
import com.googlecode.fightinglayoutbugs.helpers.ImageHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.impl.*;
import org.apache.log.LogKit;
import org.apache.log.Priority;
import org.apache.log4j.LogManager;

import javax.annotation.Nonnull;
import java.io.File;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Finds different layout bugs in a web page by executing several
 * {@link LayoutBugDetector}s. By default the following detectors
 * are enabled:<ul>
 *     <li>{@link DetectInvalidImageUrls}</li>
 *     <li>{@link DetectTextNearOrOverlappingHorizontalEdge}</li>
 *     <li>{@link DetectTextNearOrOverlappingVerticalEdge}</li>
 *     <li>{@link DetectTextWithTooLowContrast}</li>
 * </ul>
 */
public class FightingLayoutBugs extends AbstractLayoutBugDetector {

    private static final Log LOG = LogFactory.getLog(FightingLayoutBugs.class);

    private List<Runnable> _runAfterAnalysis = new ArrayList<Runnable>();

    private TextDetector _textDetector;
    private EdgeDetector _edgeDetector;
    private final List<LayoutBugDetector> _detectors = new ArrayList<LayoutBugDetector>();
    private boolean _debugMode;

    /**
     * Registers the following detectors:<ul>
     *     <li>{@link DetectInvalidImageUrls}</li>
     *     <li>{@link DetectTextNearOrOverlappingHorizontalEdge}</li>
     *     <li>{@link DetectTextNearOrOverlappingVerticalEdge}</li>
     *     <li>{@link DetectTextWithTooLowContrast}</li>
     * </ul>
     */
    public FightingLayoutBugs() {
        this(new DetectInvalidImageUrls(), new DetectTextNearOrOverlappingHorizontalEdge(),
                new DetectTextNearOrOverlappingVerticalEdge(), new DetectTextWithTooLowContrast());
    }

    public FightingLayoutBugs(LayoutBugDetector... detectors) {
        for (LayoutBugDetector d : detectors) {
            enable(d);
        }
    }

    /**
     * Sets the {@link TextDetector} to use.
     */
    public void setTextDetector(TextDetector textDetector) {
        _textDetector = textDetector;
    }

    /**
     * Sets the {@link EdgeDetector} to use.
     */
    public void setEdgeDetector(EdgeDetector edgeDetector) {
        _edgeDetector = edgeDetector;
    }

    /**
     * Call this method to enable the debug mode, which produces
     * more log output and several screenshots of intermediate
     * analysis results.
     */
    public void enableDebugMode() {
        _debugMode = true;
    }

    /**
     * Adds the given detector to the set of detectors, which will be executed,
     * when {@link #findLayoutBugsIn(WebPage) findLayoutBugsIn(...)} is called.
     * If there is already a detector of the same class registered, it will be
     * replaced by the given detector.
     */
    public void enable(LayoutBugDetector detector) {
        if (detector == null) {
            throw new IllegalArgumentException("Method parameter newDetector must not be null.");
        }
        disable(detector.getClass());
        _detectors.add(detector);
    }

    /**
     * Removes all detectors of the given class from the set of detectors, which will be executed,
     * when {@link #findLayoutBugsIn(WebPage) findLayoutBugsIn(...)} is called.
     */
    public void disable(Class<? extends LayoutBugDetector> detectorClass) {
        if (detectorClass == null) {
            throw new IllegalArgumentException("Method parameter detectorClass must not be null.");
        }
        Iterator<LayoutBugDetector> i = _detectors.iterator();
        while (i.hasNext()) {
            LayoutBugDetector detector = i.next();
            if (detector.getClass().isAssignableFrom(detectorClass)
                    || detectorClass.isAssignableFrom(detector.getClass())) {
                i.remove();
            }
        }
    }

    /**
     * Call this method to gain access to one of the {@link LayoutBugDetector}s
     * for calling setter methods on it.
     */
    public <D extends LayoutBugDetector> D configure(Class<D> detectorClass) {
        if (detectorClass == null) {
            throw new IllegalArgumentException("Method parameter detectorClass must not be  null.");
        }
        for (LayoutBugDetector detector : _detectors) {
            if (detectorClass.isAssignableFrom(detector.getClass())) {
                // noinspection unchecked
                return (D) detector;
            }
        }
        throw new IllegalArgumentException("There is no detector of class " + detectorClass.getName());
    }

    /**
     * Runs all registered {@link LayoutBugDetector}s. Before you call this method, you might:<ul>
     * <li>register new detectors via {@link #enable},</li>
     * <li>remove unwanted detectors via {@link #disable},</li>
     * <li>configure a registered detector via {@link #configure},</li>
     * <li>configure the {@link TextDetector} to be used via {@link #setTextDetector},</li>
     * <li>configure the {@link EdgeDetector} to be used via {@link #setEdgeDetector}.</li>
     * </ul>
     */
    public Collection<LayoutBug> findLayoutBugsIn(@Nonnull WebPage webPage) {
        if (webPage == null) {
            throw new IllegalArgumentException("Method parameter webPage must not be null.");
        }
        if (_debugMode) {
            setLogLevelToDebug();
            registerDebugListener();
        }
        try {
            TextDetector textDetector = (_textDetector == null ? new AnimationAwareTextDetector() : _textDetector);
            EdgeDetector edgeDetector = (_edgeDetector == null ? new SimpleEdgeDetector() : _edgeDetector);
            try {
                LOG.debug("Analyzing " + webPage.getUrl() + " ...");
                webPage.setTextDetector(textDetector);
                webPage.setEdgeDetector(edgeDetector);
                final Collection<LayoutBug> result = new ArrayList<LayoutBug>();
                for (LayoutBugDetector detector : _detectors) {
                    detector.setScreenshotDir(screenshotDir);
                    LOG.debug("Running " + detector.getClass().getSimpleName() + " ...");
                    result.addAll(detector.findLayoutBugsIn(webPage));
                }
                if (!result.isEmpty()) {
                    StringBuilder sb = new StringBuilder();
                    sb.append("Detected layout bug(s) on ").append(webPage.getUrl()).append("\n");
                    if (_debugMode) {
                        sb.append(
                                "If there is a false positive, please send an email to fighting-layout-bugs@googlegroups.com with the following information:\n");
                        sb.append("    - your test code,\n");
                        sb.append("    - all logged output, and\n");
                        sb.append("    - all screenshot files located in: ").append(screenshotDir);
                        sb.append("      (You might want to pack those into an zip archive)\n");
                        sb.append("TextDetector: ").append(textDetector.getClass().getName()).append("\n");
                        sb.append("EdgeDetector: ").append(edgeDetector.getClass().getName()).append("\n");
                        sb.append(DebugHelper.getDiagnosticInfo(webPage.getDriver()));
                    } else {
                        sb.append(
                                "If you call FightingLayoutBugs.enableDebugMode() before you call FightingLayoutBugs.findLayoutBugsIn(...) you can get more information.");
                    }
                    LOG.info(sb.toString());
                }
                return result;
            } catch (RuntimeException e) {
                String url = null;
                try {
                    url = webPage.getUrl().toString();
                } catch (Exception ignored) {
                }
                StringBuilder sb = new StringBuilder();
                sb.append("Failed to analyze ").append(url == null ? "given WebPage" : url).append(" -- ")
                        .append(e.toString()).append("\n");
                if (_debugMode) {
                    sb.append(
                            "If you want support (or want to support FLB) you can send an email to fighting-layout-bugs@googlegroups.com with the following information:\n");
                    sb.append("    - your test code,\n");
                    sb.append("    - all logged output, and\n");
                    sb.append("    - all screenshot files located in: ").append(screenshotDir);
                    sb.append("      (You might want to pack those into an zip archive)\n");
                    sb.append("TextDetector: ").append(textDetector.getClass().getName()).append("\n");
                    sb.append("EdgeDetector: ").append(edgeDetector.getClass().getName()).append("\n");
                    sb.append(DebugHelper.getDiagnosticInfo(webPage.getDriver()));
                } else {
                    sb.append(
                            "If you call FightingLayoutBugs.enableDebugMode() before you call FightingLayoutBugs.findLayoutBugsIn(...) you can get more information.");
                }
                String errorMessage = sb.toString();
                LOG.error(errorMessage);
                throw new RuntimeException(errorMessage, e);
            }
        } finally {
            for (Runnable runnable : _runAfterAnalysis) {
                try {
                    runnable.run();
                } catch (RuntimeException e) {
                    LOG.warn(runnable + " failed.", e);
                }
            }
        }
    }

    private void setLogLevelToDebug() {
        String name = FightingLayoutBugs.class.getPackage().getName();
        final Log log = LogFactory.getLog(name);
        if (log instanceof Jdk14Logger || (log instanceof AvalonLogger
                && ((AvalonLogger) log).getLogger() instanceof org.apache.avalon.framework.logger.Jdk14Logger)) {
            final Logger logger = Logger.getLogger(name);
            final Level originalLevel = logger.getLevel();
            logger.setLevel(Level.FINE);
            _runAfterAnalysis.add(new Runnable() {
                @Override
                public void run() {
                    logger.setLevel(originalLevel);
                }
            });
            enableDebugOutputToConsole(logger);
        } else if (log instanceof Log4JLogger || (log instanceof AvalonLogger
                && ((AvalonLogger) log).getLogger() instanceof org.apache.avalon.framework.logger.Log4JLogger)) {
            final org.apache.log4j.Logger logger = LogManager.getLogger(name);
            final org.apache.log4j.Level originalLevel = logger.getLevel();
            logger.setLevel(org.apache.log4j.Level.DEBUG);
            _runAfterAnalysis.add(new Runnable() {
                @Override
                public void run() {
                    logger.setLevel(originalLevel);
                }
            });
        } else if (log instanceof LogKitLogger || (log instanceof AvalonLogger
                && ((AvalonLogger) log).getLogger() instanceof org.apache.avalon.framework.logger.LogKitLogger)) {
            final org.apache.log.Logger logger = LogKit.getLoggerFor(name);
            final Priority originalLevel = logger.getPriority();
            logger.setPriority(Priority.DEBUG);
            _runAfterAnalysis.add(new Runnable() {
                @Override
                public void run() {
                    logger.setPriority(originalLevel);
                }
            });
        } else if (log instanceof SimpleLog) {
            final SimpleLog simpleLog = (SimpleLog) log;
            final int originalLevel = simpleLog.getLevel();
            simpleLog.setLevel(SimpleLog.LOG_LEVEL_DEBUG);
            _runAfterAnalysis.add(new Runnable() {
                @Override
                public void run() {
                    simpleLog.setLevel(originalLevel);
                }
            });
        }
    }

    private void enableDebugOutputToConsole(Logger logger) {
        do {
            for (final Handler handler : logger.getHandlers()) {
                if (handler instanceof ConsoleHandler) {
                    final Level originalConsoleLogLevel = handler.getLevel();
                    handler.setLevel(Level.FINE);
                    _runAfterAnalysis.add(new Runnable() {
                        @Override
                        public void run() {
                            handler.setLevel(originalConsoleLogLevel);
                        }
                    });
                }
            }
        } while (logger.getUseParentHandlers() && (logger = logger.getParent()) != null);
    }

    private void registerDebugListener() {
        final AtomicInteger i = new AtomicInteger(0);
        final NumberFormat nf = new DecimalFormat("00");
        final File screenshotDir = this.screenshotDir;
        final Visualization.Listener debugListener = new Visualization.Listener() {
            @Override
            public void algorithmStepFinished(String algorithm, String stepDescription, int[][] tempResult) {
                File pngFile = new File(screenshotDir, nf.format(i.incrementAndGet()) + "_" + algorithm + ".png");
                ImageHelper.pixelsToPngFile(tempResult, pngFile);
                LOG.debug(pngFile.getName() + " -- " + stepDescription);
            }

            @Override
            public void algorithmFinished(String algorithm, String stepDescription, int[][] result) {
                File pngFile = new File(screenshotDir, nf.format(i.incrementAndGet()) + "_" + algorithm + ".png");
                ImageHelper.pixelsToPngFile(result, pngFile);
                LOG.debug(pngFile.getName() + " -- " + stepDescription);
            }
        };
        Visualization.registerListener(debugListener);
        _runAfterAnalysis.add(new Runnable() {
            @Override
            public void run() {
                Visualization.unregisterListener(debugListener);
            }
        });
    }
}