001package ball.swing;
002/*-
003 * ##########################################################################
004 * Utilities
005 * %%
006 * Copyright (C) 2008 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import java.awt.event.WindowAdapter;
022import java.awt.event.WindowEvent;
023import java.util.concurrent.ExecutionException;
024import java.util.concurrent.Future;
025import java.util.concurrent.TimeUnit;
026import java.util.concurrent.TimeoutException;
027import javax.swing.JFrame;
028import lombok.ToString;
029
030/**
031 * {@link JFrame} implementation that provides a {@link Future} indicating
032 * when the {@link JFrame} is closed (or made invisible).
033 *
034 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
035 */
036public class ClosedFutureJFrame extends JFrame {
037    private static final long serialVersionUID = -1292559417822522869L;
038
039    /** @serial */ private final Object lock = new Object();
040    /** @serial */ private boolean isStarted = false;
041    /** @serial */ private boolean isCancelled = false;
042    /** @serial */ private final FutureImpl future = new FutureImpl();
043
044    /**
045     * Sole constructor.
046     *
047     * @param   title           The frame title.
048     */
049    public ClosedFutureJFrame(String title) {
050        super(title);
051
052        setDefaultCloseOperation(HIDE_ON_CLOSE);
053        addWindowListener(new WindowListenerImpl());
054    }
055
056    @Override
057    public void setVisible(boolean visible) {
058        synchronized (lock) {
059            isStarted |= (visible && (! isCancelled));
060            super.setVisible(visible && (! isCancelled));
061
062            if (! isVisible()) {
063                lock.notifyAll();
064            }
065        }
066    }
067
068    /**
069     * Method to get {@link Future} indicating when {@link.this}
070     * {@link JFrame} is closed.
071     *
072     * @return  The closed {@link Future}.
073     */
074    public Future<Boolean> closedFuture() { return future; }
075
076    @ToString
077    private class WindowListenerImpl extends WindowAdapter {
078        public WindowListenerImpl() { super(); }
079
080        @Override
081        public void windowClosing(WindowEvent event) { setVisible(false); }
082    }
083
084    @ToString
085    private class FutureImpl implements Future<Boolean> {
086        public FutureImpl() { }
087
088        @Override
089        public boolean cancel(boolean mayInterruptIfRunning) {
090            boolean cancelled = false;
091
092            if (isStarted) {
093                if (mayInterruptIfRunning) {
094                    cancelled = isVisible();
095                    isCancelled |= true;
096                    setVisible(false);
097                }
098            } else {
099                cancelled = true;
100                isCancelled |= true;
101            }
102
103            return cancelled;
104        }
105
106        @Override
107        public boolean isCancelled() { return isCancelled; }
108
109        @Override
110        public boolean isDone() { return (isCancelled() || (! isVisible())); }
111
112        @Override
113        public Boolean get() throws InterruptedException, ExecutionException {
114            Boolean result = null;
115
116            try {
117                result = get(0, TimeUnit.MILLISECONDS);
118            } catch (TimeoutException exception) {
119                throw new IllegalStateException(exception);
120            }
121
122            return result;
123        }
124
125        @Override
126        public Boolean get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
127            Boolean result = null;
128
129            synchronized (lock) {
130                if (! isDone()) {
131                    lock.wait(unit.toMillis(timeout), (int) (unit.toNanos(timeout) % 1000000));
132                }
133
134                result = isDone() ? true : null;
135            }
136
137            return result;
138        }
139    }
140}