001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.lang3.concurrent;
019
020import java.util.Objects;
021
022import org.apache.commons.lang3.builder.AbstractSupplier;
023import org.apache.commons.lang3.exception.ExceptionUtils;
024import org.apache.commons.lang3.function.FailableConsumer;
025import org.apache.commons.lang3.function.FailableSupplier;
026
027/**
028 * Abstracts and defines operations for {@link ConcurrentInitializer} implementations.
029 *
030 * @param <T> the type of the object managed by this initializer class.
031 * @param <E> The exception type thrown by {@link #initialize()}.
032 * @since 3.14.0
033 */
034public abstract class AbstractConcurrentInitializer<T, E extends Exception> implements ConcurrentInitializer<T> {
035
036    /**
037     * Builds a new instance for subclasses.
038     *
039     * @param <T> the type of the object managed by the initializer class.
040     * @param <I> the type of the initializer class.
041     * @param <B> the type of builder.
042     * @param <E> The exception type thrown by {@link #initialize()}.
043     */
044    public abstract static class AbstractBuilder<I extends AbstractConcurrentInitializer<T, E>, T, B extends AbstractBuilder<I, T, B, E>, E extends Exception>
045            extends AbstractSupplier<I, B, E> {
046
047        /**
048         * Closer consumer called by {@link #close()}.
049         */
050        private FailableConsumer<T, ? extends Exception> closer = FailableConsumer.nop();
051
052        /**
053         * Initializer supplier called by {@link #initialize()}.
054         */
055        private FailableSupplier<T, ? extends Exception> initializer = FailableSupplier.nul();
056
057        /**
058         * Constructs a new instance.
059         */
060        public AbstractBuilder() {
061            // empty
062        }
063
064        /**
065         * Gets the closer consumer called by {@link #close()}.
066         *
067         * @return the closer consumer called by {@link #close()}.
068         */
069        public FailableConsumer<T, ? extends Exception> getCloser() {
070            return closer;
071        }
072
073        /**
074         * Gets the initializer supplier called by {@link #initialize()}.
075         *
076         * @return the initializer supplier called by {@link #initialize()}.
077         */
078        public FailableSupplier<T, ? extends Exception> getInitializer() {
079            return initializer;
080        }
081
082        /**
083         * Sets the closer consumer called by {@link #close()}.
084         *
085         * @param closer the consumer called by {@link #close()}.
086         * @return {@code this} instance.
087         */
088        public B setCloser(final FailableConsumer<T, ? extends Exception> closer) {
089            this.closer = closer != null ? closer : FailableConsumer.nop();
090            return asThis();
091        }
092
093        /**
094         * Sets the initializer supplier called by {@link #initialize()}.
095         *
096         * @param initializer the supplier called by {@link #initialize()}.
097         * @return {@code this} instance.
098         */
099        public B setInitializer(final FailableSupplier<T, ? extends Exception> initializer) {
100            this.initializer = initializer != null ? initializer : FailableSupplier.nul();
101            return asThis();
102        }
103
104    }
105
106    /**
107     * Closer consumer called by {@link #close()}.
108     */
109    private final FailableConsumer<? super T, ? extends Exception> closer;
110
111    /**
112     * Initializer supplier called by {@link #initialize()}.
113     */
114    private final FailableSupplier<? extends T, ? extends Exception> initializer;
115
116    /**
117     * Constructs a new instance.
118     */
119    public AbstractConcurrentInitializer() {
120        this(FailableSupplier.nul(), FailableConsumer.nop());
121    }
122
123    /**
124     * Constructs a new instance.
125     *
126     * @param initializer the initializer supplier called by {@link #initialize()}.
127     * @param closer the closer consumer called by {@link #close()}.
128     */
129    AbstractConcurrentInitializer(final FailableSupplier<? extends T, ? extends Exception> initializer, final FailableConsumer<? super T, ? extends Exception> closer) {
130        this.closer = Objects.requireNonNull(closer, "closer");
131        this.initializer = Objects.requireNonNull(initializer, "initializer");
132    }
133
134    /**
135     * Calls the closer with the manager object.
136     *
137     * @throws ConcurrentException Thrown by the closer.
138     * @since 3.14.0
139     */
140    public void close() throws ConcurrentException {
141        if (isInitialized()) {
142            try {
143                closer.accept(get());
144            } catch (final Exception e) {
145                // This intentionally does not duplicate the logic in initialize
146                // or care about the generic type E.
147                //
148                // initialize may run inside a Future and it does not make sense
149                // to wrap an exception stored inside a Future. However close()
150                // always runs on the current thread so it always wraps in a
151                // ConcurrentException
152                throw new ConcurrentException(ExceptionUtils.throwUnchecked(e));
153            }
154        }
155    }
156
157    /**
158     * Gets an Exception with a type of E as defined by a concrete subclass of this class.
159     *
160     * @param e The actual exception that was thrown
161     * @return a new exception with the actual type of E, that wraps e.
162     */
163    protected abstract E getTypedException(Exception e);
164
165    /**
166     * Creates and initializes the object managed by this {@code
167     * ConcurrentInitializer}. This method is called by {@link #get()} when the object is accessed for the first time. An implementation can focus on the
168     * creation of the object. No synchronization is needed, as this is already handled by {@code get()}.
169     * <p>
170     * Subclasses and clients that do not provide an initializer are expected to implement this method.
171     * </p>
172     *
173     * @return the managed data object
174     * @throws E if an error occurs during object creation
175     */
176    @SuppressWarnings("unchecked")
177    protected T initialize() throws E {
178        try {
179            return initializer.get();
180        } catch (final Exception e) {
181            // Do this first so we don't pass a RuntimeException or Error into an exception constructor
182            ExceptionUtils.throwUnchecked(e);
183
184            // Depending on the subclass of AbstractConcurrentInitializer E can be Exception or ConcurrentException
185            // if E is Exception the if statement below will always be true, and the new Exception object created
186            // in getTypedException will never be thrown. If E is ConcurrentException and the if statement is false
187            // we throw the ConcurrentException returned from getTypedException, which wraps the original exception.
188            final E typedException = getTypedException(e);
189            if (typedException.getClass().isAssignableFrom(e.getClass())) {
190                throw (E) e;
191            }
192            throw typedException;
193        }
194    }
195
196    /**
197     * Returns true if initialization has been completed. If initialization threw an exception this will return false, but it will return true if a subsequent
198     * call to initialize completes successfully. If the implementation of ConcurrentInitializer can initialize multiple objects, this will only return true if
199     * all objects have been initialized.
200     *
201     * @return true if all initialization is complete, otherwise false
202     */
203    protected abstract boolean isInitialized();
204
205}