Skip to content

Commit fa3628a

Browse files
IGNITE-26728 Implement CustomMapper annotation
1 parent 77a63f1 commit fa3628a

File tree

10 files changed

+288
-25
lines changed

10 files changed

+288
-25
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.internal;
19+
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Annotation used to specify a custom mapping strategy for an enum type during code generation.
27+
* It allows associating a custom mapper class that defines how enum constants are serialized,
28+
* deserialized, or otherwise processed externally (e.g., to integer codes, strings, or other representations).
29+
*
30+
* <p>The class specified by {@link #value()} must implement the appropriate mapping interface
31+
* expected by the code generation framework, typically providing methods to convert between
32+
* enum constants and their external representations.</p>
33+
*
34+
* <p>Example usage:</p>
35+
* <pre>{@code
36+
* @CustomEnumMapper(className = "com.example.MyCustomColorMapper")
37+
* public enum Color {
38+
* RED, GREEN, BLUE;
39+
* }
40+
* }</pre>
41+
*
42+
* <p>In this example, {@code MyCustomColorMapper} is responsible for defining the mapping logic
43+
* between the {@code Color} enum and its external form (e.g., integers or strings).</p>
44+
*
45+
* @see #value()
46+
*/
47+
@Retention(RetentionPolicy.CLASS)
48+
@Target(ElementType.FIELD)
49+
public @interface CustomMapper {
50+
/**
51+
* Returns the fully qualified class name of the custom mapper implementation
52+
* used to handle the enum's value conversion.
53+
*
54+
* <p>The specified class must be available on the classpath during code generation
55+
* and must adhere to the expected mapper interface contract.</p>
56+
*
57+
* @return The fully qualified name of the mapper class. Must not be null or empty.
58+
*/
59+
String value() default "";
60+
}

modules/codegen2/src/main/java/org/apache/ignite/internal/MessageProcessor.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,22 @@ private List<VariableElement> orderedFields(TypeElement type) {
132132
if (el.getModifiers().contains(Modifier.STATIC)) {
133133
processingEnv.getMessager().printMessage(
134134
Diagnostic.Kind.ERROR,
135-
"Annotation @Order must be used only for non-static fields.",
135+
"Annotation @Order must only be used for non-static fields.",
136136
el);
137137
}
138+
139+
if (el.getAnnotation(CustomMapper.class) != null) {
140+
TypeMirror elType = el.asType();
141+
142+
if (elType.getKind().isPrimitive() ||
143+
processingEnv.getTypeUtils().asElement(elType).getKind() != ElementKind.ENUM)
144+
{
145+
processingEnv.getMessager().printMessage(
146+
Diagnostic.Kind.ERROR,
147+
"Annotation @CustomMapper must only be used for enum fields.",
148+
el);
149+
}
150+
}
138151
}
139152
}
140153

modules/codegen2/src/main/java/org/apache/ignite/internal/MessageSerializerGenerator.java

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -394,23 +394,43 @@ else if (assignableFrom(erasedType(type), type(Collection.class.getName()))) {
394394
}
395395

396396
else if (enumType(type)) {
397-
//TODO check CustomMapper annotation
398-
imports.add("org.apache.ignite.plugin.extensions.communication.mappers.DefaultEnumMapper");
399-
400397
Element element = env.getTypeUtils().asElement(type);
401-
402398
imports.add(element.toString());
403399

404400
String enumName = element.getSimpleName().toString();
401+
char[] enumNameChars = enumName.toCharArray();
402+
enumNameChars[0] = Character.toLowerCase(enumNameChars[0]);
403+
String enumFieldPrefix = new String(enumNameChars);
405404

406-
char[] chars = enumName.toCharArray();
407-
chars[0] = Character.toLowerCase(chars[0]);
405+
String mapperCallStmnt;
406+
407+
CustomMapper custMapperAnn = field.getAnnotation(CustomMapper.class);
408+
409+
if (custMapperAnn != null) {
410+
String fullMapperName = custMapperAnn.value();
411+
if (fullMapperName == null || fullMapperName.isEmpty())
412+
throw new IllegalArgumentException("Please specify a not-null not-empty CustomMapper class name");
413+
414+
imports.add("org.apache.ignite.plugin.extensions.communication.mappers.CustomMapper");
415+
imports.add(fullMapperName);
416+
417+
String simpleName = fullMapperName.substring(fullMapperName.lastIndexOf('.') + 1);
408418

409-
String enumValuesFieldName = new String(chars) + "Vals";
419+
String mapperFieldName = enumFieldPrefix + "Mapper";
410420

411-
fields.add("private final " + enumName + "[] " + enumValuesFieldName + " = " + enumName + ".values();");
421+
fields.add("private final CustomMapper<" + enumName + "> " + mapperFieldName + " = new " + simpleName + "();");
412422

413-
returnFalseIfEnumWriteFailed(write, "writer.writeByte", "DefaultEnumMapper.INSTANCE.encode", getExpr);
423+
mapperCallStmnt = mapperFieldName + ".encode";
424+
} else {
425+
imports.add("org.apache.ignite.plugin.extensions.communication.mappers.DefaultEnumMapper");
426+
String enumValuesFieldName = enumFieldPrefix + "Vals";
427+
428+
fields.add("private final " + enumName + "[] " + enumValuesFieldName + " = " + enumName + ".values();");
429+
430+
mapperCallStmnt = "DefaultEnumMapper.INSTANCE.encode";
431+
}
432+
433+
returnFalseIfEnumWriteFailed(write, "writer.writeByte", mapperCallStmnt, getExpr);
414434
}
415435

416436
else
@@ -575,9 +595,21 @@ else if (enumType(type)) {
575595
char[] chars = enumName.toCharArray();
576596
chars[0] = Character.toLowerCase(chars[0]);
577597

578-
String enumValuesFieldName = new String(chars) + "Vals";
598+
String enumFieldPrefix = new String(chars);
599+
600+
CustomMapper custMapperAnn = field.getAnnotation(CustomMapper.class);
601+
602+
String mapperCallStmnt;
603+
String enumValsFieldName = null;
604+
605+
if (custMapperAnn == null) {
606+
mapperCallStmnt = "DefaultEnumMapper.INSTANCE.decode";
607+
enumValsFieldName = enumFieldPrefix + "Vals";
608+
}
609+
else
610+
mapperCallStmnt = enumFieldPrefix + "Mapper.decode";
579611

580-
returnFalseIfEnumReadFailed(name, "DefaultEnumMapper.INSTANCE.decode", enumValuesFieldName);
612+
returnFalseIfEnumReadFailed(name, mapperCallStmnt, enumValsFieldName);
581613
}
582614

583615
else
@@ -703,10 +735,13 @@ private void returnFalseIfReadFailed(String var, String mtd, String... args) {
703735
* </pre>
704736
*
705737
* @param msgSetterName Variable name.
706-
* @param mapperDecodeCall Method name.
738+
* @param mapperDecodeCallStmnt Method name.
707739
*/
708-
private void returnFalseIfEnumReadFailed(String msgSetterName, String mapperDecodeCall, String enumValuesFieldName) {
709-
read.add(line("msg.%s(%s(%s, reader.readByte()));", msgSetterName, mapperDecodeCall, enumValuesFieldName));
740+
private void returnFalseIfEnumReadFailed(String msgSetterName, String mapperDecodeCallStmnt, String enumValuesFieldName) {
741+
if (enumValuesFieldName == null)
742+
read.add(line("msg.%s(%s(reader.readByte()));", msgSetterName, mapperDecodeCallStmnt));
743+
else
744+
read.add(line("msg.%s(%s(%s, reader.readByte()));", msgSetterName, mapperDecodeCallStmnt, enumValuesFieldName));
710745

711746
read.add(EMPTY);
712747

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.plugin.extensions.communication.mappers;
19+
20+
/** */
21+
public interface CustomMapper<T extends Enum<T>> {
22+
/** */
23+
public byte encode(T val);
24+
25+
/** */
26+
public T decode(byte code);
27+
}

modules/core/src/test/java/org/apache/ignite/internal/codegen/MessageProcessorTest.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,35 @@ public void testExceptionFailed() {
172172

173173
/** */
174174
@Test
175-
public void testEnumFields() {
176-
Compilation compilation = compile("EnumFieldsMessage.java");
175+
public void testDefaultMapperEnumFields() {
176+
Compilation compilation = compile("DefaultMapperEnumFieldsMessage.java");
177177

178178
assertThat(compilation).succeeded();
179179

180180
assertThat(compilation)
181-
.generatedSourceFile("org.apache.ignite.internal.codegen.EnumFieldsMessageSerializer")
182-
.hasSourceEquivalentTo(javaFile("EnumFieldsMessageSerializer.java"));
181+
.generatedSourceFile("org.apache.ignite.internal.codegen.DefaultMapperEnumFieldsMessageSerializer")
182+
.hasSourceEquivalentTo(javaFile("DefaultMapperEnumFieldsMessageSerializer.java"));
183+
}
184+
185+
/** */
186+
@Test
187+
public void testMappedByOnWrongField() {
188+
Compilation compilation = compile("CustomEnumMapperOnWrongFieldMessage.java");
189+
190+
assertThat(compilation).failed();
191+
assertThat(compilation).hadErrorContaining("Annotation @CustomMapper must only be used for enum fields.");
192+
}
193+
194+
/** */
195+
@Test
196+
public void testCustomMapperEnumFieldsMessage() {
197+
Compilation compilation = compile("CustomMapperEnumFieldsMessage.java", "TransactionIsolationCustomMapper.java");
198+
199+
assertThat(compilation).succeeded();
200+
201+
assertThat(compilation)
202+
.generatedSourceFile("org.apache.ignite.internal.codegen.CustomMapperEnumFieldsMessageSerializer")
203+
.hasSourceEquivalentTo(javaFile("CustomMapperEnumFieldsMessageSerializer.java"));
183204
}
184205

185206
/** */
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.internal;
19+
20+
import org.apache.ignite.plugin.extensions.communication.Message;
21+
22+
public class CustomEnumMapperOnWrongFieldMessage implements Message {
23+
@Order(0)
24+
@CustomMapper("org.apache.ignite.internal.CustomMapper")
25+
private int intField;
26+
27+
public int intField() {
28+
return intField;
29+
}
30+
31+
public void intField(int intField) {
32+
this.intField = intField;
33+
}
34+
35+
public short directType() {
36+
return 0;
37+
}
38+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.internal;
19+
20+
import org.apache.ignite.plugin.extensions.communication.Message;
21+
import org.apache.ignite.transactions.TransactionIsolation;
22+
23+
public class CustomMapperEnumFieldsMessage implements Message {
24+
@Order(0)
25+
@CustomMapper("org.apache.ignite.internal.TransactionIsolationCustomMapper")
26+
private TransactionIsolation txMode;
27+
28+
public TransactionIsolation txMode() {
29+
return txMode;
30+
}
31+
32+
public void txMode(TransactionIsolation txMode) {
33+
this.txMode = txMode;
34+
}
35+
36+
public short directType() {
37+
return 0;
38+
}
39+
}

modules/core/src/test/resources/codegen/EnumFieldsMessage.java renamed to modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessage.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,11 @@
1717

1818
package org.apache.ignite.internal;
1919

20-
import org.apache.ignite.cache.CacheAtomicityMode;
2120
import org.apache.ignite.internal.processors.cache.GridCacheOperation;
2221
import org.apache.ignite.plugin.extensions.communication.Message;
2322
import org.apache.ignite.transactions.TransactionIsolation;
2423

25-
public class EnumFieldsMessage implements Message {
24+
public class DefaultMapperEnumFieldsMessage implements Message {
2625
@Order(0)
2726
private TransactionIsolation publicEnum;
2827

modules/core/src/test/resources/codegen/EnumFieldsMessageSerializer.java renamed to modules/core/src/test/resources/codegen/DefaultMapperEnumFieldsMessageSerializer.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.apache.ignite.internal.codegen;
1919

20-
import org.apache.ignite.internal.EnumFieldsMessage;
20+
import org.apache.ignite.internal.DefaultMapperEnumFieldsMessage;
2121
import org.apache.ignite.internal.processors.cache.GridCacheOperation;
2222
import org.apache.ignite.plugin.extensions.communication.Message;
2323
import org.apache.ignite.plugin.extensions.communication.MessageReader;
@@ -31,15 +31,15 @@
3131
*
3232
* @see org.apache.ignite.internal.MessageProcessor
3333
*/
34-
public class EnumFieldsMessageSerializer implements MessageSerializer {
34+
public class DefaultMapperEnumFieldsMessageSerializer implements MessageSerializer {
3535
/** */
3636
private final GridCacheOperation[] gridCacheOperationVals = GridCacheOperation.values();
3737
/** */
3838
private final TransactionIsolation[] transactionIsolationVals = TransactionIsolation.values();
3939

4040
/** */
4141
@Override public boolean writeTo(Message m, MessageWriter writer) {
42-
EnumFieldsMessage msg = (EnumFieldsMessage)m;
42+
DefaultMapperEnumFieldsMessage msg = (DefaultMapperEnumFieldsMessage)m;
4343

4444
if (!writer.isHeaderWritten()) {
4545
if (!writer.writeHeader(msg.directType()))
@@ -67,7 +67,7 @@ public class EnumFieldsMessageSerializer implements MessageSerializer {
6767

6868
/** */
6969
@Override public boolean readFrom(Message m, MessageReader reader) {
70-
EnumFieldsMessage msg = (EnumFieldsMessage)m;
70+
DefaultMapperEnumFieldsMessage msg = (DefaultMapperEnumFieldsMessage)m;
7171

7272
switch (reader.state()) {
7373
case 0:

0 commit comments

Comments
 (0)