Skip to content

Commit 46f87fe

Browse files
committed
feat: support 3-digit hex values in org.eclipse.tm4e.core.theme.RGB
1 parent 4f8b93d commit 46f87fe

2 files changed

Lines changed: 336 additions & 4 deletions

File tree

  • org.eclipse.tm4e.core/src

org.eclipse.tm4e.core/src/main/java/org/eclipse/tm4e/core/theme/RGB.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,46 @@
1212
*/
1313
package org.eclipse.tm4e.core.theme;
1414

15+
import java.lang.System.Logger;
16+
import java.lang.System.Logger.Level;
17+
import java.util.Objects;
18+
1519
import org.eclipse.jdt.annotation.Nullable;
1620

1721
public class RGB {
1822

23+
private static final Logger LOGGER = System.getLogger(RGB.class.getName());
24+
1925
public static @Nullable RGB fromHex(final @Nullable String hex) {
2026
if (hex == null || hex.isBlank())
2127
return null;
2228

2329
final var offset = hex.startsWith("#") ? 1 : 0;
24-
final int r = Integer.parseInt(hex.substring(offset + 0, offset + 2), 16);
25-
final int g = Integer.parseInt(hex.substring(offset + 2, offset + 4), 16);
26-
final int b = Integer.parseInt(hex.substring(offset + 4, offset + 6), 16);
30+
final int digitLength = hex.length() - offset;
31+
32+
final String r, g, b;
33+
if (digitLength == 3) {
34+
// Expand 3-digit format: #RGB -> #RRGGBB
35+
final String r0 = hex.substring(offset + 0, offset + 1);
36+
final String g0 = hex.substring(offset + 1, offset + 2);
37+
final String b0 = hex.substring(offset + 2, offset + 3);
38+
r = r0 + r0;
39+
g = g0 + g0;
40+
b = b0 + b0;
41+
} else if (digitLength == 6) {
42+
r = hex.substring(offset + 0, offset + 2);
43+
g = hex.substring(offset + 2, offset + 4);
44+
b = hex.substring(offset + 4, offset + 6);
45+
} else {
46+
LOGGER.log(Level.WARNING, "Invalid hex color string '" + hex +
47+
"': expected format '#RGB' (3 hex digits) or '#RRGGBB' (6 hex digits)");
48+
return null;
49+
}
2750

28-
return new RGB(r, g, b);
51+
return new RGB( //
52+
Integer.parseInt(r, 16), //
53+
Integer.parseInt(g, 16), //
54+
Integer.parseInt(b, 16));
2955
}
3056

3157
public final int red;
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
/**
2+
* Copyright (c) 2025 Vegard IT GmbH and others.
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Sebastian Thomschke (Vegard IT) - initial implementation
12+
*/
13+
package org.eclipse.tm4e.core.theme;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
class RGBTest {
20+
21+
// ========== Valid 6-digit hex parsing tests ==========
22+
23+
@Test
24+
void testFromHex_sixDigitsWithHash() {
25+
final RGB rgb = RGB.fromHex("#FF0000");
26+
assertThat(rgb).isNotNull();
27+
assert rgb != null;
28+
assertThat(rgb.red).isEqualTo(255);
29+
assertThat(rgb.green).isZero();
30+
assertThat(rgb.blue).isZero();
31+
}
32+
33+
@Test
34+
void testFromHex_sixDigitsWithoutHash() {
35+
final RGB rgb = RGB.fromHex("00FF00");
36+
assertThat(rgb).isNotNull();
37+
assert rgb != null;
38+
assertThat(rgb.red).isZero();
39+
assertThat(rgb.green).isEqualTo(255);
40+
assertThat(rgb.blue).isZero();
41+
}
42+
43+
@Test
44+
void testFromHex_sixDigitsLowercase() {
45+
final RGB rgb = RGB.fromHex("#aabbcc");
46+
assertThat(rgb).isNotNull();
47+
assert rgb != null;
48+
assertThat(rgb.red).isEqualTo(170);
49+
assertThat(rgb.green).isEqualTo(187);
50+
assertThat(rgb.blue).isEqualTo(204);
51+
}
52+
53+
@Test
54+
void testFromHex_sixDigitsMixedCase() {
55+
final RGB rgb = RGB.fromHex("#AaBbCc");
56+
assertThat(rgb).isNotNull();
57+
assert rgb != null;
58+
assertThat(rgb.red).isEqualTo(170);
59+
assertThat(rgb.green).isEqualTo(187);
60+
assertThat(rgb.blue).isEqualTo(204);
61+
}
62+
63+
// ========== Valid 3-digit hex parsing tests ==========
64+
65+
@Test
66+
void testFromHex_threeDigitsWithHash() {
67+
final RGB rgb = RGB.fromHex("#F00");
68+
assertThat(rgb).isNotNull();
69+
assert rgb != null;
70+
assertThat(rgb.red).isEqualTo(255);
71+
assertThat(rgb.green).isZero();
72+
assertThat(rgb.blue).isZero();
73+
}
74+
75+
@Test
76+
void testFromHex_threeDigitsWithoutHash() {
77+
final RGB rgb = RGB.fromHex("0F0");
78+
assertThat(rgb).isNotNull();
79+
assert rgb != null;
80+
assertThat(rgb.red).isZero();
81+
assertThat(rgb.green).isEqualTo(255);
82+
assertThat(rgb.blue).isZero();
83+
}
84+
85+
@Test
86+
void testFromHex_threeDigitsExpansion() {
87+
// Test that #ABC expands to #AABBCC
88+
final RGB rgb = RGB.fromHex("#ABC");
89+
assertThat(rgb).isNotNull();
90+
assert rgb != null;
91+
assertThat(rgb.red).isEqualTo(170); // AA in hex = 170
92+
assertThat(rgb.green).isEqualTo(187); // BB in hex = 187
93+
assertThat(rgb.blue).isEqualTo(204); // CC in hex = 204
94+
}
95+
96+
@Test
97+
void testFromHex_threeDigitsWhite() {
98+
final RGB rgb = RGB.fromHex("#FFF");
99+
assertThat(rgb).isNotNull();
100+
assert rgb != null;
101+
assertThat(rgb.red).isEqualTo(255);
102+
assertThat(rgb.green).isEqualTo(255);
103+
assertThat(rgb.blue).isEqualTo(255);
104+
}
105+
106+
@Test
107+
void testFromHex_threeDigitsBlack() {
108+
final RGB rgb = RGB.fromHex("#000");
109+
assertThat(rgb).isNotNull();
110+
assert rgb != null;
111+
assertThat(rgb.red).isZero();
112+
assertThat(rgb.green).isZero();
113+
assertThat(rgb.blue).isZero();
114+
}
115+
116+
// ========== Invalid input tests ==========
117+
118+
@Test
119+
void testFromHex_null() {
120+
assertThat(RGB.fromHex(null)).isNull();
121+
}
122+
123+
@Test
124+
void testFromHex_empty() {
125+
assertThat(RGB.fromHex("")).isNull();
126+
}
127+
128+
@Test
129+
void testFromHex_blank() {
130+
assertThat(RGB.fromHex(" ")).isNull();
131+
}
132+
133+
@Test
134+
void testFromHex_tooShort_oneDigit() {
135+
assertThat(RGB.fromHex("#F")).isNull();
136+
}
137+
138+
@Test
139+
void testFromHex_tooShort_twoDigits() {
140+
assertThat(RGB.fromHex("#FF")).isNull();
141+
assertThat(RGB.fromHex("AB")).isNull();
142+
}
143+
144+
@Test
145+
void testFromHex_tooShort_fourDigits() {
146+
assertThat(RGB.fromHex("#FFFF")).isNull();
147+
assertThat(RGB.fromHex("1234")).isNull();
148+
}
149+
150+
@Test
151+
void testFromHex_tooShort_fiveDigits() {
152+
assertThat(RGB.fromHex("#FFFFF")).isNull();
153+
assertThat(RGB.fromHex("12345")).isNull();
154+
}
155+
156+
@Test
157+
void testFromHex_tooLong_sevenDigits() {
158+
assertThat(RGB.fromHex("#FFFFFFF")).isNull();
159+
assertThat(RGB.fromHex("1234567")).isNull();
160+
}
161+
162+
@Test
163+
void testFromHex_tooLong_eightDigits() {
164+
assertThat(RGB.fromHex("#FFFFFFFF")).isNull();
165+
}
166+
167+
@Test
168+
void testFromHex_invalidPrefix() {
169+
assertThat(RGB.fromHex("##FFFFFF")).isNull();
170+
}
171+
172+
@Test
173+
void testFromHex_invalidSuffix() {
174+
assertThat(RGB.fromHex("FFFFFF#")).isNull();
175+
}
176+
177+
// ========== RGB object behavior tests ==========
178+
179+
@Test
180+
void testConstructor() {
181+
final RGB rgb = new RGB(100, 150, 200);
182+
assertThat(rgb.red).isEqualTo(100);
183+
assertThat(rgb.green).isEqualTo(150);
184+
assertThat(rgb.blue).isEqualTo(200);
185+
}
186+
187+
@Test
188+
void testToString() {
189+
final RGB rgb = new RGB(255, 128, 64);
190+
assertThat(rgb).hasToString("RGB(255,128,64)");
191+
}
192+
193+
@Test
194+
void testEquals_sameValues() {
195+
final RGB rgb1 = new RGB(100, 150, 200);
196+
final RGB rgb2 = new RGB(100, 150, 200);
197+
assertThat(rgb1).isEqualTo(rgb2);
198+
}
199+
200+
@Test
201+
void testEquals_differentValues() {
202+
final RGB rgb1 = new RGB(100, 150, 200);
203+
final RGB rgb2 = new RGB(100, 150, 201);
204+
assertThat(rgb1).isNotEqualTo(rgb2);
205+
}
206+
207+
@Test
208+
void testEquals_sameInstance() {
209+
final RGB rgb = new RGB(100, 150, 200);
210+
assertThat(rgb).isEqualTo(rgb);
211+
}
212+
213+
@Test
214+
void testEquals_null() {
215+
final RGB rgb = new RGB(100, 150, 200);
216+
assertThat(rgb).isNotEqualTo(null);
217+
}
218+
219+
@Test
220+
void testEquals_differentClass() {
221+
final RGB rgb = new RGB(100, 150, 200);
222+
assertThat(rgb).isNotEqualTo("not an RGB");
223+
}
224+
225+
@Test
226+
void testHashCode_sameValues() {
227+
final RGB rgb1 = new RGB(100, 150, 200);
228+
final RGB rgb2 = new RGB(100, 150, 200);
229+
assertThat(rgb1).hasSameHashCodeAs(rgb2);
230+
}
231+
232+
@Test
233+
void testHashCode_differentValues() {
234+
final RGB rgb1 = new RGB(100, 150, 200);
235+
final RGB rgb2 = new RGB(100, 150, 201);
236+
assertThat(rgb1.hashCode()).isNotEqualTo(rgb2.hashCode());
237+
}
238+
239+
// ========== Edge cases and special values ==========
240+
241+
@Test
242+
void testFromHex_maxValues() {
243+
final RGB rgb = RGB.fromHex("#FFFFFF");
244+
assertThat(rgb).isNotNull();
245+
assert rgb != null;
246+
assertThat(rgb.red).isEqualTo(255);
247+
assertThat(rgb.green).isEqualTo(255);
248+
assertThat(rgb.blue).isEqualTo(255);
249+
}
250+
251+
@Test
252+
void testFromHex_minValues() {
253+
final RGB rgb = RGB.fromHex("#000000");
254+
assertThat(rgb).isNotNull();
255+
assert rgb != null;
256+
assertThat(rgb.red).isZero();
257+
assertThat(rgb.green).isZero();
258+
assertThat(rgb.blue).isZero();
259+
}
260+
261+
@Test
262+
void testFromHex_commonColors() {
263+
// Test some common web colors
264+
final RGB red = RGB.fromHex("#FF0000");
265+
assertThat(red).isNotNull();
266+
assert red != null;
267+
assertThat(red.red).isEqualTo(255);
268+
assertThat(red.green).isZero();
269+
assertThat(red.blue).isZero();
270+
271+
final RGB green = RGB.fromHex("#00FF00");
272+
assertThat(green).isNotNull();
273+
assert green != null;
274+
assertThat(green.red).isZero();
275+
assertThat(green.green).isEqualTo(255);
276+
assertThat(green.blue).isZero();
277+
278+
final RGB blue = RGB.fromHex("#0000FF");
279+
assertThat(blue).isNotNull();
280+
assert blue != null;
281+
assertThat(blue.red).isZero();
282+
assertThat(blue.green).isZero();
283+
assertThat(blue.blue).isEqualTo(255);
284+
285+
final RGB yellow = RGB.fromHex("#FFFF00");
286+
assertThat(yellow).isNotNull();
287+
assert yellow != null;
288+
assertThat(yellow.red).isEqualTo(255);
289+
assertThat(yellow.green).isEqualTo(255);
290+
assertThat(yellow.blue).isZero();
291+
292+
final RGB cyan = RGB.fromHex("#00FFFF");
293+
assertThat(cyan).isNotNull();
294+
assert cyan != null;
295+
assertThat(cyan.red).isZero();
296+
assertThat(cyan.green).isEqualTo(255);
297+
assertThat(cyan.blue).isEqualTo(255);
298+
299+
final RGB magenta = RGB.fromHex("#FF00FF");
300+
assertThat(magenta).isNotNull();
301+
assert magenta != null;
302+
assertThat(magenta.red).isEqualTo(255);
303+
assertThat(magenta.green).isZero();
304+
assertThat(magenta.blue).isEqualTo(255);
305+
}
306+
}

0 commit comments

Comments
 (0)