]> source.dussan.org Git - vaadin-framework.git/commitdiff
Implement new simple AbstractField
authorJohannes Dahlström <johannesd@vaadin.com>
Thu, 4 Aug 2016 19:18:57 +0000 (22:18 +0300)
committerJohannes Dahlström <johannesd@vaadin.com>
Fri, 5 Aug 2016 19:10:56 +0000 (22:10 +0300)
Change-Id: I9addcf8bec802967b1dfa39512dd140b8a4e4a25

server/src/main/java/com/vaadin/data/HasValue.java [new file with mode: 0644]
server/src/main/java/com/vaadin/event/ConnectorEvent.java
server/src/main/java/com/vaadin/event/Registration.java [new file with mode: 0644]
server/src/main/java/com/vaadin/legacy/ui/LegacyAbstractField.java
server/src/main/java/com/vaadin/legacy/ui/LegacyField.java
server/src/main/java/com/vaadin/ui/AbstractField.java [new file with mode: 0644]
server/src/main/java/com/vaadin/ui/Component.java
server/src/test/java/com/vaadin/ui/AbstractFieldTest.java [new file with mode: 0644]

diff --git a/server/src/main/java/com/vaadin/data/HasValue.java b/server/src/main/java/com/vaadin/data/HasValue.java
new file mode 100644 (file)
index 0000000..6032fcb
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data;
+
+import java.io.Serializable;
+import java.util.function.Consumer;
+
+import com.vaadin.event.ConnectorEvent;
+import com.vaadin.event.ConnectorEventListener;
+import com.vaadin.event.Registration;
+import com.vaadin.server.ClientConnector;
+
+/**
+ * A generic interface for field components and other user interface objects
+ * that have a user-editable value. Emits change events whenever the value is
+ * changed, either by the user or programmatically.
+ *
+ * @since
+ * @param <V>
+ *            the value type
+ */
+public interface HasValue<V> extends Serializable {
+
+    /**
+     * An event fired when the value of a {@code HasValue} changes.
+     *
+     * @param <V>
+     *            the value type
+     */
+    public class ValueChange<V> extends ConnectorEvent {
+
+        private final V value;
+        private final boolean userOriginated;
+
+        /**
+         * Creates a new {@code ValueChange} event containing the current value
+         * of the given value-bearing source connector.
+         * 
+         * @param <C>
+         *            the type of the source connector
+         * @param source
+         *            the source connector bearing the value, not null
+         * @param userOriginated
+         *            {@code true} if this event originates from the client,
+         *            {@code false} otherwise.
+         */
+        public <C extends ClientConnector & HasValue<V>> ValueChange(
+                C source, boolean userOriginated) {
+            super(source);
+            this.value = source.getValue();
+            this.userOriginated = userOriginated;
+        }
+
+        /**
+         * Returns the new value of the source connector.
+         * 
+         * @return the new value
+         */
+        public V getValue() {
+            return value;
+        }
+
+        /**
+         * Returns whether this event was triggered by user interaction, on the
+         * client side, or programmatically, on the server side.
+         * 
+         * @return {@code true} if this event originates from the client,
+         *         {@code false} otherwise.
+         */
+        public boolean isUserOriginated() {
+            return userOriginated;
+        }
+    }
+
+    /**
+     * A listener for value change events.
+     *
+     * @param <V>
+     *            the value type
+     * 
+     * @see ValueChange
+     * @see Registration
+     */
+    @FunctionalInterface
+    public interface ValueChangeListener<V> extends Consumer<ValueChange<V>>,
+            ConnectorEventListener {
+
+        /**
+         * Invoked when this listener receives a value change event from an
+         * event source to which it has been added.
+         * 
+         * @param event
+         *            the received event, not null
+         */
+        // In addition to customizing the Javadoc, this override is needed
+        // to make ReflectTools.findMethod work as expected. It uses
+        // Class.getDeclaredMethod, but even if it used getMethod instead, the
+        // superinterface argument type is Object, not Event, after type
+        // erasure.
+        @Override
+        public void accept(ValueChange<V> event);
+    }
+
+    /**
+     * Sets the value of this object. If the new value is not equal to
+     * {@code getValue()}, fires a value change event. May throw
+     * {@code IllegalArgumentException} if the value is not acceptable.
+     * <p>
+     * <i>Implementation note:</i> the implementing class should document
+     * whether null values are accepted or not.
+     * 
+     * @param value
+     *            the new value
+     * @throws IllegalArgumentException
+     *             if the value is invalid
+     */
+    public void setValue(V value);
+
+    /**
+     * Returns the current value of this object.
+     * <p>
+     * <i>Implementation note:</i> the implementing class should document
+     * whether null values may be returned or not.
+     * 
+     * @return the current value
+     */
+    public V getValue();
+
+    /**
+     * Adds an {@link ValueChangeListener}. The listener is called when the
+     * value of this {@code hasValue} is changed either by the user or
+     * programmatically.
+     * 
+     * @param listener
+     *            the value change listener, not null
+     * @return a registration for the listener
+     */
+    public Registration addValueChangeListener(
+            ValueChangeListener<? super V> listener);
+}
index 3dc73b864ae4fdbfc44d483d6ad485b64d72c8f1..9e9d3b00a6e346ff1af50b180dc494e9c12fb71f 100644 (file)
@@ -20,11 +20,29 @@ import java.util.EventObject;
 
 import com.vaadin.server.ClientConnector;
 
+/**
+ * A base class for user interface events fired by connectors.
+ * 
+ * @author Vaadin Ltd.
+ * @since 7.0
+ */
 public abstract class ConnectorEvent extends EventObject {
+
+    /**
+     * Creates a new event fired by the given source.
+     * 
+     * @param source
+     *            the source connector
+     */
     public ConnectorEvent(ClientConnector source) {
         super(source);
     }
 
+    /**
+     * Returns the connector that fired the event.
+     * 
+     * @return the source connector
+     */
     public ClientConnector getConnector() {
         return (ClientConnector) getSource();
     }
diff --git a/server/src/main/java/com/vaadin/event/Registration.java b/server/src/main/java/com/vaadin/event/Registration.java
new file mode 100644 (file)
index 0000000..ab393e4
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.event;
+
+import java.io.Serializable;
+
+/**
+ * A registration object for removing an event listener added to a source.
+ * 
+ * @author Vaadin Ltd.
+ * @since
+ */
+@FunctionalInterface
+public interface Registration extends Serializable {
+
+    /**
+     * Removes the associated listener from the event source.
+     */
+    public void remove();
+}
index d1b013a64a270b6297d68eaa6c95eeeaed8c3643..8cce999371e971254900995f7aff4048d15d4be8 100644 (file)
@@ -62,10 +62,10 @@ import com.vaadin.ui.declarative.DesignContext;
  * </p>
  * 
  * <p>
- * LegacyAbstractField also provides the {@link com.vaadin.data.Buffered} interface
- * for buffering the data source value. By default the LegacyField is in write
- * through-mode and {@link #setWriteThrough(boolean)}should be called to enable
- * buffering.
+ * LegacyAbstractField also provides the {@link com.vaadin.data.Buffered}
+ * interface for buffering the data source value. By default the LegacyField is
+ * in write through-mode and {@link #setWriteThrough(boolean)}should be called
+ * to enable buffering.
  * </p>
  * 
  * <p>
@@ -75,10 +75,17 @@ import com.vaadin.ui.declarative.DesignContext;
  * 
  * @author Vaadin Ltd.
  * @since 3.0
+ * 
+ * @deprecated This class is, apart from the rename, identical to the Vaadin 7
+ *             {@code com.vaadin.ui.AbstractField}. It is provided for
+ *             compatibility and migration purposes. As of 8.0, new field
+ *             implementations should extend the new
+ *             {@link com.vaadin.ui.AbstractField} instead.
  */
 @SuppressWarnings("serial")
-public abstract class LegacyAbstractField<T> extends AbstractComponent implements
-        LegacyField<T>, Property.ReadOnlyStatusChangeListener,
+@Deprecated
+public abstract class LegacyAbstractField<T> extends AbstractComponent
+        implements LegacyField<T>, Property.ReadOnlyStatusChangeListener,
         Property.ReadOnlyStatusChangeNotifier, Action.ShortcutNotifier {
 
     /* Private members */
@@ -187,11 +194,11 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
     }
 
     /**
-     * Returns the type of the LegacyField. The methods <code>getValue</code> and
-     * <code>setValue</code> must be compatible with this type: one must be able
-     * to safely cast the value returned from <code>getValue</code> to the given
-     * type and pass any variable assignable to this type as an argument to
-     * <code>setValue</code>.
+     * Returns the type of the LegacyField. The methods <code>getValue</code>
+     * and <code>setValue</code> must be compatible with this type: one must be
+     * able to safely cast the value returned from <code>getValue</code> to the
+     * given type and pass any variable assignable to this type as an argument
+     * to <code>setValue</code>.
      * 
      * @return the type of the LegacyField
      */
@@ -834,9 +841,9 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
      * Returns the current value (as returned by {@link #getValue()}) converted
      * to the data source type.
      * <p>
-     * This returns the same as {@link LegacyAbstractField#getValue()} if no converter
-     * has been set. The value is not necessarily the same as the data source
-     * value e.g. if the field is in buffered mode and has been modified.
+     * This returns the same as {@link LegacyAbstractField#getValue()} if no
+     * converter has been set. The value is not necessarily the same as the data
+     * source value e.g. if the field is in buffered mode and has been modified.
      * </p>
      * 
      * @return The converted value that is compatible with the data source type
@@ -870,7 +877,7 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
     @Override
     public void addValidator(Validator validator) {
         if (validators == null) {
-            validators = new LinkedList<Validator>();
+            validators = new LinkedList<>();
         }
         validators.add(validator);
         markAsDirty();
@@ -993,7 +1000,7 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
             }
         }
 
-        List<InvalidValueException> validationExceptions = new ArrayList<InvalidValueException>();
+        List<InvalidValueException> validationExceptions = new ArrayList<>();
         if (validators != null) {
             // Gets all the validation errors
             for (Validator v : validators) {
@@ -1384,10 +1391,10 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
     }
 
     /**
-     * Sets the internal field value. This is purely used by LegacyAbstractField to
-     * change the internal LegacyField value. It does not trigger valuechange events.
-     * It can be overridden by the inheriting classes to update all dependent
-     * variables.
+     * Sets the internal field value. This is purely used by LegacyAbstractField
+     * to change the internal LegacyField value. It does not trigger valuechange
+     * events. It can be overridden by the inheriting classes to update all
+     * dependent variables.
      * 
      * Subclasses can also override {@link #getInternalValue()} if necessary.
      * 
@@ -1619,8 +1626,8 @@ public abstract class LegacyAbstractField<T> extends AbstractComponent implement
 
     /**
      * A ready-made {@link ShortcutListener} that focuses the given
-     * {@link Focusable} (usually a {@link LegacyField}) when the keyboard shortcut is
-     * invoked.
+     * {@link Focusable} (usually a {@link LegacyField}) when the keyboard
+     * shortcut is invoked.
      * 
      */
     public static class FocusShortcut extends ShortcutListener {
index a3ac84871d189a3338ffd24dddb04ccf70804656..ff38cf28598c95fdf9b4c169d8c38f6814445e47 100644 (file)
 package com.vaadin.legacy.ui;
 
 import com.vaadin.data.BufferedValidatable;
+import com.vaadin.data.HasValue.ValueChange;
 import com.vaadin.data.Property;
 import com.vaadin.ui.Component;
-import com.vaadin.ui.Component.Event;
 import com.vaadin.ui.Component.Focusable;
 
 /**
- * LegacyField interface is implemented by all classes (field components) that have a
- * value that the user can change through the user interface.
+ * LegacyField interface is implemented by all legacy field components that have
+ * value that the user can change through the user interface.
  *
- * LegacyField components are built upon the framework defined in the LegacyField interface
- * and the {@link com.vaadin.LegacyAbstractField} base class.
+ * LegacyField components are built upon the framework defined in the
+ * LegacyField interface and the {@link com.vaadin.LegacyAbstractField} base
+ * class.
  *
  * The LegacyField interface inherits the {@link com.vaadin.ui.Component}
  * superinterface and also the {@link com.vaadin.ui.Property} interface to have
  * a value for the field.
  *
- *
  * @author Vaadin Ltd.
  *
- * @param T
+ * @param <T>
  *            the type of values in the field, which might not be the same type
  *            as that of the data source if converters are used
  *
- * @author IT Mill Ltd.
+ * @deprecated This interface is, apart from the rename, identical to the Vaadin
+ *             7 {@code com.vaadin.ui.Field}. It is provided for compatibility
+ *             and migration purposes. As of 8.0, new field components should
+ *             extend {@link com.vaadin.ui.AbstractField} instead.
  */
-public interface LegacyField<T> extends Component, BufferedValidatable, Property<T>,
+@Deprecated
+public interface LegacyField<T> extends Component, BufferedValidatable,
+        Property<T>,
         Property.ValueChangeNotifier, Property.ValueChangeListener,
         Property.Editor, Focusable {
 
@@ -85,14 +90,18 @@ public interface LegacyField<T> extends Component, BufferedValidatable, Property
     public String getRequiredError();
 
     /**
-     * An <code>Event</code> object specifying the LegacyField whose value has been
-     * changed.
+     * An <code>Event</code> object specifying the LegacyField whose value has
+     * been changed.
      *
      * @author Vaadin Ltd.
      * @since 3.0
+     * 
+     * @deprecated As of 8.0, replaced by {@link ValueChange}.
      */
+    @Deprecated
     @SuppressWarnings("serial")
-    public static class ValueChangeEvent extends Component.Event implements
+    public static class ValueChangeEvent extends
+            Component.Event implements
             Property.ValueChangeEvent {
 
         /**
@@ -114,6 +123,7 @@ public interface LegacyField<T> extends Component, BufferedValidatable, Property
         public Property getProperty() {
             return (Property) getSource();
         }
+
     }
 
     /**
diff --git a/server/src/main/java/com/vaadin/ui/AbstractField.java b/server/src/main/java/com/vaadin/ui/AbstractField.java
new file mode 100644 (file)
index 0000000..7a9d1b9
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.ui;
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
+
+import com.vaadin.data.HasValue;
+import com.vaadin.event.Registration;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.util.ReflectTools;
+
+/**
+ * An abstract implementation of a field, or a {@code Component} allowing user
+ * input. Implements {@link HasValue} to represent the input value. Examples of
+ * typical field components include text fields, date pickers, and check boxes.
+ * <p>
+ * This class replaces the Vaadin 7 {@code com.vaadin.ui.AbstractField} class.
+ * The old {@code AbstractField} is retained, under the new name
+ * {@link com.vaadin.legacy.ui.LegacyAbstractField}, for compatibility and
+ * migration purposes.
+ * 
+ * @author Vaadin Ltd.
+ * @since
+ *
+ * @param <T>
+ *            the input value type
+ */
+public abstract class AbstractField<T> extends AbstractComponent
+        implements HasValue<T> {
+
+    @Deprecated
+    private static final Method VALUE_CHANGE_METHOD = ReflectTools.findMethod(
+            ValueChangeListener.class, "accept", ValueChange.class);
+
+    @Override
+    public void setValue(T value) {
+        setValue(value, false);
+    }
+
+    /**
+     * Returns whether the value of this field can be changed by the user or
+     * not. By default fields are not read-only.
+     * 
+     * @return {@code true} if this field is in read-only mode, {@code false}
+     *         otherwise.
+     * 
+     * @see #setReadOnly(boolean)
+     */
+    @Override
+    public boolean isReadOnly() {
+        return super.isReadOnly();
+    }
+
+    /**
+     * Sets whether the value of this field can be changed by the user or not. A
+     * field in read-only mode typically looks visually different to signal to
+     * the user that the value cannot be edited.
+     * <p>
+     * The server ignores (potentially forged) value change requests from the
+     * client to fields that are read-only. Programmatically changing the field
+     * value via {@link #setValue(T)} is still possible.
+     * <p>
+     * The read-only mode is distinct from the
+     * {@linkplain Component#setEnabled(boolean) disabled} state. When disabled,
+     * a component cannot be interacted with at all, and its content should be
+     * considered irrelevant or not applicable. In contrast, the user should
+     * still be able to read the content and otherwise interact with a read-only
+     * field even though changing the value is disallowed.
+     * 
+     * @param readOnly
+     *            {@code true} to set read-only mode, {@code false} otherwise.
+     */
+    @Override
+    public void setReadOnly(boolean readOnly) {
+        super.setReadOnly(readOnly);
+    }
+
+    @Override
+    public Registration addValueChangeListener(
+            ValueChangeListener<? super T> listener) {
+        Objects.requireNonNull(listener, "listener cannot be null");
+        addListener(ValueChange.class, listener, VALUE_CHANGE_METHOD);
+        return () -> removeListener(ValueChange.class, listener);
+    }
+
+    @Override
+    public void readDesign(Element design, DesignContext designContext) {
+        super.readDesign(design, designContext);
+        Attributes attr = design.attributes();
+        if (attr.hasKey("readonly")) {
+            setReadOnly(DesignAttributeHandler.readAttribute("readonly", attr,
+                    Boolean.class));
+        }
+    }
+
+    @Override
+    public void writeDesign(Element design, DesignContext designContext) {
+        super.writeDesign(design, designContext);
+        AbstractField<T> def = designContext.getDefaultInstance(this);
+        Attributes attr = design.attributes();
+        DesignAttributeHandler.writeAttribute("readonly", attr,
+                super.isReadOnly(), def.isReadOnly(), Boolean.class);
+    }
+
+    @Override
+    protected Collection<String> getCustomAttributes() {
+        Collection<String> attributes = super.getCustomAttributes();
+        attributes.add("readonly");
+        // must be handled by subclasses
+        attributes.add("value");
+        return attributes;
+    }
+
+    /**
+     * Sets the value of this field if it has changed and fires a value change
+     * event. If the value originates from the client and this field is
+     * read-only, does nothing. Invokes {@link #doSetValue(Object) doSetValue}
+     * to actually store the value.
+     * 
+     * @param value
+     *            the new value to set
+     * @return {@code true} if this event originates from the client,
+     *         {@code false} otherwise.
+     */
+    protected void setValue(T value, boolean userOriginated) {
+        if (userOriginated && isReadOnly()) {
+            return;
+        }
+        if (Objects.equals(value, getValue())) {
+            return;
+        }
+        doSetValue(value);
+        if (!userOriginated) {
+            markAsDirty();
+        }
+        fireEvent(createValueChange(userOriginated));
+    }
+
+    /**
+     * Sets the value of this field. May do sanitization or throw
+     * {@code IllegalArgumentException} if the value is invalid. Typically saves
+     * the value to shared state.
+     * 
+     * @param value
+     *            the new value of the field
+     * @throws IllegalArgumentException
+     *             if the value is invalid
+     */
+    protected abstract void doSetValue(T value);
+
+    /**
+     * Returns a new value change event instance.
+     * 
+     * @param userOriginated
+     *            {@code true} if this event originates from the client,
+     *            {@code false} otherwise.
+     * @return the new event
+     */
+    protected ValueChange<T> createValueChange(boolean userOriginated) {
+        return new ValueChange<>(this, userOriginated);
+    }
+}
index 0e8b3851289e6e47fd987b7e10f1a79fbae49b68..9bfd823fc4c90058ef6ec62e563dd94362c94f4f 100644 (file)
@@ -379,10 +379,10 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
 
     /**
      * Tests whether the component is in the read-only mode. The user can not
-     * change the value of a read-only component. As only {@link LegacyField}
-     * components normally have a value that can be input or changed by the
-     * user, this is mostly relevant only to field components, though not
-     * restricted to them.
+     * change the value of a read-only component. As only {@link AbstractField}
+     * or {@link LegacyField} components normally have a value that can be input
+     * or changed by the user, this is mostly relevant only to field components,
+     * though not restricted to them.
      * 
      * <p>
      * Notice that the read-only mode only affects whether the user can change
@@ -406,9 +406,9 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
      * can not change the value of a read-only component.
      * 
      * <p>
-     * As only {@link LegacyField} components normally have a value that can be input
-     * or changed by the user, this is mostly relevant only to field components,
-     * though not restricted to them.
+     * As only {@link AbstractField} or{@link LegacyField} components normally
+     * have a value that can be input or changed by the user, this is mostly
+     * relevant only to field components, though not restricted to them.
      * </p>
      * 
      * <p>
@@ -596,7 +596,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
      * public class AttachExample extends CustomComponent {
      *     public AttachExample() {
      *         // ERROR: We can't access the application object yet.
-     *         ClassResource r = new ClassResource(&quot;smiley.jpg&quot;, getApplication());
+     *         ClassResource r = new ClassResource(&quot;smiley.jpg&quot;,
+     *                 getApplication());
      *         Embedded image = new Embedded(&quot;Image:&quot;, r);
      *         setCompositionRoot(image);
      *     }
@@ -622,7 +623,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
      *         super.attach(); // Must call.
      * 
      *         // Now we know who ultimately owns us.
-     *         ClassResource r = new ClassResource(&quot;smiley.jpg&quot;, getApplication());
+     *         ClassResource r = new ClassResource(&quot;smiley.jpg&quot;,
+     *                 getApplication());
      *         Embedded image = new Embedded(&quot;Image:&quot;, r);
      *         setCompositionRoot(image);
      *     }
@@ -879,7 +881,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
      *             getWindow().showNotification(&quot;Click!&quot;);
      * 
      *         // Display source component and event class names
-     *         status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+     *         status.setValue(&quot;Event from &quot; + event.getSource().getClass()
+     *                 .getName()
      *                 + &quot;: &quot; + event.getClass().getName());
      *     }
      * }
@@ -904,11 +907,13 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
          * <pre>
          * public void componentEvent(Event event) {
          *     // Act according to the source of the event
-         *     if (event.getSource() == ok &amp;&amp; event.getClass() == Button.ClickEvent.class)
+         *     if (event.getSource() == ok &amp;&amp; event
+         *             .getClass() == Button.ClickEvent.class)
          *         getWindow().showNotification(&quot;Click!&quot;);
          * 
          *     // Display source component and event class names
-         *     status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+         *     status.setValue(&quot;Event from &quot; + event.getSource().getClass()
+         *             .getName()
          *             + &quot;: &quot; + event.getClass().getName());
          * }
          * </pre>
@@ -956,7 +961,8 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
      *         if (event.getSource() == ok)
      *             getWindow().showNotification(&quot;Click!&quot;);
      * 
-     *         status.setValue(&quot;Event from &quot; + event.getSource().getClass().getName()
+     *         status.setValue(&quot;Event from &quot; + event.getSource().getClass()
+     *                 .getName()
      *                 + &quot;: &quot; + event.getClass().getName());
      *     }
      * }
@@ -1028,9 +1034,10 @@ public interface Component extends ClientConnector, Sizeable, Serializable {
      * <p>
      * Focus can be set with {@link #focus()}. This interface does not provide
      * an accessor that would allow finding out the currently focused component;
-     * focus information can be acquired for some (but not all) {@link LegacyField}
-     * components through the {@link com.vaadin.event.FieldEvents.FocusListener}
-     * and {@link com.vaadin.event.FieldEvents.BlurListener} interfaces.
+     * focus information can be acquired for some (but not all)
+     * {@link LegacyField} components through the
+     * {@link com.vaadin.event.FieldEvents.FocusListener} and
+     * {@link com.vaadin.event.FieldEvents.BlurListener} interfaces.
      * </p>
      * 
      * @see FieldEvents
diff --git a/server/src/test/java/com/vaadin/ui/AbstractFieldTest.java b/server/src/test/java/com/vaadin/ui/AbstractFieldTest.java
new file mode 100644 (file)
index 0000000..bdd63ca
--- /dev/null
@@ -0,0 +1,122 @@
+package com.vaadin.ui;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.easymock.EasyMockSupport;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.HasValue.ValueChange;
+import com.vaadin.data.HasValue.ValueChangeListener;
+import com.vaadin.server.ClientConnector;
+
+public class AbstractFieldTest extends EasyMockSupport {
+
+    class TextField extends AbstractField<String> {
+
+        String value = "";
+
+        @Override
+        public String getValue() {
+            return value;
+        }
+
+        @Override
+        protected void doSetValue(String value) {
+            this.value = value;
+        }
+    }
+
+    TextField field;
+
+    ValueChangeListener<String> l;
+    Capture<ValueChange<String>> capture;
+
+    @Before
+    public void setUp() {
+        field = new TextField();
+        l = mockListener();
+        capture = new Capture<>();
+    }
+
+    @Test
+    public void readOnlyFieldAcceptsValueChangeFromServer() {
+        field.setReadOnly(true);
+        field.setValue("foo");
+        assertEquals("foo", field.getValue());
+    }
+
+    @Test
+    public void readOnlyFieldIgnoresValueChangeFromClient() {
+        field.setReadOnly(true);
+        field.setValue("bar", true);
+        assertEquals("", field.getValue());
+    }
+
+    @Test
+    public void valueChangeListenerInvoked() {
+        l.accept(EasyMock.capture(capture));
+        replayAll();
+
+        field.setValue("foo");
+        field.addValueChangeListener(l);
+        field.setValue("bar");
+
+        assertEventEquals(capture.getValue(), "bar", field, false);
+
+        verifyAll();
+    }
+
+    @Test
+    public void valueChangeListenerInvokedFromClient() {
+        l.accept(EasyMock.capture(capture));
+        replayAll();
+
+        field.setValue("foo");
+        field.addValueChangeListener(l);
+        field.setValue("bar", true);
+
+        assertEventEquals(capture.getValue(), "bar", field, true);
+
+        verifyAll();
+    }
+
+    @Test
+    public void valueChangeListenerNotInvokedIfValueUnchanged() {
+        // expect zero invocations of l
+        replayAll();
+
+        field.setValue("foo");
+        field.addValueChangeListener(l);
+        field.setValue("foo");
+
+        verifyAll();
+    }
+
+    @Test
+    public void valueChangeListenerNotInvokedAfterRemove() {
+        // expect zero invocations of l
+        replayAll();
+
+        field.addValueChangeListener(l).remove();
+        field.setValue("foo");
+
+        verifyAll();
+    }
+
+    @SuppressWarnings("unchecked")
+    private ValueChangeListener<String> mockListener() {
+        return createStrictMock(ValueChangeListener.class);
+    }
+
+    private void assertEventEquals(ValueChange<String> e, String value,
+            ClientConnector source, boolean userOriginated) {
+        assertEquals("event value", value, e.getValue());
+        assertSame("event source", source, e.getSource());
+        assertSame("event source connector", source, e.getConnector());
+        assertEquals("event from user", userOriginated, e.isUserOriginated());
+    }
+}