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}