X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-generator-util%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fmodel%2Futil%2FBindingGeneratorUtil.java;h=15b733a06345f50f5b8e340bca9b4bd10a66b98b;hb=62d1bb7f2cf0c96ce34ca181462132dc95555daf;hp=8c3d292b401f0497839963faf9476663467c0de3;hpb=2283c637bc41bcd9478439b87b69e2e18e962be0;p=mdsal.git diff --git a/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/BindingGeneratorUtil.java b/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/BindingGeneratorUtil.java index 8c3d292b40..15b733a063 100644 --- a/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/BindingGeneratorUtil.java +++ b/binding/mdsal-binding-generator-util/src/main/java/org/opendaylight/mdsal/binding/model/util/BindingGeneratorUtil.java @@ -8,6 +8,7 @@ package org.opendaylight.mdsal.binding.model.util; import com.google.common.base.CharMatcher; +import com.google.common.collect.Collections2; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.Iterables; @@ -22,6 +23,8 @@ import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; import org.opendaylight.mdsal.binding.model.api.AccessModifier; import org.opendaylight.mdsal.binding.model.api.Restrictions; import org.opendaylight.mdsal.binding.model.api.Type; @@ -29,20 +32,17 @@ import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBu import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilderBase; import org.opendaylight.mdsal.binding.model.api.type.builder.MethodSignatureBuilder; import org.opendaylight.mdsal.binding.model.api.type.builder.TypeMemberBuilder; -import org.opendaylight.yangtools.yang.binding.BindingMapping; +import org.opendaylight.mdsal.binding.spec.naming.BindingMapping; import org.opendaylight.yangtools.yang.common.QName; -import org.opendaylight.yangtools.yang.common.QNameModule; -import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.SchemaPath; import org.opendaylight.yangtools.yang.model.api.TypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition; -import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint; import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint; import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint; +import org.opendaylight.yangtools.yang.model.api.type.RangeRestrictedTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition; -import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition; import org.opendaylight.yangtools.yang.model.util.type.BaseTypes; import org.opendaylight.yangtools.yang.model.util.type.DecimalTypeBuilder; @@ -53,24 +53,24 @@ import org.opendaylight.yangtools.yang.model.util.type.DecimalTypeBuilder; public final class BindingGeneratorUtil { /** - * Impossible to instantiate this class. All of the methods or attributes - * are static. + * Impossible to instantiate this class. All of the methods or attributes are static. */ private BindingGeneratorUtil() { + } /** * Pre-compiled replacement pattern. */ - private static final CharMatcher DOT_MATCHER = CharMatcher.is('.'); private static final CharMatcher DASH_COLON_MATCHER = CharMatcher.anyOf("-:"); private static final CharMatcher GT_MATCHER = CharMatcher.is('>'); private static final CharMatcher LT_MATCHER = CharMatcher.is('<'); + private static final Pattern UNICODE_CHAR_PATTERN = Pattern.compile("\\\\+u"); private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() { @Override - public List getLengthConstraints() { - return Collections.emptyList(); + public Optional getLengthConstraint() { + return Optional.empty(); } @Override @@ -79,8 +79,8 @@ public final class BindingGeneratorUtil { } @Override - public List getRangeConstraints() { - return Collections.emptyList(); + public Optional> getRangeConstraint() { + return Optional.empty(); } @Override @@ -90,67 +90,32 @@ public final class BindingGeneratorUtil { }; private static final Comparator> SUID_MEMBER_COMPARATOR = - (o1, o2) -> o1.getName().compareTo(o2.getName()); + Comparator.comparing(TypeMemberBuilder::getName); - private static final Comparator SUID_NAME_COMPARATOR = - (o1, o2) -> o1.getFullyQualifiedName().compareTo(o2.getFullyQualifiedName()); + private static final Comparator SUID_NAME_COMPARATOR = Comparator.comparing(Type::getFullyQualifiedName); /** - * Converts parameterName to valid JAVA parameter name. - * - * If the parameterName is one of the JAVA reserved words then - * it is prefixed with underscore character. + * Converts parameterName to valid JAVA parameter name. If the parameterName is one + * of the JAVA reserved words then it is prefixed with underscore character. * - * @param parameterName - * string with the parameter name + * @param parameterName string with the parameter name * @return string with the admissible parameter name */ public static String resolveJavaReservedWordEquivalency(final String parameterName) { - if ((parameterName != null) && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) { + if (parameterName != null && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) { return "_" + parameterName; } return parameterName; } /** - * Converts module name to valid JAVA package name. - * - * The package name consists of: - *
    - *
  • prefix - org.opendaylight.yang.gen.v
  • - *
  • module YANG version - org.opendaylight.yang.gen.v
  • - *
  • module namespace - invalid characters are replaced with dots
  • - *
  • revision prefix - .rev
  • - *
  • revision - YYYYMMDD (MM and DD aren't spread to the whole length)
  • - *
- * - * @param module - * module which contains data about namespace and revision date - * @return string with the valid JAVA package name - * @throws IllegalArgumentException - * if the revision date of the module equals - * null - * @deprecated USe {@link BindingMapping#getRootPackageName(QNameModule)} with {@link Module#getQNameModule()}. - */ - @Deprecated - public static String moduleNamespaceToPackageName(final Module module) { - return BindingMapping.getRootPackageName(module.getQNameModule()); - } - - /** - * Creates package name from specified basePackageName (package - * name for module) and schemaPath. + * Creates package name from specified basePackageName (package name for module) + * and schemaPath. Resulting package name is concatenation of basePackageName + * and all local names of YANG nodes which are parents of some node for which schemaPath is specified. * - * Resulting package name is concatenation of basePackageName - * and all local names of YANG nodes which are parents of some node for - * which schemaPath is specified. - * - * @param basePackageName - * string with package name of the module, MUST be normalized, - * otherwise this method may return an invalid string. - * @param schemaPath - * list of names of YANG nodes which are parents of some node + - * name of this node + * @param basePackageName string with package name of the module, MUST be normalized, otherwise this method may + * return an invalid string. + * @param schemaPath list of names of YANG nodes which are parents of some node + name of this node * @return string with valid JAVA package name * @throws NullPointerException if any of the arguments are null */ @@ -164,23 +129,19 @@ public final class BindingGeneratorUtil { } /** - * Creates package name from specified basePackageName (package - * name for module) and schemaPath which crosses an augmentation. - * - * Resulting package name is concatenation of basePackageName - * and all local names of YANG nodes which are parents of some node for - * which schemaPath is specified. + * Creates package name from specified basePackageName (package name for module) + * and schemaPath which crosses an augmentation. Resulting package name is concatenation + * of basePackageName and all local names of YANG nodes which are parents of some node for which + * schemaPath is specified. * - * @param basePackageName - * string with package name of the module, MUST be normalized, - * otherwise this method may return an invalid string. - * @param schemaPath - * list of names of YANG nodes which are parents of some node + - * name of this node + * @param basePackageName string with package name of the module, MUST be normalized, otherwise this method may + * return an invalid string. + * @param schemaPath list of names of YANG nodes which are parents of some node + name of this node * @return string with valid JAVA package name * @throws NullPointerException if any of the arguments are null */ - public static String packageNameForAugmentedGeneratedType(final String basePackageName, final SchemaPath schemaPath) { + public static String packageNameForAugmentedGeneratedType(final String basePackageName, + final SchemaPath schemaPath) { final int size = Iterables.size(schemaPath.getPathTowardsRoot()); if (size == 0) { return basePackageName; @@ -201,223 +162,31 @@ public final class BindingGeneratorUtil { return BindingMapping.normalizePackageName(builder.toString()); } - /** - * Creates package name from specified basePackageName (package - * name for module) and schemaPath. - * - * Resulting package name is concatenation of basePackageName - * and all local names of YANG nodes which are parents of some node for - * which schemaPath is specified. - * - * @param basePackageName - * string with package name of the module - * @param schemaPath - * list of names of YANG nodes which are parents of some node + - * name of this node - * @param isUsesAugment - * boolean true if using augment - * @return string with valid JAVA package name - * - * @deprecated Use {@link #packageNameForGeneratedType(String, SchemaPath)} or - * {@link #packageNameForAugmentedGeneratedType(String, SchemaPath)} instead. - */ - @Deprecated - public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath, - final boolean isUsesAugment) { - if (basePackageName == null) { - throw new IllegalArgumentException("Base Package Name cannot be NULL!"); - } - if (schemaPath == null) { - throw new IllegalArgumentException("Schema Path cannot be NULL!"); - } - - final Iterable iterable = schemaPath.getPathFromRoot(); - final int size = Iterables.size(iterable); - final int traversalSteps; - if (isUsesAugment) { - traversalSteps = size; - } else { - traversalSteps = size - 1; - } - - if (traversalSteps == 0) { - return BindingMapping.normalizePackageName(basePackageName); - } - - return generateNormalizedPackageName(basePackageName, iterable, traversalSteps); - } - - /** - * Generates the package name for type definition from - * typeDefinition and basePackageName. - * - * @param basePackageName - * string with the package name of the module - * @param typeDefinition - * type definition for which the package name will be generated * - * @return string with valid JAVA package name - * @throws IllegalArgumentException - *
    - *
  • if basePackageName equals null
  • - *
  • if typeDefinition equals null
  • - *
- * @deprecated This method ignores typeDefinition argument and its result is only - * BindingMapping.normalizePackageName(basePackageName). - * Aside from tests, there is not a single user in OpenDaylight codebase, - * hence it can be considered buggy and defunct. It is scheduled for removal - * in Boron release. - */ - @Deprecated - public static String packageNameForTypeDefinition(final String basePackageName, - final TypeDefinition typeDefinition) { - if (basePackageName == null) { - throw new IllegalArgumentException("Base Package Name cannot be NULL!"); - } - if (typeDefinition == null) { - throw new IllegalArgumentException("Type Definition reference cannot be NULL!"); - } - - return BindingMapping.normalizePackageName(basePackageName); - } - - /** - * Converts token to string which is in accordance with best - * practices for JAVA class names. - * - * @param token - * string which contains characters which should be converted to - * JAVA class name - * @return string which is in accordance with best practices for JAVA class - * name. - * - * @deprecated Use {@link BindingMapping#getClassName(QName)} instead. - */ - @Deprecated - public static String parseToClassName(final String token) { - return parseToCamelCase(token, true); - } - - /** - * Converts token to string which is in accordance with best - * practices for JAVA parameter names. - * - * @param token - * string which contains characters which should be converted to - * JAVA parameter name - * @return string which is in accordance with best practices for JAVA - * parameter name. - * - * @deprecated Use {@link BindingMapping#getPropertyName(String)} instead. - */ - @Deprecated public static String parseToValidParamName(final String token) { - return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false)); - } - - /** - * - * Converts string token to the cammel case format. - * - * @param token - * string which should be converted to the cammel case format - * @param uppercase - * boolean value which says whether the first character of the - * token should|shuldn't be uppercased - * @return string in the cammel case format - * @throws IllegalArgumentException - *
    - *
  • if token without white spaces is empty
  • - *
  • if token equals null
  • - *
- */ - private static String parseToCamelCase(final String token, final boolean uppercase) { - if (token == null) { - throw new IllegalArgumentException("Name can not be null"); - } - - String correctStr = DOT_MATCHER.removeFrom(token.trim()); - if (correctStr.isEmpty()) { - throw new IllegalArgumentException("Name can not be empty"); - } - - correctStr = replaceWithCamelCase(correctStr, ' '); - correctStr = replaceWithCamelCase(correctStr, '-'); - correctStr = replaceWithCamelCase(correctStr, '_'); - - char firstChar = correctStr.charAt(0); - firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar); - - if ((firstChar >= '0') && (firstChar <= '9')) { - return '_' + correctStr; - } else { - return firstChar + correctStr.substring(1); - } - } - - /** - * Replaces all the occurrences of the removalChar in the - * text with empty string and converts following character to - * upper case. - * - * @param text - * string with source text which should be converted - * @param removalChar - * character which is sought in the text - * @return string which doesn't contain removalChar and has - * following characters converted to upper case - * @throws IllegalArgumentException - * if the length of the returning string has length 0 - */ - private static String replaceWithCamelCase(final String text, final char removalChar) { - int toBeRemovedPos = text.indexOf(removalChar); - if (toBeRemovedPos == -1) { - return text; - } - - final StringBuilder sb = new StringBuilder(text); - final String toBeRemoved = String.valueOf(removalChar); - do { - sb.replace(toBeRemovedPos, toBeRemovedPos + 1, ""); - // check if 'toBeRemoved' character is not the only character in - // 'text' - if (sb.length() == 0) { - throw new IllegalArgumentException("The resulting string can not be empty"); - } - final char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos)); - sb.setCharAt(toBeRemovedPos, replacement); - toBeRemovedPos = sb.indexOf(toBeRemoved); - } while (toBeRemovedPos != -1); - - return sb.toString(); - } - private static Iterable sortedCollection(final Comparator comparator, final Collection input) { - if (input.size() > 1) { - final List ret = new ArrayList<>(input); - Collections.sort(ret, comparator); - return ret; - } else { + if (input.size() <= 1) { return input; } + + final List ret = new ArrayList<>(input); + ret.sort(comparator); + return ret; } - private static final ThreadLocal SHA1_MD = new ThreadLocal() { - @Override - protected MessageDigest initialValue() { - try { - return MessageDigest.getInstance("SHA"); - } catch (final NoSuchAlgorithmException e) { - throw new IllegalStateException("Failed to get a SHA digest provider", e); - } + private static final ThreadLocal SHA1_MD = ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance("SHA"); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalStateException("Failed to get a SHA digest provider", e); } - }; + }); public static long computeDefaultSUID(final GeneratedTypeBuilderBase to) { final ByteArrayOutputStream bout = new ByteArrayOutputStream(); - try (final DataOutputStream dout = new DataOutputStream(bout)) { + try (DataOutputStream dout = new DataOutputStream(bout)) { dout.writeUTF(to.getName()); dout.writeInt(to.isAbstract() ? 3 : 7); - for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) { + for (final Type ifc : sortedCollection(SUID_NAME_COMPARATOR, filteredImplementsTypes(to))) { dout.writeUTF(ifc.getFullyQualifiedName()); } @@ -426,7 +195,7 @@ public final class BindingGeneratorUtil { } for (final MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) { - if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) { + if (!m.getAccessModifier().equals(AccessModifier.PRIVATE)) { dout.writeUTF(m.getName()); dout.write(m.getAccessModifier().ordinal()); } @@ -440,13 +209,17 @@ public final class BindingGeneratorUtil { final byte[] hashBytes = SHA1_MD.get().digest(bout.toByteArray()); long hash = 0; for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) { - hash = (hash << 8) | (hashBytes[i] & 0xFF); + hash = hash << 8 | hashBytes[i] & 0xFF; } return hash; } - private static List currentOrEmpty(final List current, final List base) { - return current.equals(base) ? ImmutableList.of() : current; + private static Collection filteredImplementsTypes(final GeneratedTypeBuilderBase to) { + return Collections2.filter(to.getImplementsTypes(), item -> !BindingTypes.TYPE_OBJECT.equals(item)); + } + + private static > T currentOrEmpty(final T current, final T base) { + return current.equals(base) ? (T)Optional.empty() : current; } private static boolean containsConstraint(final StringTypeDefinition type, final PatternConstraint constraint) { @@ -481,7 +254,7 @@ public final class BindingGeneratorUtil { public static Restrictions getRestrictions(final TypeDefinition type) { // Old parser generated types which actually contained based restrictions, but our code deals with that when // binding to core Java types. Hence we'll emit empty restrictions for base types. - if ((type == null) || (type.getBaseType() == null)) { + if (type == null || type.getBaseType() == null) { // Handling of decimal64 has changed in the new parser. It contains range restrictions applied to the type // directly, without an extended type. We need to capture such constraints. In order to retain behavior we // need to analyze the new semantics and see if the constraints have been overridden. To do that we @@ -495,7 +268,7 @@ public final class BindingGeneratorUtil { tmpBuilder.setFractionDigits(decimal.getFractionDigits()); final DecimalTypeDefinition tmp = tmpBuilder.build(); - if (!tmp.getRangeConstraints().equals(decimal.getRangeConstraints())) { + if (!tmp.getRangeConstraint().equals(decimal.getRangeConstraint())) { return new Restrictions() { @Override public boolean isEmpty() { @@ -503,8 +276,8 @@ public final class BindingGeneratorUtil { } @Override - public List getRangeConstraints() { - return decimal.getRangeConstraints(); + public Optional> getRangeConstraint() { + return decimal.getRangeConstraint(); } @Override @@ -513,8 +286,8 @@ public final class BindingGeneratorUtil { } @Override - public List getLengthConstraints() { - return ImmutableList.of(); + public Optional getLengthConstraint() { + return Optional.empty(); } }; } @@ -523,9 +296,9 @@ public final class BindingGeneratorUtil { return EMPTY_RESTRICTIONS; } - final List length; + final Optional length; final List pattern; - final List range; + final Optional> range; /* * Take care of extended types. @@ -543,83 +316,69 @@ public final class BindingGeneratorUtil { if (type instanceof BinaryTypeDefinition) { final BinaryTypeDefinition binary = (BinaryTypeDefinition)type; final BinaryTypeDefinition base = binary.getBaseType(); - if ((base != null) && (base.getBaseType() != null)) { - length = currentOrEmpty(binary.getLengthConstraints(), base.getLengthConstraints()); + if (base != null && base.getBaseType() != null) { + length = currentOrEmpty(binary.getLengthConstraint(), base.getLengthConstraint()); } else { - length = binary.getLengthConstraints(); + length = binary.getLengthConstraint(); } pattern = ImmutableList.of(); - range = ImmutableList.of(); + range = Optional.empty(); } else if (type instanceof DecimalTypeDefinition) { - length = ImmutableList.of(); + length = Optional.empty(); pattern = ImmutableList.of(); final DecimalTypeDefinition decimal = (DecimalTypeDefinition)type; final DecimalTypeDefinition base = decimal.getBaseType(); - if ((base != null) && (base.getBaseType() != null)) { - range = currentOrEmpty(decimal.getRangeConstraints(), base.getRangeConstraints()); + if (base != null && base.getBaseType() != null) { + range = currentOrEmpty(decimal.getRangeConstraint(), base.getRangeConstraint()); } else { - range = decimal.getRangeConstraints(); + range = decimal.getRangeConstraint(); } - } else if (type instanceof IntegerTypeDefinition) { - length = ImmutableList.of(); + } else if (type instanceof RangeRestrictedTypeDefinition) { + // Integer-like types + length = Optional.empty(); pattern = ImmutableList.of(); - - final IntegerTypeDefinition integer = (IntegerTypeDefinition)type; - final IntegerTypeDefinition base = integer.getBaseType(); - if ((base != null) && (base.getBaseType() != null)) { - range = currentOrEmpty(integer.getRangeConstraints(), base.getRangeConstraints()); - } else { - range = integer.getRangeConstraints(); - } + range = extractRangeConstraint((RangeRestrictedTypeDefinition)type); } else if (type instanceof StringTypeDefinition) { final StringTypeDefinition string = (StringTypeDefinition)type; final StringTypeDefinition base = string.getBaseType(); - if ((base != null) && (base.getBaseType() != null)) { - length = currentOrEmpty(string.getLengthConstraints(), base.getLengthConstraints()); + if (base != null && base.getBaseType() != null) { + length = currentOrEmpty(string.getLengthConstraint(), base.getLengthConstraint()); } else { - length = string.getLengthConstraints(); + length = string.getLengthConstraint(); } pattern = uniquePatterns(string); - range = ImmutableList.of(); - } else if (type instanceof UnsignedIntegerTypeDefinition) { - length = ImmutableList.of(); - pattern = ImmutableList.of(); - - final UnsignedIntegerTypeDefinition unsigned = (UnsignedIntegerTypeDefinition)type; - final UnsignedIntegerTypeDefinition base = unsigned.getBaseType(); - if ((base != null) && (base.getBaseType() != null)) { - range = currentOrEmpty(unsigned.getRangeConstraints(), base.getRangeConstraints()); - } else { - range = unsigned.getRangeConstraints(); - } + range = Optional.empty(); } else { - length = ImmutableList.of(); + length = Optional.empty(); pattern = ImmutableList.of(); - range = ImmutableList.of(); + range = Optional.empty(); } // Now, this may have ended up being empty, too... - if (length.isEmpty() && pattern.isEmpty() && range.isEmpty()) { + if (!length.isPresent() && pattern.isEmpty() && !range.isPresent()) { return EMPTY_RESTRICTIONS; } // Nope, not empty allocate a holder return new Restrictions() { @Override - public List getRangeConstraints() { + public Optional> getRangeConstraint() { return range; } + @Override public List getPatternConstraints() { return pattern; } + @Override - public List getLengthConstraints() { + public Optional getLengthConstraint() { return length; } + @Override public boolean isEmpty() { return false; @@ -627,8 +386,19 @@ public final class BindingGeneratorUtil { }; } + private static > Optional> + extractRangeConstraint(final T def) { + final T base = (T) def.getBaseType(); + if (base != null && base.getBaseType() != null) { + return currentOrEmpty(def.getRangeConstraint(), base.getRangeConstraint()); + } + + return def.getRangeConstraint(); + } + /** - * Encodes angle brackets in yang statement description + * Encodes angle brackets in yang statement description. + * * @param description description of a yang statement which is used to generate javadoc comments * @return string with encoded angle brackets */ @@ -639,4 +409,30 @@ public final class BindingGeneratorUtil { } return description; } + + @Deprecated + public static String replaceAllIllegalChars(final CharSequence stringBuilder) { + return defangUnicodeEscapes(stringBuilder); + } + + /** + * Escape potential unicode references so that the resulting string is safe to put into a {@code .java} file. This + * processing is required to ensure this text we want to append does not end up with eligible backslashes. See + * Java Language Specification + * for more information. + * + * @param str Input string + * @return A string with all backslashes made ineligible + */ + public static String replaceAllIllegalChars(final String str) { + final int backslash = str.indexOf('\\'); + return backslash == -1 ? str : defangUnicodeEscapes(str); + } + + private static String defangUnicodeEscapes(final CharSequence stringBuilder) { + // TODO: we should be able to receive the first offset from the non-deprecated method and perform a manual + // check for eligibility and escape -- that would be faster I think. + final String ret = UNICODE_CHAR_PATTERN.matcher(stringBuilder).replaceAll("\\\\\\\\u"); + return ret.isEmpty() ? "" : ret; + } }