org.wings.SComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.wings.SComponent.java

Source

/*
 * Copyright 2000,2005 wingS development team.
 *
 * This file is part of wingS (http://wingsframework.org).
 *
 * wingS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * Please see COPYING for the complete licence.
 */
package org.wings;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wings.border.SBorder;
import org.wings.event.*;
import org.wings.io.Device;
import org.wings.plaf.ComponentCG;
import org.wings.plaf.Update;
import org.wings.script.JavaScriptListener;
import org.wings.script.ScriptListener;
import org.wings.session.Session;
import org.wings.session.SessionManager;
import org.wings.style.*;
import org.wings.util.ComponentVisitor;

import javax.swing.*;
import javax.swing.event.EventListenerList;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;
import java.util.List;

/**
 * Object having a graphical representation that can be displayed on the
 * screen and that can interact with the user.
 *
 * @author <a href="mailto:haaf@mercatis.de">Armin Haaf</a>
 */
public abstract class SComponent implements Cloneable, Serializable, Renderable {

    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

    private static final Log log = LogFactory.getLog(SComponent.class);

    private static final int ACTION_CONDITIONS_AMOUNT = 2;

    /* Components unique name. */
    private String name;

    /**
     * the session
     */
    private transient Session session;

    /**
     * The code generation delegate, which is responsible for
     * the visual representation of this component.
     */
    private transient ComponentCG cg;

    /**
     * Vertical alignment
     */
    private int verticalAlignment = SConstants.NO_ALIGN;

    /**
     * Horizontal alignment
     */
    private int horizontalAlignment = SConstants.NO_ALIGN;

    /**
     * The name of the style class
     */
    private String style;

    private HashMap<Selector, String> styles;

    /**
     * Map of {@link Selector} to CSS {@link Style}s currently assigned to this component.
     */
    protected Map<Selector, Style> dynamicStyles;

    /**
     * Visibility of the component.
     */
    protected boolean visible = true;

    /**
     * Visibility of the component.
     */
    protected boolean recursivelyVisible = false;

    /**
     * Enabled / disabled.
     */
    protected boolean enabled = true;

    /**
     * The container, this component resides in.
     */
    private SContainer parent;

    /**
     * The frame in which this component resides.
     */
    private SFrame parentFrame;

    /**
     * The border for the component.
     */
    private SBorder border;

    /**
     * The tooltip for this component.
     */
    private String tooltip;

    /**
     * The focus traversal Index
     */
    private int focusTraversalIndex = -1;

    /**
     * Preferred size of component in pixel.
     */
    private SDimension preferredSize;

    /**
     * This is for performance optimizations. With this flag is set, property change
     * events are generated and so every property setter method has to test if a property
     * has changed and temporarily store the old value to generate the property
     * change event
     */
    private boolean fireComponentChangeEvents = false;

    /**
     * Generate and fire {@link SParentFrameEvent}s. Performace optimitation
     */
    private boolean fireParentFrameChangeEvents = false;

    /**
     * Flag indiccating if {@link SRenderEvent}s should be fired. Used for performace reasons
     */
    private boolean fireRenderEvents = false;

    /**
     * All event listeners of this component
     */
    private EventListenerList listeners;

    private boolean showAsFormComponent = true;

    private boolean reloadForced = false;

    private SPopupMenu popupMenu;

    /*private boolean inheritsPopupMenu;*/

    private InputMap[] inputMaps;

    /**
     * Contains all script listeners of the component.
     */
    private List<ScriptListener> scriptListenerList;

    private ActionMap actionMap;

    private Map<Action, ActionEvent> actionEvents;

    private transient SRenderEvent renderEvent;

    private SParentFrameListener globalInputMapListener;

    private Map<Object, Object> clientProperties;

    /**
     * Internal constants for {@link #fireRenderEvent(int)}
     */
    public static final int START_RENDERING = 1;

    /**
     * Internal constants for {@link #fireRenderEvent(int)}
     */
    public static final int DONE_RENDERING = 2;

    /**
     * Constants for conditions on which actions are triggered. Mainly two
     * cases: the focus has either to be at the component (or at a child)
     * or somewhere in the parent frame.
     *
     * @see #setInputMap(int, javax.swing.InputMap)
     */
    public static final int WHEN_FOCUSED_OR_ANCESTOR_OF_FOCUSED_COMPONENT = 0;

    /**
     * Constants for conditions on which actions are triggered. Mainly two
     * cases: the focus has either to be at the component (or at a child)
     * or somewhere in the parent frame.
     *
     * @see #setInputMap(int, javax.swing.InputMap)
     */
    public static final int WHEN_IN_FOCUSED_FRAME = 1;

    /**
     * Global CSS selector 
     */
    public static final Selector SELECTOR_ALL = new Selector("everything");

    /**
     * Performance improvement constant
     */
    private static final ScriptListener[] EMPTY_SCRIPTLISTENERLIST = new ScriptListener[0];

    /**
     *  PropertyChangeSupport
     */
    protected final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);

    private STransferHandler transferHandler = null;

    /**
     * Default empty constructor.
     * The method updateCG is called during construction time to get a cg delegate installed (renderer).
     */
    public SComponent() {
        updateCG();
    }

    /**
     * Returns the border of this component or null if no border has been set.
     *
     * @return the border object
     * @see #setBorder(SBorder)
     */
    public SBorder getBorder() {
        return border;
    }

    /**
     * Sets the border for this component.
     *
     * @param border the border to be set for the component
     */
    public void setBorder(SBorder border) {
        SBorder oldVal = this.getBorder();
        reloadIfChange(this.border, border);
        if (this.border != null)
            this.border.setComponent(null);
        this.border = border;
        if (this.border != null)
            this.border.setComponent(this);
        propertyChangeSupport.firePropertyChange("border", oldVal, this.border);
    }

    /**
     * Return the parent container.
     *
     * @return the container this component resides in
     */
    public final SContainer getParent() {
        return parent;
    }

    /**
     * Sets the parent container. Also gets the parent frame from the parent.
     *
     * @param parent the container
     */
    public void setParent(SContainer parent) {
        SContainer oldVal = this.parent;
        this.parent = parent;
        if (parent != null)
            setParentFrame(parent.getParentFrame());
        else
            setParentFrame(null);
        propertyChangeSupport.firePropertyChange("parent", oldVal, this.parent);

        setRecursivelyVisible(parent != null ? parent.isRecursivelyVisible() : false);
    }

    /**
     * Sets the parent frame.
     *
     * @param parentFrame the frame
     */
    protected void setParentFrame(SFrame parentFrame) {
        SFrame oldVal = this.parentFrame;
        if (this.parentFrame == parentFrame) {
            return;
        }

        if (this.parentFrame != null) {
            unregister();
            fireParentFrameEvent(
                    new SParentFrameEvent(this, SParentFrameEvent.PARENTFRAME_REMOVED, this.parentFrame));
        }

        this.parentFrame = parentFrame;

        if (this.parentFrame != null) {
            register();
            // notify the listeners...
            fireParentFrameEvent(
                    new SParentFrameEvent(this, SParentFrameEvent.PARENTFRAME_ADDED, this.parentFrame));
        }

        if (this.popupMenu != null) {
            popupMenu.setParentFrame(parentFrame);
        }

        //reload();
        if (getScriptListeners().length > 0)
            reload();
        if (dynamicStyles != null && dynamicStyles.size() > 0)
            reload();

        propertyChangeSupport.firePropertyChange("parentFrame", oldVal, this.parentFrame);
    }

    public void setComponentPopupMenu(SPopupMenu popupMenu) {
        reloadIfChange(this.popupMenu, popupMenu);
        if (this.popupMenu != null) {
            getSession().getMenuManager().deregisterMenuLink(this.popupMenu, this);
            this.popupMenu.setParentFrame(null);
        }
        SPopupMenu oldVal = this.popupMenu;
        this.popupMenu = popupMenu;
        if (this.popupMenu != null) {
            getSession().getMenuManager().registerMenuLink(this.popupMenu, this);
            this.popupMenu.setParentFrame(getParentFrame());
        }
        propertyChangeSupport.firePropertyChange("componentPopupMenu", oldVal, this.popupMenu);
    }

    public SPopupMenu getComponentPopupMenu() {
        return popupMenu;
    }

    /**
     * The URL under which this component is accessible for the browser.
     * This is equivalent to the URL of the component's root frame, as this is the
     * node externalized to the browser via the {@link org.wings.resource.ReloadResource}
     * externalizer.
     *
     * @return The HTTP URL where this component can be accessed.
     */
    public RequestURL getRequestURL() {
        SFrame p = getParentFrame();
        if (p == null)
            throw new IllegalStateException("no parent frame");

        return p.getRequestURL();
    }

    /**
     * Set the preferred size of the receiving component in pixel.
     * It is not guaranteed that the component accepts this property because of
     * missing implementations in the component cg or html properties.
     * If <i>width</i> or <i>height</i> is zero, it is ignored and the browser
     * defines the size.
     *
     * @see org.wings.SComponent#getPreferredSize
     */
    public void setPreferredSize(SDimension preferredSize) {
        SDimension oldVal = this.preferredSize;
        reloadIfChange(this.preferredSize, preferredSize);
        this.preferredSize = preferredSize;
        propertyChangeSupport.firePropertyChange("preferredSize", oldVal, this.preferredSize);
    }

    /**
     * Get the preferred size of this component.
     *
     * @see SComponent#setPreferredSize
     */
    public SDimension getPreferredSize() {
        return preferredSize;
    }

    /**
     * Adds the specified component listener to receive component events from
     * this component.
     * If l is null, no exception is thrown and no action is performed.
     *
     * @param l the component listener.
     * @see org.wings.event.SComponentEvent
     * @see org.wings.event.SComponentListener
     * @see org.wings.SComponent#removeComponentListener
     */
    public final void addComponentListener(SComponentListener l) {
        addEventListener(SComponentListener.class, l);
        fireComponentChangeEvents = true;
    }

    /**
     * Removes the specified component listener so that it no longer
     * receives component events from this component. This method performs
     * no function, nor does it throw an exception, if the listener
     * specified by the argument was not previously added to this component.
     * If l is null, no exception is thrown and no action is performed.
     *
     * @param l the component listener.
     * @see org.wings.event.SComponentEvent
     * @see org.wings.event.SComponentListener
     * @see org.wings.SComponent#addComponentListener
     */
    public final void removeComponentListener(SComponentListener l) {
        removeEventListener(SComponentListener.class, l);
    }

    /**
     * Registers a "parent frame listener" to receive events from
     * this component when the parent frame chanegs i.e. via {@link #setParentFrame(SFrame)}.
     * If l is null, no exception is thrown and no action is performed.
     *
     * @param l the parent frame listener. May be <code>null</code>.
     * @see org.wings.event.SParentFrameEvent
     * @see org.wings.event.SParentFrameListener
     * @see org.wings.SComponent#removeParentFrameListener
     * @see SComponent#removeNotify()
     * @see SComponent#addNotify()
     */
    public final void addParentFrameListener(SParentFrameListener l) {
        addEventListener(SParentFrameListener.class, l);
        fireParentFrameChangeEvents = true;
    }

    /**
     * Removes the specified parent frame listener so that it no longer
     * receives events from this component. This method performs
     * no function, nor does it throw an exception, if the listener
     * specified by the argument was not previously added to this component.
     * If l is null, no exception is thrown and no action is performed.
     *
     * @param l the parent frame listener.
     * @see org.wings.event.SParentFrameEvent
     * @see org.wings.event.SParentFrameListener
     * @see org.wings.SComponent#addParentFrameListener
     * @see SComponent#removeNotify()
     * @see SComponent#addNotify()
     */
    public final void removeParentFrameListener(SParentFrameListener l) {
        removeEventListener(SParentFrameListener.class, l);
    }

    /**
     * Reports a component change.
     *
     * @param aEvent report this event to all listeners
     * @see org.wings.event.SComponentListener
     */
    protected void fireComponentChangeEvent(SComponentEvent aEvent) {
        // maybe the better way to do this is to user the getListenerList
        // and iterate through all listeners, this saves the creation of
        // an array but it must cast to the apropriate listener
        Object[] listeners = getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == SComponentListener.class) {
                // Lazily create the event:
                processComponentEvent((SComponentListener) listeners[i + 1], aEvent);
            }
        }

    }

    /**
     * Reports a parent frame change.
     *
     * @param aEvent report this event to all listeners
     * @see org.wings.event.SParentFrameListener
     */
    private void fireParentFrameEvent(SParentFrameEvent aEvent) {
        // are listeners registered?
        if (fireParentFrameChangeEvents) {
            // maybe the better way to do this is to user the getListenerList
            // and iterate through all listeners, this saves the creation of
            // an array but it must cast to the apropriate listener
            Object[] listeners = getListenerList();
            for (int i = listeners.length - 2; i >= 0; i -= 2) {
                if (listeners[i] == SParentFrameListener.class) {
                    // Lazily create the event:
                    processParentFrameEvent((SParentFrameListener) listeners[i + 1], aEvent);
                }
            }
        }

    }

    /**
     * Processes parent frame events occurring on this component by
     * dispatching them to any registered
     * <code>SParentFrameListener</code> objects.
     * <p/>
     */
    private void processParentFrameEvent(SParentFrameListener listener, SParentFrameEvent event) {
        int id = event.getID();
        switch (id) {
        case SParentFrameEvent.PARENTFRAME_ADDED:
            listener.parentFrameAdded(event);
            break;
        case SParentFrameEvent.PARENTFRAME_REMOVED:
            listener.parentFrameRemoved(event);
            break;
        }
    }

    /**
     * Processes component events occurring on this component by
     * dispatching them to any registered
     * <code>SComponentListener</code> objects.
     * <p/>
     * This method is not called unless component events are
     * enabled for this component. Component events are enabled
     * when one of the following occurs:
     * <p><ul>
     * <li>A <code>SComponentListener</code> object is registered
     * via <code>addComponentListener</code>.
     * </ul>
     *
     * @param e the component event.
     * @see org.wings.event.SComponentEvent
     * @see org.wings.event.SComponentListener
     * @see org.wings.SComponent#addComponentListener
     */
    protected void processComponentEvent(SComponentListener listener, SComponentEvent e) {
        int id = e.getID();
        switch (id) {
        case SComponentEvent.COMPONENT_RESIZED:
            listener.componentResized(e);
            break;
        case SComponentEvent.COMPONENT_MOVED:
            listener.componentMoved(e);
            break;
        case SComponentEvent.COMPONENT_SHOWN:
            listener.componentShown(e);
            break;
        case SComponentEvent.COMPONENT_HIDDEN:
            listener.componentHidden(e);
            break;
        }
    }

    /**
     * Adds the specified component listener to receive component events from
     * this component.
     * If l is null, no exception is thrown and no action is performed.
     * If there is already a ScriptListener which is equal, the new one is not
     * added.
     *
     * @param listener the component listener.
     * @see org.wings.event.SComponentEvent
     * @see org.wings.event.SComponentListener
     * @see org.wings.SComponent#removeComponentListener
     */
    public final void addScriptListener(ScriptListener listener) {
        if (scriptListenerList != null && scriptListenerList.contains(listener))
            return;

        if (scriptListenerList == null) {
            scriptListenerList = new LinkedList<ScriptListener>();
        }

        int placingPosition = -1;
        for (int i = 0; i < scriptListenerList.size() && placingPosition < 0; i++) {
            ScriptListener existingListener = scriptListenerList.get(i);
            if (existingListener.getPriority() < listener.getPriority())
                placingPosition = i;
        }
        reload();
        if (placingPosition >= 0)
            scriptListenerList.add(placingPosition, listener);
        else
            scriptListenerList.add(listener);
    }

    public final void addScriptListenerWithoutReload(ScriptListener listener) {
        if (scriptListenerList != null && scriptListenerList.contains(listener))
            return;

        if (scriptListenerList == null) {
            scriptListenerList = new LinkedList<ScriptListener>();
        }

        int placingPosition = -1;
        for (int i = 0; i < scriptListenerList.size() && placingPosition < 0; i++) {
            ScriptListener existingListener = scriptListenerList.get(i);
            if (existingListener.getPriority() < listener.getPriority())
                placingPosition = i;
        }

        if (placingPosition >= 0)
            scriptListenerList.add(placingPosition, listener);
        else
            scriptListenerList.add(listener);
    }

    /**
     * Removes the specified component listener so that it no longer
     * receives component events from this component. This method performs
     * no function, nor does it throw an exception, if the listener
     * specified by the argument was not previously added to this component.
     * If l is null, no exception is thrown and no action is performed.
     *
     * @param listener the component listener.
     * @see org.wings.event.SComponentEvent
     * @see org.wings.event.SComponentListener
     * @see org.wings.SComponent#addComponentListener
     */
    public final void removeScriptListener(ScriptListener listener) {
        if (scriptListenerList != null) {
            scriptListenerList.remove(listener);
            reload();
        }
    }

    public final void removeScriptListenerWithoutReload(ScriptListener listener) {
        if (scriptListenerList != null) {
            scriptListenerList.remove(listener);
        }
    }

    /**
     * returns the script listeners of this component
     *
     * @return the ScriptListener Array.
     */
    public ScriptListener[] getScriptListeners() {
        if (scriptListenerList != null) {
            return scriptListenerList.toArray(new ScriptListener[scriptListenerList.size()]);
        } else {
            return EMPTY_SCRIPTLISTENERLIST;
        }
    }

    /**
     * Returns the script listeners of this component.
     *
     * @return The <code>ScriptListener</code>s in a <code>List</code>.
     */
    public List<ScriptListener> getScriptListenerList() {
        if (scriptListenerList != null) {
            return Collections.unmodifiableList(scriptListenerList);
        } else {
            return Collections.emptyList();
        }
    }

    /**
     * Sets the name property of a component which must be <b>unique</b>!
     * <br/>Assigning the same name multiple times will cause strange results!
     * <p/>
     * <p>Valid names must begin with a letter ([A-Za-z]), underscores ("_") or dollars ("$") and may be followed by any number of
     * letters, digits ([0-9]), underscores ("_") and dollars ("$")
     * <p/>
     * <p>If no name is set, it is generated when necessary.
     * <p/>
     * <p><i>Explanation:</i> This property is an identifier which is used inside the generated HTML as an element identifier (id="")
     * and sometimes as a javascript function name.
     *
     * @param uniqueName A <b>unique</b> name to set. <b>Only valid identifier as described are allowed!</b>
     * @see Character
     */
    public void setName(String uniqueName) {
        if (uniqueName != null) {
            char ch = uniqueName.charAt(0);
            if (uniqueName.length() == 0 || !(Character.isLetter(ch) || ch == '_' || ch == '$'))
                throw new IllegalArgumentException(uniqueName + " is not a valid identifier");
            for (int i = 1; i < uniqueName.length(); i++) {
                ch = uniqueName.charAt(i);
                if (!(Character.isLetter(ch) || Character.isDigit(ch) || ch == '_' || ch == '$'))
                    throw new IllegalArgumentException(uniqueName + " is not a valid identifier");
            }
        }
        setNameRaw(uniqueName);
    }

    /**
     * <b>Direct setter for name. Do not use unless you explicitly know what you're doing!</b>
     * (Former package) protected raw setter for component name to avoid sanity check.
     *
     * @param uncheckedName String to use as componentn name/identifier.
     */
    public void setNameRaw(String uncheckedName) {
        String oldVal = this.name;
        reloadIfChange(this.name, uncheckedName);
        this.name = uncheckedName;
        unregister();
        register();
        propertyChangeSupport.firePropertyChange("name", oldVal, this.name);
    }

    /**
     * Gets the name property of a component. This property is an identifier,so it should be always unique.
     * For details refer to {@link #setName(String)}
     *
     * @return The name of the component.
     */
    public final String getName() {
        if (name == null)
            name = getSession().createUniqueId();
        return name;
    }

    /**
     * Return the session this component belongs to.
     *
     * @return the session
     */
    public final Session getSession() {
        if (session == null) {
            session = SessionManager.getSession();
        }

        return session;
    }

    /*
     * If a subclass implements the {@link LowLevelEventListener} interface,
     * it will be unregistered at the associated dispatcher.
     */
    final void unregister() {
        if (getSession().getDispatcher() != null && this instanceof LowLevelEventListener) {
            getSession().getDispatcher().unregister((LowLevelEventListener) this);
        }
    }

    /*
     * If a subclass implements the {@link LowLevelEventListener} interface,
     * it will be registered at the associated dispatcher.
     */
    final void register() {
        if (getSession().getDispatcher() != null && this instanceof LowLevelEventListener) {
            getSession().getDispatcher().register((LowLevelEventListener) this);
        }
    }

    /**
     * Set an CSS class name provided by the laf-provided Cascading Style Sheet (CSS), which should
     * be applied to this component.
     * <p>
     * <b>Note:</b> Probably the {@link #addStyle(String)} method is more what you want.
     * <p>By <b>default</b> this is set to the wingS component class name (i.e. "SLabel").
     * <p>The PLAFs render the value of this String to an <code>class="<i>cssClassName</i>"</code> attribute
     * inside the generated HTML code of this component.
     * <p>The default wingS plaf initializes this by default to the wingS component class name
     * (i.e. <code>SButton</code> for button instances). <br/>Please be aware if you <b>replace</b> this
     * default value, the default wingS style will no longer take effect, as they operate on these
     * default styles. To avoid this you should append your CSS styles via spaces i.e.<br>
     * <code>c.setStyle(c.getStyle + "myStyle");</code>
     * <p>Please consider using {@link #addStyle(String)} to avoid disabling of default wingS stylesheet set.
     *
     * @param cssClassName The new CSS name value for this component
     * @see #addStyle(String)
     * @see #removeStyle(String)
     */
    public void setStyle(String cssClassName) {
        String oldVal = this.style;
        reloadIfChange(style, cssClassName);
        this.style = cssClassName;
        propertyChangeSupport.firePropertyChange("style", oldVal, this.style);
    }

    /**
     * Append a style class name to the style string. Use this method if you want to append a specific CSS
     * class to a componentn without loosing the other CSS styles assigned to the component (i.e. the wingS
     * default styles.)
     * @param additionalCssClassName The style class to remove (if existing).
     * @see #removeStyle(String)
     */
    public void addStyle(String additionalCssClassName) {
        if (this.style == null || this.style.length() == 0) {
            setStyle(additionalCssClassName); // trivial case
        } else {
            if (this.style.indexOf(additionalCssClassName) < 0) {
                setStyle(this.style + " " + additionalCssClassName);
            }
        }
    }

    /**
     * Remove a style class definiton from this component.
     * @param cssStyleClassName The style class to remove (if existing).
     */
    public void removeStyle(String cssStyleClassName) {
        if (this.style != null && cssStyleClassName != null && this.style.indexOf(cssStyleClassName) >= 0) {
            if (this.style.length() == cssStyleClassName.length())
                setStyle(null); // trivial case
            else
                setStyle(this.style.replaceAll("\\b" + cssStyleClassName + "\\b", "").replaceAll("  ", " ").trim());
        }
    }

    /**
     * @return The current CSS style class name. Defaults to the unqualified wingS component class name.
     * @see #setStyle(String)
     */
    public String getStyle() {
        return style;
    }

    /**
     * Set an CSS class name for a certain area of the component which is addressed by the pseudo selector.
     *
     * @param selector The pseudo selector addressing an area of the component
     * @param cssClassName The new CSS name value for this component
     */
    public void setStyle(Selector selector, String cssClassName) {
        if (styles == null)
            styles = new HashMap<Selector, String>(4);
        if (cssClassName == null)
            styles.remove(selector);
        else
            styles.put(selector, cssClassName);
        reload();
    }

    public String getStyle(Selector selector) {
        return styles != null ? styles.get(selector) : null;
    }

    /**
     * Register a new CSS style on this component for a specfic CSS selector.
     * <p>Typically you will want to use the method {@link #setAttribute(org.wings.style.CSSProperty, String)}
     * to specify a single CSS property/value pair on this component.
     *
     * @param style A Style instance.
     */
    public void addDynamicStyle(Style style) {
        if (dynamicStyles == null)
            dynamicStyles = new HashMap<Selector, Style>(4);
        dynamicStyles.put(style.getSelector(), style);
        reload();
    }

    /**
     * Remove all CSS style definitions defined for the passed CSS selector.
     *
     * @param selector The selector. The default selector for most CSS attributes is {@link #SELECTOR_ALL}.
     */
    public void removeDynamicStyle(Selector selector) {
        if (dynamicStyles == null)
            return;
        dynamicStyles.remove(selector);
        reload();
    }

    /**
     * Returns the style defined for the passed CSS selector.
     *
     * @param selector The CSS selector the style to retrieve. See {@link org.wings.style.Style#getSelector()}.
     *         The default selector for most CSS styles is {@link #SELECTOR_ALL}.
     * @return A style (collection of css property/value pairs) or <code>null</code>
     */
    public Style getDynamicStyle(Selector selector) {
        if (dynamicStyles == null)
            return null;
        return dynamicStyles.get(selector);
    }

    /**
     * Adds the passed collection of {@link Style} definitions. Existing Styles for the same CSS selectors
     * (see {@link org.wings.style.Style#getSelector()}) are overwritten.
     *
     * @param dynamicStyles A collection collection of {@link Style} definitions.
     */
    public void setDynamicStyles(Collection dynamicStyles) {
        Map oldVal = this.dynamicStyles;
        if (dynamicStyles == null)
            return;
        if (this.dynamicStyles == null)
            this.dynamicStyles = new HashMap<Selector, Style>(4);
        for (Object dynamicStyle : dynamicStyles) {
            Style style = (Style) dynamicStyle;
            this.dynamicStyles.put(style.getSelector(), style);
        }
        reload();
        propertyChangeSupport.firePropertyChange("dynamicStyles", oldVal, this.dynamicStyles);
    }

    /**
     * Returns the collection of currently defined CSS styles on this component.
     *
     * @return A unmodifyable collection of {@link Style} instances.
     */
    public Collection getDynamicStyles() {
        if (dynamicStyles == null || dynamicStyles.size() == 0)
            return null;
        return Collections.unmodifiableCollection(dynamicStyles.values());
    }

    /**
     * Defines a free text css property / value pair to this component.
     * The CSS property will appear as an inline style in the generated HTML code.
     */
    public void setAttribute(String cssPropertyName, String value) {
        final CSSProperty property = CSSProperty.valueOf(cssPropertyName);
        if (CSSProperty.BORDER_PROPERTIES.contains(property))
            throw new IllegalArgumentException("Border properties have to be applied to the border!");
        setAttribute(SELECTOR_ALL, property, value);
    }

    /**
     * Assign or overwrite a CSS property/value pair on this component. This CSS property definition will
     * use a CSS selector which adresses this component as whole as CSS selector (<code>new CSSProperty(this)</code>).
     * The CSS property will appear as an inline style in the generated HTML code.
     *
     * @param property      The CSS property (i.e. {@link CSSProperty#BACKGROUND}).
     * @param propertyValue A valid string value for this CSS property (i.e. <code>red</code> or <code>#fff</code> in our example).
     */
    public void setAttribute(CSSProperty property, String propertyValue) {
        if (CSSProperty.BORDER_PROPERTIES.contains(property))
            throw new IllegalArgumentException("Border properties have to be applied to the border!");
        setAttribute(SELECTOR_ALL, property, propertyValue);
    }

    /**
     * Assign or overwrite a CSS property/value pair at this component. This CSS property definition will
     * use the CSS selector you passed, so in the most exotic case it could affect a totally different
     * component or component area. Typically you use this method to assign CSS property values to
     * pseudo CSS selectors {@link Selector}. This are selector affecting only a part of a component
     * and not the component at all..
     * The CSS property will appear as an inline style in the generated HTML code.
     *
     * @param selector A valid CSS selector. Typically values are i.e. the {@link #SELECTOR_ALL}
     * or other <code>SELECTOR_xxx</code> value instances declared in the component.
     * (look ie. at {@link STabbedPane#SELECTOR_CONTENT}) or manually constructed instances of
     * <code>Selector</code>. In most case {@link #setAttribute(org.wings.style.CSSProperty, String)} will be your
     * choice.
     * @param property The css property you want to define a value for
     * @param propertyValue A valid string value for this property.
     */
    public void setAttribute(Selector selector, CSSProperty property, String propertyValue) {
        Style style = getDynamicStyle(selector);
        if (style == null) {
            addDynamicStyle(new CSSStyle(selector, property, propertyValue));
        } else {
            Map<CSSProperty, String> oldStyle = style.properties();
            String oldVal = oldStyle.get(property);
            String old = style.put(property, propertyValue);
            reloadIfChange(old, propertyValue);
            propertyChangeSupport.firePropertyChange("attribute", oldVal, propertyValue);
        }
    }

    /**
     * Convenience variant of {@link #setAttribute(org.wings.style.Selector, org.wings.style.CSSProperty, String)}.
     * Converts the passed icon into a URL and applies the according CSS style.
     *
     * @param selector A valid CSS selector. Typically values are i.e. the {@link #SELECTOR_ALL}
     * or other <code>SELECTOR_xxx</code> value instances declared in the component.
     * (look ie. at {@link STabbedPane#SELECTOR_CONTENT}) or manually constructed instances of
     * <code>Selector</code>. In most case {@link #setAttribute(org.wings.style.CSSProperty, String)} will be your
     * choice.
     * @param property The css property you want to define a value for (in this case
     *                 mostly something like {@link CSSProperty#BACKGROUND_IMAGE}.
     * @param icon     The icon you want to assign.
     */
    public void setAttribute(Selector selector, CSSProperty property, SIcon icon) {
        setAttribute(selector, property, icon != null ? "url('" + icon.getURL().toString() + "')" : "none");
    }

    /**
     * Convenience variant of {@link #setAttribute(org.wings.style.Selector, org.wings.style.CSSProperty, String)}.
     * Converts the passed color into according color string.
     *
     * @param selector A valid CSS selector. Typically values are i.e. the {@link #SELECTOR_ALL}
     * or other <code>SELECTOR_xxx</code> value instances declared in the component.
     * (look ie. at {@link STabbedPane#SELECTOR_CONTENT}) or manually constructed instances of
     * <code>Selector</code>. In most case {@link #setAttribute(org.wings.style.CSSProperty, String)} will be your
     * choice.
     * @param property The css property you want to define a value for (in this case
     *                 mostly something like {@link CSSProperty#BACKGROUND_IMAGE}.
     * @param color    The color value you want to assign.
     */
    public void setAttribute(Selector selector, CSSProperty property, Color color) {
        setAttribute(selector, property, CSSStyleSheet.getAttribute(color));
    }

    public void setAttributes(Selector selector, CSSAttributeSet attributes) {
        Style style = getDynamicStyle(selector);
        if (style == null) {
            addDynamicStyle(new CSSStyle(selector, attributes));
        } else {
            Map<CSSProperty, String> oldStyleProperties = style.properties();
            boolean changed = style.putAll(attributes);
            if (changed)
                reload();
            propertyChangeSupport.firePropertyChange("attributes", oldStyleProperties, style.properties());
        }
    }

    /**
     * Returns the current background color of this component.
     *
     * @return The current background color or <code>null</code>
     */
    public Color getBackground() {
        return dynamicStyles == null || dynamicStyles.get(SELECTOR_ALL) == null ? null
                : CSSStyleSheet.getBackground((CSSAttributeSet) dynamicStyles.get(SELECTOR_ALL));
    }

    /**
     * Set the components background color.
     *
     * @param color the new background color or <code>null</code>
     */
    public void setBackground(Color color) {
        Color oldVal = this.getBackground();
        setAttribute(SELECTOR_ALL, CSSProperty.BACKGROUND_COLOR, CSSStyleSheet.getAttribute(color));
        propertyChangeSupport.firePropertyChange("background", oldVal, this.getBackground());
    }

    /**
     * Return the components foreground color.
     *
     * @return the foreground color or <code>null</code>
     */
    public Color getForeground() {
        return dynamicStyles == null || dynamicStyles.get(SELECTOR_ALL) == null ? null
                : CSSStyleSheet.getForeground((CSSAttributeSet) dynamicStyles.get(SELECTOR_ALL));
    }

    /**
     * Set the foreground color.
     *
     * @param color the new foreground color or <code>null</code>
     */
    public void setForeground(Color color) {
        Color oldVal = this.getForeground();
        setAttribute(SELECTOR_ALL, CSSProperty.COLOR, CSSStyleSheet.getAttribute(color));

        propertyChangeSupport.firePropertyChange("foreground", oldVal, this.getForeground());
    }

    /**
     * Set the font.
     *
     * @param font the new font
     */
    //TODO: firePropertyChange() in IF
    public void setFont(SFont font) {
        SFont oldVal = this.getFont();
        CSSAttributeSet attributes = CSSStyleSheet.getAttributes(font);
        Style style = getDynamicStyle(SELECTOR_ALL);
        if (style == null) {
            addDynamicStyle(new CSSStyle(SELECTOR_ALL, attributes));
        } else {
            style.remove(CSSProperty.FONT);
            style.remove(CSSProperty.FONT_FAMILY);
            style.remove(CSSProperty.FONT_SIZE);
            style.remove(CSSProperty.FONT_STYLE);
            style.remove(CSSProperty.FONT_WEIGHT);
            style.putAll(attributes);
            reload();
            propertyChangeSupport.firePropertyChange("font", oldVal, this.getFont());
        }
    }

    /**
     * Return the font used inside this component.
     *
     * @return The current font declaration or <code>null</code>
     */
    public SFont getFont() {
        return dynamicStyles == null || dynamicStyles.get(SELECTOR_ALL) == null ? null
                : CSSStyleSheet.getFont((CSSAttributeSet) dynamicStyles.get(SELECTOR_ALL));
    }

    /**
     * Set the visibility.
     *
     * @param visible wether this component will show or not
     */
    public void setVisible(boolean visible) {
        boolean old = this.visible;
        this.visible = visible;
        if (visible != old) {
            if (parent != null) {
                parent.reload();
            } else {
                reload();
            }
            propertyChangeSupport.firePropertyChange("visible", old, this.visible);

            setRecursivelyVisible(isRecursivelyVisible());
        }
    }

    /**
     * Return the <b>local</b> visibility. If set to <code>true</code> this ccmponent
     * should be visible if all parent components are visible, too.
     *
     * @return <code>true</code> If the component and it's children should show, <code>false</code> otherwise
     * @see #isRecursivelyVisible()
     */
    public boolean isVisible() {
        return visible;
    }

    /**
     * Return the visibility. If the Component itself or any of it's parent is invisible,
     * this method will return <code>false</code>.
     *
     * @return <code>true</code> if this component and all it's ancestors are visible, <code>false</code> otherwise.
     */
    public boolean isRecursivelyVisible() {
        return recursivelyVisible;
    }

    protected void setRecursivelyVisible(boolean recursivelyVisible) {
        if (isRecursivelyVisible() != recursivelyVisible) {
            this.recursivelyVisible = recursivelyVisible;
            if (fireComponentChangeEvents)
                fireComponentChangeEvent(
                        new SComponentEvent(this, isRecursivelyVisible() ? SComponentEvent.COMPONENT_SHOWN
                                : SComponentEvent.COMPONENT_HIDDEN));

            if (popupMenu != null)
                if (recursivelyVisible)
                    getSession().getMenuManager().registerMenuLink(this.popupMenu, this);
                else
                    getSession().getMenuManager().deregisterMenuLink(this.popupMenu, this);
        }
    }

    /**
     * Set wether this component should be enabled.
     *
     * @param enabled true if the component is enabled, false otherwise
     */
    public void setEnabled(boolean enabled) {
        boolean oldVal = this.enabled;
        reloadIfChange(this.enabled, enabled);
        this.enabled = enabled;
        propertyChangeSupport.firePropertyChange("enabled", oldVal, this.enabled);
    }

    /**
     * Return true if this component is enabled.
     *
     * @return true if component is enabled
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Marks this component as subject to reload.
     * The component will be registered with the ReloadManager.
     */
    public void reload() {
        getSession().getReloadManager().reload(this);
    }

    /**
     * Hands the given update to the Reload Manager.
     * @param update  the update for this component
     */
    public void update(Update update) {
        getSession().getReloadManager().addUpdate(this, update);
    }

    protected boolean isUpdatePossible() {
        return getSession().getReloadManager().isUpdateMode();
    }

    public boolean isReloadForced() {
        return reloadForced;
    }

    public void setReloadForced(boolean forced) {
        if (reloadForced != forced) {
            boolean oldVal = this.reloadForced;
            Object clientProperty = getClientProperty("onChangeSubmitListener");
            if (clientProperty != null && clientProperty instanceof JavaScriptListener) {
                removeScriptListener((JavaScriptListener) clientProperty);
                putClientProperty("onChangeSubmitListener", null);
            }
            reloadForced = forced;
            reload();
            propertyChangeSupport.firePropertyChange("reloadForced", oldVal, this.reloadForced);
        }
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     *
     * Fires an PropertyChangeEvent with the property name, the old value and the new value
     *
     * @param oldVal the old value of some property
     * @param newVal the new value of some property
     */
    protected final void reloadIfChange(Object oldVal, Object newVal) {
        if (isDifferent(oldVal, newVal))
            reload();
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     *
     * @param oldVal the old value of some property
     * @param newVal the new value of some property
     */
    protected final void reloadIfChange(int oldVal, int newVal) {
        if (oldVal != newVal)
            reload();
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     *
     * @param oldVal the old value of some property
     * @param newVal the new value of some property
     */
    protected final void reloadIfChange(boolean oldVal, boolean newVal) {
        if (oldVal != newVal)
            reload();
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     *
     * @param oldVal the old value of some property
     * @param newVal the new value of some property
     */
    protected final void reloadIfChange(byte oldVal, byte newVal) {
        if (oldVal != newVal)
            reload();
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     *
     * @param oldVal the old value of some property
     * @param newVal the new value of some property
     */
    protected final void reloadIfChange(short oldVal, short newVal) {
        if (oldVal != newVal)
            reload();
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     *
     * @param oldVal the old value of some property
     * @param newVal the new value of some property
     */
    protected final void reloadIfChange(long oldVal, long newVal) {
        if (oldVal != newVal)
            reload();
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     *
     * @param oldVal the old value of some property
     * @param newVal the new value of some property
     */
    protected final void reloadIfChange(float oldVal, float newVal) {
        if (oldVal != newVal)
            reload();
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     *
     * @param oldVal the old value of some property
     * @param newVal the new value of some property
     */
    protected final void reloadIfChange(double oldVal, double newVal) {
        if (oldVal != newVal)
            reload();
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     *
     * @param oldVal the old value of some property
     * @param newVal the new value of some property
     */
    protected final void reloadIfChange(char oldVal, char newVal) {
        if (oldVal != newVal)
            reload();
    }

    /**
     * Mark this component as subject to reload if the property,
     * that is given in its old and new fashion, changed.
     */
    @SuppressWarnings({ "unchecked" })
    public void write(Device s) throws IOException {
        try {
            if (visible) {
                cg.write(s, this);
            }
        } catch (IOException se) {
            // Typical double-clicks. Not severe
            log.debug("Not Severe: Socket exception during code generation for " + getClass().getName() + se);
        } catch (Throwable t) {
            // should we warn here? or maybe throw an error...
            log.error("Exception during code generation for " + getClass().getName(), t);
        }
    }

    /**
     * A string representation of this component. Uses the {@link #paramString()} methods
     *
     * @return string representation of this component with all properties.
     */
    @Override
    public String toString() {
        return getClass().getName() + "[" + getName() + "]";
    }

    /**
     * Generates a string describing this <code>SComponent</code>.
     * This method is mainly for debugging purposes.
     *
     * @return a string containing all properties
     */
    protected String paramString() {
        StringBuilder buffer = new StringBuilder(getClass().getName());
        buffer.append("[");

        try {
            BeanInfo info = Introspector.getBeanInfo(getClass());
            PropertyDescriptor[] descriptors = info.getPropertyDescriptors();

            boolean first = true;
            for (PropertyDescriptor descriptor : descriptors) {
                try {
                    Method getter = descriptor.getReadMethod();
                    if (getter == null || getter.getName().startsWith("getParent")) {
                        continue;
                    }
                    // System.out.println("invoking " + this.getClass().getDescription()+"."+getter.getDescription());
                    Object value = getter.invoke(this);
                    if (first) {
                        first = false;
                    } else {
                        buffer.append(",");
                    }
                    buffer.append(descriptor.getName() + "=" + value);
                } catch (Exception e) {
                    log.debug("Exception during paramString()" + e);
                }
            }
        } catch (Exception e) {
            log.debug("Exception during paramString()" + e);
        }

        buffer.append("]");
        return buffer.toString();
    }

    /**
     * Default implementation of the method in
     * {@link LowLevelEventListener}.
     */
    public String getLowLevelEventId() {
        return getName();
    }

    /**
     * Return the parent frame.
     * <p><b>NOTE:</b> You will receive <code>null</code> if you call this i.e. during
     * component creation time, as the parent frame is set when you add it to a visible {@link SContainer}.
     * Use {@link #addParentFrameListener(org.wings.event.SParentFrameListener)} in this case.
     *
     * @return the parent frame
     * @see #addParentFrameListener(org.wings.event.SParentFrameListener)
     */
    public SFrame getParentFrame() {
        return parentFrame;
    }

    /**
     * Return true, if this component is contained in a form.
     *
     * @return true, if this component resides in a form, false otherwise
     */
    public boolean getResidesInForm() {
        SComponent parent = getParent();

        boolean actuallyDoes = parent instanceof SForm;
        while (parent != null && !actuallyDoes) {
            parent = parent.getParent();
            actuallyDoes = parent instanceof SForm;
        }

        return actuallyDoes;
    }

    /**
     * Set the tooltip text. To style use HTML tags.
     *
     * @param t the new tooltip text
     */
    public void setToolTipText(String t) {
        String oldVal = this.tooltip;
        reloadIfChange(this.tooltip, t);
        this.tooltip = t;
        propertyChangeSupport.firePropertyChange("toolTipText", oldVal, this.tooltip);
    }

    /**
     * Return the tooltip text.
     *
     * @return the tooltip text
     */
    public String getToolTipText() {
        return tooltip;
    }

    /**
     * The index in which the focus is traversed using Tab. This is
     * a very simplified notion of traversing the focus, but that is,
     * what browser like interfaces currently offer. This has a bit rough
     * edge, since you have to make sure, that the index is unique within
     * the whole frame. You probably don't want to change this
     * programmatically, but this is set usually by the template property
     * manager.
     *
     * @param index the focus traversal index. Pressing the focus traversal
     *              key (usually TAB) in the browser jumps to the next index.
     *              Must not be zero.
     */
    public void setFocusTraversalIndex(int index) {
        int oldVal = this.focusTraversalIndex;
        reloadIfChange(this.focusTraversalIndex, index);
        focusTraversalIndex = index;
        propertyChangeSupport.firePropertyChange("focusTraversalIndex", oldVal, this.focusTraversalIndex);
    }

    /**
     * returns the focus traversal index.
     *
     * @see #setFocusTraversalIndex(int)
     */
    public int getFocusTraversalIndex() {
        return focusTraversalIndex;
    }

    /**
     * Clone this component.
     *
     * @return a clone of this component
     */
    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (Exception e) {
            log.error("Unable to clone component", e);
            return null;
        }
    }

    /**
     * Return the value of the horizontal alignment property.
     *
     * @return the horizontal alignment
     * @see SConstants
     */
    public int getHorizontalAlignment() {
        return horizontalAlignment;
    }

    /**
     * Set the horizontal alignment.
     *
     * @param alignment new value for the horizontal alignment
     * @see SConstants
     */
    public void setHorizontalAlignment(int alignment) {
        int oldVal = this.horizontalAlignment;
        reloadIfChange(this.horizontalAlignment, alignment);
        horizontalAlignment = alignment;
        propertyChangeSupport.firePropertyChange("horizontalAlignment", oldVal, this.horizontalAlignment);
    }

    /**
     * Set the vertical alignment.
     *
     * @param alignment new value for the vertical alignment
     * @see SConstants
     */
    public void setVerticalAlignment(int alignment) {
        int oldVal = this.verticalAlignment;
        reloadIfChange(this.verticalAlignment, alignment);
        verticalAlignment = alignment;
        propertyChangeSupport.firePropertyChange("verticalAlignment", oldVal, this.verticalAlignment);
    }

    /**
     * Return the value of the vertical alignment property.
     *
     * @return the vertical alignment
     * @see SConstants
     */
    public int getVerticalAlignment() {
        return verticalAlignment;
    }

    /**
     * @return a small HashMap
     * @see #putClientProperty
     * @see #getClientProperty
     */
    private Map<Object, Object> getClientProperties() {
        if (clientProperties == null) {
            clientProperties = new HashMap<Object, Object>(2);
        }
        return clientProperties;
    }

    /**
     * Returns the value of the property with the specified key.  Only
     * properties added with <code>putClientProperty</code> will return
     * a non-null value.
     *
     * @return the value of this property or null
     * @see #putClientProperty
     */
    public final Object getClientProperty(Object key) {
        if (clientProperties == null) {
            return null;
        } else {
            return getClientProperties().get(key);
        }
    }

    /**
     * Add an arbitrary key/value "client property" to this component.
     * <p/>
     * The <code>get/putClientProperty<code> methods provide access to
     * a small per-instance hashtable. Callers can use get/putClientProperty
     * to annotate components that were created by another module, e.g. a
     * layout manager might store per child constraints this way.  For example:
     * <pre>
     * componentA.putClientProperty("to the left of", componentB);
     * </pre>
     * <p/>
     * If value is null this method will remove the property.
     * Changes to client properties are reported with PropertyChange
     * events.  The name of the property (for the sake of PropertyChange
     * events) is <code>key.toString()</code>.
     * <p/>
     * The clientProperty dictionary is not intended to support large
     * scale extensions to SComponent nor should be it considered an
     * alternative to subclassing when designing a new component.
     *
     * @see #getClientProperty
     */
    public final void putClientProperty(Object key, Object value) {
        if (value != null) {
            getClientProperties().put(key, value);
        } else {
            getClientProperties().remove(key);
        }
    }

    /**
     * Set the look and feel delegate for this component.
     * SComponent subclasses generally override this method
     * to narrow the argument type, e.g. in STextField:
     * <pre>
     * public void setCG(TextFieldCG newCG) {
     *     super.setCG(newCG);
     * }
     * </pre>
     *
     * @see #updateCG
     * @see org.wings.plaf.CGManager#getLookAndFeel
     * @see org.wings.plaf.CGManager#getCG
     */
    @SuppressWarnings({ "unchecked" })
    public void setCG(ComponentCG newCG) {
        /* We do not check that the CG instance is different
         * before allowing the switch in order to enable the
         * same CG instance *with different default settings*
         * to be installed.
         */
        if (cg != null) {
            cg.uninstallCG(this);
        }
        ComponentCG oldCG = cg;
        cg = newCG;
        if (cg != null) {
            cg.installCG(this);
        }
        reloadIfChange(cg, oldCG);
    }

    /**
     * Return the look and feel delegate.
     *
     * @return the componet's cg
     */
    public ComponentCG getCG() {
        return cg;
    }

    /**
     * Notification from the CGFactory that the L&F has changed.
     *
     * @see SComponent#updateCG
     */
    public void updateCG() {
        if (getSession() == null) {
            log.warn("no session yet.");
        } else if (getSession().getCGManager() == null) {
            log.warn("no CGManager");
        } else {
            setCG(getSession().getCGManager().getCG(this));
        }
    }

    /**
     * Invite a ComponentVisitor.
     * Invokes visit(SComponent) on the ComponentVisitor.
     *
     * @param visitor the visitor to be invited
     */
    public void invite(ComponentVisitor visitor) throws Exception {
        visitor.visit(this);
    }

    /**
     * use this method for changing a variable. if a new value is different
     * from the old value set the new one and notify e.g. the reloadmanager...
     */
    protected static boolean isDifferent(Object oldObject, Object newObject) {
        if (oldObject == newObject)
            return false;

        if (oldObject == null)
            return true;

        return !oldObject.equals(newObject);
    }

    /**
     * Adds an event listener for the given event class
     *
     * @param type     The class/type of events to listen to.
     * @param listener The listener itself.
     */
    protected final <T extends EventListener> void addEventListener(Class<T> type, T listener) {
        if (listeners == null) {
            listeners = new EventListenerList();
        }
        listeners.add(type, listener);
    }

    /**
     * Removed named event listener.
     *
     * @param type     The class/type of events to listen to.
     * @param listener The listener itself.
     */
    protected final <T extends EventListener> void removeEventListener(Class<T> type, T listener) {
        if (listeners != null) {
            listeners.remove(type, listener);
        }
    }

    /**
     * Returns the number of listeners of the specified type for this component.
     *
     * @param type The type of listeners
     * @return The number of listeners
     * @see EventListenerList
     */
    protected final int getListenerCount(Class type) {
        if (listeners != null) {
            return listeners.getListenerCount(type);
        } else {
            return 0;
        }
    }

    /**
     * Returns all the listeners of this component. For performance reasons, this is the actual data
     * structure and so no modification of this array should be made.
     *
     * @return All listeners of this component. The result array has a pair structure,
     *         the first element of each pair is the listener type, the second the listener
     *         itself. It is guaranteed that this returns a non-null array.
     * @see EventListenerList
     */
    protected final Object[] getListenerList() {
        if (listeners == null) {
            return EMPTY_OBJECT_ARRAY;
        } else {
            return listeners.getListenerList();
        } // end of else
    }

    /**
     * Creates an typed array of all listeners of the specified type
     *
     * @param type All listeners of this type are added to the result array
     * @return an array of the specified type with all listeners of the specified type
     * @see EventListenerList
     */
    public final EventListener[] getListeners(Class<? extends EventListener> type) {
        if (listeners != null) {
            return listeners.getListeners(type);
        } else {
            return (EventListener[]) Array.newInstance(type, 0);
        }
    }

    /**
     * Adds a listenere to this component which wil get notified when the rendering of
     * this components starts. (This happens after all events / request has been processed and wingS
     * starts to build the response).
     *
     * @param renderListener
     * @see SRenderListener
     */
    public final void addRenderListener(SRenderListener renderListener) {
        addEventListener(SRenderListener.class, renderListener);
        fireRenderEvents = true;
    }

    /**
     * Removes the named render listener.
     *
     * @param renderListener Render listener to remove
     * @see #addRenderListener(org.wings.event.SRenderListener)
     * @see SRenderListener
     */
    public final void removeRenderListener(SRenderListener renderListener) {
        removeEventListener(SRenderListener.class, renderListener);
    }

    /**
     * <b>Internal method</b> called by the CGs to indicate different states of the rendering process.
     *
     * @param type Either {@link #DONE_RENDERING} or {@link #START_RENDERING}.
     */
    public final void fireRenderEvent(int type) {
        if (fireRenderEvents) {
            // maybe the better way to do this is to user the getListenerList
            // and iterate through all listeners, this saves the creation of
            // an array but it must cast to the apropriate listener
            Object[] listeners = getListenerList();
            for (int i = listeners.length - 2; i >= 0; i -= 2) {
                if (listeners[i] == SRenderListener.class) {
                    // Lazily create the event:
                    if (renderEvent == null) {
                        renderEvent = new SRenderEvent(this);
                    } // end of if ()

                    switch (type) {
                    case START_RENDERING:
                        ((SRenderListener) listeners[i + 1]).startRendering(renderEvent);
                        break;
                    case DONE_RENDERING:
                        ((SRenderListener) listeners[i + 1]).doneRendering(renderEvent);
                        break;
                    }
                }
            }
        }
    }

    /**
     * Forwards the scrollRectToVisible() message to the SComponent's
     * parent. Components that can service the request, such as
     * SScrollPane, override this method and perform the scrolling.
     *
     * @param aRect the visible Rectangle
     * @see SScrollPane
     */
    public void scrollRectToVisible(Rectangle aRect) {
        if (parent != null) {
            parent.scrollRectToVisible(aRect);
        }
    }

    /**
     * Requests the edit focus for this component for the following renderings
     * by calling {@link SFrame#setFocus(SComponent)}.
     */
    public void requestFocus() {
        if (getParentFrame() != null) {
            getParentFrame().setFocus(this);
        }
    }

    /**
     * Returns <code>true</code> if this <code>SComponent</code> is owning the edit focus.
     *
     * @return <code>true</code> if this <code>SComponent</code> is owning the edit focus otherwise <code>false</code>
     * @see #requestFocus()
     */
    public boolean isFocusOwner() {
        if (getParentFrame() != null)
            return this == getParentFrame().getFocus();
        return false;
    }

    /**
     * Set display mode (href or form-component).
     * An AbstractButton can appear as HTML-Form-Button or as
     * HTML-HREF. If button is inside a {@link SForm} the default
     * is displaying it as html form button.
     * Setting <i>showAsFormComponent</i> to <i>false</i> will
     * force displaying as href even if button is inside
     * a form. Beware that form buttons submit the whole form while
     * href buttons send a get request!
     *
     * @param showAsFormComponent if true, display as link, if false as html form component.
     */
    public void setShowAsFormComponent(boolean showAsFormComponent) {
        if (this.showAsFormComponent != showAsFormComponent) {
            boolean oldVal = this.showAsFormComponent;
            this.showAsFormComponent = showAsFormComponent;
            reload();
            propertyChangeSupport.firePropertyChange("showAsFormComponent", oldVal, this.showAsFormComponent);
        }
    }

    /**
     * Test, what display method is set.
     *
     * @return true, if displayed as link, false when displayed as html form component.
     * @see #setShowAsFormComponent(boolean)
     */
    public boolean getShowAsFormComponent() {
        return showAsFormComponent && getResidesInForm();
    }

    /**
     * Binds action names to {@link Action}s. Use for key binding feature.
     *
     * @param actionMap The new action map.
     * @see #setInputMap(javax.swing.InputMap)
     * @see ActionMap
     * @see InputMap
     */
    public void setActionMap(ActionMap actionMap) {
        ActionMap oldVal = this.actionMap;
        this.actionMap = actionMap;
        propertyChangeSupport.firePropertyChange("actionMap", oldVal, this.actionMap);
    }

    /**
     * Action map for key binding feature
     *
     * @return The current action map
     * @see #setActionMap(javax.swing.ActionMap)
     */
    public ActionMap getActionMap() {
        if (actionMap == null)
            actionMap = new ActionMap();
        return actionMap;
    }

    /**
     * Map for key binding feature. (?) Binds input keystrokes to action names (?).
     *
     * @param inputMap The current input map.
     * @see InputMap
     * @see ActionMap
     * @see #setActionMap(javax.swing.ActionMap)
     * @see JComponent#setInputMap(int, javax.swing.InputMap)
     */
    public void setInputMap(InputMap inputMap) {
        setInputMap(WHEN_FOCUSED_OR_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap);
    }

    /**
     * Sets The current input map.
     *
     * @param inputMap  The new input map
     * @param condition Either {@link #WHEN_FOCUSED_OR_ANCESTOR_OF_FOCUSED_COMPONENT} or {@link #WHEN_IN_FOCUSED_FRAME}
     * @see JComponent#setInputMap(int, javax.swing.InputMap)
     */
    public void setInputMap(int condition, InputMap inputMap) {
        initInputMaps();
        InputMap oldVal = inputMaps[condition];
        this.inputMaps[condition] = inputMap;
        registerGlobalInputMapWithFrame();
        propertyChangeSupport.firePropertyChange("inputMap", oldVal, this.inputMaps[condition]);
    }

    /**
     * @return The input map for the condition {@link #WHEN_FOCUSED_OR_ANCESTOR_OF_FOCUSED_COMPONENT}
     * @see #setInputMap(javax.swing.InputMap)
     */
    public InputMap getInputMap() {
        return getInputMap(WHEN_FOCUSED_OR_ANCESTOR_OF_FOCUSED_COMPONENT);
    }

    /**
     * @param condition Either {@link #WHEN_FOCUSED_OR_ANCESTOR_OF_FOCUSED_COMPONENT} or {@link #WHEN_IN_FOCUSED_FRAME}
     * @return The input map for the given condition.
     * @see #setInputMap(int, javax.swing.InputMap)
     */
    public InputMap getInputMap(int condition) {
        initInputMaps();
        InputMap result = inputMaps[condition];
        if (result == null) {
            inputMaps[condition] = new InputMap();
            result = inputMaps[condition];
        }
        registerGlobalInputMapWithFrame();
        return result;
    }

    private void registerGlobalInputMapWithFrame() {
        final SFrame parentFrame = getParentFrame();
        if (parentFrame != null)
            parentFrame.registerGlobalInputMapComponent(this);

        if (globalInputMapListener == null) {
            globalInputMapListener = new GlobalInputMapParentFrameListener(this);
            addParentFrameListener(globalInputMapListener);
        }
    }

    private void initInputMaps() {
        if (inputMaps == null) {
            inputMaps = new InputMap[ACTION_CONDITIONS_AMOUNT];
        }
    }

    protected void processLowLevelEvent(String name, String[] values) {
    }

    protected boolean processKeyEvents(String[] values) {
        if (actionMap == null)
            return false;

        if (log.isDebugEnabled())
            log.debug("processKeyEvents " + Arrays.asList(values));

        boolean arm = false;
        for (String value : values) {
            final Action action = actionMap.get(value);
            if (action != null) {
                if (actionEvents == null)
                    actionEvents = new HashMap<Action, ActionEvent>();

                actionEvents.put(action, new ActionEvent(this, 0, value));
                arm = true;
            }
        }
        if (arm)
            SForm.addArmedComponent((LowLevelEventListener) this);

        return arm;
    }

    /**
     * Internal event trigger used by CGs.
     * This Method is called internal and should not be called directly
     */
    public void fireFinalEvents() {
        fireKeyEvents();
    }

    /**
     * Internal method to trigger firing of key events.
     */
    protected void fireKeyEvents() {
        if (actionEvents != null) {
            for (Map.Entry<Action, ActionEvent> entry : actionEvents.entrySet()) {
                Action action = entry.getKey();
                ActionEvent event = entry.getValue();
                action.actionPerformed(event);
            }
            actionEvents.clear();
        }
    }

    /**
     * Method called to notify this <code>SComponent</code> that it has no longer a parent component.
     * This Method is called internal and should not be called directly, but can be overerloaded
     * to react on this event.
     */
    public void removeNotify() {
        /* currently nothing to do, but great to overwrite for some dangling eventListener */
    }

    /**
     * Method called to notify this <code>SComponent</code> that it has a new parent component.
     * This Method is called internal and should not be called directly, but can be overerloaded
     * to react on this event.
     */
    public void addNotify() {
        /* currently nothing to do */
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        // preprocessing, e. g. serialize static vars or transient variables as cipher text
        in.defaultReadObject(); // default serialization
        // do postprocessing here
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        try {
            // preprocessing
            out.defaultWriteObject(); // default
            // postprocessing
        } catch (IOException e) {
            log.warn("Unexpected Exception", e);
            throw e;
        }
    }

    public void setTransferHandler(STransferHandler newHandler) {
        STransferHandler oldHandler = this.transferHandler;
        this.transferHandler = newHandler;

        propertyChangeSupport.firePropertyChange("transferHandler", oldHandler, this.transferHandler);

    }

    public STransferHandler getTransferHandler() {
        return this.transferHandler;
    }

    protected STransferHandler.DropLocation dropLocationForPoint(SPoint p) {
        return null;
    }

    /**
     * Listener registering/deregistering this component on the parent frame.
     */
    private final static class GlobalInputMapParentFrameListener implements SParentFrameListener, Serializable {
        private final SComponent me;

        public GlobalInputMapParentFrameListener(SComponent me) {
            this.me = me;
        }

        public void parentFrameAdded(SParentFrameEvent e) {
            if (e.getParentFrame() != null)
                e.getParentFrame().registerGlobalInputMapComponent(me);
        }

        public void parentFrameRemoved(SParentFrameEvent e) {
            if (e.getParentFrame() != null)
                e.getParentFrame().deregisterGlobalInputMapComponent(me);
        }
    }

    /**
     * Add a PropertyChangeListener to the listener list. The listener is registered for all properties.
     *
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        if (listener == null)
            return;
        propertyChangeSupport.addPropertyChangeListener(listener);
    }

    /**
     * Remove a PropertyChangeListener from the listener list.
     *
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        if (propertyChangeSupport != null)
            propertyChangeSupport.removePropertyChangeListener(listener);
    }

    /**
     * Add a PropertyChangeListener for a specific property.
     */
    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        if (listener == null || propertyName == null)
            return;
        propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
    }

    /**
     * Remove a PropertyChangeListener for a specific property.
     */
    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        if (propertyChangeSupport != null || propertyName != null)
            propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
    }
}