Skip to content

Commit

Permalink
[all] Minor code cleanup in various enums (#1182)
Browse files Browse the repository at this point in the history
Added a valueOf function in EnumUtils to reduce code duplication and
standardize the error message for badly handled enums.

Also added unit tests for the mappings of all VeniceEnumValue.
  • Loading branch information
FelixGV authored Sep 17, 2024
1 parent eedc5d8 commit 21f4d23
Show file tree
Hide file tree
Showing 16 changed files with 323 additions and 64 deletions.
14 changes: 14 additions & 0 deletions docs/dev_guide/how_to/style_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ rules and there may be good reasons to deviate from them occasionally. When devi
explaining why we deviated, whether it was intentional, or due to the need for expediency. This helps future maintainers
understand what is actually worth cleaning up and how careful they need to be when doing it.

### Compatibility

We care about compatibility across versions of the software. This is a bidirectional statement. Old clients need to be
able to talk with new servers, and new clients with old servers as well. It also includes interactions across lifetimes
of the same process, for example, state persisted by an old version of the server code should be usable by a newer
version of the server code, and vice versa. If compatibility is impossible, then we should look for ways to achieve
correct behavior anyway (e.g. potentially at the cost of efficiency, such as the server needing to throw away and
regenerate the state).

For enums which are going to be communicated across processes or across lifetimes of the same process, consider using
[VeniceEnumValue](http://venicedb.org/javadoc/com/linkedin/venice/utils/VeniceEnumValue.html), [EnumUtils](http://venicedb.org/javadoc/com/linkedin/venice/utils/EnumUtils.html)
and related unit test classes, which provide a structure to minimize the chance that we mistakenly change the mapping of
numeric ID -> enum value.

### JavaDoc

Speaking of comments, we ideally want JavaDoc at the top of all classes. The top of class JavaDoc should indicate the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.linkedin.venice.compression;

import com.linkedin.venice.exceptions.VeniceException;
import com.linkedin.venice.utils.EnumUtils;
import com.linkedin.venice.utils.VeniceEnumValue;
import java.util.List;


/**
Expand All @@ -15,13 +15,14 @@ public enum CompressionStrategy implements VeniceEnumValue {
private final int value;
private final boolean compressionEnabled;

private static final CompressionStrategy[] TYPES_ARRAY = EnumUtils.getEnumValuesArray(CompressionStrategy.class);
private static final List<CompressionStrategy> TYPES = EnumUtils.getEnumValuesList(CompressionStrategy.class);

CompressionStrategy(int value, boolean compressionEnabled) {
this.value = value;
this.compressionEnabled = compressionEnabled;
}

@Override
public int getValue() {
return value;
}
Expand All @@ -31,14 +32,10 @@ public boolean isCompressionEnabled() {
}

public static CompressionStrategy valueOf(int value) {
try {
return TYPES_ARRAY[value];
} catch (IndexOutOfBoundsException e) {
throw new VeniceException("Invalid compression strategy: " + value);
}
return EnumUtils.valueOf(TYPES, value, CompressionStrategy.class);
}

public static int getCompressionStrategyTypesArrayLength() {
return TYPES_ARRAY.length;
return TYPES.size();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.linkedin.venice.exceptions.VeniceException;
import com.linkedin.venice.utils.EnumUtils;
import com.linkedin.venice.utils.VeniceEnumValue;
import java.util.List;


public enum ComputeOperationType implements VeniceEnumValue {
Expand All @@ -21,7 +22,7 @@ public enum ComputeOperationType implements VeniceEnumValue {

private final ReadComputeOperator operator;
private final int value;
private static final ComputeOperationType[] TYPES_ARRAY = EnumUtils.getEnumValuesArray(ComputeOperationType.class);
private static final List<ComputeOperationType> TYPES = EnumUtils.getEnumValuesList(ComputeOperationType.class);

ComputeOperationType(int value, ReadComputeOperator operator) {
this.value = value;
Expand All @@ -44,17 +45,14 @@ public Object getNewInstance() {
}

public static ComputeOperationType valueOf(int value) {
try {
return TYPES_ARRAY[value];
} catch (IndexOutOfBoundsException e) {
throw new VeniceException("Invalid compute operation type: " + value);
}
return EnumUtils.valueOf(TYPES, value, ComputeOperationType.class);
}

public static ComputeOperationType valueOf(ComputeOperation operation) {
return valueOf(operation.operationType);
}

@Override
public int getValue() {
return value;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.linkedin.venice.utils;

import com.linkedin.venice.exceptions.VeniceException;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;


public class EnumUtils {
Expand All @@ -15,8 +20,12 @@ public class EnumUtils {
* valuable since it's a hot path call. If these assumptions change (e.g. if we deprecate some message
* types such that there are gaps, then we may need to relax some constraints here and increase checks
* in valueOf(int) instead.
*
* The list returned by this utility function should:
* - be stored statically
* - be accessed via {@link #valueOf(List, int, Class)}
*/
public static <V extends VeniceEnumValue> V[] getEnumValuesArray(Class<V> enumToProvideArrayOf) {
public static <V extends VeniceEnumValue> List<V> getEnumValuesList(Class<V> enumToProvideArrayOf) {
int maxValue = -1;
String name = enumToProvideArrayOf.getSimpleName();
for (V type: enumToProvideArrayOf.getEnumConstants()) {
Expand Down Expand Up @@ -44,6 +53,22 @@ public static <V extends VeniceEnumValue> V[] getEnumValuesArray(Class<V> enumTo
name + " values should not have gaps, but " + i + " is not associated with any type!");
}
}
return array;
return Collections.unmodifiableList(Arrays.asList(array));
}

public static <V extends VeniceEnumValue> V valueOf(List<V> valuesList, int value, Class<V> enumClass) {
return valueOf(valuesList, value, enumClass, VeniceException::new);
}

public static <V extends VeniceEnumValue> V valueOf(
List<V> valuesList,
int value,
Class<V> enumClass,
Function<String, VeniceException> exceptionConstructor) {
try {
return valuesList.get(value);
} catch (IndexOutOfBoundsException e) {
throw exceptionConstructor.apply("Invalid enum value for " + enumClass.getSimpleName() + ": " + value);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
package com.linkedin.venice.utils;

/**
* N.B.: Although there is no way to force this via Java interfaces, the convention is that all enums implementing this
* interface should have static "valueOf" function to return the correct enum value from a given numeric value, i.e.:
*
* {@snippet id='valueOf':
* public static MyEnumType valueOf(int value) {
* return EnumUtils.valueOf(TYPES_ARRAY, value, MyEnumTpe.class);
* }
* }
*
* Note that VeniceEnumValueTest makes it easy to test the above, and we should have a subclass of that test for all
* implementations of this interface.
*/
public interface VeniceEnumValue {
int getValue();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.linkedin.venice.compression;

import com.linkedin.alpini.base.misc.CollectionUtil;
import com.linkedin.venice.utils.VeniceEnumValueTest;
import java.util.Map;


public class CompressionStrategyTest extends VeniceEnumValueTest<CompressionStrategy> {
public CompressionStrategyTest() {
super(CompressionStrategy.class);
}

@Override
protected Map<Integer, CompressionStrategy> expectedMapping() {
return CollectionUtil.<Integer, CompressionStrategy>mapBuilder()
.put(0, CompressionStrategy.NO_OP)
.put(1, CompressionStrategy.GZIP)
.put(2, CompressionStrategy.ZSTD)
.put(3, CompressionStrategy.ZSTD_WITH_DICT)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.linkedin.venice.compute.protocol.request.enums;

import com.linkedin.alpini.base.misc.CollectionUtil;
import com.linkedin.venice.utils.VeniceEnumValueTest;
import java.util.Map;


public class ComputeOperationTypeTest extends VeniceEnumValueTest<ComputeOperationType> {
public ComputeOperationTypeTest() {
super(ComputeOperationType.class);
}

@Override
protected Map<Integer, ComputeOperationType> expectedMapping() {
return CollectionUtil.<Integer, ComputeOperationType>mapBuilder()
.put(0, ComputeOperationType.DOT_PRODUCT)
.put(1, ComputeOperationType.COSINE_SIMILARITY)
.put(2, ComputeOperationType.HADAMARD_PRODUCT)
.put(3, ComputeOperationType.COUNT)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.linkedin.venice.kafka.protocol.VersionSwap;
import com.linkedin.venice.utils.EnumUtils;
import com.linkedin.venice.utils.VeniceEnumValue;
import java.util.List;


/**
Expand All @@ -28,12 +29,13 @@ public enum ControlMessageType implements VeniceEnumValue {

/** The value is the byte used on the wire format */
private final int value;
private static final ControlMessageType[] TYPES_ARRAY = EnumUtils.getEnumValuesArray(ControlMessageType.class);
private static final List<ControlMessageType> TYPES = EnumUtils.getEnumValuesList(ControlMessageType.class);

ControlMessageType(int value) {
this.value = value;
}

@Override
public int getValue() {
return value;
}
Expand Down Expand Up @@ -76,11 +78,7 @@ public Object getNewInstance() {
}

public static ControlMessageType valueOf(int value) {
try {
return TYPES_ARRAY[value];
} catch (IndexOutOfBoundsException e) {
throw new VeniceMessageException("Invalid control message type: " + value);
}
return EnumUtils.valueOf(TYPES, value, ControlMessageType.class, VeniceMessageException::new);
}

public static ControlMessageType valueOf(ControlMessage controlMessage) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.linkedin.venice.kafka.protocol.Update;
import com.linkedin.venice.utils.EnumUtils;
import com.linkedin.venice.utils.VeniceEnumValue;
import java.util.List;


/**
Expand All @@ -22,7 +23,7 @@ public enum MessageType implements VeniceEnumValue {
PUT(0, Constants.PUT_KEY_HEADER_BYTE), DELETE(1, Constants.PUT_KEY_HEADER_BYTE),
CONTROL_MESSAGE(2, Constants.CONTROL_MESSAGE_KEY_HEADER_BYTE), UPDATE(3, Constants.UPDATE_KEY_HEADER_BYTE);

private static final MessageType[] TYPES_ARRAY = EnumUtils.getEnumValuesArray(MessageType.class);
private static final List<MessageType> TYPES = EnumUtils.getEnumValuesList(MessageType.class);

private final int value;
private final byte keyHeaderByte;
Expand All @@ -36,6 +37,7 @@ public enum MessageType implements VeniceEnumValue {
* @return This is the value used in {@link com.linkedin.venice.kafka.protocol.KafkaMessageEnvelope#messageType}
* to distinguish message types.
*/
@Override
public int getValue() {
return value;
}
Expand Down Expand Up @@ -73,11 +75,7 @@ public Object getNewInstance() {
}

public static MessageType valueOf(int value) {
try {
return TYPES_ARRAY[value];
} catch (IndexOutOfBoundsException e) {
throw new VeniceMessageException("Invalid message type: " + value);
}
return EnumUtils.valueOf(TYPES, value, MessageType.class, VeniceMessageException::new);
}

public static MessageType valueOf(KafkaMessageEnvelope kafkaMessageEnvelope) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public enum MetaStoreDataType implements VeniceEnumValue {
VALUE_SCHEMAS_WRITTEN_PER_STORE_VERSION(6, Arrays.asList(KEY_STRING_STORE_NAME, KEY_STRING_VERSION_NUMBER)),
HEARTBEAT(7, Collections.singletonList(KEY_STRING_STORE_NAME));

private static final MetaStoreDataType[] TYPES_ARRAY = EnumUtils.getEnumValuesArray(MetaStoreDataType.class);
private static final List<MetaStoreDataType> TYPES = EnumUtils.getEnumValuesList(MetaStoreDataType.class);

private final int value;
private final List<String> requiredKeys;
Expand All @@ -42,16 +42,13 @@ public enum MetaStoreDataType implements VeniceEnumValue {
this.requiredKeys = requiredKeys;
}

@Override
public int getValue() {
return value;
}

public static MetaStoreDataType valueOf(int value) {
try {
return TYPES_ARRAY[value];
} catch (IndexOutOfBoundsException e) {
throw new VeniceException("Invalid compression strategy: " + value);
}
return EnumUtils.valueOf(TYPES, value, MetaStoreDataType.class);
}

public StoreMetaKey getStoreMetaKey(Map<String, String> params) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.linkedin.venice.writer;

import com.linkedin.venice.exceptions.VeniceException;
import com.linkedin.venice.utils.EnumUtils;
import com.linkedin.venice.utils.VeniceEnumValue;
import java.util.List;


/**
Expand All @@ -19,7 +19,7 @@ public enum LeaderCompleteState implements VeniceEnumValue {
LEADER_COMPLETED(1);

private final int value;
private static final LeaderCompleteState[] TYPES_ARRAY = EnumUtils.getEnumValuesArray(LeaderCompleteState.class);
private static final List<LeaderCompleteState> TYPES = EnumUtils.getEnumValuesList(LeaderCompleteState.class);

LeaderCompleteState(int value) {
this.value = value;
Expand All @@ -34,11 +34,7 @@ public static LeaderCompleteState getLeaderCompleteState(boolean isLeaderComplet
}

public static LeaderCompleteState valueOf(int value) {
try {
return TYPES_ARRAY[value];
} catch (IndexOutOfBoundsException e) {
throw new VeniceException("Invalid LeaderCompleteState: " + value);
}
return EnumUtils.valueOf(TYPES, value, LeaderCompleteState.class);
}

@Override
Expand Down
Loading

0 comments on commit 21f4d23

Please sign in to comment.