Skip to content

Commit 71357fb

Browse files
Merge pull request #65 from nettee-space/feature/status-code
Common > Status Code: 비트에 의미를 담은 상태 코드 공급기 구현
2 parents 901b3dc + 03bd75a commit 71357fb

File tree

13 files changed

+845
-80
lines changed

13 files changed

+845
-80
lines changed

common/build.gradle.kts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// FIXME remove this after root build.gradle.kts supplies below dependencies.
2+
dependencies {
3+
testImplementation("org.springframework:spring-web:6.2.3")
4+
5+
testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
6+
testImplementation("io.mockk:mockk:1.13.12")
7+
testImplementation(kotlin("script-runtime"))
8+
testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.3")
9+
}
10+
11+
// FIXME remove this after root build.gradle.kts supplies below task.
12+
kotlin {
13+
sourceSets {
14+
test {
15+
kotlin.srcDirs(listOf("src/test/kotlin"))
16+
}
17+
}
18+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package nettee.common.marker;
2+
3+
public sealed interface TypeSafeMarker permits TypeSafeMarker.Present, TypeSafeMarker.Missing {
4+
final class Present implements TypeSafeMarker { private Present() {} }
5+
final class Missing implements TypeSafeMarker { private Missing() {} }
6+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package nettee.common.status;
2+
3+
import nettee.common.marker.TypeSafeMarker;
4+
import nettee.common.marker.TypeSafeMarker.Missing;
5+
import nettee.common.marker.TypeSafeMarker.Present;
6+
7+
import java.util.Collection;
8+
9+
import static nettee.common.status.exception.StatusCodeErrorCode.CATEGORY_BITS_OVERFLOW;
10+
import static nettee.common.status.exception.StatusCodeErrorCode.INSTANCE_DETAIL_BITS_OVERFLOW;
11+
import static nettee.common.status.exception.StatusCodeErrorCode.SYS_INFO_OVERFLOW;
12+
13+
/**
14+
* {@code long} 타입 이내에서 GP bits, system information bits, category bits, instance detail bits 구간별 사이즈를 커스텀하여
15+
* 사용할 수 있도록 일반화한 클래스입니다.
16+
*
17+
* @param <C> Category bits 입력을 필수로 합니다. (컴파일타임 체크)
18+
* @param <I> Instance detail bits 입력을 필수로 합니다. (컴파일타임 체크)
19+
*/
20+
public class CustomStatusParameters<
21+
C extends TypeSafeMarker,
22+
I extends TypeSafeMarker
23+
> {
24+
private final CustomStatusParametersSupplier supplier;
25+
26+
private long generalPurposeBits;
27+
private long systemInfoBits;
28+
29+
private long categoryBits;
30+
private long instanceBits;
31+
32+
CustomStatusParameters(CustomStatusParametersSupplier supplier) {
33+
this.supplier = supplier;
34+
}
35+
36+
public static CustomStatusParameters<Missing, Missing> generateWith(CustomStatusParametersSupplier supplier) {
37+
return new CustomStatusParameters<>(supplier);
38+
}
39+
40+
public CustomStatusParameters<C, I> generalPurposeFeatures(
41+
Collection<String> extendedFeatures,
42+
LongGeneralPurposeFeatures... features
43+
) {
44+
generalPurposeBits = supplier.features().getValueOf(extendedFeatures, features);
45+
return this;
46+
}
47+
48+
public CustomStatusParameters<C, I> generalPurposeFeatures(LongGeneralPurposeFeatures... features) {
49+
generalPurposeBits = supplier.features().getValueOf(features);
50+
return this;
51+
}
52+
53+
public CustomStatusParameters<C, I> systemInfoBits(long systemInfoBits) {
54+
long sysInfoMax = (1L << supplier.systemInfoBitSize()) - 1;
55+
if (systemInfoBits > sysInfoMax) {
56+
throw SYS_INFO_OVERFLOW.exception();
57+
}
58+
this.systemInfoBits = systemInfoBits;
59+
return this;
60+
}
61+
62+
public CustomStatusParameters<Present, I> categoryBits(long categoryBits) {
63+
long categoryMax = (1L << supplier.categoryBitSize()) - 1;
64+
if (categoryBits > categoryMax) {
65+
throw CATEGORY_BITS_OVERFLOW.exception();
66+
}
67+
this.categoryBits = categoryBits;
68+
@SuppressWarnings("unchecked")
69+
var instance = (CustomStatusParameters<Present, I>) this;
70+
return instance;
71+
}
72+
73+
public CustomStatusParameters<C, Present> instanceBits(long instanceBits) {
74+
long instanceDetailMax = (1L << supplier.instanceDetailBitSize()) - 1;
75+
if (instanceBits > instanceDetailMax) {
76+
throw INSTANCE_DETAIL_BITS_OVERFLOW.exception();
77+
}
78+
this.instanceBits = instanceBits;
79+
@SuppressWarnings("unchecked")
80+
var instance = (CustomStatusParameters<C, Present>) this;
81+
return instance;
82+
}
83+
84+
public int generalPurposeBitsShift() {
85+
return supplier.systemInfoBitSize() + supplier.categoryBitSize() + supplier.instanceDetailBitSize();
86+
}
87+
88+
public int systemInfoBitsShift() {
89+
return supplier.categoryBitSize() + supplier.instanceDetailBitSize();
90+
}
91+
92+
public int categoryBitsShift() {
93+
return supplier.instanceDetailBitSize();
94+
}
95+
96+
public long generalPurposeBits() {
97+
return generalPurposeBits;
98+
}
99+
100+
public long systemInfoBits() {
101+
return systemInfoBits;
102+
}
103+
104+
public long categoryBits() {
105+
return categoryBits;
106+
}
107+
108+
public long instanceBits() {
109+
return instanceBits;
110+
}
111+
112+
public enum LongGeneralPurposeFeatures {
113+
ALL,
114+
READ,
115+
UPDATE,
116+
SUBITEM_READ,
117+
SUBITEM_UPDATE
118+
}
119+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
package nettee.common.status;
2+
3+
import nettee.common.marker.TypeSafeMarker.Missing;
4+
import nettee.common.status.CustomStatusParameters.LongGeneralPurposeFeatures;
5+
6+
import java.util.Collection;
7+
import java.util.Map;
8+
import java.util.function.Supplier;
9+
10+
import static nettee.common.status.exception.StatusCodeErrorCode.GP_BITS_NOT_DISTINCT;
11+
import static nettee.common.status.exception.StatusCodeErrorCode.GP_BITS_OUT_OF_BOUND;
12+
import static nettee.common.status.exception.StatusCodeErrorCode.GP_CUSTOM_KEY_NOT_CORRECT;
13+
import static nettee.common.status.exception.StatusCodeErrorCode.TOTAL_BITS_OVERFLOW;
14+
15+
public final class CustomStatusParametersSupplier implements Supplier<CustomStatusParameters<Missing, Missing>> {
16+
private final int sysInfoSize;
17+
private final int categorySize;
18+
private final int instanceDetailSize;
19+
private final LongGeneralPurposeFeaturesValue featuresBits;
20+
21+
public CustomStatusParametersSupplier(
22+
int generalPurposeBitSize,
23+
int systemInfoBitSize,
24+
int categoryBitSize,
25+
int instanceBitSize,
26+
LongGeneralPurposeFeaturesValue featuresBits
27+
) {
28+
// validate the total size
29+
int totalSize = generalPurposeBitSize + systemInfoBitSize + categoryBitSize + instanceBitSize;
30+
if (totalSize > Long.BYTES * 8) {
31+
throw TOTAL_BITS_OVERFLOW.exception();
32+
}
33+
34+
// validate that the GP list fits within the GP size.
35+
featuresBits.validateSize(generalPurposeBitSize);
36+
37+
this.sysInfoSize = systemInfoBitSize;
38+
this.categorySize = categoryBitSize;
39+
this.instanceDetailSize = instanceBitSize;
40+
this.featuresBits = featuresBits;
41+
}
42+
43+
public int systemInfoBitSize() {
44+
return sysInfoSize;
45+
}
46+
47+
public int categoryBitSize() {
48+
return categorySize;
49+
}
50+
51+
public int instanceDetailBitSize() {
52+
return instanceDetailSize;
53+
}
54+
55+
public LongGeneralPurposeFeaturesValue features() {
56+
return featuresBits;
57+
}
58+
59+
@Override
60+
public CustomStatusParameters<Missing, Missing> get() {
61+
return new CustomStatusParameters<>(this);
62+
}
63+
64+
/**
65+
* 일반 목적 기능(GP features)을 독립된 비트 영역으로 표현하기 위한 클래스입니다.
66+
*/
67+
public static final class LongGeneralPurposeFeaturesValue {
68+
final private long read;
69+
final private long update;
70+
final private long subItemRead;
71+
final private long subItemUpdate;
72+
final private Map<String, Long> extended;
73+
final private long max;
74+
75+
/**
76+
* @param read '일반 조회 목적'을 표현할 비트를 입력합니다.
77+
* @param update '일반 수정 목적'을 표현할 비트를 입력합니다.
78+
* @param subItemRead '하위항목 일반 조회 목적'을 표현할 비트를 입력합니다.
79+
* @param subItemUpdate '하위항목 일반 수정 목적'을 표현할 비트를 입력합니다.
80+
* @param extended 그 외 확장된 목적의 비트를 map 구조로 전달합니다. (Key: String, Value: Long)
81+
*/
82+
public LongGeneralPurposeFeaturesValue(
83+
long read,
84+
long update,
85+
long subItemRead,
86+
long subItemUpdate,
87+
Map<String, Long> extended
88+
) {
89+
long bitsOverlapped = read | update | subItemRead | subItemUpdate;
90+
long bitsSum = read + update + subItemRead + subItemUpdate;
91+
92+
if (bitsOverlapped != bitsSum) {
93+
throw GP_BITS_NOT_DISTINCT.exception();
94+
}
95+
96+
if (extended != null && !extended.isEmpty()) {
97+
for (var value : extended.values()) {
98+
bitsOverlapped |= value;
99+
bitsSum += value;
100+
if (bitsOverlapped != bitsSum) {
101+
throw GP_BITS_NOT_DISTINCT.exception();
102+
}
103+
}
104+
}
105+
106+
this.read = read;
107+
this.update = update;
108+
this.subItemRead = subItemRead;
109+
this.subItemUpdate = subItemUpdate;
110+
this.extended = extended;
111+
this.max = bitsOverlapped;
112+
}
113+
114+
public long read() {
115+
return read;
116+
}
117+
118+
public long update() {
119+
return update;
120+
}
121+
122+
public long subItemRead() {
123+
return subItemRead;
124+
}
125+
126+
public long subItemUpdate() {
127+
return subItemUpdate;
128+
}
129+
130+
public Map<String, Long> extended() {
131+
return extended;
132+
}
133+
134+
public long max() {
135+
return max;
136+
}
137+
138+
public long getValueOf(Collection<String> extendedKeys, LongGeneralPurposeFeatures... features) {
139+
long value = 0;
140+
141+
for (var extendedKey : extendedKeys) {
142+
if (!extended.containsKey(extendedKey)) {
143+
throw GP_CUSTOM_KEY_NOT_CORRECT.exception();
144+
}
145+
146+
value |= extended.get(extendedKey);
147+
}
148+
149+
value |= getValueOf(features);
150+
151+
return value;
152+
}
153+
154+
public long getValueOf(LongGeneralPurposeFeatures... features) {
155+
long value = 0;
156+
157+
for (var feature : features) {
158+
if (feature == LongGeneralPurposeFeatures.ALL || value == max) {
159+
return max;
160+
}
161+
162+
value |= switch (feature) {
163+
case READ -> read;
164+
case UPDATE -> update;
165+
case SUBITEM_READ -> subItemRead;
166+
case SUBITEM_UPDATE -> subItemUpdate;
167+
default -> throw new Error("");
168+
};
169+
}
170+
171+
return value;
172+
}
173+
174+
public void validateSize(int size) {
175+
if (max >= (1L << size)) {
176+
throw GP_BITS_OUT_OF_BOUND.exception();
177+
}
178+
}
179+
}
180+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package nettee.common.status;
2+
3+
public final class StatusCodeConstants {
4+
private StatusCodeConstants() {}
5+
6+
public static final class Default {
7+
private Default() {}
8+
9+
public static final int GENERAL_PURPOSE_BIT_SIZE = 7;
10+
public static final int SYSTEM_INFORMATION_BIT_SIZE = 8;
11+
public static final int CATEGORY_BIT_SIZE = 8;
12+
public static final int INSTANCE_DETAIL_BIT_SIZE = 8;
13+
14+
public static final int GENERAL_PURPOSE_SHIFT =
15+
SYSTEM_INFORMATION_BIT_SIZE + CATEGORY_BIT_SIZE + INSTANCE_DETAIL_BIT_SIZE;
16+
public static final int SYSTEM_INFORMATION_SHIFT = CATEGORY_BIT_SIZE + INSTANCE_DETAIL_BIT_SIZE;
17+
public static final int CATEGORY_SHIFT = INSTANCE_DETAIL_BIT_SIZE;
18+
}
19+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package nettee.common.status;
2+
3+
import nettee.common.marker.TypeSafeMarker.Present;
4+
5+
import static nettee.common.status.StatusCodeConstants.Default.CATEGORY_SHIFT;
6+
import static nettee.common.status.StatusCodeConstants.Default.GENERAL_PURPOSE_SHIFT;
7+
import static nettee.common.status.StatusCodeConstants.Default.SYSTEM_INFORMATION_SHIFT;
8+
9+
public final class StatusCodeUtil {
10+
private StatusCodeUtil() {}
11+
12+
public static int getAsInt(StatusParameters<Present, Present> parameters) {
13+
return (parameters.generalPurposeBits() << GENERAL_PURPOSE_SHIFT)
14+
| (parameters.systemInfoBits() << SYSTEM_INFORMATION_SHIFT)
15+
| (parameters.categoryBits() << CATEGORY_SHIFT)
16+
| parameters.instanceBits();
17+
}
18+
19+
public static long getAsLong(CustomStatusParameters<Present, Present> parameters) {
20+
int gpShift = parameters.generalPurposeBitsShift();
21+
int sysShift = parameters.systemInfoBitsShift();
22+
int cateShift = parameters.categoryBitsShift();
23+
24+
return (parameters.generalPurposeBits() << gpShift)
25+
| (parameters.systemInfoBits() << sysShift)
26+
| (parameters.categoryBits() << cateShift)
27+
| parameters.instanceBits();
28+
}
29+
}

0 commit comments

Comments
 (0)