SocketOptionsMapper.java
/*
* junixsocket
*
* Copyright 2009-2024 Christian Kohlschütter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.newsclub.net.unix;
import java.net.SocketOption;
import java.net.SocketOptions;
import java.net.StandardSocketOptions;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Maps new SocketOption classes to the old integer-based scheme.
*
* @author Christian Kohlschütter
*/
final class SocketOptionsMapper {
private static final Map<SocketOption<?>, SocketOptionRef> SOCKET_OPTIONS = new HashMap<>();
static final Set<SocketOption<?>> SUPPORTED_SOCKET_OPTIONS;
static {
// Interesting fact of the day:
//
// The old int-optID API expects SocketException to be thrown for unknown values,
// and the new Java 9+ SocketOption API expects UnsupportedOperationException to be
// thrown.
//
// However, at least up to Java 11, the new API calls the old API under the hood
// (see SocketImpl), so in reality, SocketException is thrown then.
//
// In Java 15 (?), they refactored the class, and the automatic mapping no longer occurs
// for custom socket implementations, which means we have to roll our own mapping.
//
// As you see below, unlike Java 11's SocketImpl, our getOption/setOption implementation
// is not a series of chained if-statements. Instead, we have a central place where
// the mapping is defined using generics, maps and some Java boilerplate. I don't think
// it's necessarily faster but since we now have a single, central place where these mappings
// are defined, it feels cleaner and better to not repeat yourself.
registerSocketOption(StandardSocketOptions.SO_KEEPALIVE, SocketOptions.SO_KEEPALIVE, false);
registerSocketOption(StandardSocketOptions.SO_SNDBUF, SocketOptions.SO_SNDBUF, true);
registerSocketOption(StandardSocketOptions.SO_RCVBUF, SocketOptions.SO_RCVBUF, true);
registerSocketOption(StandardSocketOptions.SO_REUSEADDR, SocketOptions.SO_REUSEADDR, true);
registerSocketOption(StandardSocketOptions.SO_LINGER, SocketOptions.SO_LINGER, true);
registerSocketOption(StandardSocketOptions.IP_TOS, SocketOptions.IP_TOS, false);
registerSocketOption(StandardSocketOptions.TCP_NODELAY, SocketOptions.TCP_NODELAY, false);
Set<SocketOption<?>> supportedOptions = new HashSet<>();
for (Map.Entry<SocketOption<?>, SocketOptionRef> en : SOCKET_OPTIONS.entrySet()) {
if (en.getValue().supported) {
supportedOptions.add(en.getKey());
}
}
SUPPORTED_SOCKET_OPTIONS = Collections.unmodifiableSet(supportedOptions);
}
private static <T> void registerSocketOption(SocketOption<T> option, int socketOptionsId,
boolean supported) {
SOCKET_OPTIONS.put(option, new SocketOptionRef(socketOptionsId, supported));
}
static Integer resolve(SocketOption<?> option) {
SocketOptionRef ref = SOCKET_OPTIONS.get(option);
if (ref == null) {
return null;
} else {
return ref.optionId;
}
}
private static final class SocketOptionRef {
private final int optionId;
private final boolean supported;
SocketOptionRef(int optionId, boolean supported) {
this.optionId = optionId;
this.supported = supported;
}
}
}