Skip to content

Common > Status Code: 비트에 의미를 담은 상태 코드 공급기 구현 #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5f62791
feat(common): add type-safe markers (`Present`, `Missing`)
merge-simpson Apr 29, 2025
9705127
fix(board): use type-safe markers of common module
merge-simpson Apr 29, 2025
bff9a82
feat(common): add `StatusParameters` for status code
merge-simpson Apr 29, 2025
223058c
feat(common): add `StatusCodeUtil` calculating code via `StatusParame…
merge-simpson Apr 29, 2025
ec69f40
refactor(common): add constants about status code
merge-simpson Apr 29, 2025
364c633
refactor(common): make constants depend on other constants
merge-simpson Apr 29, 2025
2f0fbd9
feat(common): add status code exception, error codes
merge-simpson May 1, 2025
fd0f4e2
refactor(common): reorder constants of status code error code
merge-simpson May 1, 2025
4415682
feat(common): append status code error codes
merge-simpson May 1, 2025
78d836d
feat(common): add long type custom status code parameters and its sup…
merge-simpson May 1, 2025
8aa819d
chore(common): remove useless comment
merge-simpson May 1, 2025
9973187
docs(common): explain custom status parameters
merge-simpson May 1, 2025
aedc9b7
build(common): add test implementations
merge-simpson May 1, 2025
d02aea5
test(common): test status code GP bits
merge-simpson May 1, 2025
b55cc18
test(common): test status code system info bits
merge-simpson May 1, 2025
c72f0d7
test(common): test status code category, instance detail bits
merge-simpson May 1, 2025
fefea45
test(common): refactor status code util test with dynamic-generated test
merge-simpson May 1, 2025
dca9740
chore(common): format code style `StatusCodeErrorCode`
merge-simpson May 1, 2025
de3f19c
chore(common): todo in `StatusCodeUtil`
merge-simpson May 1, 2025
15180a4
refactor(common): replace some fields of `CustomStatusParameters` wit…
merge-simpson May 3, 2025
9b53ab8
feat(common): add methods to get gp shift, sys info shift, cate shift…
merge-simpson May 3, 2025
31f88ac
feat(common): get long status code from `CustomStatusParameters` in `…
merge-simpson May 4, 2025
cc2e30b
build(common): test implementation "spring-web"
merge-simpson May 4, 2025
b0e4b8a
test(common): test `CustomStatusParameters` with `StatusCodeUtil`
merge-simpson May 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// FIXME remove this after root build.gradle.kts supplies below dependencies.
dependencies {
testImplementation("org.springframework:spring-web:6.2.3")

testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
testImplementation("io.mockk:mockk:1.13.12")
testImplementation(kotlin("script-runtime"))
testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.3")
}

// FIXME remove this after root build.gradle.kts supplies below task.
kotlin {
sourceSets {
test {
kotlin.srcDirs(listOf("src/test/kotlin"))
}
}
}
6 changes: 6 additions & 0 deletions common/src/main/java/nettee/common/marker/TypeSafeMarker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package nettee.common.marker;

public sealed interface TypeSafeMarker permits TypeSafeMarker.Present, TypeSafeMarker.Missing {
final class Present implements TypeSafeMarker { private Present() {} }
final class Missing implements TypeSafeMarker { private Missing() {} }
}
119 changes: 119 additions & 0 deletions common/src/main/java/nettee/common/status/CustomStatusParameters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package nettee.common.status;

import nettee.common.marker.TypeSafeMarker;
import nettee.common.marker.TypeSafeMarker.Missing;
import nettee.common.marker.TypeSafeMarker.Present;

import java.util.Collection;

import static nettee.common.status.exception.StatusCodeErrorCode.CATEGORY_BITS_OVERFLOW;
import static nettee.common.status.exception.StatusCodeErrorCode.INSTANCE_DETAIL_BITS_OVERFLOW;
import static nettee.common.status.exception.StatusCodeErrorCode.SYS_INFO_OVERFLOW;

/**
* {@code long} 타입 이내에서 GP bits, system information bits, category bits, instance detail bits 구간별 사이즈를 커스텀하여
* 사용할 수 있도록 일반화한 클래스입니다.
*
* @param <C> Category bits 입력을 필수로 합니다. (컴파일타임 체크)
* @param <I> Instance detail bits 입력을 필수로 합니다. (컴파일타임 체크)
*/
public class CustomStatusParameters<
C extends TypeSafeMarker,
I extends TypeSafeMarker
> {
private final CustomStatusParametersSupplier supplier;

private long generalPurposeBits;
private long systemInfoBits;

private long categoryBits;
private long instanceBits;

CustomStatusParameters(CustomStatusParametersSupplier supplier) {
this.supplier = supplier;
}

public static CustomStatusParameters<Missing, Missing> generateWith(CustomStatusParametersSupplier supplier) {
return new CustomStatusParameters<>(supplier);
}

public CustomStatusParameters<C, I> generalPurposeFeatures(
Collection<String> extendedFeatures,
LongGeneralPurposeFeatures... features
) {
generalPurposeBits = supplier.features().getValueOf(extendedFeatures, features);
return this;
}

public CustomStatusParameters<C, I> generalPurposeFeatures(LongGeneralPurposeFeatures... features) {
generalPurposeBits = supplier.features().getValueOf(features);
return this;
}

public CustomStatusParameters<C, I> systemInfoBits(long systemInfoBits) {
long sysInfoMax = (1L << supplier.systemInfoBitSize()) - 1;
if (systemInfoBits > sysInfoMax) {
throw SYS_INFO_OVERFLOW.exception();
}
this.systemInfoBits = systemInfoBits;
return this;
}

public CustomStatusParameters<Present, I> categoryBits(long categoryBits) {
long categoryMax = (1L << supplier.categoryBitSize()) - 1;
if (categoryBits > categoryMax) {
throw CATEGORY_BITS_OVERFLOW.exception();
}
this.categoryBits = categoryBits;
@SuppressWarnings("unchecked")
var instance = (CustomStatusParameters<Present, I>) this;
return instance;
}

public CustomStatusParameters<C, Present> instanceBits(long instanceBits) {
long instanceDetailMax = (1L << supplier.instanceDetailBitSize()) - 1;
if (instanceBits > instanceDetailMax) {
throw INSTANCE_DETAIL_BITS_OVERFLOW.exception();
}
this.instanceBits = instanceBits;
@SuppressWarnings("unchecked")
var instance = (CustomStatusParameters<C, Present>) this;
return instance;
}

public int generalPurposeBitsShift() {
return supplier.systemInfoBitSize() + supplier.categoryBitSize() + supplier.instanceDetailBitSize();
}

public int systemInfoBitsShift() {
return supplier.categoryBitSize() + supplier.instanceDetailBitSize();
}

public int categoryBitsShift() {
return supplier.instanceDetailBitSize();
}

public long generalPurposeBits() {
return generalPurposeBits;
}

public long systemInfoBits() {
return systemInfoBits;
}

public long categoryBits() {
return categoryBits;
}

public long instanceBits() {
return instanceBits;
}

public enum LongGeneralPurposeFeatures {
ALL,
READ,
UPDATE,
SUBITEM_READ,
SUBITEM_UPDATE
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package nettee.common.status;

import nettee.common.marker.TypeSafeMarker.Missing;
import nettee.common.status.CustomStatusParameters.LongGeneralPurposeFeatures;

import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;

import static nettee.common.status.exception.StatusCodeErrorCode.GP_BITS_NOT_DISTINCT;
import static nettee.common.status.exception.StatusCodeErrorCode.GP_BITS_OUT_OF_BOUND;
import static nettee.common.status.exception.StatusCodeErrorCode.GP_CUSTOM_KEY_NOT_CORRECT;
import static nettee.common.status.exception.StatusCodeErrorCode.TOTAL_BITS_OVERFLOW;

public final class CustomStatusParametersSupplier implements Supplier<CustomStatusParameters<Missing, Missing>> {
private final int sysInfoSize;
private final int categorySize;
private final int instanceDetailSize;
private final LongGeneralPurposeFeaturesValue featuresBits;

public CustomStatusParametersSupplier(
int generalPurposeBitSize,
int systemInfoBitSize,
int categoryBitSize,
int instanceBitSize,
LongGeneralPurposeFeaturesValue featuresBits
) {
// validate the total size
int totalSize = generalPurposeBitSize + systemInfoBitSize + categoryBitSize + instanceBitSize;
if (totalSize > Long.BYTES * 8) {
throw TOTAL_BITS_OVERFLOW.exception();
}

// validate that the GP list fits within the GP size.
featuresBits.validateSize(generalPurposeBitSize);

this.sysInfoSize = systemInfoBitSize;
this.categorySize = categoryBitSize;
this.instanceDetailSize = instanceBitSize;
this.featuresBits = featuresBits;
}

public int systemInfoBitSize() {
return sysInfoSize;
}

public int categoryBitSize() {
return categorySize;
}

public int instanceDetailBitSize() {
return instanceDetailSize;
}

public LongGeneralPurposeFeaturesValue features() {
return featuresBits;
}

@Override
public CustomStatusParameters<Missing, Missing> get() {
return new CustomStatusParameters<>(this);
}

/**
* 일반 목적 기능(GP features)을 독립된 비트 영역으로 표현하기 위한 클래스입니다.
*/
public static final class LongGeneralPurposeFeaturesValue {
final private long read;
final private long update;
final private long subItemRead;
final private long subItemUpdate;
final private Map<String, Long> extended;
final private long max;

/**
* @param read '일반 조회 목적'을 표현할 비트를 입력합니다.
* @param update '일반 수정 목적'을 표현할 비트를 입력합니다.
* @param subItemRead '하위항목 일반 조회 목적'을 표현할 비트를 입력합니다.
* @param subItemUpdate '하위항목 일반 수정 목적'을 표현할 비트를 입력합니다.
* @param extended 그 외 확장된 목적의 비트를 map 구조로 전달합니다. (Key: String, Value: Long)
*/
public LongGeneralPurposeFeaturesValue(
long read,
long update,
long subItemRead,
long subItemUpdate,
Map<String, Long> extended
) {
long bitsOverlapped = read | update | subItemRead | subItemUpdate;
long bitsSum = read + update + subItemRead + subItemUpdate;

if (bitsOverlapped != bitsSum) {
throw GP_BITS_NOT_DISTINCT.exception();
}

if (extended != null && !extended.isEmpty()) {
for (var value : extended.values()) {
bitsOverlapped |= value;
bitsSum += value;
if (bitsOverlapped != bitsSum) {
throw GP_BITS_NOT_DISTINCT.exception();
}
}
}

this.read = read;
this.update = update;
this.subItemRead = subItemRead;
this.subItemUpdate = subItemUpdate;
this.extended = extended;
this.max = bitsOverlapped;
}

public long read() {
return read;
}

public long update() {
return update;
}

public long subItemRead() {
return subItemRead;
}

public long subItemUpdate() {
return subItemUpdate;
}

public Map<String, Long> extended() {
return extended;
}

public long max() {
return max;
}

public long getValueOf(Collection<String> extendedKeys, LongGeneralPurposeFeatures... features) {
long value = 0;

for (var extendedKey : extendedKeys) {
if (!extended.containsKey(extendedKey)) {
throw GP_CUSTOM_KEY_NOT_CORRECT.exception();
}

value |= extended.get(extendedKey);
}

value |= getValueOf(features);

return value;
}

public long getValueOf(LongGeneralPurposeFeatures... features) {
long value = 0;

for (var feature : features) {
if (feature == LongGeneralPurposeFeatures.ALL || value == max) {
return max;
}

value |= switch (feature) {
case READ -> read;
case UPDATE -> update;
case SUBITEM_READ -> subItemRead;
case SUBITEM_UPDATE -> subItemUpdate;
default -> throw new Error("");
};
}

return value;
}

public void validateSize(int size) {
if (max >= (1L << size)) {
throw GP_BITS_OUT_OF_BOUND.exception();
}
}
}
}
19 changes: 19 additions & 0 deletions common/src/main/java/nettee/common/status/StatusCodeConstants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package nettee.common.status;

public final class StatusCodeConstants {
private StatusCodeConstants() {}

public static final class Default {
private Default() {}

public static final int GENERAL_PURPOSE_BIT_SIZE = 7;
public static final int SYSTEM_INFORMATION_BIT_SIZE = 8;
public static final int CATEGORY_BIT_SIZE = 8;
public static final int INSTANCE_DETAIL_BIT_SIZE = 8;

public static final int GENERAL_PURPOSE_SHIFT =
SYSTEM_INFORMATION_BIT_SIZE + CATEGORY_BIT_SIZE + INSTANCE_DETAIL_BIT_SIZE;
public static final int SYSTEM_INFORMATION_SHIFT = CATEGORY_BIT_SIZE + INSTANCE_DETAIL_BIT_SIZE;
public static final int CATEGORY_SHIFT = INSTANCE_DETAIL_BIT_SIZE;
}
}
29 changes: 29 additions & 0 deletions common/src/main/java/nettee/common/status/StatusCodeUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package nettee.common.status;

import nettee.common.marker.TypeSafeMarker.Present;

import static nettee.common.status.StatusCodeConstants.Default.CATEGORY_SHIFT;
import static nettee.common.status.StatusCodeConstants.Default.GENERAL_PURPOSE_SHIFT;
import static nettee.common.status.StatusCodeConstants.Default.SYSTEM_INFORMATION_SHIFT;

public final class StatusCodeUtil {
private StatusCodeUtil() {}

public static int getAsInt(StatusParameters<Present, Present> parameters) {
return (parameters.generalPurposeBits() << GENERAL_PURPOSE_SHIFT)
| (parameters.systemInfoBits() << SYSTEM_INFORMATION_SHIFT)
| (parameters.categoryBits() << CATEGORY_SHIFT)
| parameters.instanceBits();
}

public static long getAsLong(CustomStatusParameters<Present, Present> parameters) {
int gpShift = parameters.generalPurposeBitsShift();
int sysShift = parameters.systemInfoBitsShift();
int cateShift = parameters.categoryBitsShift();

return (parameters.generalPurposeBits() << gpShift)
| (parameters.systemInfoBits() << sysShift)
| (parameters.categoryBits() << cateShift)
| parameters.instanceBits();
}
}
Loading