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