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 *      https://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.io.channels;
019
020import java.io.Closeable;
021import java.lang.reflect.Proxy;
022import java.nio.channels.AsynchronousChannel;
023import java.nio.channels.ByteChannel;
024import java.nio.channels.Channel;
025import java.nio.channels.GatheringByteChannel;
026import java.nio.channels.InterruptibleChannel;
027import java.nio.channels.NetworkChannel;
028import java.nio.channels.ReadableByteChannel;
029import java.nio.channels.ScatteringByteChannel;
030import java.nio.channels.SeekableByteChannel;
031import java.nio.channels.WritableByteChannel;
032import java.util.LinkedHashSet;
033import java.util.Objects;
034import java.util.Set;
035
036/**
037 * Creates a close-shielding proxy for a {@link Channel}.
038 *
039 * <p>The returned proxy implements all {@link Channel} sub-interfaces that are both supported by this implementation and actually implemented by the given
040 * delegate.</p>
041 *
042 * <p>The following interfaces are supported:</p>
043 *
044 * <ul>
045 * <li>{@link AsynchronousChannel}</li>
046 * <li>{@link ByteChannel}</li>
047 * <li>{@link Channel}</li>
048 * <li>{@link GatheringByteChannel}</li>
049 * <li>{@link InterruptibleChannel}</li>
050 * <li>{@link NetworkChannel}</li>
051 * <li>{@link ReadableByteChannel}</li>
052 * <li>{@link ScatteringByteChannel}</li>
053 * <li>{@link SeekableByteChannel}</li>
054 * <li>{@link WritableByteChannel}</li>
055 * </ul>
056 *
057 * @see Channel
058 * @see Closeable
059 * @since 2.21.0
060 */
061public final class CloseShieldChannel {
062
063    private static final Class<?>[] EMPTY = {};
064
065    private static Set<Class<?>> collectChannelInterfaces(final Class<?> type, final Set<Class<?>> out) {
066        Class<?> currentType = type;
067        // Visit interfaces
068        while (currentType != null) {
069            for (final Class<?> iface : currentType.getInterfaces()) {
070                if (CloseShieldChannelHandler.isSupported(iface) && out.add(iface)) {
071                    collectChannelInterfaces(iface, out);
072                }
073            }
074            currentType = currentType.getSuperclass();
075        }
076        return out;
077    }
078
079    /**
080     * Wraps a channel to shield it from being closed.
081     *
082     * @param channel The underlying channel to shield, not {@code null}.
083     * @param <T>     A supported channel type.
084     * @return A proxy that shields {@code close()} and enforces closed semantics on other calls.
085     * @throws ClassCastException if {@code T} is not a supported channel type.
086     * @throws NullPointerException if {@code channel} is {@code null}.
087     */
088    @SuppressWarnings({ "unchecked", "resource" }) // caller closes
089    public static <T extends Channel> T wrap(final T channel) {
090        Objects.requireNonNull(channel, "channel");
091        // Fast path: already our shield
092        if (Proxy.isProxyClass(channel.getClass()) && Proxy.getInvocationHandler(channel) instanceof CloseShieldChannelHandler) {
093            return channel;
094        }
095        // Collect only Channel sub-interfaces.
096        final Set<Class<?>> set = collectChannelInterfaces(channel.getClass(), new LinkedHashSet<>());
097        // fallback to root surface
098        return (T) Proxy.newProxyInstance(channel.getClass().getClassLoader(), // use delegate's loader
099                set.isEmpty() ? new Class<?>[] { Channel.class } : set.toArray(EMPTY), new CloseShieldChannelHandler(channel));
100    }
101
102    private CloseShieldChannel() {
103        // no instance
104    }
105}