From 13b99cfa265913ab8cb2d948f2ab2bf152a959e4 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Fri, 26 Aug 2016 11:36:24 +0300 Subject: Move remaining Vaadin 7 classes to the compatibility package Change-Id: I3be37350a638028d89fb527a3dfb09e74fdebeed --- .../src/main/java/com/vaadin/v7/data/Buffered.java | 197 ++++++ .../com/vaadin/v7/data/BufferedValidatable.java | 45 ++ .../src/main/java/com/vaadin/v7/data/Property.java | 425 +++++++++++ .../main/java/com/vaadin/v7/data/Validatable.java | 130 ++++ .../main/java/com/vaadin/v7/data/Validator.java | 198 ++++++ .../com/vaadin/v7/data/util/AbstractProperty.java | 270 +++++++ .../main/java/com/vaadin/v7/data/util/ListSet.java | 276 ++++++++ .../com/vaadin/v7/data/util/MethodProperty.java | 774 +++++++++++++++++++++ .../v7/data/util/MethodPropertyDescriptor.java | 148 ++++ .../vaadin/v7/data/util/NestedMethodProperty.java | 269 +++++++ .../v7/data/util/NestedPropertyDescriptor.java | 72 ++ .../com/vaadin/v7/data/util/ObjectProperty.java | 142 ++++ .../com/vaadin/v7/data/util/PropertyFormatter.java | 257 +++++++ .../com/vaadin/v7/data/util/TextFileProperty.java | 157 +++++ .../v7/data/util/TransactionalPropertyWrapper.java | 153 ++++ .../v7/data/util/VaadinPropertyDescriptor.java | 55 ++ .../server/AbstractPropertyListenersTest.java | 30 + .../vaadin/tests/server/PropertyFormatterTest.java | 75 ++ 18 files changed, 3673 insertions(+) create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/Buffered.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/BufferedValidatable.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/Property.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/Validatable.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/Validator.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/AbstractProperty.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/ListSet.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodProperty.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/MethodPropertyDescriptor.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedMethodProperty.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/NestedPropertyDescriptor.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/ObjectProperty.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/PropertyFormatter.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/TextFileProperty.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/TransactionalPropertyWrapper.java create mode 100644 compatibility-server/src/main/java/com/vaadin/v7/data/util/VaadinPropertyDescriptor.java create mode 100644 compatibility-server/src/test/java/com/vaadin/tests/server/AbstractPropertyListenersTest.java create mode 100644 compatibility-server/src/test/java/com/vaadin/tests/server/PropertyFormatterTest.java (limited to 'compatibility-server') 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; + +/** + *

+ * Defines the interface to commit and discard changes to an object, supporting + * buffering. + * + *

+ * In buffered 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. + * + *

+ * In non-buffered 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 commit 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. + *

+ * 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. + *

+ * 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 true if the value in the object has been modified + * since the last data source update, false 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; + +/** + *

+ * This interface defines the combination of Validatable and + * Buffered interfaces. The combination of the interfaces defines + * if the invalid data is committed to datasource. + *

+ * + * @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 + * false. + */ + public boolean isInvalidCommitted(); + + /** + * Sets if the invalid data should be committed to datasource. The default + * is false. + */ + 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; + +/** + *

+ * The Property 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. + *

+ * + *

+ * The Property also defines the events + * ReadOnlyStatusChangeEvent and ValueChangeEvent, and + * the associated listener and notifier interfaces. + *

+ * + *

+ * The Property.Viewer 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 Property interface. + *

+ * + *

+ * The Property.editor interface should be implemented if the value + * needs to be changed through the implementing class. + *

+ * + * @param T + * type of values of the property + * + * @author Vaadin Ltd + * @since 3.0 + */ +public interface Property 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. + *

+ * Implementing this functionality is optional. If the functionality is + * missing, one should declare the Property to be in read-only mode and + * throw Property.ReadOnlyException in this function. + *

+ * + * 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 getValue and + * setValue must be compatible with this type: one must be able + * to safely cast the value returned from getValue to the given + * type and pass any variable assignable to this type as an argument to + * setValue. + * + * @return type of the Property + */ + public Class getType(); + + /** + * Tests if the Property is in read-only mode. In read-only mode calls to + * the method setValue will throw + * ReadOnlyException and will not modify the value of the + * Property. + * + * @return true if the Property is in read-only mode, + * false 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 + * isReadOnly 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 + * The type of the property + * @author Vaadin Ltd + * @since 7.0 + */ + public interface Transactional extends Property { + + /** + * Starts a transaction. + * + *

+ * 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. + *

+ *

+ * {@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. + *

+ */ + public void startTransaction(); + + /** + * Commits and ends the transaction that is in progress. + *

+ * If the value is changed as a result of this operation, a + * {@link ValueChangeEvent} is emitted if such are supported. + *

+ * This method has no effect if there is no transaction is in progress. + *

+ * This method must never throw an exception. + */ + public void commit(); + + /** + * Aborts and rolls back the transaction that is in progress. + *

+ * The value is reset to the value before the transaction started. No + * {@link ValueChangeEvent} is emitted as a result of this. + *

+ * This method has no effect if there is no transaction is in progress. + *

+ * This method must never throw an exception. + */ + public void rollback(); + } + + /** + * Exception 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 ReadOnlyException without a detail + * message. + */ + public ReadOnlyException() { + } + + /** + * Constructs a new ReadOnlyException 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. + *

+ * 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 + * ReadOnlyException being thrown. + *

+ * + * @author Vaadin Ltd. + * @since 3.0 + */ + public interface Editor extends Property.Viewer, Serializable { + + } + + /* Value change event */ + + /** + * An Event 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 listener interface for receiving + * ValueChangeEvent 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 ValueChangeEvent + * listeners. If a Property wishes to allow other objects to receive + * ValueChangeEvent generated by it, it must implement this + * interface. + *

+ * Note : The general Java convention is not to explicitly declare that a + * class generates events, but to directly define the + * addListener and removeListener 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. + *

+ * + * @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 Event 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 + * ReadOnlyStatusChangeEvent 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 + * ReadOnlyStatusChangeEvent listeners. If a Property wishes to + * allow other objects to receive ReadOnlyStatusChangeEvent + * generated by it, it must implement this interface. + *

+ * Note : The general Java convention is not to explicitly declare that a + * class generates events, but to directly define the + * addListener and removeListener 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. + *

+ * + * @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; + +/** + *

+ * 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. + *

+ * + * @author Vaadin Ltd. + * @since 3.0 + * @see com.vaadin.v7.data.Validator + */ +public interface Validatable extends Serializable { + + /** + *

+ * 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. + *

+ * + * @param validator + * the new validator + */ + void addValidator(Validator validator); + + /** + *

+ * Removes a previously registered validator from the object. The specified + * validator is removed from the object and its validate method + * is no longer called in {@link #isValid()}. + *

+ * + * @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(); + + /** + *

+ * Returns a collection of all validators currently registered for the + * object. The collection may be immutable. Calling + * removeValidator for this Validatable while iterating over + * the collection may be unsafe (e.g. may throw + * ConcurrentModificationException.) + *

+ * + * @return A collection of validators + */ + public Collection getValidators(); + + /** + *

+ * 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 + * false. + *

+ * + * @return true if the registered validators concur that the + * value is valid, false otherwise + */ + public boolean isValid(); + + /** + *

+ * 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 + * Validator.InvalidValueException + *

+ * + * @throws Validator.InvalidValueException + * if the value is not valid + */ + public void validate() throws Validator.InvalidValueException; + + /** + *

+ * Checks the validabtable object accept invalid values.The default value is + * true. + *

+ * + */ + public boolean isInvalidAllowed(); + + /** + *

+ * Should the validabtable object accept invalid values. Supporting this + * configuration possibility is optional. By default invalid values are + * allowed. + *

+ * + * @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. + *

+ * Implementors of this class can be added to any + * {@link com.vaadin.v7.data.Validatable Validatable} implementor to verify its + * value. + *

+ *

+ * {@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. + *

+ *

+ * Validators must not have any side effects. + *

+ *

+ * 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. + *

+ * + * @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. + * + *

+ * 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. + *

+ * + * @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 implements Property, + Property.ValueChangeNotifier, Property.ReadOnlyStatusChangeNotifier { + + /** + * List of listeners who are interested in the read-only status changes of + * the Property + */ + private LinkedList readOnlyStatusChangeListeners = null; + + /** + * List of listeners who are interested in the value changes of the Property + */ + private LinkedList 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 Event 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(); + } + 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 Event 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(); + } + 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 extends ArrayList { + private HashSet 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 duplicates = new HashMap(); + + public ListSet() { + super(); + itemSet = new HashSet(); + } + + public ListSet(Collection c) { + super(c); + itemSet = new HashSet(c.size()); + itemSet.addAll(c); + } + + public ListSet(int initialCapacity) { + super(initialCapacity); + itemSet = new HashSet(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 c) { + boolean modified = false; + Iterator 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 c) { + ensureCapacity(size() + c.size()); + + boolean modified = false; + Iterator 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 toRemove = new HashSet(); + 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 v = (ListSet) super.clone(); + v.itemSet = new HashSet(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; + +/** + *

+ * 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. + *

+ * + *

+ * 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. + *

+ * + *

+ * A valid getter method must always be available, but instance of this class + * can be constructed with a null setter method in which case the + * resulting MethodProperty is read-only. + *

+ * + *

+ * 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. + *

+ * + * @author Vaadin Ltd. + * @since 3.0 + */ +@SuppressWarnings("serial") +public class MethodProperty extends AbstractProperty { + + /** + * 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 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 class1 = (Class) 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); + } + } + + /** + *

+ * Creates a new instance of MethodProperty 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. + *

+ *

+ * The getter method of a MethodProperty instantiated with this + * constructor will be called with no arguments, and the setter method with + * only the new value as the sole argument. + *

+ * + *

+ * If the setter method is unavailable, the resulting + * MethodProperty will be read-only, otherwise it will be + * read-write. + *

+ * + *

+ * 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. + *

+ * + * @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) convertPrimitiveType(returnType); + if (type.isPrimitive()) { + throw new MethodException(this, + "Bean property " + beanPropertyName + + " getter return type must not be void"); + } + } else { + type = (Class) returnType; + } + + setArguments(DEFAULT_GET_ARGS, DEFAULT_SET_ARGS, 0); + this.instance = instance; + } + + /** + *

+ * Creates a new instance of MethodProperty from named getter + * and setter methods. The getter method of a MethodProperty + * instantiated with this constructor will be called with no arguments, and + * the setter method with only the new value as the sole argument. + *

+ * + *

+ * If the setter method is null, the resulting + * MethodProperty will be read-only, otherwise it will be + * read-write. + *

+ * + * @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 type, Object instance, + String getMethodName, String setMethodName) { + this(type, instance, getMethodName, setMethodName, new Object[] {}, + new Object[] { null }, 0); + } + + /** + *

+ * Creates a new instance of MethodProperty with the getter and + * setter methods. The getter method of a MethodProperty + * instantiated with this constructor will be called with no arguments, and + * the setter method with only the new value as the sole argument. + *

+ * + *

+ * If the setter method is null, the resulting + * MethodProperty will be read-only, otherwise it will be + * read-write. + *

+ * + * @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 type, Object instance, + Method getMethod, Method setMethod) { + this(type, instance, getMethod, setMethod, new Object[] {}, + new Object[] { null }, 0); + } + + /** + *

+ * Creates a new instance of MethodProperty from named getter + * and setter methods and argument lists. The getter method of a + * MethodProperty 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. + *

+ * + *

+ * For example, if the setArgs contains A, + * B and C, and setArgumentIndex = + * 1, the call methodProperty.setValue(X) would result + * in the setter method to be called with the parameter set of + * {A, X, C} + *

+ * + * @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 setArgs to be + * replaced with newValue when + * {@link #setValue(Object newValue)} is called. + */ + @SuppressWarnings("unchecked") + public MethodProperty(Class 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) convertPrimitiveType(type); + + setArguments(getArgs, setArgs, setArgumentIndex); + this.instance = instance; + } + + /** + *

+ * Creates a new instance of MethodProperty from the getter and + * setter methods, and argument lists. + *

+ *

+ * 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. + *

+ * + * @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 setArgs to be + * replaced with newValue when + * {@link #setValue(Object newValue)} is called. + */ + @SuppressWarnings("unchecked") + // cannot use "Class" 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 convertedType = (Class) 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 getValue and + * setValue must be compatible with this type: one must be able + * to safely cast the value returned from getValue to the given + * type and pass any variable assignable to this type as an argument to + * setValue. + * + * @return type of the Property + */ + @Override + public final Class getType() { + return type; + } + + /** + * Tests if the object is in read-only mode. In read-only mode calls to + * setValue will throw ReadOnlyException and will + * not modify the value of the Property. + * + * @return true if the object is in read-only mode, + * false 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); + } + } + + /** + *

+ * Sets the setter method and getter method argument lists. + *

+ * + * @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 setArgs to be + * replaced with newValue 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 Property.ReadOnlyException + * 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); + } + } + + /** + * Exception 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 MethodException 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 MethodException 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 + * bean type + * + * @since 6.6 + */ +public class MethodPropertyDescriptor + implements VaadinPropertyDescriptor { + + 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 class1 = (Class) 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(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 extends AbstractProperty { + + // needed for de-serialization + private String propertyName; + + // chain of getter methods + private transient List 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 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". + *

+ * 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 getMethods = new ArrayList(); + + 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) convertPrimitiveType(type); + this.propertyName = propertyName; + this.getMethods = getMethods; + this.setMethod = setMethod; + } + + @Override + public Class 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 Property.ReadOnlyException + * 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 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 + * bean type + * + * @since 6.6 + */ +public class NestedPropertyDescriptor + implements VaadinPropertyDescriptor { + + 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 beanType) + throws IllegalArgumentException { + this.name = name; + NestedMethodProperty property = new NestedMethodProperty( + 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(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 extends AbstractProperty { + + /** + * The value contained by the Property. + */ + private T value; + + /** + * Data type of the Property's value. + */ + private final Class 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 + public ObjectProperty(T value) { + this(value, (Class) 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 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. value must be assignable + * to this type. + * @param readOnly + * Sets the read-only mode. + */ + public ObjectProperty(T value, Class type, boolean readOnly) { + this(value, type); + setReadOnly(readOnly); + } + + /** + * Returns the type of the ObjectProperty. The methods getValue + * and setValue must be compatible with this type: one must be + * able to safely cast the value returned from getValue to the + * given type and pass any variable assignable to this type as an argument + * to setValue. + * + * @return type of the Property + */ + @Override + public final Class 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 Property.ReadOnlyException + * 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}. + * + *

+ * 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. + *

+ * + *

+ * For example + *

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);
+            }
+
+        });
adds formatter for Double-typed property that extends + * standard "1.0" notation with more zeroes. + *

+ * + * @param T + * type of the underlying property (a PropertyFormatter is always a + * Property<String>) + * + * @deprecated As of 7.0, replaced by {@link Converter} + * @author Vaadin Ltd. + * @since 5.3.0 + */ +@SuppressWarnings("serial") +@Deprecated +public abstract class PropertyFormatter extends AbstractProperty + implements Property.Viewer, Property.ValueChangeListener, + Property.ReadOnlyStatusChangeListener { + + /** Datasource that stores the actual value. */ + Property 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 propertyDataSource) { + + setPropertyDataSource(propertyDataSource); + } + + /** + * Gets the current data source of the formatter, if any. + * + * @return the current data source as a Property, or null if + * none defined. + */ + @Override + public Property getPropertyDataSource() { + return dataSource; + } + + /** + * Sets the specified Property as the data source for the formatter. + * + * + *

+ * Remember that new data sources getValue() must return objects that are + * compatible with parse() and format() methods. + *

+ * + * @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 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 { + + 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 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 + */ +public class TransactionalPropertyWrapper extends AbstractProperty + implements ValueChangeNotifier, Property.Transactional { + + private Property 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 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 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 + * bean type + * + * @since 6.6 + */ +public interface VaadinPropertyDescriptor 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("")); + } + + @Test + public void testReadOnlyStatusChangeListenerAddGetRemove() + throws Exception { + testListenerAddGetRemove(AbstractProperty.class, + ReadOnlyStatusChangeEvent.class, + ReadOnlyStatusChangeListener.class, + new ObjectProperty("")); + } +} 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 + } + + } +} -- cgit v1.2.3