All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.torchmind.observable.concurrent.AbstractBlockingObservable Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
/*
 * Copyright 2017 Johannes Donath 
 * and other copyright owners as documented in the project's IP log.
 *
 * 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.torchmind.observable.concurrent;

import com.torchmind.observable.Observable;
import com.torchmind.observable.ReadOnlyObservable;
import com.torchmind.observable.listener.ChangeListener;
import com.torchmind.observable.listener.ValidationListener;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Provides a blocking abstract implementation of the observable specification.
 *
 * @author Johannes Donath
 */
public abstract class AbstractBlockingObservable extends
    AbstractConcurrentReadOnlyObservable implements Observable {

  private final ReadWriteLock lock;
  private final ValidationListener validationListener;
  private final Set> bidirectionalBinding = new HashSet<>();
  private V value;
  private final ChangeListener bindingListener = (ChangeListener) (property, oldValue, newValue) -> {
    // in case our invalidation flag is set, we have already received this update and probably
    // discovered a circular reference between this observable and the caller and thus do not need
    // to actually perform this update
    if (!this.isValid()) {
      return;
    }

    // otherwise we'll simply set the invalidation flag, update the value and call our subscribers
    // before turning off the invalidation flag once again to complete the cycle
    this.setInternal(newValue);
  };
  private ReadOnlyObservable binding;

  public AbstractBlockingObservable(@Nullable ValidationListener validationListener, V value,
      boolean fair) {
    this.value = value;
    this.validationListener = validationListener;
    this.lock = new ReentrantReadWriteLock(fair);
  }

  public AbstractBlockingObservable(@Nullable ValidationListener validationListener, V value) {
    this(validationListener, value, false);
  }

  public AbstractBlockingObservable(V value) {
    this(null, value);
  }

  public AbstractBlockingObservable() {
    this(null);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public V get() {
    this.lock.readLock().lock();

    try {
      return this.value;
    } finally {
      this.lock.readLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void set(V value) {
    if (this.binding != null) {
      throw new IllegalStateException(
          "Cannot change observable: Value is bound to another observable");
    }

    this.setInternal(value);
  }

  /**
   * Provides an internal setter for the purposes of skipping state sanity checks when necessary.
   */
  private void setInternal(V value) {
    this.lock.writeLock().lock();

    try {
      // even when we receive updates from a binding, we'll validate whether this value is valid
      // Note, however, that this may cause unexpected behavior as the initial caller may not properly
      // handle the exception
      if (this.validationListener != null) {
        this.validationListener.validate(this, value);
      }

      V oldValue = this.value;
      this.value = value;

      this.publishChange(oldValue, value);
    } finally {
      this.lock.writeLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isValid() {
    // generally we will consider the observable invalid as long as its write lock is actively being
    // used by another thread (or possibly the same thread) as it will be lifted after the changes
    // have been published - Note that this step makes this operation slightly more expensive than
    // its non thread safe counterparts
    if (this.lock.writeLock().tryLock()) {
      this.lock.writeLock().unlock();

      return true;
    }

    return false;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void bindTo(@Nonnull ReadOnlyObservable observable) {
    this.lock.writeLock().lock();

    try {
      // if we are already bound to the passed observable we will simply ignore this call to avoid
      // double testing (nor is an update necessary)
      if (this.binding == observable) {
        return;
      }

      if (this.binding != null || !this.bidirectionalBinding.isEmpty()) {
        throw new IllegalStateException(
            "Cannot bind to observable: Already in another binding relationship");
      }

      // register the binding locally (to ensure it stays loaded with weak registrations) and register
      // our local change listener
      this.binding = observable;
      observable.registerListener(this.bindingListener);

      // assume the value of the passed observable as part of the registration process to emulate the
      // effects of changed values
      this.setInternal(observable.get());
    } finally {
      this.lock.writeLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void bindBidirectionallyTo(@Nonnull Observable observable) {
    this.lock.writeLock().lock();

    try {
      if (this.binding != null) {
        throw new IllegalStateException(
            "Cannot bind to observable: Already in another binding relationship");
      }

      // if we already have a binding relationship with the passed observable we will simply ignore
      // this call as we are probably dealing with the recursive call initiated by this implementation
      if (this.bidirectionalBinding.contains(observable)) {
        return;
      }

      // register the binding locally (to ensure it stays loaded with weak registrations) and register
      // our local change listener
      this.bidirectionalBinding.add(observable);
      observable.registerListener(this.bindingListener);

      // assume the value of the passed observable as part of the registration process to emulate the
      // effects of changed values if the binding was initiated by this observable
      if (!observable.isBoundBidirectionallyTo(this)) {
        this.setInternal(observable.get());
      }

      // instruct the other side to create the respective mirror of this relationship on their side
      observable.bindBidirectionallyTo(this);
    } finally {
      this.lock.writeLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isBound() {
    this.lock.readLock().lock();

    try {
      return this.binding != null || !this.bidirectionalBinding.isEmpty();
    } finally {
      this.lock.readLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isBoundTo(@Nonnull ReadOnlyObservable observable) {
    this.lock.readLock().lock();

    try {
      return this.binding == observable;
    } finally {
      this.lock.readLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isBoundBidirectionallyTo(@Nonnull Observable observable) {
    this.lock.readLock().lock();

    try {
      return this.binding == null && this.bidirectionalBinding.contains(observable);
    } finally {
      this.lock.readLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isBoundBidirectionally() {
    this.lock.readLock().lock();

    try {
      return this.binding == null && !this.bidirectionalBinding.isEmpty();
    } finally {
      this.lock.readLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void unbind() {
    this.lock.readLock().lock();

    try {
      if (this.binding == null) {
        throw new IllegalStateException(
            "Cannot unbind: No unidirectional binding relationship present");
      }

      this.binding.removeListener(this.bindingListener);
      this.binding = null;
    } finally {
      this.lock.readLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void unbindAll() {
    this.lock.writeLock().lock();

    try {
      if (this.binding == null && this.bidirectionalBinding.isEmpty()) {
        throw new IllegalStateException("Cannot unbind: No binding relationships present");
      }

      // remove unidirectional bindings (if any) as a present binding implies that we aren't dealing
      // with any bidirectional bindings either
      if (this.binding != null) {
        this.binding.removeListener(this.bindingListener);
        this.binding = null;
      } else {
        new HashSet<>(this.bidirectionalBinding).forEach(this::unbindBidirectional);
      }
    } finally {
      this.lock.writeLock().unlock();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void unbindBidirectional(@Nonnull Observable observable) {
    this.lock.writeLock().lock();

    try {
      observable.removeListener(this.bindingListener);
      this.bidirectionalBinding.remove(observable);

      // remove the other side of the relationship if this relationship still exists (this check is
      // performed to prevent infinite recursions and unexpected exceptions)
      if (observable.isBoundBidirectionallyTo(this)) {
        observable.unbindBidirectional(this);
      }
    } finally {
      this.lock.writeLock().unlock();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy