uk.co.real_logic.sbe.generation.java.JavaUtil Maven / Gradle / Ivy
Show all versions of sbe-tool Show documentation
/*
* Copyright 2013-2024 Real Logic Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.co.real_logic.sbe.generation.java;
import org.agrona.Strings;
import uk.co.real_logic.sbe.PrimitiveType;
import uk.co.real_logic.sbe.SbeTool;
import uk.co.real_logic.sbe.ValidationUtil;
import uk.co.real_logic.sbe.generation.Generators;
import uk.co.real_logic.sbe.ir.Token;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
/**
* Utilities for mapping between {@link uk.co.real_logic.sbe.ir.Ir} and the Java language.
*/
public class JavaUtil
{
/**
* Separator symbols for {@link Object#toString()} implementations on codecs.
*/
enum Separator
{
BEGIN_GROUP('['),
END_GROUP(']'),
BEGIN_COMPOSITE('('),
END_COMPOSITE(')'),
BEGIN_SET('{'),
END_SET('}'),
BEGIN_ARRAY('['),
END_ARRAY(']'),
FIELD('|'),
KEY_VALUE('='),
ENTRY(',');
private final char symbol;
Separator(final char symbol)
{
this.symbol = symbol;
}
void appendToGeneratedBuilder(final StringBuilder builder, final String indent)
{
builder.append(indent).append("builder.append('").append(symbol).append("');").append('\n');
}
/**
* {@inheritDoc}
*/
public String toString()
{
return String.valueOf(symbol);
}
}
private static final Map TYPE_NAME_BY_PRIMITIVE_TYPE_MAP =
new EnumMap<>(PrimitiveType.class);
static
{
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.CHAR, "byte");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.INT8, "byte");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.INT16, "short");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.INT32, "int");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.INT64, "long");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.UINT8, "short");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.UINT16, "int");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.UINT32, "long");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.UINT64, "long");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.FLOAT, "float");
TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.put(PrimitiveType.DOUBLE, "double");
}
/**
* Indexes known charset aliases to the name of the instance in {@link StandardCharsets}.
*/
static final HashMap STD_CHARSETS = new HashMap<>();
static
{
try
{
for (final Field field : StandardCharsets.class.getDeclaredFields())
{
if (Charset.class.isAssignableFrom(field.getType()) && Modifier.isStatic(field.getModifiers()) &&
Modifier.isPublic(field.getModifiers()))
{
final Charset charset = (Charset)field.get(null);
final String name = field.getName();
String oldName = STD_CHARSETS.put(charset.name(), name);
if (null != oldName)
{
throw new IllegalStateException("Duplicate charset alias: old=" + oldName + ", new=" + name);
}
for (final String alias : charset.aliases())
{
oldName = STD_CHARSETS.put(alias, name);
if (null != oldName)
{
throw new IllegalStateException(
"Duplicate charset alias: old=" + oldName + ", new=" + alias);
}
}
}
}
}
catch (final IllegalAccessException ex)
{
throw new RuntimeException(ex);
}
}
/**
* Map the name of a {@link uk.co.real_logic.sbe.PrimitiveType} to a Java primitive type name.
*
* @param primitiveType to map.
* @return the name of the Java primitive that most closely maps.
*/
public static String javaTypeName(final PrimitiveType primitiveType)
{
return TYPE_NAME_BY_PRIMITIVE_TYPE_MAP.get(primitiveType);
}
/**
* Format a property name for generated code.
*
* If the formatted property name is a keyword then {@link SbeTool#KEYWORD_APPEND_TOKEN} is appended if set.
*
* @param value to be formatted.
* @return the string formatted as a property name.
* @throws IllegalStateException if a keyword and {@link SbeTool#KEYWORD_APPEND_TOKEN} is not set.
*/
public static String formatPropertyName(final String value)
{
return formatForJavaKeyword(Generators.toLowerFirstChar(value));
}
/**
* Format a name for generated code.
*
* If the formatted name is a keyword then {@link SbeTool#KEYWORD_APPEND_TOKEN} is appended if set.
*
* @param value to be formatted.
* @return the string formatted as a valid name.
* @throws IllegalStateException if a keyword and {@link SbeTool#KEYWORD_APPEND_TOKEN} is not set.
*/
public static String formatForJavaKeyword(final String value)
{
if (ValidationUtil.isJavaKeyword(value))
{
final String keywordAppendToken = System.getProperty(SbeTool.KEYWORD_APPEND_TOKEN);
if (null == keywordAppendToken)
{
throw new IllegalStateException(
"Invalid property name='" + value +
"' please correct the schema or consider setting system property: " + SbeTool.KEYWORD_APPEND_TOKEN);
}
return value + keywordAppendToken;
}
return value;
}
/**
* Format a Getter name for generated code.
*
* @param propertyName to be formatted.
* @return the property name formatted as a getter name.
*/
public static String formatGetterName(final String propertyName)
{
return "get" + Generators.toUpperFirstChar(propertyName);
}
/**
* Format a class name for the generated code.
*
* @param className to be formatted.
* @return the formatted class name.
*/
public static String formatClassName(final String className)
{
return Generators.toUpperFirstChar(className);
}
/**
* Shortcut to append a line of generated code
*
* @param builder string builder to which to append the line
* @param indent current text indentation
* @param line line to be appended
*/
public static void append(final StringBuilder builder, final String indent, final String line)
{
builder.append(indent).append(line).append('\n');
}
/**
* Code to fetch an instance of {@link java.nio.charset.Charset} corresponding to the given encoding.
*
* @param encoding as a string name (eg. UTF-8).
* @return the code to fetch the associated Charset.
*/
public static String charset(final String encoding)
{
final String charsetName = STD_CHARSETS.get(encoding);
if (null != charsetName)
{
return "java.nio.charset.StandardCharsets." + charsetName;
}
else
{
final String canonicalName = Charset.isSupported(encoding) ? Charset.forName(encoding).name() : encoding;
return "java.nio.charset.Charset.forName(\"" + canonicalName + "\")";
}
}
/**
* Code to fetch the name of the {@link Charset} given the encoding.
*
* @param encoding as a string name (eg. UTF-8).
* @return the code to fetch the associated Charset name.
*/
public static String charsetName(final String encoding)
{
final String charsetName = STD_CHARSETS.get(encoding);
if (null != charsetName)
{
return "java.nio.charset.StandardCharsets." + charsetName + ".name()";
}
else
{
return "\"" + (Charset.isSupported(encoding) ? Charset.forName(encoding).name() : encoding) + "\"";
}
}
/**
* Checks if the given encoding represents an ASCII charset.
*
* @param encoding as a string name (e.g. ASCII).
* @return {@code true} if the encoding denotes an ASCII charset.
*/
public static boolean isAsciiEncoding(final String encoding)
{
return "US_ASCII".equals(STD_CHARSETS.get(encoding));
}
/**
* Checks if the given encoding represents a UTF-8 charset.
*
* @param encoding as a string name (e.g. unicode-1-1-utf-8).
* @return {@code true} if the encoding denotes a UTF-8 charset.
*/
public static boolean isUtf8Encoding(final String encoding)
{
return "UTF_8".equals(STD_CHARSETS.get(encoding));
}
/**
* Generate a literal value to be used in code generation.
*
* @param type of the lateral value.
* @param value of the lateral.
* @return a String representation of the Java literal.
*/
public static String generateLiteral(final PrimitiveType type, final String value)
{
String literal = "";
final String castType = javaTypeName(type);
switch (type)
{
case CHAR:
case UINT8:
case INT8:
case INT16:
literal = "(" + castType + ")" + value;
break;
case UINT16:
case INT32:
literal = value;
break;
case UINT32:
literal = value + "L";
break;
case FLOAT:
literal = value.endsWith("NaN") ? "Float.NaN" : value + "f";
break;
case INT64:
literal = value + "L";
break;
case UINT64:
literal = "0x" + Long.toHexString(Long.parseLong(value)) + "L";
break;
case DOUBLE:
literal = value.endsWith("NaN") ? "Double.NaN" : value + "d";
break;
}
return literal;
}
/**
* Generate the Javadoc comment header for a type.
*
* @param sb to append to.
* @param indent level for the comment.
* @param typeToken for the type.
*/
public static void generateTypeJavadoc(final StringBuilder sb, final String indent, final Token typeToken)
{
final String description = typeToken.description();
if (Strings.isEmpty(description))
{
return;
}
sb.append('\n')
.append(indent).append("/**\n")
.append(indent).append(" * ");
escapeJavadoc(sb, description);
sb.append('\n')
.append(indent).append(" */\n");
}
/**
* Generate the Javadoc comment header for a bitset choice option decode method.
*
* @param out to append to.
* @param indent level for the comment.
* @param optionToken for the type.
* @throws IOException on failing to write to output.
*/
public static void generateOptionDecodeJavadoc(final Appendable out, final String indent, final Token optionToken)
throws IOException
{
final String description = optionToken.description();
if (Strings.isEmpty(description))
{
return;
}
out.append(indent).append("/**\n")
.append(indent).append(" * ");
escapeJavadoc(out, description);
out.append('\n')
.append(indent).append(" *\n")
.append(indent).append(" * @return true if ").append(optionToken.name()).append(" set or false if not.\n")
.append(indent).append(" */\n");
}
/**
* Generate the Javadoc comment header for a bitset choice option encode method.
*
* @param out to append to.
* @param indent level for the comment.
* @param optionToken for the type.
* @throws IOException on failing to write to output.
*/
public static void generateOptionEncodeJavadoc(final Appendable out, final String indent, final Token optionToken)
throws IOException
{
final String description = optionToken.description();
if (Strings.isEmpty(description))
{
return;
}
out.append(indent).append("/**\n")
.append(indent).append(" * ");
escapeJavadoc(out, description);
final String name = optionToken.name();
out.append('\n')
.append(indent).append(" *\n")
.append(indent).append(" * @param value true if ").append(name).append(" is set or false if not.\n")
.append(indent).append(" * @return this for a fluent API.\n")
.append(indent).append(" */\n");
}
/**
* Generate the Javadoc comment header for flyweight property.
*
* @param sb to append to.
* @param indent level for the comment.
* @param propertyToken for the property name.
* @param typeName for the property type.
*/
public static void generateFlyweightPropertyJavadoc(
final StringBuilder sb, final String indent, final Token propertyToken, final String typeName)
{
final String description = propertyToken.description();
if (Strings.isEmpty(description))
{
return;
}
sb.append('\n')
.append(indent).append("/**\n")
.append(indent).append(" * ");
escapeJavadoc(sb, description);
sb.append('\n')
.append(indent).append(" *\n")
.append(indent).append(" * @return ").append(typeName).append(" : ");
escapeJavadoc(sb, description);
sb.append("\n")
.append(indent).append(" */");
}
/**
* Generate the Javadoc comment header for group encode property.
*
* @param sb to append to.
* @param indent level for the comment.
* @param propertyToken for the property name.
* @param typeName for the property type.
*/
public static void generateGroupEncodePropertyJavadoc(
final StringBuilder sb, final String indent, final Token propertyToken, final String typeName)
{
final String description = propertyToken.description();
if (Strings.isEmpty(description))
{
return;
}
sb.append('\n')
.append(indent).append("/**\n")
.append(indent).append(" * ");
escapeJavadoc(sb, description);
sb.append("\n")
.append(indent).append(" *\n")
.append(indent).append(" * @param count of times the group will be encoded.\n")
.append(indent).append(" * @return ").append(typeName).append(" : encoder for the group.\n")
.append(indent).append(" */");
}
static String encoderName(final String className)
{
return formatClassName(className) + "Encoder";
}
static String decoderName(final String className)
{
return formatClassName(className) + "Decoder";
}
private static void escapeJavadoc(final Appendable out, final String doc) throws IOException
{
for (int i = 0, length = doc.length(); i < length; i++)
{
final char c = doc.charAt(i);
switch (c)
{
case '<':
out.append("<");
break;
case '>':
out.append(">");
break;
default:
out.append(c);
break;
}
}
}
private static void escapeJavadoc(final StringBuilder sb, final String doc)
{
for (int i = 0, length = doc.length(); i < length; i++)
{
final char c = doc.charAt(i);
switch (c)
{
case '<':
sb.append("<");
break;
case '>':
sb.append(">");
break;
case '&':
sb.append("&");
break;
default:
sb.append(c);
break;
}
}
}
}