/* * 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.server; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import com.vaadin.event.EventRouter; import com.vaadin.event.MethodEventSource; import com.vaadin.shared.Registration; import com.vaadin.shared.communication.ClientRpc; import com.vaadin.shared.communication.ServerRpc; import com.vaadin.shared.communication.SharedState; import com.vaadin.shared.ui.ComponentStateUtil; import com.vaadin.ui.Component; import com.vaadin.ui.Component.Event; import com.vaadin.ui.HasComponents; import com.vaadin.ui.LegacyComponent; import com.vaadin.ui.UI; import elemental.json.JsonObject; import elemental.json.JsonValue; /** * An abstract base class for ClientConnector implementations. This class * provides all the basic functionality required for connectors. * * @author Vaadin Ltd * @since 7.0.0 */ public abstract class AbstractClientConnector implements ClientConnector, MethodEventSource { /** * A map from client to server RPC interface class name to the RPC call * manager that handles incoming RPC calls for that interface. */ private final Map<String, ServerRpcManager<?>> rpcManagerMap = new HashMap<>(); /** * A map from server to client RPC interface class to the RPC proxy that * sends ourgoing RPC calls for that interface. */ private final Map<Class<?>, ClientRpc> rpcProxyMap = new HashMap<>(); /** * Shared state object to be communicated from the server to the client when * modified. */ private SharedState sharedState; private Class<? extends SharedState> stateType; /** * Pending RPC method invocations to be sent. */ private ArrayList<ClientMethodInvocation> pendingInvocations = new ArrayList<>(); private String connectorId; private final ArrayList<Extension> extensions = new ArrayList<>(); /** * The EventRouter used for the event model. */ private EventRouter eventRouter = null; private ErrorHandler errorHandler = null; private static final ConcurrentHashMap<Class<? extends AbstractClientConnector>, Class<? extends SharedState>> stateTypeCache = new ConcurrentHashMap<>(); @Override public Registration addAttachListener(AttachListener listener) { return addListener(AttachEvent.ATTACH_EVENT_IDENTIFIER, AttachEvent.class, listener, AttachListener.attachMethod); } @Override @Deprecated public void removeAttachListener(AttachListener listener) { removeListener(AttachEvent.ATTACH_EVENT_IDENTIFIER, AttachEvent.class, listener); } @Override public Registration addDetachListener(DetachListener listener) { return addListener(DetachEvent.DETACH_EVENT_IDENTIFIER, DetachEvent.class, listener, DetachListener.detachMethod); } @Override @Deprecated public void removeDetachListener(DetachListener listener) { removeListener(DetachEvent.DETACH_EVENT_IDENTIFIER, DetachEvent.class, listener); } /** * @deprecated As of 7.0, use {@link #markAsDirty()} instead. Note that you * typically do not need to call {@link #markAsDirty()} as * {@link #getState()} will mark the connector dirty and the * framework will then check what, if anything, needs to be sent * to the client. {@link LegacyComponent}s which rely on paint * might still need to call this or {@link #markAsDirty()} . */ @Deprecated @Override public void requestRepaint() { markAsDirty(); } /* Documentation copied from interface */ @Override public void markAsDirty() { assert getSession() == null || getSession().hasLock() : buildLockAssertMessage( "markAsDirty()"); UI uI = getUI(); if (uI != null) { uI.getConnectorTracker().markDirty(this); } } private String buildLockAssertMessage(String method) { if (VaadinService.isOtherSessionLocked(getSession())) { return "The session of this connecor is not locked, but there is another session that is locked. " + "This might be caused by accidentally using a connector that belongs to another session."; } else { return "Session must be locked when " + method + " is called"; } } /** * Registers an RPC interface implementation for this component. * * A component can listen to multiple RPC interfaces, and subclasses can * register additional implementations. * * @since 7.0 * * @param implementation * RPC interface implementation * @param rpcInterfaceType * RPC interface class for which the implementation should be * registered */ protected <T extends ServerRpc> void registerRpc(T implementation, Class<T> rpcInterfaceType) { rpcManagerMap.put(rpcInterfaceType.getName(), new ServerRpcManager<>(implementation, rpcInterfaceType)); } /** * Registers an RPC interface implementation for this component. * * A component can listen to multiple RPC interfaces, and subclasses can * register additional implementations. * * @since 7.0 * * @param implementation * RPC interface implementation. Also used to deduce the type. */ protected <T extends ServerRpc> void registerRpc(T implementation) { // Search upwards until an interface is found. It must be found as T // extends ServerRpc Class<?> cls = implementation.getClass(); Class<ServerRpc> serverRpcClass = getServerRpcInterface(cls); while (cls != null && serverRpcClass == null) { cls = cls.getSuperclass(); serverRpcClass = getServerRpcInterface(cls); } if (serverRpcClass == null) { throw new RuntimeException( "No interface T extends ServerRpc found in the class hierarchy."); } registerRpc(implementation, serverRpcClass); } @SuppressWarnings("unchecked") private Class<ServerRpc> getServerRpcInterface( Class<?> implementationClass) { Class<ServerRpc> serverRpcClass = null; if (implementationClass != null) { for (Class<?> candidateInterface : implementationClass .getInterfaces()) { if (ServerRpc.class.isAssignableFrom(candidateInterface)) { if (serverRpcClass != null) { throw new RuntimeException( "Use registerRpc(T implementation, Class<T> rpcInterfaceType) if the Rpc implementation implements more than one interface"); } serverRpcClass = (Class<ServerRpc>) candidateInterface; } } } return serverRpcClass; } /** * Returns the shared state for this connector. The shared state object is * shared between the server connector and the client connector. Changes are * only communicated from the server to the client and not in the other * direction. * <p> * As a side effect, marks the connector dirty so any changes done to the * state will be sent to the client. Use {@code getState(false)} to avoid * marking the connector as dirty. * </p> * * @return The shared state for this connector. Never null. */ protected SharedState getState() { return getState(true); } /** * Returns the shared state for this connector. * * @param markAsDirty * true if the connector should automatically be marked dirty, * false otherwise * * @return The shared state for this connector. Never null. * @see #getState() */ protected SharedState getState(boolean markAsDirty) { assert getSession() == null || getSession().hasLock() : buildLockAssertMessage( "getState()"); if (null == sharedState) { sharedState = createState(); } if (markAsDirty) { UI ui = getUI(); if (ui != null && !ui.getConnectorTracker().isDirty(this) && !ui.getConnectorTracker().isWritingResponse()) { ui.getConnectorTracker().markDirty(this); } } return sharedState; } @Override public JsonObject encodeState() { return LegacyCommunicationManager.encodeState(this, getState(false)); } /** * Creates the shared state bean to be used in server to client * communication. * <p> * By default a state object of the defined return type of * {@link #getState()} is created. Subclasses can override this method and * return a new instance of the correct state class but this should rarely * be necessary. * </p> * <p> * No configuration of the values of the state should be performed in * {@link #createState()}. * * @since 7.0 * * @return new shared state object */ protected SharedState createState() { try { return getStateType().newInstance(); } catch (Exception e) { throw new RuntimeException("Error creating state of type " + getStateType().getName() + " for " + getClass().getName(), e); } } @Override public Class<? extends SharedState> getStateType() { // Lazy load because finding type can be expensive because of the // exceptions flying around if (stateType == null) { // Cache because we don't need to do this once per instance stateType = stateTypeCache.get(this.getClass()); if (stateType == null) { stateType = findStateType(); stateTypeCache.put(this.getClass(), stateType); } } return stateType; } private Class<? extends SharedState> findStateType() { try { Class<?> class1 = getClass(); while (class1 != null) { try { Method m = class1.getDeclaredMethod("getState", (Class[]) null); Class<?> type = m.getReturnType(); if (!m.isSynthetic()) { return type.asSubclass(SharedState.class); } } catch (NoSuchMethodException nsme) { } // Try in superclass instead class1 = class1.getSuperclass(); } throw new NoSuchMethodException( getClass().getCanonicalName() + ".getState()"); } catch (Exception e) { throw new RuntimeException( "Error finding state type for " + getClass().getName(), e); } } /** * Returns an RPC proxy for a given server to client RPC interface for this * component. * * TODO more javadoc, subclasses, ... * * @param rpcInterface * RPC interface type * * @since 7.0 */ protected <T extends ClientRpc> T getRpcProxy(final Class<T> rpcInterface) { // create, initialize and return a dynamic proxy for RPC try { if (!rpcProxyMap.containsKey(rpcInterface)) { Class<?> proxyClass = Proxy.getProxyClass( rpcInterface.getClassLoader(), rpcInterface); Constructor<?> constructor = proxyClass .getConstructor(InvocationHandler.class); T rpcProxy = rpcInterface.cast(constructor .newInstance(new RpcInvocationHandler(rpcInterface))); // cache the proxy rpcProxyMap.put(rpcInterface, rpcProxy); } return (T) rpcProxyMap.get(rpcInterface); } catch (Exception e) { // TODO exception handling? throw new RuntimeException(e); } } private class RpcInvocationHandler implements InvocationHandler, Serializable { private final String rpcInterfaceName; public RpcInvocationHandler(Class<?> rpcInterface) { rpcInterfaceName = rpcInterface.getName().replaceAll("\\$", "."); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getDeclaringClass() == Object.class) { // Don't add Object methods such as toString and hashCode as // invocations return method.invoke(this, args); } addMethodInvocationToQueue(rpcInterfaceName, method, args); return null; } } /** * For internal use: adds a method invocation to the pending RPC call queue. * * @param interfaceName * RPC interface name * @param method * RPC method * @param parameters * RPC all parameters * * @since 7.0 */ protected void addMethodInvocationToQueue(String interfaceName, Method method, Object[] parameters) { // add to queue pendingInvocations.add(new ClientMethodInvocation(this, interfaceName, method, parameters)); // TODO no need to do full repaint if only RPC calls requestRepaint(); } @Override public ServerRpcManager<?> getRpcManager(String rpcInterfaceName) { return rpcManagerMap.get(rpcInterfaceName); } @Override public List<ClientMethodInvocation> retrievePendingRpcCalls() { if (pendingInvocations.isEmpty()) { return Collections.emptyList(); } else { List<ClientMethodInvocation> result = pendingInvocations; pendingInvocations = new ArrayList<>(); return Collections.unmodifiableList(result); } } @Override public String getConnectorId() { if (connectorId == null) { if (getSession() == null) { throw new RuntimeException( "Component must be attached to a session when getConnectorId() is called for the first time"); } connectorId = getSession().createConnectorId(this); } return connectorId; } /** * Finds the {@link VaadinSession} to which this connector belongs. If the * connector has not been attached, <code>null</code> is returned. * * @return The connector's session, or <code>null</code> if not attached */ protected VaadinSession getSession() { UI uI = getUI(); if (uI == null) { return null; } else { return uI.getSession(); } } /** * Finds a UI ancestor of this connector. <code>null</code> is returned if * no UI ancestor is found (typically because the connector is not attached * to a proper hierarchy). * * @return the UI ancestor of this connector, or <code>null</code> if none * is found. */ @Override public UI getUI() { ClientConnector connector = this; while (connector != null) { if (connector instanceof UI) { return (UI) connector; } connector = connector.getParent(); } return null; } private static Logger getLogger() { return Logger.getLogger(AbstractClientConnector.class.getName()); } /** * @deprecated As of 7.0, use {@link #markAsDirtyRecursive()} instead */ @Override @Deprecated public void requestRepaintAll() { markAsDirtyRecursive(); } @Override public void markAsDirtyRecursive() { markAsDirty(); for (ClientConnector connector : getAllChildrenIterable(this)) { connector.markAsDirtyRecursive(); } } /** * Get an Iterable for iterating over all child connectors, including both * extensions and child components. * * @param connector * the connector to get children for * @return an Iterable giving all child connectors. */ public static Iterable<? extends ClientConnector> getAllChildrenIterable( final ClientConnector connector) { Collection<Extension> extensions = connector.getExtensions(); boolean hasComponents = connector instanceof HasComponents; boolean hasExtensions = extensions.size() > 0; if (!hasComponents && !hasExtensions) { // If has neither component nor extensions, return immutable empty // list as iterable. return Collections.emptyList(); } if (hasComponents && !hasExtensions) { // only components return (HasComponents) connector; } if (!hasComponents && hasExtensions) { // only extensions return extensions; } // combine the iterators of extensions and components to a new iterable. final Iterator<Component> componentsIterator = ((HasComponents) connector) .iterator(); final Iterator<Extension> extensionsIterator = extensions.iterator(); Iterable<? extends ClientConnector> combinedIterable = () -> new Iterator<ClientConnector>() { @Override public boolean hasNext() { return componentsIterator.hasNext() || extensionsIterator.hasNext(); } @Override public ClientConnector next() { if (componentsIterator.hasNext()) { return componentsIterator.next(); } if (extensionsIterator.hasNext()) { return extensionsIterator.next(); } throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; return combinedIterable; } @Override public Collection<Extension> getExtensions() { return Collections.unmodifiableCollection(extensions); } /** * Add an extension to this connector. This method is protected to allow * extensions to select which targets they can extend. * * @param extension * the extension to add */ protected void addExtension(Extension extension) { ClientConnector previousParent = extension.getParent(); if (equals(previousParent)) { // Nothing to do, already attached return; } else if (previousParent != null) { throw new IllegalStateException( "Moving an extension from one parent to another is not supported"); } extensions.add(extension); extension.setParent(this); markAsDirty(); } @Override public void removeExtension(Extension extension) { if (extension.getParent() != this) { throw new IllegalArgumentException( "This connector is not the parent for given extension"); } extension.setParent(null); extensions.remove(extension); markAsDirty(); } /* * (non-Javadoc) * * @see com.vaadin.server.ClientConnector#isAttached() */ @Override public boolean isAttached() { return getSession() != null; } @Override public void attach() { markAsDirty(); getUI().getConnectorTracker().registerConnector(this); for (ClientConnector connector : getAllChildrenIterable(this)) { connector.attach(); } fireEvent(new AttachEvent(this)); } /** * {@inheritDoc} * * <p> * The {@link #getSession()} and {@link #getUI()} methods might return * <code>null</code> after this method is called. * </p> */ @Override public void detach() { for (ClientConnector connector : getAllChildrenIterable(this)) { connector.detach(); } fireEvent(new DetachEvent(this)); getUI().getConnectorTracker().unregisterConnector(this); } @Override public boolean isConnectorEnabled() { if (getParent() == null) { // No parent -> the component cannot receive updates from the client return false; } else { return getParent().isConnectorEnabled(); } } @Override public void beforeClientResponse(boolean initial) { // Do nothing by default } @Override public boolean handleConnectorRequest(VaadinRequest request, VaadinResponse response, String path) throws IOException { DownloadStream stream = null; String[] parts = path.split("/", 2); String key = parts[0]; VaadinSession session = getSession(); session.lock(); try { ConnectorResource resource = (ConnectorResource) getResource(key); if (resource == null) { return false; } stream = resource.getStream(); } finally { session.unlock(); } stream.writeResponse(request, response); return true; } /** * Gets a resource defined using {@link #setResource(String, Resource)} with * the corresponding key. * * @param key * the string identifier of the resource * @return a resource, or <code>null</code> if there's no resource * associated with the given key * * @see #setResource(String, Resource) */ protected Resource getResource(String key) { return ResourceReference .getResource(getState(false).resources.get(key)); } /** * Registers a resource with this connector using the given key. This will * make the URL for retrieving the resource available to the client-side * connector using * {@link com.vaadin.terminal.gwt.client.ui.AbstractConnector#getResourceUrl(String)} * with the same key. * * @param key * the string key to associate the resource with * @param resource * the resource to set, or <code>null</code> to clear a previous * association. */ protected void setResource(String key, Resource resource) { ResourceReference resourceReference = ResourceReference.create(resource, this, key); if (resourceReference == null) { getState().resources.remove(key); } else { getState().resources.put(key, resourceReference); } } /* Listener code starts. Should be refactored. */ /** * <p> * Registers a new listener with the specified activation method to listen * events generated by this component. If the activation method does not * have any arguments the event object will not be passed to it when it's * called. * </p> * * <p> * This method additionally informs the event-api to route events with the * given eventIdentifier to the components handleEvent function call. * </p> * * <p> * For more information on the inheritable event mechanism see the * {@link com.vaadin.event com.vaadin.event package documentation}. * </p> * * @param eventIdentifier * the identifier of the event to listen for * @param eventType * the type of the listened event. Events of this type or its * subclasses activate the listener. * @param target * the object instance who owns the activation method. * @param method * the activation method. * @return a registration object for removing the listener * @since 8.0 */ protected Registration addListener(String eventIdentifier, Class<?> eventType, Object target, Method method) { if (eventRouter == null) { eventRouter = new EventRouter(); } boolean needRepaint = !eventRouter.hasListeners(eventType); Registration registration = eventRouter.addListener(eventType, target, method); if (needRepaint) { ComponentStateUtil.addRegisteredEventListener(getState(), eventIdentifier); } return registration; } /** * Checks if the given {@link Event} type is listened for this component. * * @param eventType * the event type to be checked * @return true if a listener is registered for the given event type */ protected boolean hasListeners(Class<?> eventType) { return eventRouter != null && eventRouter.hasListeners(eventType); } /** * Removes all registered listeners matching the given parameters. Since * this method receives the event type and the listener object as * parameters, it will unregister all <code>object</code>'s methods that are * registered to listen to events of type <code>eventType</code> generated * by this component. * * <p> * This method additionally informs the event-api to stop routing events * with the given eventIdentifier to the components handleEvent function * call. * </p> * * <p> * For more information on the inheritable event mechanism see the * {@link com.vaadin.event com.vaadin.event package documentation}. * </p> * * @param eventIdentifier * the identifier of the event to stop listening for * @param eventType * the exact event type the <code>object</code> listens to. * @param target * the target object that has registered to listen to events of * type <code>eventType</code> with one or more methods. * * @since 6.2 * @deprecated use a {@link Registration} from * {@link #addListener(Class, Object, Method)} to remove a * listener */ @Deprecated protected void removeListener(String eventIdentifier, Class<?> eventType, Object target) { if (eventRouter != null) { eventRouter.removeListener(eventType, target); if (!eventRouter.hasListeners(eventType)) { ComponentStateUtil.removeRegisteredEventListener(getState(), eventIdentifier); } } } /** * <p> * Registers a new listener with the specified activation method to listen * events generated by this component. If the activation method does not * have any arguments the event object will not be passed to it when it's * called. * </p> * * <p> * For more information on the inheritable event mechanism see the * {@link com.vaadin.event com.vaadin.event package documentation}. * </p> * * @param eventType * the type of the listened event. Events of this type or its * subclasses activate the listener. * @param target * the object instance who owns the activation method. * @param method * the activation method. * @return a registration object for removing the listener */ @Override public Registration addListener(Class<?> eventType, Object target, Method method) { if (eventRouter == null) { eventRouter = new EventRouter(); } return eventRouter.addListener(eventType, target, method); } /** * <p> * Convenience method for registering a new listener with the specified * activation method to listen events generated by this component. If the * activation method does not have any arguments the event object will not * be passed to it when it's called. * </p> * * <p> * This version of <code>addListener</code> gets the name of the activation * method as a parameter. The actual method is reflected from * <code>object</code>, and unless exactly one match is found, * <code>java.lang.IllegalArgumentException</code> is thrown. * </p> * * <p> * For more information on the inheritable event mechanism see the * {@link com.vaadin.event com.vaadin.event package documentation}. * </p> * * <p> * Note: Using this method is discouraged because it cannot be checked * during compilation. Use {@link #addListener(Class, Object, Method)} or * {@link #addListener(String, Class, Object, Method) instead. </p> * * @param eventType * the type of the listened event. Events of this type or its * subclasses activate the listener. * @param target * the object instance who owns the activation method. * @param methodName * the name of the activation method. * @return a registration object for removing the listener * @deprecated As of 7.0. This method should be avoided. Use * {@link #addListener(Class, Object, Method)} or * {@link #addListener(String, Class, Object, Method)} instead. * @since 8.0 */ @Override @Deprecated public Registration addListener(Class<?> eventType, Object target, String methodName) { if (eventRouter == null) { eventRouter = new EventRouter(); } return eventRouter.addListener(eventType, target, methodName); } /** * Removes all registered listeners matching the given parameters. Since * this method receives the event type and the listener object as * parameters, it will unregister all <code>object</code>'s methods that are * registered to listen to events of type <code>eventType</code> generated * by this component. * * <p> * For more information on the inheritable event mechanism see the * {@link com.vaadin.event com.vaadin.event package documentation}. * </p> * * @param eventType * the exact event type the <code>object</code> listens to. * @param target * the target object that has registered to listen to events of * type <code>eventType</code> with one or more methods. * @deprecated use a {@link Registration} from {@link #addListener} to * remove a listener */ @Deprecated @Override public void removeListener(Class<?> eventType, Object target) { if (eventRouter != null) { eventRouter.removeListener(eventType, target); } } /** * Removes one registered listener method. The given method owned by the * given object will no longer be called when the specified events are * generated by this component. * * <p> * For more information on the inheritable event mechanism see the * {@link com.vaadin.event com.vaadin.event package documentation}. * </p> * * @param eventType * the exact event type the <code>object</code> listens to. * @param target * target object that has registered to listen to events of type * <code>eventType</code> with one or more methods. * @param method * the method owned by <code>target</code> that's registered to * listen to events of type <code>eventType</code>. * @deprecated use a {@link Registration} from * {@link #addListener(Class, Object, Method)} to remove a * listener */ @Override @Deprecated public void removeListener(Class<?> eventType, Object target, Method method) { if (eventRouter != null) { eventRouter.removeListener(eventType, target, method); } } /** * <p> * Removes one registered listener method. The given method owned by the * given object will no longer be called when the specified events are * generated by this component. * </p> * * <p> * This version of <code>removeListener</code> gets the name of the * activation method as a parameter. The actual method is reflected from * <code>target</code>, and unless exactly one match is found, * <code>java.lang.IllegalArgumentException</code> is thrown. * </p> * * <p> * For more information on the inheritable event mechanism see the * {@link com.vaadin.event com.vaadin.event package documentation}. * </p> * * @param eventType * the exact event type the <code>object</code> listens to. * @param target * the target object that has registered to listen to events of * type <code>eventType</code> with one or more methods. * @param methodName * the name of the method owned by <code>target</code> that's * registered to listen to events of type <code>eventType</code>. * @deprecated As of 7.0. This method should be avoided. Use * {@link #removeListener(Class, Object, Method)} instead. */ @Deprecated @Override public void removeListener(Class<?> eventType, Object target, String methodName) { if (eventRouter != null) { eventRouter.removeListener(eventType, target, methodName); } } /** * Returns all listeners that are registered for the given event type or one * of its subclasses. * * @param eventType * The type of event to return listeners for. * @return A collection with all registered listeners. Empty if no listeners * are found. */ public Collection<?> getListeners(Class<?> eventType) { if (eventRouter == null) { return Collections.emptyList(); } return eventRouter.getListeners(eventType); } /** * Sends the event to all listeners. * * @param event * the Event to be sent to all listeners. */ protected void fireEvent(EventObject event) { if (eventRouter != null) { eventRouter.fireEvent(event); } } /* * (non-Javadoc) * * @see com.vaadin.server.ClientConnector#getErrorHandler() */ @Override public ErrorHandler getErrorHandler() { return errorHandler; } /* * (non-Javadoc) * * @see com.vaadin.server.ClientConnector#setErrorHandler(com.vaadin.server. * ErrorHandler) */ @Override public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler; } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } /* * This equals method must return true when we're comparing an object to * its proxy. This happens a lot with CDI (and possibly Spring) when * we're injecting Components. See #14639 */ if (obj instanceof AbstractClientConnector) { AbstractClientConnector connector = (AbstractClientConnector) obj; return connector.isThis(this); } return false; } /** * For internal use only, may be changed or removed in future versions. * <p> * This method must be protected, because otherwise it will not be redefined * by the proxy to actually be called on the underlying instance. * <p> * See #14639 * * @deprecated only defined for framework hacks, do not use. */ @Deprecated protected boolean isThis(Object that) { return this == that; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return super.hashCode(); } /** * Sets the expected value of a state property so that changes can be * properly sent to the client. This needs to be done in cases where a state * change originates from the client, since otherwise the server-side would * fail to recognize if the value is changed back to its previous value. * * @param propertyName * the name of the shared state property to update * @param newValue * the new diffstate reference value */ protected void updateDiffstate(String propertyName, JsonValue newValue) { if (!isAttached()) { return; } JsonObject diffState = getUI().getConnectorTracker().getDiffState(this); if (diffState == null) { return; } assert diffState.hasKey(propertyName) : "Diffstate for " + getClass().getName() + " has no property named " + propertyName; diffState.put(propertyName, newValue); } }