All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.server.ComponentSizeValidator Maven / Gradle / Ivy

There is a newer version: 8.27.3
Show newest version
/*
 * Vaadin Framework 7
 *
 * Copyright (C) 2000-2024 Vaadin Ltd
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See  for the full
 * license.
 */
package com.vaadin.server;

import java.io.PrintStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.vaadin.server.Sizeable.Unit;
import com.vaadin.ui.AbstractOrderedLayout;
import com.vaadin.ui.AbstractSplitPanel;
import com.vaadin.ui.Component;
import com.vaadin.ui.ComponentContainer;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Form;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.GridLayout.Area;
import com.vaadin.ui.Layout;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;

@SuppressWarnings({ "serial", "deprecation" })
public class ComponentSizeValidator implements Serializable {

    private final static int LAYERS_SHOWN = 4;

    /**
     * Recursively checks given component and its subtree for invalid layout
     * setups. Prints errors to std err stream.
     *
     * @param component
     *            component to check
     * @return set of first level errors found
     */
    public static List validateComponentRelativeSizes(
            Component component, List errors,
            InvalidLayout parent) {

        if (component != null) {
            boolean invalidHeight = !checkHeights(component);
            boolean invalidWidth = !checkWidths(component);

            if (invalidHeight || invalidWidth) {
                InvalidLayout error = new InvalidLayout(component,
                        invalidHeight, invalidWidth);
                if (parent != null) {
                    parent.addError(error);
                } else {
                    if (errors == null) {
                        errors = new LinkedList();
                    }
                    errors.add(error);
                }
                parent = error;
            }
        }

        if (component instanceof Panel) {
            Panel panel = (Panel) component;
            errors = validateComponentRelativeSizes(panel.getContent(), errors,
                    parent);
        } else if (component instanceof ComponentContainer) {
            ComponentContainer lo = (ComponentContainer) component;
            Iterator it = lo.getComponentIterator();
            while (it.hasNext()) {
                errors = validateComponentRelativeSizes(it.next(), errors,
                        parent);
            }
        } else if (component instanceof Form) {
            Form form = (Form) component;
            if (form.getLayout() != null) {
                errors = validateComponentRelativeSizes(form.getLayout(),
                        errors, parent);
            }
            if (form.getFooter() != null) {
                errors = validateComponentRelativeSizes(form.getFooter(),
                        errors, parent);
            }
        }

        return errors;
    }

    private static void printServerError(String msg,
            Stack attributes, boolean widthError,
            PrintStream errorStream) {
        StringBuffer err = new StringBuffer();
        err.append("Vaadin DEBUG\n");

        StringBuilder indent = new StringBuilder("");
        ComponentInfo ci;
        if (attributes != null) {
            while (attributes.size() > LAYERS_SHOWN) {
                attributes.pop();
            }
            while (!attributes.empty()) {
                ci = attributes.pop();
                showComponent(ci.component, ci.info, err, indent, widthError);
            }
        }

        err.append("Layout problem detected: ");
        err.append(msg);
        err.append("\n");
        err.append(
                "Relative sizes were replaced by undefined sizes, components may not render as expected.\n");
        errorStream.println(err);

    }

    public static boolean checkHeights(Component component) {
        try {
            if (!hasRelativeHeight(component)) {
                return true;
            }
            if (component instanceof Window) {
                return true;
            }
            if (component.getParent() == null) {
                return true;
            }

            return parentCanDefineHeight(component);
        } catch (Exception e) {
            getLogger().log(Level.FINER,
                    "An exception occurred while validating sizes.", e);
            return true;
        }
    }

    public static boolean checkWidths(Component component) {
        try {
            if (!hasRelativeWidth(component)) {
                return true;
            }
            if (component instanceof Window) {
                return true;
            }
            if (component.getParent() == null) {
                return true;
            }

            return parentCanDefineWidth(component);
        } catch (Exception e) {
            getLogger().log(Level.FINER,
                    "An exception occurred while validating sizes.", e);
            return true;
        }
    }

    public static class InvalidLayout implements Serializable {

        private final Component component;

        private final boolean invalidHeight;
        private final boolean invalidWidth;

        private final Vector subErrors = new Vector();

        public InvalidLayout(Component component, boolean height,
                boolean width) {
            this.component = component;
            invalidHeight = height;
            invalidWidth = width;
        }

        public void addError(InvalidLayout error) {
            subErrors.add(error);
        }

        public void reportErrors(StringBuilder clientJSON,
                PrintStream serverErrorStream) {
            clientJSON.append("{");

            Component parent = component.getParent();
            String paintableId = component.getConnectorId();

            clientJSON.append("\"id\":\"" + paintableId + "\"");

            if (invalidHeight) {
                Stack attributes = null;
                String msg = "";
                // set proper error messages
                if (parent instanceof AbstractOrderedLayout) {
                    AbstractOrderedLayout ol = (AbstractOrderedLayout) parent;
                    boolean vertical = false;

                    if (ol instanceof VerticalLayout) {
                        vertical = true;
                    }

                    if (vertical) {
                        msg = "Component with relative height inside a VerticalLayout with no height defined.";
                        attributes = getHeightAttributes(component);
                    } else {
                        msg = "At least one of a HorizontalLayout's components must have non relative height if the height of the layout is not defined";
                        attributes = getHeightAttributes(component);
                    }
                } else if (parent instanceof GridLayout) {
                    msg = "At least one of the GridLayout's components in each row should have non relative height if the height of the layout is not defined.";
                    attributes = getHeightAttributes(component);
                } else {
                    // default error for non sized parent issue
                    msg = "A component with relative height needs a parent with defined height.";
                    attributes = getHeightAttributes(component);
                }
                printServerError(msg, attributes, false, serverErrorStream);
                clientJSON.append(",\"heightMsg\":\"" + msg + "\"");
            }
            if (invalidWidth) {
                Stack attributes = null;
                String msg = "";
                if (parent instanceof AbstractOrderedLayout) {
                    AbstractOrderedLayout ol = (AbstractOrderedLayout) parent;
                    boolean horizontal = true;

                    if (ol instanceof VerticalLayout) {
                        horizontal = false;
                    }

                    if (horizontal) {
                        msg = "Component with relative width inside a HorizontalLayout with no width defined";
                        attributes = getWidthAttributes(component);
                    } else {
                        msg = "At least one of a VerticalLayout's components must have non relative width if the width of the layout is not defined";
                        attributes = getWidthAttributes(component);
                    }
                } else if (parent instanceof GridLayout) {
                    msg = "At least one of the GridLayout's components in each column should have non relative width if the width of the layout is not defined.";
                    attributes = getWidthAttributes(component);
                } else {
                    // default error for non sized parent issue
                    msg = "A component with relative width needs a parent with defined width.";
                    attributes = getWidthAttributes(component);
                }
                clientJSON.append(",\"widthMsg\":\"" + msg + "\"");
                printServerError(msg, attributes, true, serverErrorStream);
            }
            if (subErrors.size() > 0) {
                serverErrorStream.println("Sub errors >>");
                clientJSON.append(", \"subErrors\" : [");
                boolean first = true;
                for (InvalidLayout subError : subErrors) {
                    if (!first) {
                        clientJSON.append(",");
                    } else {
                        first = false;
                    }
                    subError.reportErrors(clientJSON, serverErrorStream);
                }
                clientJSON.append("]");
                serverErrorStream.println("<< Sub erros");
            }
            clientJSON.append("}");
        }
    }

    private static class ComponentInfo implements Serializable {
        Component component;
        String info;

        public ComponentInfo(Component component, String info) {
            this.component = component;
            this.info = info;
        }

    }

    private static Stack getHeightAttributes(
            Component component) {
        Stack attributes = new Stack();
        attributes
                .add(new ComponentInfo(component, getHeightString(component)));
        Component parent = component.getParent();
        attributes.add(new ComponentInfo(parent, getHeightString(parent)));

        while ((parent = parent.getParent()) != null) {
            attributes.add(new ComponentInfo(parent, getHeightString(parent)));
        }

        return attributes;
    }

    private static Stack getWidthAttributes(
            Component component) {
        Stack attributes = new Stack();
        attributes.add(new ComponentInfo(component, getWidthString(component)));
        Component parent = component.getParent();
        attributes.add(new ComponentInfo(parent, getWidthString(parent)));

        while ((parent = parent.getParent()) != null) {
            attributes.add(new ComponentInfo(parent, getWidthString(parent)));
        }

        return attributes;
    }

    private static String getWidthString(Component component) {
        String width = "width: ";
        if (hasRelativeWidth(component)) {
            width += "RELATIVE, " + component.getWidth() + " %";
        } else if (component instanceof Window
                && component.getParent() == null) {
            width += "MAIN WINDOW";
        } else if (component.getWidth() >= 0) {
            width += "ABSOLUTE, " + component.getWidth() + " "
                    + component.getWidthUnits().getSymbol();
        } else {
            width += "UNDEFINED";
        }

        return width;
    }

    private static String getHeightString(Component component) {
        String height = "height: ";
        if (hasRelativeHeight(component)) {
            height += "RELATIVE, " + component.getHeight() + " %";
        } else if (component instanceof Window
                && component.getParent() == null) {
            height += "MAIN WINDOW";
        } else if (component.getHeight() > 0) {
            height += "ABSOLUTE, " + component.getHeight() + " "
                    + component.getHeightUnits().getSymbol();
        } else {
            height += "UNDEFINED";
        }

        return height;
    }

    private static void showComponent(Component component, String attribute,
            StringBuffer err, StringBuilder indent, boolean widthError) {

        FileLocation createLoc = creationLocations.get(component);

        FileLocation sizeLoc;
        if (widthError) {
            sizeLoc = widthLocations.get(component);
        } else {
            sizeLoc = heightLocations.get(component);
        }

        err.append(indent);
        indent.append("  ");
        err.append("- ");

        err.append(component.getClass().getSimpleName());
        err.append("/").append(Integer.toHexString(component.hashCode()));

        if (component.getCaption() != null) {
            err.append(" \"");
            err.append(component.getCaption());
            err.append("\"");
        }

        if (component.getId() != null) {
            err.append(" id: ");
            err.append(component.getId());
        }

        if (createLoc != null) {
            err.append(", created at (" + createLoc.file + ":"
                    + createLoc.lineNumber + ")");

        }

        if (attribute != null) {
            err.append(" (");
            err.append(attribute);
            if (sizeLoc != null) {
                err.append(", set at (" + sizeLoc.file + ":"
                        + sizeLoc.lineNumber + ")");
            }

            err.append(")");
        }
        err.append("\n");

    }

    private static boolean hasNonRelativeHeightComponent(
            AbstractOrderedLayout ol) {
        Iterator it = ol.getComponentIterator();
        while (it.hasNext()) {
            if (!hasRelativeHeight(it.next())) {
                return true;
            }
        }
        return false;
    }

    public static boolean parentCanDefineHeight(Component component) {
        Component parent = component.getParent();
        if (parent == null) {
            // main window, valid situation
            return true;
        }
        if (parent.getHeight() < 0) {
            // Undefined height
            if (parent instanceof Window) {
                // Sub window with undefined size has a min-height
                return true;
            }

            if (parent instanceof AbstractOrderedLayout) {
                boolean horizontal = true;
                if (parent instanceof VerticalLayout) {
                    horizontal = false;
                }
                if (horizontal && hasNonRelativeHeightComponent(
                        (AbstractOrderedLayout) parent)) {
                    return true;
                } else {
                    return false;
                }

            } else if (parent instanceof GridLayout) {
                GridLayout gl = (GridLayout) parent;
                Area componentArea = gl.getComponentArea(component);
                boolean rowHasHeight = false;
                for (int row = componentArea.getRow1(); !rowHasHeight
                        && row <= componentArea.getRow2(); row++) {
                    for (int column = 0; !rowHasHeight
                            && column < gl.getColumns(); column++) {
                        Component c = gl.getComponent(column, row);
                        if (c != null) {
                            rowHasHeight = !hasRelativeHeight(c);
                        }
                    }
                }
                if (!rowHasHeight) {
                    return false;
                } else {
                    // Other components define row height
                    return true;
                }
            }

            if (parent instanceof Panel || parent instanceof AbstractSplitPanel
                    || parent instanceof TabSheet
                    || parent instanceof CustomComponent) {
                // height undefined, we know how how component works and no
                // exceptions
                // TODO horiz SplitPanel ??
                return false;
            } else {
                // We cannot generally know if undefined component can serve
                // space for children (like CustomLayout or component built by
                // third party) so we assume they can
                return true;
            }

        } else if (hasRelativeHeight(parent)) {
            // Relative height
            if (parent.getParent() != null) {
                return parentCanDefineHeight(parent);
            } else {
                return true;
            }
        } else {
            // Absolute height
            return true;
        }
    }

    private static boolean hasRelativeHeight(Component component) {
        return (component.getHeightUnits() == Unit.PERCENTAGE
                && component.getHeight() > 0);
    }

    private static boolean hasNonRelativeWidthComponent(
            AbstractOrderedLayout ol) {
        Iterator it = ol.getComponentIterator();
        while (it.hasNext()) {
            if (!hasRelativeWidth(it.next())) {
                return true;
            }
        }
        return false;
    }

    private static boolean hasRelativeWidth(Component paintable) {
        return paintable.getWidth() > 0
                && paintable.getWidthUnits() == Unit.PERCENTAGE;
    }

    public static boolean parentCanDefineWidth(Component component) {
        Component parent = component.getParent();
        if (parent == null) {
            // main window, valid situation
            return true;
        }
        if (parent instanceof Window) {
            // Sub window with undefined size has a min-width
            return true;
        }

        if (parent.getWidth() < 0) {
            // Undefined width

            if (parent instanceof AbstractOrderedLayout) {
                AbstractOrderedLayout ol = (AbstractOrderedLayout) parent;
                boolean horizontal = true;
                if (ol instanceof VerticalLayout) {
                    horizontal = false;
                }

                if (!horizontal && hasNonRelativeWidthComponent(ol)) {
                    // valid situation, other components defined width
                    return true;
                } else {
                    return false;
                }
            } else if (parent instanceof GridLayout) {
                GridLayout gl = (GridLayout) parent;
                Area componentArea = gl.getComponentArea(component);
                boolean columnHasWidth = false;
                for (int col = componentArea.getColumn1(); !columnHasWidth
                        && col <= componentArea.getColumn2(); col++) {
                    for (int row = 0; !columnHasWidth
                            && row < gl.getRows(); row++) {
                        Component c = gl.getComponent(col, row);
                        if (c != null) {
                            columnHasWidth = !hasRelativeWidth(c);
                        }
                    }
                }
                if (!columnHasWidth) {
                    return false;
                } else {
                    // Other components define column width
                    return true;
                }
            } else if (parent instanceof Form) {
                /*
                 * If some other part of the form is not relative it determines
                 * the component width
                 */
                return hasNonRelativeWidthComponent((Form) parent);
            } else if (parent instanceof AbstractSplitPanel
                    || parent instanceof TabSheet
                    || parent instanceof CustomComponent) {
                // FIXME Could we use com.vaadin package name here and
                // fail for all component containers?
                // FIXME Actually this should be moved to containers so it can
                // be implemented for custom containers
                // TODO vertical splitpanel with another non relative component?
                return false;
            } else if (parent instanceof Window) {
                // Sub window can define width based on caption
                if (parent.getCaption() != null
                        && !parent.getCaption().equals("")) {
                    return true;
                } else {
                    return false;
                }
            } else if (parent instanceof Panel) {
                // TODO Panel should be able to define width based on caption
                return false;
            } else {
                return true;
            }
        } else if (hasRelativeWidth(parent)) {
            // Relative width
            if (parent.getParent() == null) {
                return true;
            }

            return parentCanDefineWidth(parent);
        } else {
            return true;
        }

    }

    private static boolean hasNonRelativeWidthComponent(Form form) {
        Layout layout = form.getLayout();
        Layout footer = form.getFooter();

        if (layout != null && !hasRelativeWidth(layout)) {
            return true;
        }
        if (footer != null && !hasRelativeWidth(footer)) {
            return true;
        }

        return false;
    }

    private static Map creationLocations = new HashMap();
    private static Map widthLocations = new HashMap();
    private static Map heightLocations = new HashMap();

    public static class FileLocation implements Serializable {
        public String method;
        public String file;
        public String className;
        public String classNameSimple;
        public int lineNumber;

        public FileLocation(StackTraceElement traceElement) {
            file = traceElement.getFileName();
            className = traceElement.getClassName();
            classNameSimple = className
                    .substring(className.lastIndexOf('.') + 1);
            lineNumber = traceElement.getLineNumber();
            method = traceElement.getMethodName();
        }
    }

    public static void setCreationLocation(Object object) {
        setLocation(creationLocations, object);
    }

    public static void setWidthLocation(Object object) {
        setLocation(widthLocations, object);
    }

    public static void setHeightLocation(Object object) {
        setLocation(heightLocations, object);
    }

    private static void setLocation(Map map,
            Object object) {
        StackTraceElement[] traceLines = Thread.currentThread().getStackTrace();
        for (StackTraceElement traceElement : traceLines) {
            Class cls;
            try {
                String className = traceElement.getClassName();
                if (className.startsWith("java.")
                        || className.startsWith("sun.")) {
                    continue;
                }

                cls = Class.forName(className);
                if (cls == ComponentSizeValidator.class
                        || cls == Thread.class) {
                    continue;
                }

                if (Component.class.isAssignableFrom(cls)
                        && !CustomComponent.class.isAssignableFrom(cls)) {
                    continue;
                }
                FileLocation cl = new FileLocation(traceElement);
                map.put(object, cl);
                return;
            } catch (Exception e) {
                getLogger().log(Level.FINER,
                        "An exception occurred while validating sizes.", e);
            }

        }
    }

    private static Logger getLogger() {
        return Logger.getLogger(ComponentSizeValidator.class.getName());
    }

    /**
     * Validates the layout and returns a collection of errors
     *
     * @since 7.1
     * @param ui
     *            The UI to validate
     * @return A collection of errors. An empty collection if there are no
     *         errors.
     */
    public static List validateLayouts(UI ui) {
        List invalidRelativeSizes = ComponentSizeValidator
                .validateComponentRelativeSizes(ui.getContent(),
                        new ArrayList(),
                        null);

        // Also check any existing subwindows
        if (ui.getWindows() != null) {
            for (Window subWindow : ui.getWindows()) {
                invalidRelativeSizes = ComponentSizeValidator
                        .validateComponentRelativeSizes(subWindow.getContent(),
                                invalidRelativeSizes, null);
            }
        }
        return invalidRelativeSizes;

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy