1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 22:53:22 +00:00

Make PartialOptions immutable

- Switch to using OptionalInt/OptionalLong instead of @Nullable
   Long/Integers. I know IntelliJ complains, but it avoids the risk of
   implicit unboxing.

 - Instead of mutating PartialOptions, we now define a merge() function
   which returns the new options. This simplifies the logic in
   AddressRule a whole bunch.
This commit is contained in:
Jonathan Coates 2022-10-31 09:08:39 +00:00
parent 14cb97cba1
commit 7701b343fb
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
6 changed files with 70 additions and 62 deletions

View File

@ -18,10 +18,7 @@
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.util.Arrays; import java.util.*;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Mod( ComputerCraft.MOD_ID ) @Mod( ComputerCraft.MOD_ID )
@ -44,8 +41,8 @@ public final class ComputerCraft
public static boolean httpEnabled = true; public static boolean httpEnabled = true;
public static boolean httpWebsocketEnabled = true; public static boolean httpWebsocketEnabled = true;
public static List<AddressRule> httpRules = Collections.unmodifiableList( Arrays.asList( public static List<AddressRule> httpRules = Collections.unmodifiableList( Arrays.asList(
AddressRule.parse( "$private", null, Action.DENY.toPartial() ), AddressRule.parse( "$private", OptionalInt.empty(), Action.DENY.toPartial() ),
AddressRule.parse( "*", null, Action.ALLOW.toPartial() ) AddressRule.parse( "*", OptionalInt.empty(), Action.ALLOW.toPartial() )
) ); ) );
public static int httpMaxRequests = 16; public static int httpMaxRequests = 16;

View File

@ -6,13 +6,17 @@
package dan200.computercraft.core.apis.http.options; package dan200.computercraft.core.apis.http.options;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.OptionalInt;
import java.util.OptionalLong;
public enum Action public enum Action
{ {
ALLOW, ALLOW,
DENY; DENY;
private final PartialOptions partial = new PartialOptions( this, null, null, null, null ); private final PartialOptions partial = new PartialOptions(
this, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty(), OptionalInt.empty()
);
@Nonnull @Nonnull
public PartialOptions toPartial() public PartialOptions toPartial()

View File

@ -16,6 +16,7 @@
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.OptionalInt;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
@ -29,10 +30,10 @@ public final class AddressRule
public static final int WEBSOCKET_MESSAGE = 128 * 1024; public static final int WEBSOCKET_MESSAGE = 128 * 1024;
private final AddressPredicate predicate; private final AddressPredicate predicate;
private final Integer port; private final OptionalInt port;
private final PartialOptions partial; private final PartialOptions partial;
private AddressRule( @Nonnull AddressPredicate predicate, @Nullable Integer port, @Nonnull PartialOptions partial ) private AddressRule( @Nonnull AddressPredicate predicate, OptionalInt port, @Nonnull PartialOptions partial )
{ {
this.predicate = predicate; this.predicate = predicate;
this.partial = partial; this.partial = partial;
@ -40,7 +41,7 @@ private AddressRule( @Nonnull AddressPredicate predicate, @Nullable Integer port
} }
@Nullable @Nullable
public static AddressRule parse( String filter, @Nullable Integer port, @Nonnull PartialOptions partial ) public static AddressRule parse( String filter, OptionalInt port, @Nonnull PartialOptions partial )
{ {
int cidr = filter.indexOf( '/' ); int cidr = filter.indexOf( '/' );
if( cidr >= 0 ) if( cidr >= 0 )
@ -72,7 +73,7 @@ else if( filter.equalsIgnoreCase( "$private" ) )
*/ */
private boolean matches( String domain, int port, InetAddress address, Inet4Address ipv4Address ) private boolean matches( String domain, int port, InetAddress address, Inet4Address ipv4Address )
{ {
if( this.port != null && this.port != port ) return false; if( this.port.isPresent() && this.port.getAsInt() != port ) return false;
return predicate.matches( domain ) return predicate.matches( domain )
|| predicate.matches( address ) || predicate.matches( address )
|| (ipv4Address != null && predicate.matches( ipv4Address )); || (ipv4Address != null && predicate.matches( ipv4Address ));
@ -80,8 +81,7 @@ private boolean matches( String domain, int port, InetAddress address, Inet4Addr
public static Options apply( Iterable<? extends AddressRule> rules, String domain, InetSocketAddress socketAddress ) public static Options apply( Iterable<? extends AddressRule> rules, String domain, InetSocketAddress socketAddress )
{ {
PartialOptions options = null; PartialOptions options = PartialOptions.DEFAULT;
boolean hasMany = false;
int port = socketAddress.getPort(); int port = socketAddress.getPort();
InetAddress address = socketAddress.getAddress(); InetAddress address = socketAddress.getAddress();
@ -91,24 +91,9 @@ public static Options apply( Iterable<? extends AddressRule> rules, String domai
for( AddressRule rule : rules ) for( AddressRule rule : rules )
{ {
if( !rule.matches( domain, port, address, ipv4Address ) ) continue; if( !rule.matches( domain, port, address, ipv4Address ) ) continue;
options = options.merge( rule.partial );
if( options == null )
{
options = rule.partial;
}
else
{
if( !hasMany )
{
options = options.copy();
hasMany = true;
}
options.merge( rule.partial );
}
} }
return (options == null ? PartialOptions.DEFAULT : options).toOptions(); return options.toOptions();
} }
} }

View File

@ -14,6 +14,8 @@
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
@ -48,7 +50,7 @@ public static UnmodifiableConfig makeRule( String host, Action action )
public static boolean checkRule( UnmodifiableConfig builder ) public static boolean checkRule( UnmodifiableConfig builder )
{ {
String hostObj = get( builder, "host", String.class ).orElse( null ); String hostObj = get( builder, "host", String.class ).orElse( null );
Integer port = get( builder, "port", Number.class ).map( Number::intValue ).orElse( null ); OptionalInt port = unboxOptInt( get( builder, "port", Number.class ) );
return hostObj != null && checkEnum( builder, "action", Action.class ) return hostObj != null && checkEnum( builder, "action", Action.class )
&& check( builder, "port", Number.class ) && check( builder, "port", Number.class )
&& check( builder, "timeout", Number.class ) && check( builder, "timeout", Number.class )
@ -65,11 +67,11 @@ public static AddressRule parseRule( UnmodifiableConfig builder )
if( hostObj == null ) return null; if( hostObj == null ) return null;
Action action = getEnum( builder, "action", Action.class ).orElse( null ); Action action = getEnum( builder, "action", Action.class ).orElse( null );
Integer port = get( builder, "port", Number.class ).map( Number::intValue ).orElse( null ); OptionalInt port = unboxOptInt( get( builder, "port", Number.class ) );
Integer timeout = get( builder, "timeout", Number.class ).map( Number::intValue ).orElse( null ); OptionalInt timeout = unboxOptInt( get( builder, "timeout", Number.class ) );
Long maxUpload = get( builder, "max_upload", Number.class ).map( Number::longValue ).orElse( null ); OptionalLong maxUpload = unboxOptLong( get( builder, "max_upload", Number.class ).map( Number::longValue ) );
Long maxDownload = get( builder, "max_download", Number.class ).map( Number::longValue ).orElse( null ); OptionalLong maxDownload = unboxOptLong( get( builder, "max_download", Number.class ).map( Number::longValue ) );
Integer websocketMessage = get( builder, "websocket_message", Number.class ).map( Number::intValue ).orElse( null ); OptionalInt websocketMessage = unboxOptInt( get( builder, "websocket_message", Number.class ).map( Number::intValue ) );
PartialOptions options = new PartialOptions( PartialOptions options = new PartialOptions(
action, action,
@ -122,6 +124,16 @@ private static <T extends Enum<T>> Optional<T> getEnum( UnmodifiableConfig confi
return get( config, field, String.class ).map( x -> parseEnum( klass, x ) ); return get( config, field, String.class ).map( x -> parseEnum( klass, x ) );
} }
private static OptionalLong unboxOptLong( Optional<? extends Number> value )
{
return value.map( Number::intValue ).map( OptionalLong::of ).orElse( OptionalLong.empty() );
}
private static OptionalInt unboxOptInt( Optional<? extends Number> value )
{
return value.map( Number::intValue ).map( OptionalInt::of ).orElse( OptionalInt.empty() );
}
@Nullable @Nullable
private static <T extends Enum<T>> T parseEnum( Class<T> klass, String x ) private static <T extends Enum<T>> T parseEnum( Class<T> klass, String x )
{ {

View File

@ -5,21 +5,25 @@
*/ */
package dan200.computercraft.core.apis.http.options; package dan200.computercraft.core.apis.http.options;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.util.OptionalInt;
import java.util.OptionalLong;
public final class PartialOptions public final class PartialOptions
{ {
static final PartialOptions DEFAULT = new PartialOptions( null, null, null, null, null ); public static final PartialOptions DEFAULT = new PartialOptions(
null, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty(), OptionalInt.empty()
);
Action action; private final @Nullable Action action;
Long maxUpload; private final OptionalLong maxUpload;
Long maxDownload; private final OptionalLong maxDownload;
Integer timeout; private final OptionalInt timeout;
Integer websocketMessage; private final OptionalInt websocketMessage;
Options options; private @Nullable Options options;
PartialOptions( Action action, Long maxUpload, Long maxDownload, Integer timeout, Integer websocketMessage ) public PartialOptions( @Nullable Action action, OptionalLong maxUpload, OptionalLong maxDownload, OptionalInt timeout, OptionalInt websocketMessage )
{ {
this.action = action; this.action = action;
this.maxUpload = maxUpload; this.maxUpload = maxUpload;
@ -28,31 +32,36 @@ public final class PartialOptions
this.websocketMessage = websocketMessage; this.websocketMessage = websocketMessage;
} }
@Nonnull
Options toOptions() Options toOptions()
{ {
if( options != null ) return options; if( options != null ) return options;
return options = new Options( return options = new Options(
action == null ? Action.DENY : action, action == null ? Action.DENY : action,
maxUpload == null ? AddressRule.MAX_UPLOAD : maxUpload, maxUpload.orElse( AddressRule.MAX_UPLOAD ),
maxDownload == null ? AddressRule.MAX_DOWNLOAD : maxDownload, maxDownload.orElse( AddressRule.MAX_DOWNLOAD ),
timeout == null ? AddressRule.TIMEOUT : timeout, timeout.orElse( AddressRule.TIMEOUT ),
websocketMessage == null ? AddressRule.WEBSOCKET_MESSAGE : websocketMessage websocketMessage.orElse( AddressRule.WEBSOCKET_MESSAGE )
); );
} }
void merge( @Nonnull PartialOptions other ) /**
* Perform a left-biased union of two {@link PartialOptions}.
*
* @param other The other partial options to combine with.
* @return The merged options map.
*/
PartialOptions merge( PartialOptions other )
{ {
if( action == null && other.action != null ) action = other.action; // Short circuit for DEFAULT. This has no effect on the outcome, but avoids an allocation.
if( maxUpload == null && other.maxUpload != null ) maxUpload = other.maxUpload; if( this == DEFAULT ) return other;
if( maxDownload == null && other.maxDownload != null ) maxDownload = other.maxDownload;
if( timeout == null && other.timeout != null ) timeout = other.timeout;
if( websocketMessage == null && other.websocketMessage != null ) websocketMessage = other.websocketMessage;
}
PartialOptions copy() return new PartialOptions(
{ action == null && other.action != null ? other.action : action,
return new PartialOptions( action, maxUpload, maxDownload, timeout, websocketMessage ); maxUpload.isPresent() ? maxUpload : other.maxUpload,
maxDownload.isPresent() ? maxDownload : other.maxDownload,
timeout.isPresent() ? timeout : other.timeout,
websocketMessage.isPresent() ? websocketMessage : other.websocketMessage
);
} }
} }

View File

@ -12,6 +12,7 @@
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collections; import java.util.Collections;
import java.util.OptionalInt;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ -21,8 +22,8 @@ public class AddressRuleTest
public void matchesPort() public void matchesPort()
{ {
Iterable<AddressRule> rules = Collections.singletonList( AddressRule.parse( Iterable<AddressRule> rules = Collections.singletonList( AddressRule.parse(
"127.0.0.1", 8080, "127.0.0.1", OptionalInt.of( 8080 ),
new PartialOptions( Action.ALLOW, null, null, null, null ) Action.ALLOW.toPartial()
) ); ) );
assertEquals( apply( rules, "localhost", 8080 ).action, Action.ALLOW ); assertEquals( apply( rules, "localhost", 8080 ).action, Action.ALLOW );