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