summaryrefslogtreecommitdiffstats
path: root/compatibility-server
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2016-08-26 11:36:24 +0300
committerArtur Signell <artur@vaadin.com>2016-08-26 18:08:07 +0000
commit13b99cfa265913ab8cb2d948f2ab2bf152a959e4 (patch)
treeb4268a68c0703839f10b559972f7e9a7be8fb9b7 /compatibility-server
parent44cb252e45650c1a4686d7d695af99b04a284084 (diff)
downloadvaadin-framework-13b99cfa265913ab8cb2d948f2ab2bf152a959e4.tar.gz
vaadin-framework-13b99cfa265913ab8cb2d948f2ab2bf152a959e4.zip
Move remaining Vaadin 7 classes to the compatibility package
Change-Id: I3be37350a638028d89fb527a3dfb09e74fdebeed
Diffstat (limited to 'compatibility-server')
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/Buffered.java197
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/BufferedValidatable.java45
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/Property.java425
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/Validatable.java130
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/Validator.java198
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/AbstractProperty.java270
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/ListSet.java276
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodProperty.java774
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodPropertyDescriptor.java148
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedMethodProperty.java269
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedPropertyDescriptor.java72
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/ObjectProperty.java142
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/PropertyFormatter.java257
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/TextFileProperty.java157
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/TransactionalPropertyWrapper.java153
-rw-r--r--compatibility-server/src/main/java/com/vaadin/v7/data/util/VaadinPropertyDescriptor.java55
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/AbstractPropertyListenersTest.java30
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/PropertyFormatterTest.java75
18 files changed, 3673 insertions, 0 deletions
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/Buffered.java b/compatibility-server/src/main/java/com/vaadin/v7/data/Buffered.java
new file mode 100644
index 0000000000..025a43fbba
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/Buffered.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2000-2016 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.v7.data;
+
+import java.io.Serializable;
+
+import com.vaadin.server.AbstractErrorMessage;
+import com.vaadin.server.ErrorMessage;
+import com.vaadin.server.ErrorMessage.ErrorLevel;
+import com.vaadin.server.ErrorMessageProducer;
+import com.vaadin.server.UserError;
+import com.vaadin.v7.data.Validator.InvalidValueException;
+
+/**
+ * <p>
+ * Defines the interface to commit and discard changes to an object, supporting
+ * buffering.
+ *
+ * <p>
+ * In <i>buffered</i> mode the initial value is read from the data source and
+ * then buffered. Any subsequential writes or reads will be done on the buffered
+ * value. Calling {@link #commit()} will write the buffered value to the data
+ * source while calling {@link #discard()} while discard the buffered value and
+ * re-read the value from the data source.
+ *
+ * <p>
+ * In <i>non-buffered</i> mode the value is always read directly from the data
+ * source. Any write is done directly to the data source with no buffering in
+ * between. Reads are also done directly from the data source. Calling
+ * {@link #commit()} or {@link #discard()} in this mode is efficiently a no-op.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+public interface Buffered extends Serializable {
+
+ /**
+ * Updates all changes since the previous commit to the data source. The
+ * value stored in the object will always be updated into the data source
+ * when <code>commit</code> is called.
+ *
+ * @throws SourceException
+ * if the operation fails because of an exception is thrown by
+ * the data source. The cause is included in the exception.
+ * @throws InvalidValueException
+ * if the operation fails because validation is enabled and the
+ * values do not validate
+ */
+ public void commit() throws SourceException, InvalidValueException;
+
+ /**
+ * Discards all changes since last commit. The object updates its value from
+ * the data source.
+ *
+ * @throws SourceException
+ * if the operation fails because of an exception is thrown by
+ * the data source. The cause is included in the exception.
+ */
+ public void discard() throws SourceException;
+
+ /**
+ * Sets the buffered mode to the specified status.
+ * <p>
+ * When in buffered mode, an internal buffer will be used to store changes
+ * until {@link #commit()} is called. Calling {@link #discard()} will revert
+ * the internal buffer to the value of the data source.
+ * <p>
+ * When in non-buffered mode both read and write operations will be done
+ * directly on the data source. In this mode the {@link #commit()} and
+ * {@link #discard()} methods serve no purpose.
+ *
+ * @param buffered
+ * true if buffered mode should be turned on, false otherwise
+ * @since 7.0
+ */
+ public void setBuffered(boolean buffered);
+
+ /**
+ * Checks the buffered mode
+ *
+ * @return true if buffered mode is on, false otherwise
+ * @since 7.0
+ */
+ public boolean isBuffered();
+
+ /**
+ * Tests if the value stored in the object has been modified since it was
+ * last updated from the data source.
+ *
+ * @return <code>true</code> if the value in the object has been modified
+ * since the last data source update, <code>false</code> if not.
+ */
+ public boolean isModified();
+
+ /**
+ * An exception that signals that one or more exceptions occurred while a
+ * buffered object tried to access its data source or if there is a problem
+ * in processing a data source.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @SuppressWarnings("serial")
+ public class SourceException extends RuntimeException
+ implements Serializable, ErrorMessageProducer {
+
+ /** Source class implementing the buffered interface */
+ private final Buffered source;
+
+ /** Original cause of the source exception */
+ private Throwable[] causes = {};
+
+ /**
+ * Creates a source exception that does not include a cause.
+ *
+ * @param source
+ * the source object implementing the Buffered interface.
+ */
+ public SourceException(Buffered source) {
+ this.source = source;
+ }
+
+ /**
+ * Creates a source exception from multiple causes.
+ *
+ * @param source
+ * the source object implementing the Buffered interface.
+ * @param causes
+ * the original causes for this exception.
+ */
+ public SourceException(Buffered source, Throwable... causes) {
+ this.source = source;
+ this.causes = causes;
+ }
+
+ /**
+ * Gets the cause of the exception.
+ *
+ * @return The (first) cause for the exception, null if no cause.
+ */
+ @Override
+ public final Throwable getCause() {
+ if (causes.length == 0) {
+ return null;
+ }
+ return causes[0];
+ }
+
+ /**
+ * Gets all the causes for this exception.
+ *
+ * @return throwables that caused this exception
+ */
+ public final Throwable[] getCauses() {
+ return causes;
+ }
+
+ /**
+ * Gets a source of the exception.
+ *
+ * @return the Buffered object which generated this exception.
+ */
+ public Buffered getSource() {
+ return source;
+ }
+
+ @Override
+ public ErrorMessage getErrorMessage() {
+ // no message, only the causes to be painted
+ UserError error = new UserError(null);
+ // in practice, this was always ERROR in Vaadin 6 unless tweaked in
+ // custom exceptions implementing ErrorMessage
+ error.setErrorLevel(ErrorLevel.ERROR);
+ // causes
+ for (Throwable nestedException : getCauses()) {
+ error.addCause(AbstractErrorMessage
+ .getErrorMessageForException(nestedException));
+ }
+ return error;
+ }
+
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/BufferedValidatable.java b/compatibility-server/src/main/java/com/vaadin/v7/data/BufferedValidatable.java
new file mode 100644
index 0000000000..ea0b3c0ebd
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/BufferedValidatable.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2016 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.v7.data;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ * This interface defines the combination of <code>Validatable</code> and
+ * <code>Buffered</code> interfaces. The combination of the interfaces defines
+ * if the invalid data is committed to datasource.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+public interface BufferedValidatable
+ extends Buffered, Validatable, Serializable {
+
+ /**
+ * Tests if the invalid data is committed to datasource. The default is
+ * <code>false</code>.
+ */
+ public boolean isInvalidCommitted();
+
+ /**
+ * Sets if the invalid data should be committed to datasource. The default
+ * is <code>false</code>.
+ */
+ public void setInvalidCommitted(boolean isCommitted);
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/Property.java b/compatibility-server/src/main/java/com/vaadin/v7/data/Property.java
new file mode 100644
index 0000000000..21129a64f9
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/Property.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2000-2016 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.v7.data;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ * The <code>Property</code> is a simple data object that contains one typed
+ * value. This interface contains methods to inspect and modify the stored value
+ * and its type, and the object's read-only state.
+ * </p>
+ *
+ * <p>
+ * The <code>Property</code> also defines the events
+ * <code>ReadOnlyStatusChangeEvent</code> and <code>ValueChangeEvent</code>, and
+ * the associated <code>listener</code> and <code>notifier</code> interfaces.
+ * </p>
+ *
+ * <p>
+ * The <code>Property.Viewer</code> interface should be used to attach the
+ * Property to an external data source. This way the value in the data source
+ * can be inspected using the <code>Property</code> interface.
+ * </p>
+ *
+ * <p>
+ * The <code>Property.editor</code> interface should be implemented if the value
+ * needs to be changed through the implementing class.
+ * </p>
+ *
+ * @param T
+ * type of values of the property
+ *
+ * @author Vaadin Ltd
+ * @since 3.0
+ */
+public interface Property<T> extends Serializable {
+
+ /**
+ * Gets the value stored in the Property. The returned object is compatible
+ * with the class returned by getType().
+ *
+ * @return the value stored in the Property
+ */
+ public T getValue();
+
+ /**
+ * Sets the value of the Property.
+ * <p>
+ * Implementing this functionality is optional. If the functionality is
+ * missing, one should declare the Property to be in read-only mode and
+ * throw <code>Property.ReadOnlyException</code> in this function.
+ * </p>
+ *
+ * Note : Since Vaadin 7.0, setting the value of a non-String property as a
+ * String is no longer supported.
+ *
+ * @param newValue
+ * New value of the Property. This should be assignable to the
+ * type returned by getType
+ *
+ * @throws Property.ReadOnlyException
+ * if the object is in read-only mode
+ */
+ public void setValue(T newValue) throws Property.ReadOnlyException;
+
+ /**
+ * Returns the type of the Property. 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 type of the Property
+ */
+ public Class<? extends T> getType();
+
+ /**
+ * Tests if the Property is in read-only mode. In read-only mode calls to
+ * the method <code>setValue</code> will throw
+ * <code>ReadOnlyException</code> and will not modify the value of the
+ * Property.
+ *
+ * @return <code>true</code> if the Property is in read-only mode,
+ * <code>false</code> if it's not
+ */
+ public boolean isReadOnly();
+
+ /**
+ * Sets the Property's read-only mode to the specified status.
+ *
+ * This functionality is optional, but all properties must implement the
+ * <code>isReadOnly</code> mode query correctly.
+ *
+ * @param newStatus
+ * new read-only status of the Property
+ */
+ public void setReadOnly(boolean newStatus);
+
+ /**
+ * A Property that is capable of handle a transaction that can end in commit
+ * or rollback.
+ *
+ * Note that this does not refer to e.g. database transactions but rather
+ * two-phase commit that allows resetting old field values (in e.g. a
+ * FieldGroup) if the commit of one of the properties fails after other
+ * properties have already been committed.
+ *
+ * @param <T>
+ * The type of the property
+ * @author Vaadin Ltd
+ * @since 7.0
+ */
+ public interface Transactional<T> extends Property<T> {
+
+ /**
+ * Starts a transaction.
+ *
+ * <p>
+ * If the value is set during a transaction the value must not replace
+ * the original value until {@link #commit()} is called. Still,
+ * {@link #getValue()} must return the current value set in the
+ * transaction. Calling {@link #rollback()} while in a transaction must
+ * rollback the value to what it was before the transaction started.
+ * </p>
+ * <p>
+ * {@link ValueChangeEvent}s must not be emitted for internal value
+ * changes during a transaction. If the value changes as a result of
+ * {@link #commit()}, a {@link ValueChangeEvent} should be emitted.
+ * </p>
+ */
+ public void startTransaction();
+
+ /**
+ * Commits and ends the transaction that is in progress.
+ * <p>
+ * If the value is changed as a result of this operation, a
+ * {@link ValueChangeEvent} is emitted if such are supported.
+ * <p>
+ * This method has no effect if there is no transaction is in progress.
+ * <p>
+ * This method must never throw an exception.
+ */
+ public void commit();
+
+ /**
+ * Aborts and rolls back the transaction that is in progress.
+ * <p>
+ * The value is reset to the value before the transaction started. No
+ * {@link ValueChangeEvent} is emitted as a result of this.
+ * <p>
+ * This method has no effect if there is no transaction is in progress.
+ * <p>
+ * This method must never throw an exception.
+ */
+ public void rollback();
+ }
+
+ /**
+ * <code>Exception</code> object that signals that a requested Property
+ * modification failed because it's in read-only mode.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @SuppressWarnings("serial")
+ public class ReadOnlyException extends RuntimeException {
+
+ /**
+ * Constructs a new <code>ReadOnlyException</code> without a detail
+ * message.
+ */
+ public ReadOnlyException() {
+ }
+
+ /**
+ * Constructs a new <code>ReadOnlyException</code> with the specified
+ * detail message.
+ *
+ * @param msg
+ * the detail message
+ */
+ public ReadOnlyException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Interface implemented by the viewer classes capable of using a Property
+ * as a data source.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface Viewer extends Serializable {
+
+ /**
+ * Sets the Property that serves as the data source of the viewer.
+ *
+ * @param newDataSource
+ * the new data source Property
+ */
+ public void setPropertyDataSource(Property newDataSource);
+
+ /**
+ * Gets the Property serving as the data source of the viewer.
+ *
+ * @return the Property serving as the viewers data source
+ */
+ public Property getPropertyDataSource();
+ }
+
+ /**
+ * Interface implemented by the editor classes capable of editing the
+ * Property.
+ * <p>
+ * Implementing this interface means that the Property serving as the data
+ * source of the editor can be modified through the editor. It does not
+ * restrict the editor from editing the Property internally, though if the
+ * Property is in a read-only mode, attempts to modify it will result in the
+ * <code>ReadOnlyException</code> being thrown.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface Editor extends Property.Viewer, Serializable {
+
+ }
+
+ /* Value change event */
+
+ /**
+ * An <code>Event</code> object specifying the Property whose value has been
+ * changed.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface ValueChangeEvent extends Serializable {
+
+ /**
+ * Retrieves the Property that has been modified.
+ *
+ * @return source Property of the event
+ */
+ public Property getProperty();
+ }
+
+ /**
+ * The <code>listener</code> interface for receiving
+ * <code>ValueChangeEvent</code> objects.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface ValueChangeListener extends Serializable {
+
+ /**
+ * Notifies this listener that the Property's value has changed.
+ *
+ * @param event
+ * value change event object
+ */
+ public void valueChange(Property.ValueChangeEvent event);
+ }
+
+ /**
+ * The interface for adding and removing <code>ValueChangeEvent</code>
+ * listeners. If a Property wishes to allow other objects to receive
+ * <code>ValueChangeEvent</code> generated by it, it must implement this
+ * interface.
+ * <p>
+ * Note : The general Java convention is not to explicitly declare that a
+ * class generates events, but to directly define the
+ * <code>addListener</code> and <code>removeListener</code> methods. That
+ * way the caller of these methods has no real way of finding out if the
+ * class really will send the events, or if it just defines the methods to
+ * be able to implement an interface.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface ValueChangeNotifier extends Serializable {
+
+ /**
+ * Registers a new value change listener for this Property.
+ *
+ * @param listener
+ * the new Listener to be registered
+ */
+ public void addValueChangeListener(
+ Property.ValueChangeListener listener);
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addValueChangeListener(ValueChangeListener)}
+ **/
+ @Deprecated
+ public void addListener(Property.ValueChangeListener listener);
+
+ /**
+ * Removes a previously registered value change listener.
+ *
+ * @param listener
+ * listener to be removed
+ */
+ public void removeValueChangeListener(
+ Property.ValueChangeListener listener);
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeValueChangeListener(ValueChangeListener)}
+ **/
+ @Deprecated
+ public void removeListener(Property.ValueChangeListener listener);
+ }
+
+ /* ReadOnly Status change event */
+
+ /**
+ * An <code>Event</code> object specifying the Property whose read-only
+ * status has been changed.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface ReadOnlyStatusChangeEvent extends Serializable {
+
+ /**
+ * Property whose read-only state has changed.
+ *
+ * @return source Property of the event.
+ */
+ public Property getProperty();
+ }
+
+ /**
+ * The listener interface for receiving
+ * <code>ReadOnlyStatusChangeEvent</code> objects.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface ReadOnlyStatusChangeListener extends Serializable {
+
+ /**
+ * Notifies this listener that a Property's read-only status has
+ * changed.
+ *
+ * @param event
+ * Read-only status change event object
+ */
+ public void readOnlyStatusChange(
+ Property.ReadOnlyStatusChangeEvent event);
+ }
+
+ /**
+ * The interface for adding and removing
+ * <code>ReadOnlyStatusChangeEvent</code> listeners. If a Property wishes to
+ * allow other objects to receive <code>ReadOnlyStatusChangeEvent</code>
+ * generated by it, it must implement this interface.
+ * <p>
+ * Note : The general Java convention is not to explicitly declare that a
+ * class generates events, but to directly define the
+ * <code>addListener</code> and <code>removeListener</code> methods. That
+ * way the caller of these methods has no real way of finding out if the
+ * class really will send the events, or if it just defines the methods to
+ * be able to implement an interface.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface ReadOnlyStatusChangeNotifier extends Serializable {
+
+ /**
+ * Registers a new read-only status change listener for this Property.
+ *
+ * @param listener
+ * the new Listener to be registered
+ */
+ public void addReadOnlyStatusChangeListener(
+ Property.ReadOnlyStatusChangeListener listener);
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addReadOnlyStatusChangeListener(ReadOnlyStatusChangeListener)}
+ **/
+ @Deprecated
+ public void addListener(Property.ReadOnlyStatusChangeListener listener);
+
+ /**
+ * Removes a previously registered read-only status change listener.
+ *
+ * @param listener
+ * listener to be removed
+ */
+ public void removeReadOnlyStatusChangeListener(
+ Property.ReadOnlyStatusChangeListener listener);
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeReadOnlyStatusChangeListener(ReadOnlyStatusChangeListener)}
+ **/
+ @Deprecated
+ public void removeListener(
+ Property.ReadOnlyStatusChangeListener listener);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/Validatable.java b/compatibility-server/src/main/java/com/vaadin/v7/data/Validatable.java
new file mode 100644
index 0000000000..5c75d594ba
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/Validatable.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2000-2016 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.v7.data;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * <p>
+ * Interface for validatable objects. Defines methods to verify if the object's
+ * value is valid or not, and to add, remove and list registered validators of
+ * the object.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ * @see com.vaadin.v7.data.Validator
+ */
+public interface Validatable extends Serializable {
+
+ /**
+ * <p>
+ * Adds a new validator for this object. The validator's
+ * {@link Validator#validate(Object)} method is activated every time the
+ * object's value needs to be verified, that is, when the {@link #isValid()}
+ * method is called. This usually happens when the object's value changes.
+ * </p>
+ *
+ * @param validator
+ * the new validator
+ */
+ void addValidator(Validator validator);
+
+ /**
+ * <p>
+ * Removes a previously registered validator from the object. The specified
+ * validator is removed from the object and its <code>validate</code> method
+ * is no longer called in {@link #isValid()}.
+ * </p>
+ *
+ * @param validator
+ * the validator to remove
+ */
+ void removeValidator(Validator validator);
+
+ /**
+ * Removes all validators from this object, as if
+ * {@link #removeValidator(Validator) removeValidator} was called for each
+ * registered validator.
+ */
+ void removeAllValidators();
+
+ /**
+ * <p>
+ * Returns a collection of all validators currently registered for the
+ * object. The collection may be immutable. Calling
+ * <code>removeValidator</code> for this Validatable while iterating over
+ * the collection may be unsafe (e.g. may throw
+ * <code>ConcurrentModificationException</code>.)
+ * </p>
+ *
+ * @return A collection of validators
+ */
+ public Collection<Validator> getValidators();
+
+ /**
+ * <p>
+ * Tests the current value of the object against all registered validators.
+ * The registered validators are iterated and for each the
+ * {@link Validator#validate(Object)} method is called. If any validator
+ * throws the {@link Validator.InvalidValueException} this method returns
+ * <code>false</code>.
+ * </p>
+ *
+ * @return <code>true</code> if the registered validators concur that the
+ * value is valid, <code>false</code> otherwise
+ */
+ public boolean isValid();
+
+ /**
+ * <p>
+ * Checks the validity of the validatable. If the validatable is valid this
+ * method should do nothing, and if it's not valid, it should throw
+ * <code>Validator.InvalidValueException</code>
+ * </p>
+ *
+ * @throws Validator.InvalidValueException
+ * if the value is not valid
+ */
+ public void validate() throws Validator.InvalidValueException;
+
+ /**
+ * <p>
+ * Checks the validabtable object accept invalid values.The default value is
+ * <code>true</code>.
+ * </p>
+ *
+ */
+ public boolean isInvalidAllowed();
+
+ /**
+ * <p>
+ * Should the validabtable object accept invalid values. Supporting this
+ * configuration possibility is optional. By default invalid values are
+ * allowed.
+ * </p>
+ *
+ * @param invalidValueAllowed
+ *
+ * @throws UnsupportedOperationException
+ * if the setInvalidAllowed is not supported.
+ */
+ public void setInvalidAllowed(boolean invalidValueAllowed)
+ throws UnsupportedOperationException;
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/Validator.java b/compatibility-server/src/main/java/com/vaadin/v7/data/Validator.java
new file mode 100644
index 0000000000..a6ad0331ed
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/Validator.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2000-2016 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.v7.data;
+
+import java.io.Serializable;
+
+import com.vaadin.server.AbstractErrorMessage;
+import com.vaadin.server.AbstractErrorMessage.ContentMode;
+import com.vaadin.server.ErrorMessage;
+import com.vaadin.server.ErrorMessage.ErrorLevel;
+import com.vaadin.server.ErrorMessageProducer;
+import com.vaadin.server.UserError;
+import com.vaadin.server.VaadinServlet;
+
+/**
+ * Interface that implements a method for validating if an {@link Object} is
+ * valid or not.
+ * <p>
+ * Implementors of this class can be added to any
+ * {@link com.vaadin.v7.data.Validatable Validatable} implementor to verify its
+ * value.
+ * </p>
+ * <p>
+ * {@link #validate(Object)} can be used to check if a value is valid. An
+ * {@link InvalidValueException} with an appropriate validation error message is
+ * thrown if the value is not valid.
+ * </p>
+ * <p>
+ * Validators must not have any side effects.
+ * </p>
+ * <p>
+ * Since Vaadin 7, the method isValid(Object) does not exist in the interface -
+ * {@link #validate(Object)} should be used instead, and the exception caught
+ * where applicable. Concrete classes implementing {@link Validator} can still
+ * internally implement and use isValid(Object) for convenience or to ease
+ * migration from earlier Vaadin versions.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+public interface Validator extends Serializable {
+
+ /**
+ * Checks the given value against this validator. If the value is valid the
+ * method does nothing. If the value is invalid, an
+ * {@link InvalidValueException} is thrown.
+ *
+ * @param value
+ * the value to check
+ * @throws Validator.InvalidValueException
+ * if the value is invalid
+ */
+ public void validate(Object value) throws Validator.InvalidValueException;
+
+ /**
+ * Exception that is thrown by a {@link Validator} when a value is invalid.
+ *
+ * <p>
+ * The default implementation of InvalidValueException does not support HTML
+ * in error messages. To enable HTML support, override
+ * {@link #getHtmlMessage()} and use the subclass in validators.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @SuppressWarnings("serial")
+ public class InvalidValueException extends RuntimeException
+ implements ErrorMessageProducer {
+
+ /**
+ * Array of one or more validation errors that are causing this
+ * validation error.
+ */
+ private InvalidValueException[] causes = null;
+
+ /**
+ * Constructs a new {@code InvalidValueException} with the specified
+ * message.
+ *
+ * @param message
+ * The detail message of the problem.
+ */
+ public InvalidValueException(String message) {
+ this(message, new InvalidValueException[] {});
+ }
+
+ /**
+ * Constructs a new {@code InvalidValueException} with a set of causing
+ * validation exceptions. The causing validation exceptions are included
+ * when the exception is painted to the client.
+ *
+ * @param message
+ * The detail message of the problem.
+ * @param causes
+ * One or more {@code InvalidValueException}s that caused
+ * this exception.
+ */
+ public InvalidValueException(String message,
+ InvalidValueException... causes) {
+ super(message);
+ if (causes == null) {
+ throw new NullPointerException(
+ "Possible causes array must not be null");
+ }
+
+ this.causes = causes;
+ }
+
+ /**
+ * Check if the error message should be hidden.
+ *
+ * An empty (null or "") message is invisible unless it contains nested
+ * exceptions that are visible.
+ *
+ * @return true if the error message should be hidden, false otherwise
+ */
+ public boolean isInvisible() {
+ String msg = getMessage();
+ if (msg != null && msg.length() > 0) {
+ return false;
+ }
+ if (causes != null) {
+ for (int i = 0; i < causes.length; i++) {
+ if (!causes[i].isInvisible()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the message of the error in HTML.
+ *
+ * Note that this API may change in future versions.
+ */
+ public String getHtmlMessage() {
+ return VaadinServlet.safeEscapeForHtml(getLocalizedMessage());
+ }
+
+ /**
+ * Returns the {@code InvalidValueExceptions} that caused this
+ * exception.
+ *
+ * @return An array containing the {@code InvalidValueExceptions} that
+ * caused this exception. Returns an empty array if this
+ * exception was not caused by other exceptions.
+ */
+ public InvalidValueException[] getCauses() {
+ return causes;
+ }
+
+ @Override
+ public ErrorMessage getErrorMessage() {
+ UserError error = new UserError(getHtmlMessage(), ContentMode.HTML,
+ ErrorLevel.ERROR);
+ for (Validator.InvalidValueException nestedException : getCauses()) {
+ error.addCause(AbstractErrorMessage
+ .getErrorMessageForException(nestedException));
+ }
+ return error;
+ }
+
+ }
+
+ /**
+ * A specific type of {@link InvalidValueException} that indicates that
+ * validation failed because the value was empty. What empty means is up to
+ * the thrower.
+ *
+ * @author Vaadin Ltd.
+ * @since 5.3.0
+ */
+ @SuppressWarnings("serial")
+ public class EmptyValueException extends Validator.InvalidValueException {
+
+ public EmptyValueException(String message) {
+ super(message);
+ }
+
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/AbstractProperty.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/AbstractProperty.java
new file mode 100644
index 0000000000..33d22777ad
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/AbstractProperty.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.logging.Logger;
+
+import com.vaadin.v7.data.Property;
+
+/**
+ * Abstract base class for {@link Property} implementations.
+ *
+ * Handles listener management for {@link ValueChangeListener}s and
+ * {@link ReadOnlyStatusChangeListener}s.
+ *
+ * @since 6.6
+ */
+public abstract class AbstractProperty<T> implements Property<T>,
+ Property.ValueChangeNotifier, Property.ReadOnlyStatusChangeNotifier {
+
+ /**
+ * List of listeners who are interested in the read-only status changes of
+ * the Property
+ */
+ private LinkedList<ReadOnlyStatusChangeListener> readOnlyStatusChangeListeners = null;
+
+ /**
+ * List of listeners who are interested in the value changes of the Property
+ */
+ private LinkedList<ValueChangeListener> valueChangeListeners = null;
+
+ /**
+ * Is the Property read-only?
+ */
+ private boolean readOnly;
+
+ /**
+ * {@inheritDoc}
+ *
+ * Override for additional restrictions on what is considered a read-only
+ * property.
+ */
+ @Override
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ @Override
+ public void setReadOnly(boolean newStatus) {
+ boolean oldStatus = isReadOnly();
+ readOnly = newStatus;
+ if (oldStatus != isReadOnly()) {
+ fireReadOnlyStatusChange();
+ }
+ }
+
+ /* Events */
+
+ /**
+ * An <code>Event</code> object specifying the Property whose read-only
+ * status has been changed.
+ */
+ protected static class ReadOnlyStatusChangeEvent
+ extends java.util.EventObject
+ implements Property.ReadOnlyStatusChangeEvent {
+
+ /**
+ * Constructs a new read-only status change event for this object.
+ *
+ * @param source
+ * source object of the event.
+ */
+ protected ReadOnlyStatusChangeEvent(Property source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property whose read-only state has changed.
+ *
+ * @return source Property of the event.
+ */
+ @Override
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+
+ }
+
+ /**
+ * Registers a new read-only status change listener for this Property.
+ *
+ * @param listener
+ * the new Listener to be registered.
+ */
+ @Override
+ public void addReadOnlyStatusChangeListener(
+ Property.ReadOnlyStatusChangeListener listener) {
+ if (readOnlyStatusChangeListeners == null) {
+ readOnlyStatusChangeListeners = new LinkedList<ReadOnlyStatusChangeListener>();
+ }
+ readOnlyStatusChangeListeners.add(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addReadOnlyStatusChangeListener(com.vaadin.v7.data.Property.ReadOnlyStatusChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(Property.ReadOnlyStatusChangeListener listener) {
+ addReadOnlyStatusChangeListener(listener);
+ }
+
+ /**
+ * Removes a previously registered read-only status change listener.
+ *
+ * @param listener
+ * the listener to be removed.
+ */
+ @Override
+ public void removeReadOnlyStatusChangeListener(
+ Property.ReadOnlyStatusChangeListener listener) {
+ if (readOnlyStatusChangeListeners != null) {
+ readOnlyStatusChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeReadOnlyStatusChangeListener(com.vaadin.v7.data.Property.ReadOnlyStatusChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(Property.ReadOnlyStatusChangeListener listener) {
+ removeReadOnlyStatusChangeListener(listener);
+ }
+
+ /**
+ * Sends a read only status change event to all registered listeners.
+ */
+ protected void fireReadOnlyStatusChange() {
+ if (readOnlyStatusChangeListeners != null) {
+ final Object[] l = readOnlyStatusChangeListeners.toArray();
+ final Property.ReadOnlyStatusChangeEvent event = new ReadOnlyStatusChangeEvent(
+ this);
+ for (int i = 0; i < l.length; i++) {
+ ((Property.ReadOnlyStatusChangeListener) l[i])
+ .readOnlyStatusChange(event);
+ }
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying the Property whose value has been
+ * changed.
+ */
+ private static class ValueChangeEvent extends java.util.EventObject
+ implements Property.ValueChangeEvent {
+
+ /**
+ * Constructs a new value change event for this object.
+ *
+ * @param source
+ * source object of the event.
+ */
+ protected ValueChangeEvent(Property source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property whose value has changed.
+ *
+ * @return source Property of the event.
+ */
+ @Override
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+
+ }
+
+ @Override
+ public void addValueChangeListener(ValueChangeListener listener) {
+ if (valueChangeListeners == null) {
+ valueChangeListeners = new LinkedList<ValueChangeListener>();
+ }
+ valueChangeListeners.add(listener);
+
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(ValueChangeListener listener) {
+ addValueChangeListener(listener);
+ }
+
+ @Override
+ public void removeValueChangeListener(ValueChangeListener listener) {
+ if (valueChangeListeners != null) {
+ valueChangeListeners.remove(listener);
+ }
+
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(ValueChangeListener listener) {
+ removeValueChangeListener(listener);
+ }
+
+ /**
+ * Sends a value change event to all registered listeners.
+ */
+ protected void fireValueChange() {
+ if (valueChangeListeners != null) {
+ final Object[] l = valueChangeListeners.toArray();
+ final Property.ValueChangeEvent event = new ValueChangeEvent(this);
+ for (int i = 0; i < l.length; i++) {
+ ((Property.ValueChangeListener) l[i]).valueChange(event);
+ }
+ }
+ }
+
+ public Collection<?> getListeners(Class<?> eventType) {
+ if (Property.ValueChangeEvent.class.isAssignableFrom(eventType)) {
+ if (valueChangeListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections.unmodifiableCollection(valueChangeListeners);
+ }
+ } else if (Property.ReadOnlyStatusChangeEvent.class
+ .isAssignableFrom(eventType)) {
+ if (readOnlyStatusChangeListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections
+ .unmodifiableCollection(readOnlyStatusChangeListeners);
+ }
+ }
+
+ return Collections.EMPTY_LIST;
+ }
+
+ private static Logger getLogger() {
+ return Logger.getLogger(AbstractProperty.class.getName());
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/ListSet.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/ListSet.java
new file mode 100644
index 0000000000..2fd182238d
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/ListSet.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * ListSet is an internal Vaadin class which implements a combination of a List
+ * and a Set. The main purpose of this class is to provide a list with a fast
+ * {@link #contains(Object)} method. Each inserted object must by unique (as
+ * specified by {@link #equals(Object)}). The {@link #set(int, Object)} method
+ * allows duplicates because of the way {@link Collections#sort(java.util.List)}
+ * works.
+ *
+ * This class is subject to change and should not be used outside Vaadin core.
+ */
+public class ListSet<E> extends ArrayList<E> {
+ private HashSet<E> itemSet = null;
+
+ /**
+ * Contains a map from an element to the number of duplicates it has. Used
+ * to temporarily allow duplicates in the list.
+ */
+ private HashMap<E, Integer> duplicates = new HashMap<E, Integer>();
+
+ public ListSet() {
+ super();
+ itemSet = new HashSet<E>();
+ }
+
+ public ListSet(Collection<? extends E> c) {
+ super(c);
+ itemSet = new HashSet<E>(c.size());
+ itemSet.addAll(c);
+ }
+
+ public ListSet(int initialCapacity) {
+ super(initialCapacity);
+ itemSet = new HashSet<E>(initialCapacity);
+ }
+
+ // Delegate contains operations to the set
+ @Override
+ public boolean contains(Object o) {
+ return itemSet.contains(o);
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return itemSet.containsAll(c);
+ }
+
+ // Methods for updating the set when the list is updated.
+ @Override
+ public boolean add(E e) {
+ if (contains(e)) {
+ // Duplicates are not allowed
+ return false;
+ }
+
+ if (super.add(e)) {
+ itemSet.add(e);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Works as java.util.ArrayList#add(int, java.lang.Object) but returns
+ * immediately if the element is already in the ListSet.
+ */
+ @Override
+ public void add(int index, E element) {
+ if (contains(element)) {
+ // Duplicates are not allowed
+ return;
+ }
+
+ super.add(index, element);
+ itemSet.add(element);
+ }
+
+ @Override
+ public boolean addAll(Collection<? extends E> c) {
+ boolean modified = false;
+ Iterator<? extends E> i = c.iterator();
+ while (i.hasNext()) {
+ E e = i.next();
+ if (contains(e)) {
+ continue;
+ }
+
+ if (add(e)) {
+ itemSet.add(e);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ @Override
+ public boolean addAll(int index, Collection<? extends E> c) {
+ ensureCapacity(size() + c.size());
+
+ boolean modified = false;
+ Iterator<? extends E> i = c.iterator();
+ while (i.hasNext()) {
+ E e = i.next();
+ if (contains(e)) {
+ continue;
+ }
+
+ add(index++, e);
+ itemSet.add(e);
+ modified = true;
+ }
+
+ return modified;
+ }
+
+ @Override
+ public void clear() {
+ super.clear();
+ itemSet.clear();
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ if (!contains(o)) {
+ return -1;
+ }
+
+ return super.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ if (!contains(o)) {
+ return -1;
+ }
+
+ return super.lastIndexOf(o);
+ }
+
+ @Override
+ public E remove(int index) {
+ E e = super.remove(index);
+
+ if (e != null) {
+ itemSet.remove(e);
+ }
+
+ return e;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (super.remove(o)) {
+ itemSet.remove(o);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected void removeRange(int fromIndex, int toIndex) {
+ HashSet<E> toRemove = new HashSet<E>();
+ for (int idx = fromIndex; idx < toIndex; idx++) {
+ toRemove.add(get(idx));
+ }
+ super.removeRange(fromIndex, toIndex);
+ itemSet.removeAll(toRemove);
+ }
+
+ @Override
+ public E set(int index, E element) {
+ if (contains(element)) {
+ // Element already exist in the list
+ if (get(index) == element) {
+ // At the same position, nothing to be done
+ return element;
+ } else {
+ // Adding at another position. We assume this is a sort
+ // operation and temporarily allow it.
+
+ // We could just remove (null) the old element and keep the list
+ // unique. This would require finding the index of the old
+ // element (indexOf(element)) which is not a fast operation in a
+ // list. So we instead allow duplicates temporarily.
+ addDuplicate(element);
+ }
+ }
+
+ E old = super.set(index, element);
+ removeFromSet(old);
+ itemSet.add(element);
+
+ return old;
+ }
+
+ /**
+ * Removes "e" from the set if it no longer exists in the list.
+ *
+ * @param e
+ */
+ private void removeFromSet(E e) {
+ Integer dupl = duplicates.get(e);
+ if (dupl != null) {
+ // A duplicate was present so we only decrement the duplicate count
+ // and continue
+ if (dupl == 1) {
+ // This is what always should happen. A sort sets the items one
+ // by one, temporarily breaking the uniqueness requirement.
+ duplicates.remove(e);
+ } else {
+ duplicates.put(e, dupl - 1);
+ }
+ } else {
+ // The "old" value is no longer in the list.
+ itemSet.remove(e);
+ }
+
+ }
+
+ /**
+ * Marks the "element" can be found more than once from the list. Allowed in
+ * {@link #set(int, Object)} to make sorting work.
+ *
+ * @param element
+ */
+ private void addDuplicate(E element) {
+ Integer nr = duplicates.get(element);
+ if (nr == null) {
+ nr = 1;
+ } else {
+ nr++;
+ }
+
+ /*
+ * Store the number of duplicates of this element so we know later on if
+ * we should remove an element from the set or if it was a duplicate (in
+ * removeFromSet)
+ */
+ duplicates.put(element, nr);
+
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object clone() {
+ ListSet<E> v = (ListSet<E>) super.clone();
+ v.itemSet = new HashSet<E>(itemSet);
+ return v;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodProperty.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodProperty.java
new file mode 100644
index 0000000000..86fb0ae40d
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodProperty.java
@@ -0,0 +1,774 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import static com.vaadin.util.ReflectTools.convertPrimitiveType;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.shared.util.SharedUtil;
+import com.vaadin.util.SerializerHelper;
+import com.vaadin.v7.data.Property;
+
+/**
+ * <p>
+ * Proxy class for creating Properties from pairs of getter and setter methods
+ * of a Bean property. An instance of this class can be thought as having been
+ * attached to a field of an object. Accessing the object through the Property
+ * interface directly manipulates the underlying field.
+ * </p>
+ *
+ * <p>
+ * It's assumed that the return value returned by the getter method is
+ * assignable to the type of the property, and the setter method parameter is
+ * assignable to that value.
+ * </p>
+ *
+ * <p>
+ * A valid getter method must always be available, but instance of this class
+ * can be constructed with a <code>null</code> setter method in which case the
+ * resulting MethodProperty is read-only.
+ * </p>
+ *
+ * <p>
+ * MethodProperty implements Property.ValueChangeNotifier, but does not
+ * automatically know whether or not the getter method will actually return a
+ * new value - value change listeners are always notified when setValue is
+ * called, without verifying what the getter returns.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class MethodProperty<T> extends AbstractProperty<T> {
+
+ /**
+ * The object that includes the property the MethodProperty is bound to.
+ */
+ private transient Object instance;
+
+ /**
+ * Argument arrays for the getter and setter methods.
+ */
+ private transient Object[] setArgs, getArgs;
+
+ /**
+ * The getter and setter methods.
+ */
+ private transient Method setMethod, getMethod;
+
+ /**
+ * Index of the new value in the argument list for the setter method. If the
+ * setter method requires several parameters, this index tells which one is
+ * the actual value to change.
+ */
+ private int setArgumentIndex;
+
+ /**
+ * Type of the property.
+ */
+ private transient Class<? extends T> type;
+
+ private static final Object[] DEFAULT_GET_ARGS = new Object[0];
+
+ private static final Object[] DEFAULT_SET_ARGS = new Object[1];
+
+ /* Special serialization to handle method references */
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws IOException {
+ out.defaultWriteObject();
+ SerializerHelper.writeClass(out, type);
+ out.writeObject(instance);
+ out.writeObject(setArgs);
+ out.writeObject(getArgs);
+ if (setMethod != null) {
+ out.writeObject(setMethod.getName());
+ SerializerHelper.writeClassArray(out,
+ setMethod.getParameterTypes());
+ } else {
+ out.writeObject(null);
+ out.writeObject(null);
+ }
+ if (getMethod != null) {
+ out.writeObject(getMethod.getName());
+ SerializerHelper.writeClassArray(out,
+ getMethod.getParameterTypes());
+ } else {
+ out.writeObject(null);
+ out.writeObject(null);
+ }
+ }
+
+ /* Special serialization to handle method references */
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ try {
+ @SuppressWarnings("unchecked")
+ // business assumption; type parameters not checked at runtime
+ Class<T> class1 = (Class<T>) SerializerHelper.readClass(in);
+ type = class1;
+ instance = in.readObject();
+ Object[] setArgs = (Object[]) in.readObject();
+ Object[] getArgs = (Object[]) in.readObject();
+ setArguments(getArgs, setArgs, setArgumentIndex);
+ String name = (String) in.readObject();
+ Class<?>[] paramTypes = SerializerHelper.readClassArray(in);
+ if (instance != null && name != null) {
+ setMethod = instance.getClass().getMethod(name, paramTypes);
+ } else {
+ setMethod = null;
+ }
+
+ name = (String) in.readObject();
+ paramTypes = SerializerHelper.readClassArray(in);
+ if (instance != null && name != null) {
+ getMethod = instance.getClass().getMethod(name, paramTypes);
+ } else {
+ getMethod = null;
+ }
+ } catch (SecurityException e) {
+ getLogger().log(Level.SEVERE, "Internal deserialization error", e);
+ } catch (NoSuchMethodException e) {
+ getLogger().log(Level.SEVERE, "Internal deserialization error", e);
+ }
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> from a named bean
+ * property. This constructor takes an object and the name of a bean
+ * property and initializes itself with the accessor methods for the
+ * property.
+ * </p>
+ * <p>
+ * The getter method of a <code>MethodProperty</code> instantiated with this
+ * constructor will be called with no arguments, and the setter method with
+ * only the new value as the sole argument.
+ * </p>
+ *
+ * <p>
+ * If the setter method is unavailable, the resulting
+ * <code>MethodProperty</code> will be read-only, otherwise it will be
+ * read-write.
+ * </p>
+ *
+ * <p>
+ * Method names are constructed from the bean property by adding
+ * get/is/are/set prefix and capitalising the first character in the name of
+ * the given bean property.
+ * </p>
+ *
+ * @param instance
+ * the object that includes the property.
+ * @param beanPropertyName
+ * the name of the property to bind to.
+ */
+ @SuppressWarnings("unchecked")
+ public MethodProperty(Object instance, String beanPropertyName) {
+
+ final Class<?> beanClass = instance.getClass();
+
+ // Assure that the first letter is upper cased (it is a common
+ // mistake to write firstName, not FirstName).
+ beanPropertyName = SharedUtil.capitalize(beanPropertyName);
+
+ // Find the get method
+ getMethod = null;
+ try {
+ getMethod = initGetterMethod(beanPropertyName, beanClass);
+ } catch (final java.lang.NoSuchMethodException ignored) {
+ throw new MethodException(this,
+ "Bean property " + beanPropertyName + " can not be found");
+ }
+
+ // In case the get method is found, resolve the type
+ Class<?> returnType = getMethod.getReturnType();
+
+ // Finds the set method
+ setMethod = null;
+ try {
+ setMethod = beanClass.getMethod("set" + beanPropertyName,
+ new Class[] { returnType });
+ } catch (final java.lang.NoSuchMethodException skipped) {
+ }
+
+ // Gets the return type from get method
+ if (returnType.isPrimitive()) {
+ type = (Class<T>) convertPrimitiveType(returnType);
+ if (type.isPrimitive()) {
+ throw new MethodException(this,
+ "Bean property " + beanPropertyName
+ + " getter return type must not be void");
+ }
+ } else {
+ type = (Class<T>) returnType;
+ }
+
+ setArguments(DEFAULT_GET_ARGS, DEFAULT_SET_ARGS, 0);
+ this.instance = instance;
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> from named getter
+ * and setter methods. The getter method of a <code>MethodProperty</code>
+ * instantiated with this constructor will be called with no arguments, and
+ * the setter method with only the new value as the sole argument.
+ * </p>
+ *
+ * <p>
+ * If the setter method is <code>null</code>, the resulting
+ * <code>MethodProperty</code> will be read-only, otherwise it will be
+ * read-write.
+ * </p>
+ *
+ * @param type
+ * the type of the property.
+ * @param instance
+ * the object that includes the property.
+ * @param getMethodName
+ * the name of the getter method.
+ * @param setMethodName
+ * the name of the setter method.
+ *
+ */
+ public MethodProperty(Class<? extends T> type, Object instance,
+ String getMethodName, String setMethodName) {
+ this(type, instance, getMethodName, setMethodName, new Object[] {},
+ new Object[] { null }, 0);
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> with the getter and
+ * setter methods. The getter method of a <code>MethodProperty</code>
+ * instantiated with this constructor will be called with no arguments, and
+ * the setter method with only the new value as the sole argument.
+ * </p>
+ *
+ * <p>
+ * If the setter method is <code>null</code>, the resulting
+ * <code>MethodProperty</code> will be read-only, otherwise it will be
+ * read-write.
+ * </p>
+ *
+ * @param type
+ * the type of the property.
+ * @param instance
+ * the object that includes the property.
+ * @param getMethod
+ * the getter method.
+ * @param setMethod
+ * the setter method.
+ */
+ public MethodProperty(Class<? extends T> type, Object instance,
+ Method getMethod, Method setMethod) {
+ this(type, instance, getMethod, setMethod, new Object[] {},
+ new Object[] { null }, 0);
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> from named getter
+ * and setter methods and argument lists. The getter method of a
+ * <code>MethodProperty</code> instantiated with this constructor will be
+ * called with the getArgs as arguments. The setArgs will be used as the
+ * arguments for the setter method, though the argument indexed by the
+ * setArgumentIndex will be replaced with the argument passed to the
+ * {@link #setValue(Object newValue)} method.
+ * </p>
+ *
+ * <p>
+ * For example, if the <code>setArgs</code> contains <code>A</code>,
+ * <code>B</code> and <code>C</code>, and <code>setArgumentIndex =
+ * 1</code>, the call <code>methodProperty.setValue(X)</code> would result
+ * in the setter method to be called with the parameter set of
+ * <code>{A, X, C}</code>
+ * </p>
+ *
+ * @param type
+ * the type of the property.
+ * @param instance
+ * the object that includes the property.
+ * @param getMethodName
+ * the name of the getter method.
+ * @param setMethodName
+ * the name of the setter method.
+ * @param getArgs
+ * the fixed argument list to be passed to the getter method.
+ * @param setArgs
+ * the fixed argument list to be passed to the setter method.
+ * @param setArgumentIndex
+ * the index of the argument in <code>setArgs</code> to be
+ * replaced with <code>newValue</code> when
+ * {@link #setValue(Object newValue)} is called.
+ */
+ @SuppressWarnings("unchecked")
+ public MethodProperty(Class<? extends T> type, Object instance,
+ String getMethodName, String setMethodName, Object[] getArgs,
+ Object[] setArgs, int setArgumentIndex) {
+
+ // Check the setargs and setargs index
+ if (setMethodName != null && setArgs == null) {
+ throw new IndexOutOfBoundsException("The setArgs can not be null");
+ }
+ if (setMethodName != null && (setArgumentIndex < 0
+ || setArgumentIndex >= setArgs.length)) {
+ throw new IndexOutOfBoundsException(
+ "The setArgumentIndex must be >= 0 and < setArgs.length");
+ }
+
+ // Set type
+ this.type = type;
+
+ // Find set and get -methods
+ final Method[] m = instance.getClass().getMethods();
+
+ // Finds get method
+ boolean found = false;
+ for (int i = 0; i < m.length; i++) {
+
+ // Tests the name of the get Method
+ if (!m[i].getName().equals(getMethodName)) {
+
+ // name does not match, try next method
+ continue;
+ }
+
+ // Tests return type
+ if (!type.equals(m[i].getReturnType())) {
+ continue;
+ }
+
+ // Tests the parameter types
+ final Class<?>[] c = m[i].getParameterTypes();
+ if (c.length != getArgs.length) {
+
+ // not the right amount of parameters, try next method
+ continue;
+ }
+ int j = 0;
+ while (j < c.length) {
+ if (getArgs[j] != null
+ && !c[j].isAssignableFrom(getArgs[j].getClass())) {
+
+ // parameter type does not match, try next method
+ break;
+ }
+ j++;
+ }
+ if (j == c.length) {
+
+ // all paramteters matched
+ if (found == true) {
+ throw new MethodException(this,
+ "Could not uniquely identify " + getMethodName
+ + "-method");
+ } else {
+ found = true;
+ getMethod = m[i];
+ }
+ }
+ }
+ if (found != true) {
+ throw new MethodException(this,
+ "Could not find " + getMethodName + "-method");
+ }
+
+ // Finds set method
+ if (setMethodName != null) {
+
+ // Finds setMethod
+ found = false;
+ for (int i = 0; i < m.length; i++) {
+
+ // Checks name
+ if (!m[i].getName().equals(setMethodName)) {
+
+ // name does not match, try next method
+ continue;
+ }
+
+ // Checks parameter compatibility
+ final Class<?>[] c = m[i].getParameterTypes();
+ if (c.length != setArgs.length) {
+
+ // not the right amount of parameters, try next method
+ continue;
+ }
+ int j = 0;
+ while (j < c.length) {
+ if (setArgs[j] != null
+ && !c[j].isAssignableFrom(setArgs[j].getClass())) {
+
+ // parameter type does not match, try next method
+ break;
+ } else if (j == setArgumentIndex && !c[j].equals(type)) {
+
+ // Property type is not the same as setArg type
+ break;
+ }
+ j++;
+ }
+ if (j == c.length) {
+
+ // all parameters match
+ if (found == true) {
+ throw new MethodException(this,
+ "Could not identify unique " + setMethodName
+ + "-method");
+ } else {
+ found = true;
+ setMethod = m[i];
+ }
+ }
+ }
+ if (found != true) {
+ throw new MethodException(this,
+ "Could not identify " + setMethodName + "-method");
+ }
+ }
+
+ // Gets the return type from get method
+ this.type = (Class<T>) convertPrimitiveType(type);
+
+ setArguments(getArgs, setArgs, setArgumentIndex);
+ this.instance = instance;
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> from the getter and
+ * setter methods, and argument lists.
+ * </p>
+ * <p>
+ * This constructor behaves exactly like
+ * {@link #MethodProperty(Class type, Object instance, String getMethodName, String setMethodName, Object [] getArgs, Object [] setArgs, int setArgumentIndex)}
+ * except that instead of names of the getter and setter methods this
+ * constructor is given the actual methods themselves.
+ * </p>
+ *
+ * @param type
+ * the type of the property.
+ * @param instance
+ * the object that includes the property.
+ * @param getMethod
+ * the getter method.
+ * @param setMethod
+ * the setter method.
+ * @param getArgs
+ * the fixed argument list to be passed to the getter method.
+ * @param setArgs
+ * the fixed argument list to be passed to the setter method.
+ * @param setArgumentIndex
+ * the index of the argument in <code>setArgs</code> to be
+ * replaced with <code>newValue</code> when
+ * {@link #setValue(Object newValue)} is called.
+ */
+ @SuppressWarnings("unchecked")
+ // cannot use "Class<? extends T>" because of automatic primitive type
+ // conversions
+ public MethodProperty(Class<?> type, Object instance, Method getMethod,
+ Method setMethod, Object[] getArgs, Object[] setArgs,
+ int setArgumentIndex) {
+
+ if (getMethod == null) {
+ throw new MethodException(this,
+ "Property GET-method cannot not be null: " + type);
+ }
+
+ if (setMethod != null) {
+ if (setArgs == null) {
+ throw new IndexOutOfBoundsException(
+ "The setArgs can not be null");
+ }
+ if (setArgumentIndex < 0 || setArgumentIndex >= setArgs.length) {
+ throw new IndexOutOfBoundsException(
+ "The setArgumentIndex must be >= 0 and < setArgs.length");
+ }
+ }
+
+ // Gets the return type from get method
+ Class<? extends T> convertedType = (Class<? extends T>) convertPrimitiveType(
+ type);
+
+ this.getMethod = getMethod;
+ this.setMethod = setMethod;
+ setArguments(getArgs, setArgs, setArgumentIndex);
+ this.instance = instance;
+ this.type = convertedType;
+ }
+
+ /**
+ * Find a getter method for a property (getXyz(), isXyz() or areXyz()).
+ *
+ * @param propertyName
+ * name of the property
+ * @param beanClass
+ * class in which to look for the getter methods
+ * @return Method
+ * @throws NoSuchMethodException
+ * if no getter found
+ */
+ static Method initGetterMethod(String propertyName,
+ final Class<?> beanClass) throws NoSuchMethodException {
+ propertyName = SharedUtil.capitalize(propertyName);
+
+ Method getMethod = null;
+ try {
+ getMethod = beanClass.getMethod("get" + propertyName,
+ new Class[] {});
+ } catch (final java.lang.NoSuchMethodException ignored) {
+ try {
+ getMethod = beanClass.getMethod("is" + propertyName,
+ new Class[] {});
+ } catch (final java.lang.NoSuchMethodException ignoredAsWell) {
+ getMethod = beanClass.getMethod("are" + propertyName,
+ new Class[] {});
+ }
+ }
+ return getMethod;
+ }
+
+ /**
+ * Returns the type of the Property. 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 type of the Property
+ */
+ @Override
+ public final Class<? extends T> getType() {
+ return type;
+ }
+
+ /**
+ * Tests if the object is in read-only mode. In read-only mode calls to
+ * <code>setValue</code> will throw <code>ReadOnlyException</code> and will
+ * not modify the value of the Property.
+ *
+ * @return <code>true</code> if the object is in read-only mode,
+ * <code>false</code> if it's not
+ */
+ @Override
+ public boolean isReadOnly() {
+ return super.isReadOnly() || (setMethod == null);
+ }
+
+ /**
+ * Gets the value stored in the Property. The value is resolved by calling
+ * the specified getter method with the argument specified at instantiation.
+ *
+ * @return the value of the Property
+ */
+ @Override
+ public T getValue() {
+ try {
+ if (instance == null) {
+ return null;
+ } else {
+ return (T) getMethod.invoke(instance, getArgs);
+ }
+ } catch (final Throwable e) {
+ throw new MethodException(this, e);
+ }
+ }
+
+ /**
+ * <p>
+ * Sets the setter method and getter method argument lists.
+ * </p>
+ *
+ * @param getArgs
+ * the fixed argument list to be passed to the getter method.
+ * @param setArgs
+ * the fixed argument list to be passed to the setter method.
+ * @param setArgumentIndex
+ * the index of the argument in <code>setArgs</code> to be
+ * replaced with <code>newValue</code> when
+ * {@link #setValue(Object newValue)} is called.
+ */
+ public void setArguments(Object[] getArgs, Object[] setArgs,
+ int setArgumentIndex) {
+ if (getArgs.length == 0) {
+ this.getArgs = DEFAULT_GET_ARGS;
+ } else {
+ this.getArgs = Arrays.copyOf(getArgs, getArgs.length);
+ }
+ if (Arrays.equals(setArgs, DEFAULT_SET_ARGS)) {
+ this.setArgs = DEFAULT_SET_ARGS;
+ } else {
+ this.setArgs = Arrays.copyOf(setArgs, setArgs.length);
+ }
+ this.setArgumentIndex = setArgumentIndex;
+ }
+
+ /**
+ * Sets the value of the property.
+ *
+ * Note that since Vaadin 7, no conversions are performed and the value must
+ * be of the correct type.
+ *
+ * @param newValue
+ * the New value of the property.
+ * @throws <code>Property.ReadOnlyException</code>
+ * if the object is in read-only mode.
+ * @see #invokeSetMethod(Object)
+ */
+ @Override
+ public void setValue(T newValue) throws Property.ReadOnlyException {
+
+ // Checks the mode
+ if (isReadOnly()) {
+ throw new Property.ReadOnlyException();
+ }
+
+ invokeSetMethod(newValue);
+ fireValueChange();
+ }
+
+ /**
+ * Internal method to actually call the setter method of the wrapped
+ * property.
+ *
+ * @param value
+ */
+ protected void invokeSetMethod(T value) {
+
+ try {
+ // Construct a temporary argument array only if needed
+ if (setArgs.length == 1) {
+ setMethod.invoke(instance, new Object[] { value });
+ } else {
+
+ // Sets the value to argument array
+ final Object[] args = new Object[setArgs.length];
+ for (int i = 0; i < setArgs.length; i++) {
+ args[i] = (i == setArgumentIndex) ? value : setArgs[i];
+ }
+ setMethod.invoke(instance, args);
+ }
+ } catch (final InvocationTargetException e) {
+ final Throwable targetException = e.getTargetException();
+ throw new MethodException(this, targetException);
+ } catch (final Exception e) {
+ throw new MethodException(this, e);
+ }
+ }
+
+ /**
+ * <code>Exception</code> object that signals that there were problems
+ * calling or finding the specified getter or setter methods of the
+ * property.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ @SuppressWarnings("rawtypes")
+ // Exceptions cannot be parameterized, ever.
+ public static class MethodException extends RuntimeException {
+
+ /**
+ * The method property from which the exception originates from
+ */
+ private final Property property;
+
+ /**
+ * Cause of the method exception
+ */
+ private Throwable cause;
+
+ /**
+ * Constructs a new <code>MethodException</code> with the specified
+ * detail message.
+ *
+ * @param property
+ * the property.
+ * @param msg
+ * the detail message.
+ */
+ public MethodException(Property property, String msg) {
+ super(msg);
+ this.property = property;
+ }
+
+ /**
+ * Constructs a new <code>MethodException</code> from another exception.
+ *
+ * @param property
+ * the property.
+ * @param cause
+ * the cause of the exception.
+ */
+ public MethodException(Property property, Throwable cause) {
+ this.property = property;
+ this.cause = cause;
+ }
+
+ /**
+ * @see java.lang.Throwable#getCause()
+ */
+ @Override
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * Gets the method property this exception originates from.
+ *
+ * @return MethodProperty or null if not a valid MethodProperty
+ */
+ public MethodProperty getMethodProperty() {
+ return (property instanceof MethodProperty)
+ ? (MethodProperty) property : null;
+ }
+
+ /**
+ * Gets the method property this exception originates from.
+ *
+ * @return Property from which the exception originates
+ */
+ public Property getProperty() {
+ return property;
+ }
+ }
+
+ /**
+ * Sends a value change event to all registered listeners.
+ *
+ * Public for backwards compatibility, visibility may be reduced in future
+ * versions.
+ */
+ @Override
+ public void fireValueChange() {
+ super.fireValueChange();
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(MethodProperty.class.getName());
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodPropertyDescriptor.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodPropertyDescriptor.java
new file mode 100644
index 0000000000..dfffb5b189
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodPropertyDescriptor.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.util.ReflectTools;
+import com.vaadin.util.SerializerHelper;
+import com.vaadin.v7.data.Property;
+
+/**
+ * Property descriptor that is able to create simple {@link MethodProperty}
+ * instances for a bean, using given accessors.
+ *
+ * @param <BT>
+ * bean type
+ *
+ * @since 6.6
+ */
+public class MethodPropertyDescriptor<BT>
+ implements VaadinPropertyDescriptor<BT> {
+
+ private final String name;
+ private Class<?> propertyType;
+ private transient Method readMethod;
+ private transient Method writeMethod;
+
+ /**
+ * Creates a property descriptor that can create MethodProperty instances to
+ * access the underlying bean property.
+ *
+ * @param name
+ * of the property
+ * @param propertyType
+ * type (class) of the property
+ * @param readMethod
+ * getter {@link Method} for the property
+ * @param writeMethod
+ * setter {@link Method} for the property or null if read-only
+ * property
+ */
+ public MethodPropertyDescriptor(String name, Class<?> propertyType,
+ Method readMethod, Method writeMethod) {
+ this.name = name;
+ this.propertyType = ReflectTools.convertPrimitiveType(propertyType);
+ this.readMethod = readMethod;
+ this.writeMethod = writeMethod;
+ }
+
+ /* Special serialization to handle method references */
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws IOException {
+ out.defaultWriteObject();
+ SerializerHelper.writeClass(out, propertyType);
+
+ if (writeMethod != null) {
+ out.writeObject(writeMethod.getName());
+ SerializerHelper.writeClass(out, writeMethod.getDeclaringClass());
+ SerializerHelper.writeClassArray(out,
+ writeMethod.getParameterTypes());
+ } else {
+ out.writeObject(null);
+ out.writeObject(null);
+ out.writeObject(null);
+ }
+
+ if (readMethod != null) {
+ out.writeObject(readMethod.getName());
+ SerializerHelper.writeClass(out, readMethod.getDeclaringClass());
+ SerializerHelper.writeClassArray(out,
+ readMethod.getParameterTypes());
+ } else {
+ out.writeObject(null);
+ out.writeObject(null);
+ out.writeObject(null);
+ }
+ }
+
+ /* Special serialization to handle method references */
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ try {
+ @SuppressWarnings("unchecked")
+ // business assumption; type parameters not checked at runtime
+ Class<BT> class1 = (Class<BT>) SerializerHelper.readClass(in);
+ propertyType = ReflectTools.convertPrimitiveType(class1);
+
+ String name = (String) in.readObject();
+ Class<?> writeMethodClass = SerializerHelper.readClass(in);
+ Class<?>[] paramTypes = SerializerHelper.readClassArray(in);
+ if (name != null) {
+ writeMethod = writeMethodClass.getMethod(name, paramTypes);
+ } else {
+ writeMethod = null;
+ }
+
+ name = (String) in.readObject();
+ Class<?> readMethodClass = SerializerHelper.readClass(in);
+ paramTypes = SerializerHelper.readClassArray(in);
+ if (name != null) {
+ readMethod = readMethodClass.getMethod(name, paramTypes);
+ } else {
+ readMethod = null;
+ }
+ } catch (SecurityException e) {
+ getLogger().log(Level.SEVERE, "Internal deserialization error", e);
+ } catch (NoSuchMethodException e) {
+ getLogger().log(Level.SEVERE, "Internal deserialization error", e);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Class<?> getPropertyType() {
+ return propertyType;
+ }
+
+ @Override
+ public Property<?> createProperty(Object bean) {
+ return new MethodProperty<Object>(propertyType, bean, readMethod,
+ writeMethod);
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(MethodPropertyDescriptor.class.getName());
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedMethodProperty.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedMethodProperty.java
new file mode 100644
index 0000000000..38bf7300aa
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedMethodProperty.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import static com.vaadin.util.ReflectTools.convertPrimitiveType;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.vaadin.shared.util.SharedUtil;
+import com.vaadin.v7.data.Property;
+import com.vaadin.v7.data.util.MethodProperty.MethodException;
+
+/**
+ * Nested accessor based property for a bean.
+ *
+ * The property is specified in the dotted notation, e.g. "address.street", and
+ * can contain multiple levels of nesting.
+ *
+ * When accessing the property value, all intermediate getters must exist and
+ * should return non-null values when the property value is accessed. If an
+ * intermediate getter returns null, a null value will be returned.
+ *
+ * @see MethodProperty
+ *
+ * @since 6.6
+ */
+public class NestedMethodProperty<T> extends AbstractProperty<T> {
+
+ // needed for de-serialization
+ private String propertyName;
+
+ // chain of getter methods
+ private transient List<Method> getMethods;
+ /**
+ * The setter method.
+ */
+ private transient Method setMethod;
+
+ /**
+ * Bean instance used as a starting point for accessing the property value.
+ */
+ private Object instance;
+
+ private Class<? extends T> type;
+
+ /* Special serialization to handle method references */
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws IOException {
+ out.defaultWriteObject();
+ // getMethods and setMethod are reconstructed on read based on
+ // propertyName
+ }
+
+ /* Special serialization to handle method references */
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+
+ initialize(instance.getClass(), propertyName);
+ }
+
+ /**
+ * Constructs a nested method property for a given object instance. The
+ * property name is a dot separated string pointing to a nested property,
+ * e.g. "manager.address.street".
+ * <p>
+ * Calling getValue will return null if any intermediate getter returns null
+ *
+ * @param instance
+ * top-level bean to which the property applies
+ * @param propertyName
+ * dot separated nested property name
+ * @throws IllegalArgumentException
+ * if the property name is invalid
+ */
+ public NestedMethodProperty(Object instance, String propertyName) {
+ this.instance = instance;
+ initialize(instance.getClass(), propertyName);
+ }
+
+ /**
+ * For internal use to deduce property type etc. without a bean instance.
+ * Calling {@link #setValue(Object)} or {@link #getValue()} on properties
+ * constructed this way is not supported.
+ *
+ * @param instanceClass
+ * class of the top-level bean
+ * @param propertyName
+ */
+ NestedMethodProperty(Class<?> instanceClass, String propertyName) {
+ instance = null;
+ initialize(instanceClass, propertyName);
+ }
+
+ /**
+ * Initializes most of the internal fields based on the top-level bean
+ * instance and property name (dot-separated string).
+ *
+ * @param beanClass
+ * class of the top-level bean to which the property applies
+ * @param propertyName
+ * dot separated nested property name
+ * @throws IllegalArgumentException
+ * if the property name is invalid
+ */
+ private void initialize(Class<?> beanClass, String propertyName)
+ throws IllegalArgumentException {
+
+ List<Method> getMethods = new ArrayList<Method>();
+
+ String lastSimplePropertyName = propertyName;
+ Class<?> lastClass = beanClass;
+
+ // first top-level property, then go deeper in a loop
+ Class<?> propertyClass = beanClass;
+ String[] simplePropertyNames = propertyName.split("\\.");
+ if (propertyName.endsWith(".") || 0 == simplePropertyNames.length) {
+ throw new IllegalArgumentException(
+ "Invalid property name '" + propertyName + "'");
+ }
+ for (int i = 0; i < simplePropertyNames.length; i++) {
+ String simplePropertyName = simplePropertyNames[i].trim();
+ if (simplePropertyName.length() > 0) {
+ lastSimplePropertyName = simplePropertyName;
+ lastClass = propertyClass;
+ try {
+ Method getter = MethodProperty.initGetterMethod(
+ simplePropertyName, propertyClass);
+ propertyClass = getter.getReturnType();
+ getMethods.add(getter);
+ } catch (final java.lang.NoSuchMethodException e) {
+ throw new IllegalArgumentException("Bean property '"
+ + simplePropertyName + "' not found", e);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Empty or invalid bean property identifier in '"
+ + propertyName + "'");
+ }
+ }
+
+ // In case the get method is found, resolve the type
+ Method lastGetMethod = getMethods.get(getMethods.size() - 1);
+ Class<?> type = lastGetMethod.getReturnType();
+
+ // Finds the set method
+ Method setMethod = null;
+ try {
+ // Assure that the first letter is upper cased (it is a common
+ // mistake to write firstName, not FirstName).
+ lastSimplePropertyName = SharedUtil
+ .capitalize(lastSimplePropertyName);
+
+ setMethod = lastClass.getMethod("set" + lastSimplePropertyName,
+ new Class[] { type });
+ } catch (final NoSuchMethodException skipped) {
+ }
+
+ this.type = (Class<? extends T>) convertPrimitiveType(type);
+ this.propertyName = propertyName;
+ this.getMethods = getMethods;
+ this.setMethod = setMethod;
+ }
+
+ @Override
+ public Class<? extends T> getType() {
+ return type;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return super.isReadOnly() || (null == setMethod);
+ }
+
+ /**
+ * Gets the value stored in the Property. The value is resolved by calling
+ * the specified getter method with the argument specified at instantiation.
+ *
+ * @return the value of the Property
+ */
+ @Override
+ public T getValue() {
+ try {
+ Object object = instance;
+ for (Method m : getMethods) {
+ object = m.invoke(object);
+ if (object == null) {
+ return null;
+ }
+ }
+ return (T) object;
+ } catch (final Throwable e) {
+ throw new MethodException(this, e);
+ }
+ }
+
+ /**
+ * Sets the value of the property. The new value must be assignable to the
+ * type of this property.
+ *
+ * @param newValue
+ * the New value of the property.
+ * @throws <code>Property.ReadOnlyException</code>
+ * if the object is in read-only mode.
+ * @see #invokeSetMethod(Object)
+ */
+ @Override
+ public void setValue(T newValue) throws ReadOnlyException {
+ // Checks the mode
+ if (isReadOnly()) {
+ throw new Property.ReadOnlyException();
+ }
+
+ invokeSetMethod(newValue);
+ fireValueChange();
+ }
+
+ /**
+ * Internal method to actually call the setter method of the wrapped
+ * property.
+ *
+ * @param value
+ */
+ protected void invokeSetMethod(T value) {
+ try {
+ Object object = instance;
+ for (int i = 0; i < getMethods.size() - 1; i++) {
+ object = getMethods.get(i).invoke(object);
+ }
+ setMethod.invoke(object, new Object[] { value });
+ } catch (final InvocationTargetException e) {
+ throw new MethodException(this, e.getTargetException());
+ } catch (final Exception e) {
+ throw new MethodException(this, e);
+ }
+ }
+
+ /**
+ * Returns an unmodifiable list of getter methods to call in sequence to get
+ * the property value.
+ *
+ * This API may change in future versions.
+ *
+ * @return unmodifiable list of getter methods corresponding to each segment
+ * of the property name
+ */
+ protected List<Method> getGetMethods() {
+ return Collections.unmodifiableList(getMethods);
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedPropertyDescriptor.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedPropertyDescriptor.java
new file mode 100644
index 0000000000..7bbf6253a5
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedPropertyDescriptor.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import com.vaadin.v7.data.Property;
+
+/**
+ * Property descriptor that is able to create nested property instances for a
+ * bean.
+ *
+ * The property is specified in the dotted notation, e.g. "address.street", and
+ * can contain multiple levels of nesting.
+ *
+ * @param <BT>
+ * bean type
+ *
+ * @since 6.6
+ */
+public class NestedPropertyDescriptor<BT>
+ implements VaadinPropertyDescriptor<BT> {
+
+ private final String name;
+ private final Class<?> propertyType;
+
+ /**
+ * Creates a property descriptor that can create MethodProperty instances to
+ * access the underlying bean property.
+ *
+ * @param name
+ * of the property in a dotted path format, e.g. "address.street"
+ * @param beanType
+ * type (class) of the top-level bean
+ * @throws IllegalArgumentException
+ * if the property name is invalid
+ */
+ public NestedPropertyDescriptor(String name, Class<BT> beanType)
+ throws IllegalArgumentException {
+ this.name = name;
+ NestedMethodProperty<?> property = new NestedMethodProperty<Object>(
+ beanType, name);
+ this.propertyType = property.getType();
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Class<?> getPropertyType() {
+ return propertyType;
+ }
+
+ @Override
+ public Property<?> createProperty(BT bean) {
+ return new NestedMethodProperty<Object>(bean, name);
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/ObjectProperty.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/ObjectProperty.java
new file mode 100644
index 0000000000..cd9f6c36c7
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/ObjectProperty.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import com.vaadin.v7.data.Property;
+
+/**
+ * A simple data object containing one typed value. This class is a
+ * straightforward implementation of the the {@link com.vaadin.v7.data.Property}
+ * interface.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class ObjectProperty<T> extends AbstractProperty<T> {
+
+ /**
+ * The value contained by the Property.
+ */
+ private T value;
+
+ /**
+ * Data type of the Property's value.
+ */
+ private final Class<T> type;
+
+ /**
+ * Creates a new instance of ObjectProperty with the given value. The type
+ * of the property is automatically initialized to be the type of the given
+ * value.
+ *
+ * @param value
+ * the Initial value of the Property.
+ */
+ @SuppressWarnings("unchecked")
+ // the cast is safe, because an object of type T has class Class<T>
+ public ObjectProperty(T value) {
+ this(value, (Class<T>) value.getClass());
+ }
+
+ /**
+ * Creates a new instance of ObjectProperty with the given value and type.
+ *
+ * Since Vaadin 7, only values of the correct type are accepted, and no
+ * automatic conversions are performed.
+ *
+ * @param value
+ * the Initial value of the Property.
+ * @param type
+ * the type of the value. The value must be assignable to given
+ * type.
+ */
+ public ObjectProperty(T value, Class<T> type) {
+
+ // Set the values
+ this.type = type;
+ setValue(value);
+ }
+
+ /**
+ * Creates a new instance of ObjectProperty with the given value, type and
+ * read-only mode status.
+ *
+ * Since Vaadin 7, only the correct type of values is accepted, see
+ * {@link #ObjectProperty(Object, Class)}.
+ *
+ * @param value
+ * the Initial value of the property.
+ * @param type
+ * the type of the value. <code>value</code> must be assignable
+ * to this type.
+ * @param readOnly
+ * Sets the read-only mode.
+ */
+ public ObjectProperty(T value, Class<T> type, boolean readOnly) {
+ this(value, type);
+ setReadOnly(readOnly);
+ }
+
+ /**
+ * Returns the type of the ObjectProperty. 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 type of the Property
+ */
+ @Override
+ public final Class<T> getType() {
+ return type;
+ }
+
+ /**
+ * Gets the value stored in the Property.
+ *
+ * @return the value stored in the Property
+ */
+ @Override
+ public T getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of the property.
+ *
+ * Note that since Vaadin 7, no conversions are performed and the value must
+ * be of the correct type.
+ *
+ * @param newValue
+ * the New value of the property.
+ * @throws <code>Property.ReadOnlyException</code>
+ * if the object is in read-only mode
+ */
+ @Override
+ public void setValue(T newValue) throws Property.ReadOnlyException {
+
+ // Checks the mode
+ if (isReadOnly()) {
+ throw new Property.ReadOnlyException();
+ }
+
+ this.value = newValue;
+
+ fireValueChange();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/PropertyFormatter.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/PropertyFormatter.java
new file mode 100644
index 0000000000..c95f97a1f1
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/PropertyFormatter.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import com.vaadin.v7.data.Property;
+import com.vaadin.v7.data.util.converter.Converter;
+
+/**
+ * Formatting proxy for a {@link Property}.
+ *
+ * <p>
+ * This class can be used to implement formatting for any type of Property
+ * datasources. The idea is to connect this as proxy between UI component and
+ * the original datasource.
+ * </p>
+ *
+ * <p>
+ * For example <code>
+ * <pre>textfield.setPropertyDataSource(new PropertyFormatter(property) {
+ public String format(Object value) {
+ return ((Double) value).toString() + "000000000";
+ }
+
+ public Object parse(String formattedValue) throws Exception {
+ return Double.parseDouble(formattedValue);
+ }
+
+ });</pre></code> adds formatter for Double-typed property that extends
+ * standard "1.0" notation with more zeroes.
+ * </p>
+ *
+ * @param T
+ * type of the underlying property (a PropertyFormatter is always a
+ * Property&lt;String&gt;)
+ *
+ * @deprecated As of 7.0, replaced by {@link Converter}
+ * @author Vaadin Ltd.
+ * @since 5.3.0
+ */
+@SuppressWarnings("serial")
+@Deprecated
+public abstract class PropertyFormatter<T> extends AbstractProperty<String>
+ implements Property.Viewer, Property.ValueChangeListener,
+ Property.ReadOnlyStatusChangeListener {
+
+ /** Datasource that stores the actual value. */
+ Property<T> dataSource;
+
+ /**
+ * Construct a new {@code PropertyFormatter} that is not connected to any
+ * data source. Call {@link #setPropertyDataSource(Property)} later on to
+ * attach it to a property.
+ *
+ */
+ protected PropertyFormatter() {
+ }
+
+ /**
+ * Construct a new formatter that is connected to given data source. Calls
+ * {@link #format(Object)} which can be a problem if the formatter has not
+ * yet been initialized.
+ *
+ * @param propertyDataSource
+ * to connect this property to.
+ */
+ public PropertyFormatter(Property<T> propertyDataSource) {
+
+ setPropertyDataSource(propertyDataSource);
+ }
+
+ /**
+ * Gets the current data source of the formatter, if any.
+ *
+ * @return the current data source as a Property, or <code>null</code> if
+ * none defined.
+ */
+ @Override
+ public Property<T> getPropertyDataSource() {
+ return dataSource;
+ }
+
+ /**
+ * Sets the specified Property as the data source for the formatter.
+ *
+ *
+ * <p>
+ * Remember that new data sources getValue() must return objects that are
+ * compatible with parse() and format() methods.
+ * </p>
+ *
+ * @param newDataSource
+ * the new data source Property.
+ */
+ @Override
+ public void setPropertyDataSource(Property newDataSource) {
+
+ boolean readOnly = false;
+ String prevValue = null;
+
+ if (dataSource != null) {
+ if (dataSource instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) dataSource)
+ .removeListener(this);
+ }
+ if (dataSource instanceof Property.ReadOnlyStatusChangeListener) {
+ ((Property.ReadOnlyStatusChangeNotifier) dataSource)
+ .removeListener(this);
+ }
+ readOnly = isReadOnly();
+ prevValue = getValue();
+ }
+
+ dataSource = newDataSource;
+
+ if (dataSource != null) {
+ if (dataSource instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) dataSource).addListener(this);
+ }
+ if (dataSource instanceof Property.ReadOnlyStatusChangeListener) {
+ ((Property.ReadOnlyStatusChangeNotifier) dataSource)
+ .addListener(this);
+ }
+ }
+
+ if (isReadOnly() != readOnly) {
+ fireReadOnlyStatusChange();
+ }
+ String newVal = getValue();
+ if ((prevValue == null && newVal != null)
+ || (prevValue != null && !prevValue.equals(newVal))) {
+ fireValueChange();
+ }
+ }
+
+ /* Documented in the interface */
+ @Override
+ public Class<String> getType() {
+ return String.class;
+ }
+
+ /**
+ * Get the formatted value.
+ *
+ * @return If the datasource returns null, this is null. Otherwise this is
+ * String given by format().
+ */
+ @Override
+ public String getValue() {
+ T value = dataSource == null ? null : dataSource.getValue();
+ if (value == null) {
+ return null;
+ }
+ return format(value);
+ }
+
+ /** Reflects the read-only status of the datasource. */
+ @Override
+ public boolean isReadOnly() {
+ return dataSource == null ? false : dataSource.isReadOnly();
+ }
+
+ /**
+ * This method must be implemented to format the values received from
+ * DataSource.
+ *
+ * @param value
+ * Value object got from the datasource. This is guaranteed to be
+ * non-null and of the type compatible with getType() of the
+ * datasource.
+ * @return
+ */
+ abstract public String format(T value);
+
+ /**
+ * Parse string and convert it to format compatible with datasource.
+ *
+ * The method is required to assure that parse(format(x)) equals x.
+ *
+ * @param formattedValue
+ * This is guaranteed to be non-null string.
+ * @return Non-null value compatible with datasource.
+ * @throws Exception
+ * Any type of exception can be thrown to indicate that the
+ * conversion was not succesful.
+ */
+ abstract public T parse(String formattedValue) throws Exception;
+
+ /**
+ * Sets the Property's read-only mode to the specified status.
+ *
+ * @param newStatus
+ * the new read-only status of the Property.
+ */
+ @Override
+ public void setReadOnly(boolean newStatus) {
+ if (dataSource != null) {
+ dataSource.setReadOnly(newStatus);
+ }
+ }
+
+ @Override
+ public void setValue(String newValue) throws ReadOnlyException {
+ if (dataSource == null) {
+ return;
+ }
+ if (newValue == null) {
+ if (dataSource.getValue() != null) {
+ dataSource.setValue(null);
+ fireValueChange();
+ }
+ } else {
+ try {
+ dataSource.setValue(parse(newValue.toString()));
+ if (!newValue.equals(getValue())) {
+ fireValueChange();
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Could not parse value", e);
+ }
+ }
+ }
+
+ /**
+ * Listens for changes in the datasource.
+ *
+ * This should not be called directly.
+ */
+ @Override
+ public void valueChange(com.vaadin.v7.data.Property.ValueChangeEvent event) {
+ fireValueChange();
+ }
+
+ /**
+ * Listens for changes in the datasource.
+ *
+ * This should not be called directly.
+ */
+ @Override
+ public void readOnlyStatusChange(
+ com.vaadin.v7.data.Property.ReadOnlyStatusChangeEvent event) {
+ fireReadOnlyStatusChange();
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/TextFileProperty.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/TextFileProperty.java
new file mode 100644
index 0000000000..ce5bed8968
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/TextFileProperty.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+
+/**
+ * Property implementation for wrapping a text file.
+ *
+ * Supports reading and writing of a File from/to String.
+ *
+ * {@link ValueChangeListener}s are supported, but only fire when
+ * setValue(Object) is explicitly called. {@link ReadOnlyStatusChangeListener}s
+ * are supported but only fire when setReadOnly(boolean) is explicitly called.
+ *
+ */
+@SuppressWarnings("serial")
+public class TextFileProperty extends AbstractProperty<String> {
+
+ private File file;
+ private Charset charset = null;
+
+ /**
+ * Wrap given file with property interface.
+ *
+ * Setting the file to null works, but getValue() will return null.
+ *
+ * @param file
+ * File to be wrapped.
+ */
+ public TextFileProperty(File file) {
+ this.file = file;
+ }
+
+ /**
+ * Wrap the given file with the property interface and specify character
+ * set.
+ *
+ * Setting the file to null works, but getValue() will return null.
+ *
+ * @param file
+ * File to be wrapped.
+ * @param charset
+ * Charset to be used for reading and writing the file.
+ */
+ public TextFileProperty(File file, Charset charset) {
+ this.file = file;
+ this.charset = charset;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#getType()
+ */
+ @Override
+ public Class<String> getType() {
+ return String.class;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#getValue()
+ */
+ @Override
+ public String getValue() {
+ if (file == null) {
+ return null;
+ }
+ try {
+ FileInputStream fis = new FileInputStream(file);
+ InputStreamReader isr = charset == null ? new InputStreamReader(fis)
+ : new InputStreamReader(fis, charset);
+ BufferedReader r = new BufferedReader(isr);
+ StringBuilder b = new StringBuilder();
+ char buf[] = new char[8 * 1024];
+ int len;
+ while ((len = r.read(buf)) != -1) {
+ b.append(buf, 0, len);
+ }
+ r.close();
+ isr.close();
+ fis.close();
+ return b.toString();
+ } catch (FileNotFoundException e) {
+ return null;
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#isReadOnly()
+ */
+ @Override
+ public boolean isReadOnly() {
+ return file == null || super.isReadOnly() || !file.canWrite();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#setValue(java.lang.Object)
+ */
+ @Override
+ public void setValue(String newValue) throws ReadOnlyException {
+ if (isReadOnly()) {
+ throw new ReadOnlyException();
+ }
+ if (file == null) {
+ return;
+ }
+
+ try {
+ FileOutputStream fos = new FileOutputStream(file);
+ OutputStreamWriter osw = charset == null
+ ? new OutputStreamWriter(fos)
+ : new OutputStreamWriter(fos, charset);
+ BufferedWriter w = new BufferedWriter(osw);
+ w.append(newValue.toString());
+ w.flush();
+ w.close();
+ osw.close();
+ fos.close();
+ fireValueChange();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/TransactionalPropertyWrapper.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/TransactionalPropertyWrapper.java
new file mode 100644
index 0000000000..21afa4bc92
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/TransactionalPropertyWrapper.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import com.vaadin.v7.data.Property;
+import com.vaadin.v7.data.Property.ValueChangeEvent;
+import com.vaadin.v7.data.Property.ValueChangeNotifier;
+
+/**
+ * Wrapper class that helps implement two-phase commit for a non-transactional
+ * property.
+ *
+ * When accessing the property through the wrapper, getting and setting the
+ * property value take place immediately. However, the wrapper keeps track of
+ * the old value of the property so that it can be set for the property in case
+ * of a roll-back. This can result in the underlying property value changing
+ * multiple times (first based on modifications made by the application, then
+ * back upon roll-back).
+ *
+ * Value change events on the {@link TransactionalPropertyWrapper} are only
+ * fired at the end of a successful transaction, whereas listeners attached to
+ * the underlying property may receive multiple value change events.
+ *
+ * @see com.vaadin.v7.data.Property.Transactional
+ *
+ * @author Vaadin Ltd
+ * @since 7.0
+ *
+ * @param <T>
+ */
+public class TransactionalPropertyWrapper<T> extends AbstractProperty<T>
+ implements ValueChangeNotifier, Property.Transactional<T> {
+
+ private Property<T> wrappedProperty;
+ private boolean inTransaction = false;
+ private boolean valueChangePending;
+ private T valueBeforeTransaction;
+ private final ValueChangeListener listener = new ValueChangeListener() {
+
+ @Override
+ public void valueChange(ValueChangeEvent event) {
+ fireValueChange();
+ }
+ };
+
+ public TransactionalPropertyWrapper(Property<T> wrappedProperty) {
+ this.wrappedProperty = wrappedProperty;
+ if (wrappedProperty instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) wrappedProperty)
+ .addValueChangeListener(listener);
+ }
+ }
+
+ /**
+ * Removes the ValueChangeListener from wrapped Property that was added by
+ * TransactionalPropertyWrapper.
+ *
+ * @since 7.1.15
+ */
+ public void detachFromProperty() {
+ if (wrappedProperty instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) wrappedProperty)
+ .removeValueChangeListener(listener);
+ }
+ }
+
+ @Override
+ public Class getType() {
+ return wrappedProperty.getType();
+ }
+
+ @Override
+ public T getValue() {
+ return wrappedProperty.getValue();
+ }
+
+ @Override
+ public void setValue(T newValue) throws ReadOnlyException {
+ // Causes a value change to be sent to this listener which in turn fires
+ // a new value change event for this property
+ wrappedProperty.setValue(newValue);
+ }
+
+ @Override
+ public void startTransaction() {
+ inTransaction = true;
+ valueBeforeTransaction = getValue();
+ }
+
+ @Override
+ public void commit() {
+ endTransaction();
+ }
+
+ @Override
+ public void rollback() {
+ try {
+ wrappedProperty.setValue(valueBeforeTransaction);
+ } finally {
+ valueChangePending = false;
+ endTransaction();
+ }
+ }
+
+ protected void endTransaction() {
+ inTransaction = false;
+ valueBeforeTransaction = null;
+ if (valueChangePending) {
+ fireValueChange();
+ }
+ }
+
+ @Override
+ protected void fireValueChange() {
+ if (inTransaction) {
+ valueChangePending = true;
+ } else {
+ super.fireValueChange();
+ }
+ }
+
+ public Property<T> getWrappedProperty() {
+ return wrappedProperty;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return wrappedProperty.isReadOnly();
+ }
+
+ @Override
+ public void setReadOnly(boolean newStatus) {
+ boolean oldStatus = isReadOnly();
+ wrappedProperty.setReadOnly(newStatus);
+ if (oldStatus != isReadOnly()) {
+ fireReadOnlyStatusChange();
+ }
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/v7/data/util/VaadinPropertyDescriptor.java b/compatibility-server/src/main/java/com/vaadin/v7/data/util/VaadinPropertyDescriptor.java
new file mode 100644
index 0000000000..71c562d425
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/v7/data/util/VaadinPropertyDescriptor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2016 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.v7.data.util;
+
+import java.io.Serializable;
+
+import com.vaadin.v7.data.Property;
+
+/**
+ * Property descriptor that can create a property instance for a bean.
+ *
+ * Used by {@link BeanItem} and {@link AbstractBeanContainer} to keep track of
+ * the set of properties of items.
+ *
+ * @param <BT>
+ * bean type
+ *
+ * @since 6.6
+ */
+public interface VaadinPropertyDescriptor<BT> extends Serializable {
+ /**
+ * Returns the name of the property.
+ *
+ * @return
+ */
+ public String getName();
+
+ /**
+ * Returns the type of the property.
+ *
+ * @return Class<?>
+ */
+ public Class<?> getPropertyType();
+
+ /**
+ * Creates a new {@link Property} instance for this property for a bean.
+ *
+ * @param bean
+ * @return
+ */
+ public Property<?> createProperty(BT bean);
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractPropertyListenersTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractPropertyListenersTest.java
new file mode 100644
index 0000000000..854f3666ac
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractPropertyListenersTest.java
@@ -0,0 +1,30 @@
+package com.vaadin.tests.server;
+
+import org.junit.Test;
+
+import com.vaadin.tests.server.component.AbstractListenerMethodsTestBase;
+import com.vaadin.v7.data.Property.ReadOnlyStatusChangeEvent;
+import com.vaadin.v7.data.Property.ReadOnlyStatusChangeListener;
+import com.vaadin.v7.data.Property.ValueChangeEvent;
+import com.vaadin.v7.data.Property.ValueChangeListener;
+import com.vaadin.v7.data.util.AbstractProperty;
+import com.vaadin.v7.data.util.ObjectProperty;
+
+public class AbstractPropertyListenersTest
+ extends AbstractListenerMethodsTestBase {
+
+ @Test
+ public void testValueChangeListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(AbstractProperty.class, ValueChangeEvent.class,
+ ValueChangeListener.class, new ObjectProperty<String>(""));
+ }
+
+ @Test
+ public void testReadOnlyStatusChangeListenerAddGetRemove()
+ throws Exception {
+ testListenerAddGetRemove(AbstractProperty.class,
+ ReadOnlyStatusChangeEvent.class,
+ ReadOnlyStatusChangeListener.class,
+ new ObjectProperty<String>(""));
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/PropertyFormatterTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/PropertyFormatterTest.java
new file mode 100644
index 0000000000..775018642a
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/PropertyFormatterTest.java
@@ -0,0 +1,75 @@
+package com.vaadin.tests.server;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+
+import org.junit.Test;
+
+import com.vaadin.v7.data.util.ObjectProperty;
+import com.vaadin.v7.data.util.PropertyFormatter;
+
+@SuppressWarnings("unchecked")
+public class PropertyFormatterTest {
+
+ class TestFormatter extends PropertyFormatter {
+
+ @Override
+ public String format(Object value) {
+ boolean isCorrectType = getExpectedClass()
+ .isAssignableFrom(value.getClass());
+ assertTrue(isCorrectType);
+ return "FOO";
+ }
+
+ @Override
+ public Object parse(String formattedValue) throws Exception {
+ return getExpectedClass().newInstance();
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ private Class expectedClass;
+
+ @SuppressWarnings("rawtypes")
+ private Class getExpectedClass() {
+ return expectedClass;
+ }
+
+ /**
+ * The object passed to format should be same as property's type.
+ *
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ */
+ @Test
+ @SuppressWarnings({ "rawtypes" })
+ public void testCorrectTypeForFormat()
+ throws InstantiationException, IllegalAccessException {
+ Class[] testedTypes = new Class[] { Integer.class, Boolean.class,
+ Double.class, String.class, Date.class };
+ Object[] testValues = new Object[] { new Integer(3), Boolean.FALSE,
+ new Double(3.3), "bar", new Date() };
+
+ int i = 0;
+ for (Class class1 : testedTypes) {
+ expectedClass = class1;
+
+ TestFormatter formatter = new TestFormatter();
+
+ // Should just return null, without formatting
+ Object value = formatter.getValue();
+
+ // test with property which value is null
+ formatter.setPropertyDataSource(
+ new ObjectProperty(null, expectedClass));
+ formatter.getValue(); // calls format
+
+ // test with a value
+ formatter.setPropertyDataSource(
+ new ObjectProperty(testValues[i++], expectedClass));
+ formatter.getValue(); // calls format
+ }
+
+ }
+}