Skip to content

Commit 021c634

Browse files
committed
Implementation of subtypes in Java
TODO: - extend language subtype tests - extend SerializeUtil tests in runtime - update documentation in main README.md for Java extension - update design notes and discussion
1 parent afca1a3 commit 021c634

File tree

8 files changed

+316
-6
lines changed

8 files changed

+316
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<#include "FileHeader.inc.ftl">
2+
<#include "DocComment.inc.ftl">
3+
<@standard_header generatorDescription, packageName/>
4+
5+
<#if withCodeComments && docComments??>
6+
<@doc_comments docComments/>
7+
</#if>
8+
public final class ${name} implements zserio.runtime.ZserioSubtype
9+
{
10+
@Override
11+
public String getBaseTypePackage()
12+
{
13+
return <#if basePackageName?? && basePackageName != "">"${basePackageName}"<#else>null</#if>;
14+
}
15+
16+
@Override
17+
public String getBaseTypeName()
18+
{
19+
return "${baseTypeName}";
20+
}
21+
22+
@Override
23+
public Class<?> getBaseClass()
24+
{
25+
return ${baseClassName}.class;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package zserio.runtime;
2+
3+
/**
4+
* Interface implemented by all subtypes generated by Zserio.
5+
*/
6+
public interface ZserioSubtype
7+
{
8+
/**
9+
* Gets the package of the base type which is resolved from the subtype.
10+
*
11+
* @return The package of the base type or null if the base type is simple type.
12+
*/
13+
String getBaseTypePackage();
14+
15+
/**
16+
* Gets the name of the base type which is resolved from the subtype.
17+
*
18+
* @return The name of the base type.
19+
*/
20+
String getBaseTypeName();
21+
22+
/**
23+
* Gets the class of the base type which is resolved from the subtype.
24+
*
25+
* @return The class of the base type or the class of boxing type if the base type is simple type.
26+
*/
27+
Class<?> getBaseClass();
28+
}

compiler/extensions/java/runtime/src/zserio/runtime/io/SerializeUtil.java

+129-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import zserio.runtime.ZserioEnum;
1515
import zserio.runtime.ZserioError;
16+
import zserio.runtime.ZserioSubtype;
1617

1718
/**
1819
* Provides help methods for serialization and deserialization of generated objects.
@@ -71,7 +72,7 @@ public static <T extends Writer> BitBuffer serialize(T object)
7172
*
7273
* @return Generated object created from given bit buffer.
7374
*/
74-
public static <T> T deserialize(final Class<T> clazz, BitBuffer bitBuffer, Object... arguments)
75+
public static <T> T deserialize(Class<T> clazz, BitBuffer bitBuffer, Object... arguments)
7576
{
7677
return deserializeFromBytes(clazz, bitBuffer.getBuffer(), arguments);
7778
}
@@ -136,7 +137,7 @@ public static <T extends Writer> byte[] serializeToBytes(T object)
136137
*
137138
* @return Generated object created from given byte array.
138139
*/
139-
public static <T> T deserializeFromBytes(final Class<T> clazz, byte[] buffer, Object... arguments)
140+
public static <T> T deserializeFromBytes(Class<T> clazz, byte[] buffer, Object... arguments)
140141
{
141142
try (final BitStreamReader reader = new ByteArrayBitStreamReader(buffer))
142143
{
@@ -148,6 +149,42 @@ public static <T> T deserializeFromBytes(final Class<T> clazz, byte[] buffer, Ob
148149
}
149150
}
150151

152+
/**
153+
* Deserializes byte array to the generated object given by the class name.
154+
* <p>
155+
* This method resolves subtypes to the base types before deserialization.
156+
* <p>
157+
* This method can potentially use all bits of the last byte even if not all of them were written during
158+
* serialization (because there is no way how to specify exact number of bits). Thus, it could allow reading
159+
* behind stream (possibly in case of damaged data).
160+
* <p>
161+
* Example:
162+
* <blockquote><pre>
163+
* import zserio.runtime.io.SerializeUtil;
164+
*
165+
* final SomeZserioObject object = new SomeZserioObject();
166+
* final byte[] buffer = SerializeUtil.serializeToBytes(object);
167+
* final SomeZserioObject readObject = SerializeUtil.deserializeFromBytes("SomeZserioObject", buffer);
168+
* </pre></blockquote>
169+
*
170+
* @param className Class name of the generated object to deserialize.
171+
* @param buffer Byte array which represents generated object in binary format.
172+
* @param arguments Additional arguments needed for reader constructor (optional).
173+
*
174+
* @return Generated object created from given byte array.
175+
*/
176+
public static Object deserializeFromBytes(String className, byte[] buffer, Object... arguments)
177+
{
178+
try (final BitStreamReader reader = new ByteArrayBitStreamReader(buffer))
179+
{
180+
return deserializeFromReader(className, reader, arguments);
181+
}
182+
catch (IOException exception)
183+
{
184+
throw new ZserioError("SerializeUtil: " + exception, exception);
185+
}
186+
}
187+
151188
/**
152189
* Serializes generated object to the file using file name.
153190
* <p>
@@ -231,11 +268,39 @@ public static <T extends Writer> void serializeToFile(T object, File file)
231268
*
232269
* @return Generated object created from given file contents.
233270
*/
234-
public static <T> T deserializeFromFile(final Class<T> clazz, String fileName, Object... arguments)
271+
public static <T> T deserializeFromFile(Class<T> clazz, String fileName, Object... arguments)
235272
{
236273
return deserializeFromFile(clazz, new File(fileName), arguments);
237274
}
238275

276+
/**
277+
* Deserializes file to the generated object using class name and file name.
278+
* <p>
279+
* This method resolves subtypes to the base types before deserialization.
280+
* <p>
281+
* This is a convenient method for users to easily read given generated object from file.
282+
* <p>
283+
* Example:
284+
* <blockquote><pre>
285+
* import zserio.runtime.io.SerializeUtil;
286+
*
287+
* final String fileName = "FileName.bin";
288+
* final SomeZserioObject object = new SomeZserioObject();
289+
* SerializeUtil.serializeToFile(object, fileName);
290+
* final SomeZserioObject readObject = SerializeUtil.deserializeFromFile("SomeZserioObject", fileName);
291+
* </pre></blockquote>
292+
*
293+
* @param className Class name of the generated object to deserialize.
294+
* @param fileName Name of the file which represents generated object in binary format.
295+
* @param arguments Additional arguments needed for reader constructor (optional).
296+
*
297+
* @return Generated object created from given file contents.
298+
*/
299+
public static Object deserializeFromFile(String className, String fileName, Object... arguments)
300+
{
301+
return deserializeFromFile(className, new File(fileName), arguments);
302+
}
303+
239304
/**
240305
* Deserializes file to the generated object.
241306
* <p>
@@ -258,7 +323,7 @@ public static <T> T deserializeFromFile(final Class<T> clazz, String fileName, O
258323
*
259324
* @return Generated object created from given file contents.
260325
*/
261-
public static <T> T deserializeFromFile(final Class<T> clazz, File file, Object... arguments)
326+
public static <T> T deserializeFromFile(Class<T> clazz, File file, Object... arguments)
262327
{
263328
try
264329
{
@@ -274,15 +339,73 @@ public static <T> T deserializeFromFile(final Class<T> clazz, File file, Object.
274339
}
275340
}
276341

342+
/**
343+
* Deserializes file to the generated object using class name.
344+
* <p>
345+
* This method resolves subtypes to the base types before deserialization.
346+
* <p>
347+
* This is a convenient method for users to easily read given generated object from file.
348+
* <p>
349+
* Example:
350+
* <blockquote><pre>
351+
* import zserio.runtime.io.SerializeUtil;
352+
*
353+
* final String fileName = "FileName.bin";
354+
* final SomeZserioObject object = new SomeZserioObject();
355+
* SerializeUtil.serializeToFile(object, fileName);
356+
* final SomeZserioObject readObject = SerializeUtil.deserializeFromFile("SomeZserioObject", fileName);
357+
* </pre></blockquote>
358+
*
359+
* @param className Class name of the generated object to deserialize.
360+
* @param file File which represents generated object in binary format.
361+
* @param arguments Additional arguments needed for reader constructor (optional).
362+
*
363+
* @return Generated object created from given file contents.
364+
*/
365+
public static Object deserializeFromFile(String className, File file, Object... arguments)
366+
{
367+
try
368+
{
369+
final byte[] fileContent = Files.readAllBytes(file.toPath());
370+
try (final BitStreamReader reader = new ByteArrayBitStreamReader(fileContent))
371+
{
372+
return deserializeFromReader(className, reader, arguments);
373+
}
374+
}
375+
catch (IOException exception)
376+
{
377+
throw new ZserioError("SerializeUtil: " + exception, exception);
378+
}
379+
}
380+
277381
private static <T extends Writer> void serializeToWriter(T object, BitStreamWriter writer)
278382
throws IOException
279383
{
280384
object.initializeOffsets(writer.getBitPosition());
281385
object.write(writer);
282386
}
283387

284-
private static <T> T deserializeFromReader(
285-
final Class<T> clazz, BitStreamReader reader, Object... arguments)
388+
private static Object deserializeFromReader(String className, BitStreamReader reader, Object... arguments)
389+
{
390+
try
391+
{
392+
Class<?> clazz = Class.forName(className);
393+
if (clazz.isInstance(ZserioSubtype.class))
394+
{
395+
final Method baseClassMethod = clazz.getMethod("getBaseClass");
396+
clazz = (Class<?>)baseClassMethod.invoke(null);
397+
}
398+
399+
return deserializeFromReader(clazz, reader, arguments);
400+
}
401+
catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException |
402+
IllegalArgumentException | InvocationTargetException exception)
403+
{
404+
throw new ZserioError("SerializeUtil: " + exception, exception);
405+
}
406+
}
407+
408+
private static <T> T deserializeFromReader(Class<T> clazz, BitStreamReader reader, Object... arguments)
286409
{
287410
try
288411
{

compiler/extensions/java/src/zserio/extension/java/JavaExtension.java

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public void process(Root rootNode, ExtensionParameters parameters) throws Zserio
8282
emitters.add(new SqlTableEmitter(outputFileManager, javaParameters, packedTypesCollector));
8383
emitters.add(new ConstEmitter(outputFileManager, javaParameters, packedTypesCollector));
8484
emitters.add(new ServiceEmitter(outputFileManager, javaParameters, packedTypesCollector));
85+
emitters.add(new SubtypeEmitter(outputFileManager, javaParameters, packedTypesCollector));
8586
emitters.add(new PubsubEmitter(outputFileManager, javaParameters, packedTypesCollector));
8687

8788
// emit Java code

compiler/extensions/java/src/zserio/extension/java/JavaNativeMapper.java

+32
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import zserio.ast.StdIntegerType;
2323
import zserio.ast.StringType;
2424
import zserio.ast.StructureType;
25+
import zserio.ast.Subtype;
2526
import zserio.ast.TypeInstantiation;
2627
import zserio.ast.TypeReference;
2728
import zserio.ast.UnionType;
@@ -55,6 +56,7 @@
5556
import zserio.extension.java.types.NativeSqlDatabaseType;
5657
import zserio.extension.java.types.NativeSqlTableType;
5758
import zserio.extension.java.types.NativeStringType;
59+
import zserio.extension.java.types.NativeSubtype;
5860

5961
/**
6062
* Java native mapper.
@@ -148,6 +150,19 @@ public NativeIntegralType getJavaIntegralType(TypeInstantiation typeInstantiatio
148150
return (NativeIntegralType)nativeType;
149151
}
150152

153+
public JavaNativeType getNullableJavaType(TypeReference typeReference) throws ZserioExtensionException
154+
{
155+
final JavaNativeTypes javaTypes = getJavaTypes(typeReference);
156+
final JavaNativeType nativeNullableType = javaTypes != null ? javaTypes.getNullableType() : null;
157+
if (nativeNullableType == null)
158+
{
159+
throw new ZserioExtensionException(
160+
"Unhandled type '" + typeReference.getClass().getName() + "' in JavaNativeMapper!");
161+
}
162+
163+
return nativeNullableType;
164+
}
165+
151166
public JavaNativeType getNullableJavaType(TypeInstantiation typeInstantiation)
152167
throws ZserioExtensionException
153168
{
@@ -329,6 +344,23 @@ public void visitUnionType(UnionType type)
329344
javaTypes = mapCompoundType(type);
330345
}
331346

347+
@Override
348+
public void visitSubtype(Subtype type)
349+
{
350+
try
351+
{
352+
final JavaNativeType nativeBaseType = getJavaType(type.getBaseTypeReference());
353+
final PackageName packageName = type.getPackage().getPackageName();
354+
final String name = type.getName();
355+
final JavaNativeType javaType = new NativeSubtype(packageName, name, nativeBaseType.isSimple());
356+
javaTypes = new JavaNativeTypes(javaType);
357+
}
358+
catch (ZserioExtensionException exception)
359+
{
360+
thrownException = exception;
361+
}
362+
}
363+
332364
@Override
333365
public void visitEnumType(EnumType type)
334366
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package zserio.extension.java;
2+
3+
import zserio.ast.Subtype;
4+
import zserio.extension.common.OutputFileManager;
5+
import zserio.extension.common.PackedTypesCollector;
6+
import zserio.extension.common.ZserioExtensionException;
7+
8+
/**
9+
* Emitter for subtypes.
10+
*/
11+
public final class SubtypeEmitter extends JavaDefaultEmitter
12+
{
13+
public SubtypeEmitter(OutputFileManager outputFileManager, JavaExtensionParameters javaParameters,
14+
PackedTypesCollector packedTypesCollector)
15+
{
16+
super(outputFileManager, javaParameters, packedTypesCollector);
17+
}
18+
19+
@Override
20+
public void beginSubtype(Subtype subtype) throws ZserioExtensionException
21+
{
22+
final SubtypeEmitterTemplateData templateData =
23+
new SubtypeEmitterTemplateData(getTemplateDataContext(), subtype);
24+
processTemplate(TEMPLATE_NAME, templateData, subtype.getPackage(), subtype.getName());
25+
}
26+
27+
private static final String TEMPLATE_NAME = "Subtype.java.ftl";
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package zserio.extension.java;
2+
3+
import zserio.ast.Subtype;
4+
import zserio.ast.TypeReference;
5+
import zserio.extension.common.ZserioExtensionException;
6+
import zserio.extension.java.types.JavaNativeType;
7+
8+
/**
9+
* FreeMarker template data for SubtypeEmitter.
10+
*/
11+
public final class SubtypeEmitterTemplateData extends UserTypeTemplateData
12+
{
13+
public SubtypeEmitterTemplateData(TemplateDataContext context, Subtype subtype)
14+
throws ZserioExtensionException
15+
{
16+
super(context, subtype, subtype);
17+
18+
final TypeReference baseTypeReference = subtype.getBaseTypeReference();
19+
final JavaNativeMapper javaNativeMapper = context.getJavaNativeMapper();
20+
21+
final JavaNativeType nullableBaseNativeType = javaNativeMapper.getNullableJavaType(baseTypeReference);
22+
final JavaNativeType baseNativeType = javaNativeMapper.getJavaType(baseTypeReference);
23+
24+
basePackageName = JavaFullNameFormatter.getFullName(baseNativeType.getPackageName());
25+
baseTypeName = baseNativeType.getName();
26+
baseClassName = nullableBaseNativeType.getFullName();
27+
}
28+
29+
public String getBasePackageName()
30+
{
31+
return basePackageName;
32+
}
33+
34+
public String getBaseTypeName()
35+
{
36+
return baseTypeName;
37+
}
38+
39+
public String getBaseClassName()
40+
{
41+
return baseClassName;
42+
}
43+
44+
private final String basePackageName;
45+
private final String baseTypeName;
46+
private final String baseClassName;
47+
}

0 commit comments

Comments
 (0)