001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2023 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.MethodHandles.Lookup;
018import java.lang.invoke.VarHandle;
019
020import java.util.Objects;
021
022import org.microbean.bean.Bean;
023import org.microbean.bean.Creation;
024import org.microbean.bean.Id;
025import org.microbean.bean.Factory;
026import org.microbean.bean.ReferenceSelector;
027
028import org.microbean.qualifier.NamedAttributeMap;
029
030import org.microbean.scope.ScopeMember;
031
032public abstract class Scopelet<S extends Scopelet<S>> implements AutoCloseable, Factory<S>, ScopeMember {
033
034  private static final VarHandle ME;
035
036  static {
037    final Lookup lookup = MethodHandles.lookup();
038    try {
039      ME = lookup.findVarHandle(Scopelet.class, "me", Scopelet.class);
040    } catch (final NoSuchFieldException | IllegalAccessException e) {
041      throw (Error)new ExceptionInInitializerError(e.getMessage()).initCause(e);
042    }
043  }
044
045  private volatile S me;
046
047  private volatile boolean closed;
048
049  private final NamedAttributeMap<?> scopeId;
050
051  protected Scopelet(final NamedAttributeMap<?> scopeId) {
052    super();
053    this.scopeId = Objects.requireNonNull(scopeId, "scopeId");
054  }
055
056  public abstract Id id();
057
058  public final Bean<S> bean() {
059    return new Bean<>(this.id(), this);
060  }
061
062  @Override // Factory<S>
063  @SuppressWarnings("unchecked")
064  public final S create(final Creation<S> c, final ReferenceSelector referenceSelector) {
065    if (ME.compareAndSet(this, null, this)) { // volatile write
066      if (referenceSelector != null) {
067        // TODO: emit initialized event
068      }
069    }
070    return (S)this;
071  }
072
073  @Override // Factory<S>
074  public final S singleton() {
075    return this.me; // volatile read
076  }
077
078  @Override // Factory<S>
079  public boolean destroys() {
080    return true;
081  }
082
083  @Override // Object
084  public int hashCode() {
085    int hashCode = 17;
086    hashCode = 31 * hashCode + this.id().hashCode();
087    hashCode = 31 * hashCode + this.scopeId().hashCode();
088    return hashCode;
089  }
090
091  @Override // Object
092  public boolean equals(final Object other) {
093    if (other == this) {
094      return true;
095    } else if (other != null && other.getClass().equals(this.getClass())) {
096      final Scopelet<?> her = (Scopelet<?>)other;
097      return
098        Objects.equals(this.id(), her.id()) &&
099        Objects.equals(this.scopeId(), her.scopeId());
100    } else {
101      return false;
102    }
103  }
104
105  @Override // ScopeMember
106  public final NamedAttributeMap<?> governingScopeId() {
107    return this.id().governingScopeId();
108  }
109
110  @Override // ScopeMember
111  public final boolean governedBy(final NamedAttributeMap<?> scopeId) {
112    return this.id().governedBy(scopeId);
113  }
114
115
116  /*
117   * Repository-like concerns.
118   */
119
120
121  public final NamedAttributeMap<?> scopeId() {
122    return this.scopeId;
123  }
124
125  public boolean active() {
126    return !this.closed(); // volatile read
127  }
128
129  public boolean containsId(final Object id) {
130    if (!this.active()) {
131      throw new InactiveScopeletException();
132    }
133    return this.get(id) != null;
134  }
135
136  // id is nullable.
137  public <I> I get(final Object id) {
138    if (!this.active()) {
139      throw new InactiveScopeletException();
140    }
141    return this.instance(id, null, null, null);
142  }
143
144  // All parameters are nullable.
145  public abstract <I> I instance(final Object id,
146                                 final Factory<I> factory,
147                                 final Creation<I> c,
148                                 final ReferenceSelector r);
149
150  // id is nullable.
151  public abstract boolean remove(final Object id);
152
153  @Override // AutoCloseable
154  public void close() {
155    if (!this.closed()) {
156      this.closed = true;
157    }
158  }
159
160  protected final boolean closed() {
161    return this.closed;
162  }
163
164}