001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2023–2024 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
006 * the License.  You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
012 * specific language governing permissions and limitations under the License.
013 */
014package org.microbean.scopelet;
015
016import java.lang.invoke.MethodHandles;
017import java.lang.invoke.VarHandle;
018
019import java.util.Objects;
020
021import java.util.function.Supplier;
022
023import org.microbean.bean.Creation;
024import org.microbean.bean.ReferenceSelector;
025
026/**
027 * An {@link AutoCloseable} pairing of an instance that can be destroyed with a {@link Destructor} that can destroy it
028 * and an {@link AutoCloseable} that can release its dependent objects when needed.
029 *
030 * @param <I> the type of the instance
031 *
032 * @author <a href="https://about.me/lairdnelson" target="_parent">Laird Nelson</a>
033 */
034public final class Instance<I> implements AutoCloseable, Supplier<I> {
035
036  private static final VarHandle CLOSED;
037
038  static {
039    try {
040      CLOSED = MethodHandles.lookup().findVarHandle(Instance.class, "closed", boolean.class);
041    } catch (final NoSuchFieldException | IllegalAccessException reflectiveOperationException) {
042      throw (Error)new ExceptionInInitializerError(reflectiveOperationException.getMessage()).initCause(reflectiveOperationException);
043    }
044  }
045
046  private final I object;
047
048  private final Destructor<I> destroyer;
049
050  private final AutoCloseable releaser;
051
052  private final Creation<I> creation;
053
054  private final ReferenceSelector referenceSelector;
055
056  private volatile boolean closed;
057
058  public Instance(final I object,
059                  final Destructor<I> destroyer,
060                  final Creation<I> creation,
061                  final ReferenceSelector referenceSelector) {
062    this(object, destroyer, creation, creation, referenceSelector);
063  }
064
065  private Instance(final I object,
066                   final Destructor<I> destroyer,
067                   final AutoCloseable releaser, // often the same object as creation
068                   final Creation<I> creation, // often the same object as releaser
069                   final ReferenceSelector referenceSelector) {
070    super();
071    // All of these things are nullable on purpose.
072    this.object = object;
073    this.releaser = releaser;
074    this.destroyer = destroyer;
075    this.creation = creation;
076    this.referenceSelector = referenceSelector;
077  }
078
079  @Override
080  public final I get() {
081    if (this.closed()) {
082      throw new IllegalStateException("closed");
083    }
084    return this.object;
085  }
086
087  @Override
088  public final void close() {
089    if (CLOSED.compareAndSet(this, false, true)) { // volatile read/write
090      RuntimeException t = null;
091      try {
092        if (this.destroyer != null) {
093          this.destroyer.destroy(this.object, this.releaser, this.creation, this.referenceSelector);
094        }
095      } catch (final RuntimeException e) {
096        t = e;
097      } finally {
098        if (this.releaser != null) {
099          try {
100            this.releaser.close();
101          } catch (final RuntimeException | Error e) {
102            if (t == null) {
103              throw e;
104            }
105            t.addSuppressed(e);
106          } catch (final Exception e) {
107            if (e instanceof InterruptedException) {
108              Thread.currentThread().interrupt();
109            }
110            if (t == null) {
111              throw new ScopeletException(e.getMessage(), e);
112            }
113            t.addSuppressed(e);
114          }
115        }
116      }
117      if (t != null) {
118        throw t;
119      }
120    }
121  }
122
123  public final boolean closed() {
124    return this.closed; // volatile read
125  }
126
127  @Override
128  public final int hashCode() {
129    // We don't want "closedness" to factor in here because it isn't part of equals().  But we want to use the results
130    // of get().  Fortunately, that method is final.  So we can just use direct field access.
131    return this.object.hashCode();
132  }
133
134  @Override
135  public final boolean equals(final Object other) {
136    if (other == this) {
137      return true;
138    } else if (other != null && this.getClass() == other.getClass()) {
139      // We don't want "closedness" to factor in here because it isn't part of hashCode().  But we want to use the
140      // results of get().  Fortunately, that method is final.  So we can just use direct field access.
141      return Objects.equals(this.object, ((Instance<?>)other).object);
142    } else {
143      return false;
144    }
145  }
146
147  @Override
148  public final String toString() {
149    return String.valueOf(this.get());
150  }
151
152  public static interface Destructor<I> {
153
154    public void destroy(final I i, final AutoCloseable acr, final Creation<I> c, final ReferenceSelector rs);
155
156  }
157
158}