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

com.igormaznitsa.meta.common.utils.Deferrers Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 Igor Maznitsa.
 *
 * 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.igormaznitsa.meta.common.utils;

import static com.igormaznitsa.meta.common.utils.Assertions.assertNotNull;

import com.igormaznitsa.meta.annotation.MustNotContainNull;
import com.igormaznitsa.meta.annotation.Warning;
import com.igormaznitsa.meta.annotation.Weight;
import com.igormaznitsa.meta.common.exceptions.MetaErrorListeners;
import com.igormaznitsa.meta.common.exceptions.UnexpectedProcessingError;
import com.igormaznitsa.meta.common.interfaces.Disposable;
import java.io.Closeable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.ThreadSafe;

/**
 * Auxiliary tool to defer some actions and process them in some point in the future. It checks stack depth and executes only locally (for the stack level) defer actions. It works
 * through ThreadLocal so that actions saved separately for every thread.
 *
 * @see ThreadLocal
 * @since 1.0
 */
@ThreadSafe
public final class Deferrers {

  /**
   * Inside registry for defer actions.
   *
   * @since 1.0
   */
  @MustNotContainNull
  private static final ThreadLocal> REGISTRY =
      ThreadLocal.withInitial(ArrayList::new);

  private Deferrers() {
  }

  /**
   * Defer some action.
   *
   * @param deferred action to be deferred.
   * @return the same object from arguments
   * @since 1.0
   */
  @Weight(Weight.Unit.NORMAL)
  public static Deferred defer(@Nonnull final Deferred deferred) {
    REGISTRY.get().add(assertNotNull(deferred));
    return deferred;
  }

  /**
   * Defer object containing public close() method. It catches all exceptions during closing and make notifications only for global error listeners. It finds a public 'close'
   * method of the object and call that through reflection.
   *
   * @param        type of the object to be processed
   * @param closeable an object with close() method.
   * @return the same object from arguments.
   * @since 1.0
   */
  @Warning("using reflection")
  @Weight(Weight.Unit.NORMAL)
  public static  T deferredClose(@Nullable final T closeable) {
    if (closeable != null) {
      defer(new Deferred() {
        private static final long serialVersionUID = 2265124256013043847L;

        @Override
        public void executeDeferred() {
          try {
            closeable.getClass().getMethod("close").invoke(closeable);
          } catch (Exception thr) {
            MetaErrorListeners.fireError("Error during deferred closing action", thr);
          }
        }
      });
    }
    return closeable;
  }

  /**
   * Defer closing of a closeable object.
   *
   * @param        type of closeable object
   * @param closeable an object implements java.io.Closeable
   * @return the same closeable object from arguments
   * @since 1.0
   */
  @Weight(Weight.Unit.NORMAL)
  public static  T defer(@Nullable final T closeable) {
    if (closeable != null) {
      defer(new Deferred() {
        private static final long serialVersionUID = 2265124256013043847L;

        @Override
        public void executeDeferred() {
          IOUtils.closeQuietly(closeable);
        }
      });
    }
    return closeable;
  }

  /**
   * Defer execution of some runnable action.
   *
   * @param runnable some runnable action to be executed in future
   * @return the same runnable object from arguments.
   * @throws AssertionError if the runnable object is null
   */
  @Weight(Weight.Unit.NORMAL)
  public static Runnable defer(@Nonnull final Runnable runnable) {
    assertNotNull(runnable);
    defer(new Deferred() {
      private static final long serialVersionUID = 2061489024868070733L;
      private final Runnable value = runnable;

      @Override
      public void executeDeferred() {
        this.value.run();
      }
    });
    return runnable;
  }

  /**
   * Defer execution of some disposable object.
   *
   * @param disposable some disposable object to be processed.
   * @return the same object from arguments
   * @throws AssertionError if the disposable object is null
   * @see Disposable
   */
  @Weight(Weight.Unit.NORMAL)
  public static Disposable defer(@Nonnull final Disposable disposable) {
    assertNotNull(disposable);
    defer(new Deferred() {
      private static final long serialVersionUID = 7940162959962038010L;
      private final Disposable value = disposable;

      @Override
      public void executeDeferred() {
        this.value.dispose();
      }
    });
    return disposable;
  }

  /**
   * Cancel all defer actions globally.
   *
   * @since 1.0
   */
  @Weight(Weight.Unit.NORMAL)
  public static void cancelAllDeferredActionsGlobally() {
    final List list = REGISTRY.get();
    list.clear();
    REGISTRY.remove();
  }

  /**
   * Cancel all defer actions for the current stack depth.
   *
   * @since 1.0
   */
  @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth")
  public static void cancelDeferredActions() {
    final int stackDepth = ThreadUtils.stackDepth();

    final List list = REGISTRY.get();

    list.removeIf(deferred -> deferred.getStackDepth() >= stackDepth);
    if (list.isEmpty()) {
      REGISTRY.remove();
    }
  }

  /**
   * Process all defer actions for the current stack depth level.
   *
   * @since 1.0
   */
  @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth")
  public static void processDeferredActions() {
    final int stackDepth = ThreadUtils.stackDepth();

    final List list = REGISTRY.get();
    final Iterator iterator = list.iterator();

    while (iterator.hasNext()) {
      final Deferred deferred = iterator.next();
      if (deferred.getStackDepth() >= stackDepth) {
        try {
          deferred.executeDeferred();
        } catch (Exception ex) {
          final UnexpectedProcessingError error =
              new UnexpectedProcessingError("Error during deferred action processing", ex);
          MetaErrorListeners.fireError(error.getMessage(), error);
        } finally {
          iterator.remove();
        }
      }
    }
    if (list.isEmpty()) {
      REGISTRY.remove();
    }
  }

  /**
   * Check that presented defer actions for the current thread.
   *
   * @return true if presented, false otherwise
   * @since 1.0
   */
  @Weight(Weight.Unit.NORMAL)
  public static boolean isEmpty() {
    final boolean result = REGISTRY.get().isEmpty();
    if (result) {
      REGISTRY.remove();
    }
    return result;
  }

  /**
   * Class wrapping executeDeferred method and stack depth for action.
   *
   * @since 1.0
   */
  @Immutable
  @Weight(Weight.Unit.VARIABLE)
  public abstract static class Deferred implements Serializable {

    private static final long serialVersionUID = -1134788854676942497L;

    private final int stackDepth;

    /**
     * The Constructor.
     *
     * @since 1.0
     */
    @Weight(value = Weight.Unit.VARIABLE, comment = "Depends on the current call stack depth@")
    public Deferred() {
      this.stackDepth = ThreadUtils.stackDepth() - 1;
    }

    /**
     * Get the stack depth detected during object creation.
     *
     * @return the stack depth
     * @since 1.0
     */
    public int getStackDepth() {
      return this.stackDepth;
    }

    /**
     * Execute call.
     *
     * @throws Exception it will be thrown for error.
     * @since 1.0
     */
    public abstract void executeDeferred() throws Exception;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy