From 4ffe2b96c6070f79a9d8863eddc67e5197d9efdd Mon Sep 17 00:00:00 2001 From: azerr Date: Sat, 13 Jul 2024 10:10:56 +0200 Subject: [PATCH] feat:Qute: Cannot locate hyphenated template name Fixes #975 Signed-off-by: azerr --- .../qute/jdt/internal/QuteJavaConstants.java | 5 +- .../AbstractQuteTemplateLinkCollector.java | 71 +++++- .../datamodel/CheckedTemplateSupport.java | 40 ++- .../datamodel/TemplateFieldSupport.java | 5 +- .../datamodel/TemplateRecordsSupport.java | 7 +- .../qute/jdt/utils/JDTQuteProjectUtils.java | 37 ++- .../qute/jdt/utils/TemplateNameStrategy.java | 9 + .../io/quarkus/runtime/util/StringUtil.java | 238 ++++++++++++++++++ 8 files changed, 388 insertions(+), 24 deletions(-) create mode 100644 qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/TemplateNameStrategy.java create mode 100644 qute.jdt/com.redhat.qute.jdt/src/main/java/io/quarkus/runtime/util/StringUtil.java diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java index 41ae37f2a..7a530cfc2 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/QuteJavaConstants.java @@ -29,7 +29,7 @@ public class QuteJavaConstants { public static final String TEMPLATE_CLASS = "io.quarkus.qute.Template"; public static final String TEMPLATE_INSTANCE_INTERFACE = "io.quarkus.qute.TemplateInstance"; - + public static final String ENGINE_BUILDER_CLASS = "io.quarkus.qute.EngineBuilder"; public static final String VALUE_ANNOTATION_NAME = "value"; @@ -50,6 +50,9 @@ public class QuteJavaConstants { public static final String CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS = "ignoreFragments"; public static final String CHECKED_TEMPLATE_ANNOTATION_BASE_PATH = "basePath"; + public static final String CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME = "defaultName"; + public static final String CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME_HYPHENATED_ELEMENT_NAME = "<>"; + public static final String CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME_UNDERSCORED_ELEMENT_NAME = "<>"; // @TemplateExtension diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/AbstractQuteTemplateLinkCollector.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/AbstractQuteTemplateLinkCollector.java index e1db10e3f..a7c208ccc 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/AbstractQuteTemplateLinkCollector.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/java/AbstractQuteTemplateLinkCollector.java @@ -13,6 +13,9 @@ import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION; import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_BASE_PATH; +import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME; +import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME_HYPHENATED_ELEMENT_NAME; +import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME_UNDERSCORED_ELEMENT_NAME; import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS; import static com.redhat.qute.jdt.internal.QuteJavaConstants.OLD_CHECKED_TEMPLATE_ANNOTATION; import static com.redhat.qute.jdt.internal.QuteJavaConstants.TEMPLATE_CLASS; @@ -51,6 +54,7 @@ import com.redhat.qute.jdt.utils.IJDTUtils; import com.redhat.qute.jdt.utils.JDTQuteProjectUtils; import com.redhat.qute.jdt.utils.JDTTypeUtils; +import com.redhat.qute.jdt.utils.TemplateNameStrategy; import com.redhat.qute.jdt.utils.TemplatePathInfo; /** @@ -138,7 +142,9 @@ public boolean visit(FieldDeclaration node) { .getLocationExpressionFromConstructorParameter(variable.getName().getIdentifier()); } String fieldName = variable.getName().getIdentifier(); - collectTemplateLink(null, node, locationExpression, getTypeDeclaration(node), null, fieldName, false); + TemplateNameStrategy templateNameStrategy = TemplateNameStrategy.ELEMENT_NAME; + collectTemplateLink(null, node, locationExpression, getTypeDeclaration(node), null, fieldName, false, + templateNameStrategy); } } return super.visit(node); @@ -186,10 +192,12 @@ public boolean visit(TypeDeclaration node) { // public static native TemplateInstance book(Book book); boolean ignoreFragments = isIgnoreFragments(annotation); String basePath = getBasePath(annotation); + TemplateNameStrategy templateNameStrategy = getDefaultName(annotation); List body = node.bodyDeclarations(); for (Object declaration : body) { if (declaration instanceof MethodDeclaration) { - collectTemplateLink(basePath, (MethodDeclaration) declaration, node, ignoreFragments); + collectTemplateLink(basePath, (MethodDeclaration) declaration, node, ignoreFragments, + templateNameStrategy); } } } @@ -208,8 +216,9 @@ public boolean visit(TypeDeclaration node) { @Override public boolean visit(RecordDeclaration node) { if (isImplementTemplateInstance(node)) { + TemplateNameStrategy templateNameStrategy = TemplateNameStrategy.ELEMENT_NAME; String recordName = node.getName().getIdentifier(); - collectTemplateLink(null, node, null, node, null, recordName, false); + collectTemplateLink(null, node, null, node, null, recordName, false, templateNameStrategy); } return super.visit(node); } @@ -275,18 +284,52 @@ private static boolean isIgnoreFragments(Annotation checkedTemplateAnnotation) { * @return the basePath value declared in the @CheckedTemplate * annotation */ - public static String getBasePath(Annotation checkedTemplateAnnotation) { + private static String getBasePath(Annotation checkedTemplateAnnotation) { String basePath = null; try { - Expression ignoreFragmentExpr = AnnotationUtils.getAnnotationMemberValueExpression( - checkedTemplateAnnotation, CHECKED_TEMPLATE_ANNOTATION_BASE_PATH); - basePath = AnnotationUtils.getString(ignoreFragmentExpr); + Expression basePathExpr = AnnotationUtils.getAnnotationMemberValueExpression(checkedTemplateAnnotation, + CHECKED_TEMPLATE_ANNOTATION_BASE_PATH); + basePath = AnnotationUtils.getString(basePathExpr); } catch (Exception e) { // Do nothing } return basePath; } + /** + * Returns the defaultName value declared in the @CheckedTemplate + * annotation, relative to the templates root, to search the templates from. + * + * @CheckedTemplate(defaultName=@CheckedTemplate.HYPHENATED_ELEMENT_NAME) + * + * + * @param checkedTemplateAnnotation the CheckedTemplate annotation. + * @return the defaultName value declared in the @CheckedTemplate + * annotation + */ + private static TemplateNameStrategy getDefaultName(Annotation checkedTemplateAnnotation) { + TemplateNameStrategy defaultName = TemplateNameStrategy.ELEMENT_NAME; + try { + Expression defaultNameExpr = AnnotationUtils.getAnnotationMemberValueExpression(checkedTemplateAnnotation, + CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME); + defaultName = getDefaultName(AnnotationUtils.getString(defaultNameExpr)); + } catch (Exception e) { + // Do nothing + } + return defaultName; + } + + private static TemplateNameStrategy getDefaultName(String defaultName) { + switch (defaultName) { + case CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME_HYPHENATED_ELEMENT_NAME: + return TemplateNameStrategy.HYPHENATED_ELEMENT_NAME; + + case CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME_UNDERSCORED_ELEMENT_NAME: + return TemplateNameStrategy.UNDERSCORED_ELEMENT_NAME; + } + return TemplateNameStrategy.ELEMENT_NAME; + } + @Override public void endVisit(TypeDeclaration node) { levelTypeDecl--; @@ -302,24 +345,28 @@ private static TypeDeclaration getTypeDeclaration(ASTNode node) { } private void collectTemplateLink(String basePath, MethodDeclaration methodDeclaration, TypeDeclaration type, - boolean ignoreFragment) { + boolean ignoreFragment, TemplateNameStrategy templateNameStrategy) { String className = null; boolean innerClass = levelTypeDecl > 1; if (innerClass) { className = JDTTypeUtils.getSimpleClassName(typeRoot.getElementName()); } String methodName = methodDeclaration.getName().getIdentifier(); - collectTemplateLink(basePath, methodDeclaration, null, type, className, methodName, ignoreFragment); + collectTemplateLink(basePath, methodDeclaration, null, type, className, methodName, ignoreFragment, + templateNameStrategy); } private void collectTemplateLink(String basePath, ASTNode fieldOrMethod, StringLiteral locationAnnotation, - AbstractTypeDeclaration type, String className, String fieldOrMethodName, boolean ignoreFragment) { + AbstractTypeDeclaration type, String className, String fieldOrMethodName, boolean ignoreFragment, + TemplateNameStrategy templateNameStrategy) { try { String location = locationAnnotation != null ? locationAnnotation.getLiteralValue() : null; IProject project = typeRoot.getJavaProject().getProject(); TemplatePathInfo templatePathInfo = location != null - ? JDTQuteProjectUtils.getTemplatePath(basePath, null, location, ignoreFragment) - : JDTQuteProjectUtils.getTemplatePath(basePath, className, fieldOrMethodName, ignoreFragment); + ? JDTQuteProjectUtils.getTemplatePath(basePath, null, location, ignoreFragment, + templateNameStrategy) + : JDTQuteProjectUtils.getTemplatePath(basePath, className, fieldOrMethodName, ignoreFragment, + templateNameStrategy); IFile templateFile = null; if (location == null) { templateFile = getTemplateFile(project, templatePathInfo.getTemplateUri()); diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/CheckedTemplateSupport.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/CheckedTemplateSupport.java index 409eada31..f5becddec 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/CheckedTemplateSupport.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/CheckedTemplateSupport.java @@ -13,6 +13,7 @@ import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION; import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_BASE_PATH; +import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME; import static com.redhat.qute.jdt.internal.QuteJavaConstants.CHECKED_TEMPLATE_ANNOTATION_IGNORE_FRAGMENTS; import static com.redhat.qute.jdt.internal.QuteJavaConstants.OLD_CHECKED_TEMPLATE_ANNOTATION; import static com.redhat.qute.jdt.utils.JDTQuteProjectUtils.getTemplatePath; @@ -43,6 +44,7 @@ import com.redhat.qute.jdt.template.datamodel.SearchContext; import com.redhat.qute.jdt.utils.AnnotationUtils; import com.redhat.qute.jdt.utils.JDTTypeUtils; +import com.redhat.qute.jdt.utils.TemplateNameStrategy; import com.redhat.qute.jdt.utils.TemplatePathInfo; /** @@ -91,8 +93,9 @@ protected void processAnnotation(IJavaElement javaElement, IAnnotation checkedTe IType type = (IType) javaElement; boolean ignoreFragments = isIgnoreFragments(checkedTemplateAnnotation); String basePath = getBasePath(checkedTemplateAnnotation); - collectDataModelTemplateForCheckedTemplate(type, basePath, ignoreFragments, context.getTypeResolver(type), - context.getDataModelProject().getTemplates(), monitor); + TemplateNameStrategy templateNameStrategy = getDefaultName(checkedTemplateAnnotation); + collectDataModelTemplateForCheckedTemplate(type, basePath, ignoreFragments, templateNameStrategy, + context.getTypeResolver(type), context.getDataModelProject().getTemplates(), monitor); } } @@ -145,6 +148,32 @@ private static String getBasePath(IAnnotation checkedTemplateAnnotation) { return basePath; } + /** + * Returns the defaultName value declared in the @CheckedTemplate + * annotation, relative to the templates root, to search the templates from. + * + * @CheckedTemplate(defaultName=@CheckedTemplate.HYPHENATED_ELEMENT_NAME) + * + * + * @param checkedTemplateAnnotation the CheckedTemplate annotation. + * @return the defaultName value declared in the @CheckedTemplate + * annotation + */ + private static TemplateNameStrategy getDefaultName(IAnnotation checkedTemplateAnnotation) { + TemplateNameStrategy defaultName = TemplateNameStrategy.ELEMENT_NAME; + try { + for (IMemberValuePair pair : checkedTemplateAnnotation.getMemberValuePairs()) { + if (CHECKED_TEMPLATE_ANNOTATION_DEFAULT_NAME.equalsIgnoreCase(pair.getMemberName())) { + String value = AnnotationUtils.getValueAsString(pair); + System.err.println(value); + } + } + } catch (Exception e) { + // Do nothing + } + return defaultName; + } + /** * Collect data model template from @CheckedTemplate. * @@ -158,8 +187,8 @@ private static String getBasePath(IAnnotation checkedTemplateAnnotation) { * @throws JavaModelException */ private static void collectDataModelTemplateForCheckedTemplate(IType type, String basePath, boolean ignoreFragments, - ITypeResolver typeResolver, List> templates, IProgressMonitor monitor) - throws JavaModelException { + TemplateNameStrategy templateNameStrategy, ITypeResolver typeResolver, + List> templates, IProgressMonitor monitor) throws JavaModelException { boolean innerClass = type.getParent() != null && type.getParent().getElementType() == IJavaElement.TYPE; String className = !innerClass ? null : JDTTypeUtils.getSimpleClassName( @@ -172,7 +201,8 @@ private static void collectDataModelTemplateForCheckedTemplate(IType type, Strin for (IMethod method : methods) { // src/main/resources/templates/${className}/${methodName}.qute.html - TemplatePathInfo templatePathInfo = getTemplatePath(basePath, className, method.getElementName(), ignoreFragments); + TemplatePathInfo templatePathInfo = getTemplatePath(basePath, className, method.getElementName(), + ignoreFragments, templateNameStrategy); // Get or create template String templateUri = templatePathInfo.getTemplateUri(); diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateFieldSupport.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateFieldSupport.java index 9c63bb4cd..e94aacfd1 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateFieldSupport.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateFieldSupport.java @@ -40,6 +40,7 @@ import com.redhat.qute.jdt.template.datamodel.AbstractFieldDeclarationTypeReferenceDataModelProvider; import com.redhat.qute.jdt.template.datamodel.SearchContext; import com.redhat.qute.jdt.utils.AnnotationUtils; +import com.redhat.qute.jdt.utils.TemplateNameStrategy; /** * Template field support. @@ -123,7 +124,9 @@ private static DataModelTemplate createTemplateDataModel(IFi : getLocation(field); String fieldName = field.getElementName(); // src/main/resources/templates/${methodName}.qute.html - String templateUri = getTemplatePath(null, null, location != null ? location : fieldName, true).getTemplateUri(); + TemplateNameStrategy templateNameStrategy = TemplateNameStrategy.ELEMENT_NAME; + String templateUri = getTemplatePath(null, null, location != null ? location : fieldName, true, + templateNameStrategy).getTemplateUri(); // Create template data model with: // - template uri : Qute template file which must be bind with data model. diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateRecordsSupport.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateRecordsSupport.java index 942015eac..ffe68e5cd 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateRecordsSupport.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/internal/template/datamodel/TemplateRecordsSupport.java @@ -28,6 +28,7 @@ import com.redhat.qute.jdt.internal.template.TemplateDataSupport; import com.redhat.qute.jdt.template.datamodel.AbstractInterfaceImplementationDataModelProvider; import com.redhat.qute.jdt.template.datamodel.SearchContext; +import com.redhat.qute.jdt.utils.TemplateNameStrategy; /** * Template Records support for template files: @@ -83,7 +84,8 @@ private static DataModelTemplate createTemplateDataModel(ITy String recordName = type.getElementName(); // src/main/resources/templates/${recordName}.qute.html - String templateUri = getTemplatePath(null, null, recordName, true).getTemplateUri(); + TemplateNameStrategy templateNameStrategy = TemplateNameStrategy.ELEMENT_NAME; + String templateUri = getTemplatePath(null, null, recordName, true, templateNameStrategy).getTemplateUri(); // Create template data model with: // - template uri : Qute template file which must be bind with data model. @@ -98,7 +100,8 @@ private static DataModelTemplate createTemplateDataModel(ITy for (IField field : type.getRecordComponents()) { DataModelParameter parameter = new DataModelParameter(); parameter.setKey(field.getElementName()); - parameter.setSourceType(typeResolver.resolveTypeSignature(field.getTypeSignature(), field.getDeclaringType())); + parameter.setSourceType( + typeResolver.resolveTypeSignature(field.getTypeSignature(), field.getDeclaringType())); template.getParameters().add(parameter); } diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/JDTQuteProjectUtils.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/JDTQuteProjectUtils.java index 65ffa37fc..8fedc7a14 100644 --- a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/JDTQuteProjectUtils.java +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/JDTQuteProjectUtils.java @@ -13,6 +13,7 @@ import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -29,6 +30,8 @@ import com.redhat.qute.commons.ProjectInfo; import com.redhat.qute.jdt.internal.QuteJavaConstants; +import io.quarkus.runtime.util.StringUtil; + /** * JDT Qute utilities. * @@ -87,7 +90,7 @@ public static String getProjectUri(IJavaProject project) { * @return the project URI of the given project. */ public static String getProjectURI(IProject project) { - return project.getName(); // .getLocation().toOSString(); + return project.getName(); } /** @@ -112,7 +115,7 @@ public static boolean hasQuteSupport(IJavaProject javaProject) { } public static TemplatePathInfo getTemplatePath(String basePath, String className, String methodOrFieldName, - boolean ignoreFragments) { + boolean ignoreFragments, TemplateNameStrategy templateNameStrategy) { String fragmentId = null; StringBuilder templateUri = new StringBuilder(TEMPLATES_BASE_DIR); if (basePath != null && !DEFAULTED.equals(basePath)) { @@ -127,10 +130,38 @@ public static TemplatePathInfo getTemplatePath(String basePath, String className methodOrFieldName = methodOrFieldName.substring(0, fragmentIndex); } } - templateUri.append(methodOrFieldName); + templateUri.append(defaultedName(templateNameStrategy, methodOrFieldName)); return new TemplatePathInfo(templateUri.toString(), fragmentId); } + /** + * + * @param defaultNameStrategy + * @param value + * @return + * @see QuteProcessor#defaultName + */ + private static String defaultedName(TemplateNameStrategy defaultNameStrategy, String value) { + switch (defaultNameStrategy) { + case ELEMENT_NAME: + return value; + case HYPHENATED_ELEMENT_NAME: + return StringUtil.hyphenate(value); + case UNDERSCORED_ELEMENT_NAME: + return String.join("_", new Iterable() { + @Override + public Iterator iterator() { + return StringUtil.lowerCase(StringUtil.camelHumpsIterator(value)); + } + }); + default: + return value; + // throw new IllegalArgumentException("Unsupported + // @CheckedTemplate#defaultName() value: " + defaultNameStrategy); + } + } + /** * Appends a segment to a path, add trailing "/" if necessary * diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/TemplateNameStrategy.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/TemplateNameStrategy.java new file mode 100644 index 000000000..7a44b64ae --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/com/redhat/qute/jdt/utils/TemplateNameStrategy.java @@ -0,0 +1,9 @@ +package com.redhat.qute.jdt.utils; + +public enum TemplateNameStrategy { + + ELEMENT_NAME, + HYPHENATED_ELEMENT_NAME, + UNDERSCORED_ELEMENT_NAME, + OTHER +} diff --git a/qute.jdt/com.redhat.qute.jdt/src/main/java/io/quarkus/runtime/util/StringUtil.java b/qute.jdt/com.redhat.qute.jdt/src/main/java/io/quarkus/runtime/util/StringUtil.java new file mode 100644 index 000000000..5560755ca --- /dev/null +++ b/qute.jdt/com.redhat.qute.jdt/src/main/java/io/quarkus/runtime/util/StringUtil.java @@ -0,0 +1,238 @@ +package io.quarkus.runtime.util; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * This class is acopy/paste from https://github.com/quarkusio/quarkus/blob/main/core/runtime/src/main/java/io/quarkus/runtime/util/StringUtil.java + */ +public final class StringUtil { + private StringUtil() { + } + + public static String changePrefix(String name, String oldPrefix, String newPrefix) { + if (!name.startsWith(oldPrefix)) { + return name; + } + // don't use here String::replaceFirst because it uses regex + final int oldLen = oldPrefix.length(); + final int diff = newPrefix.length() - oldLen; + final int nameLen = name.length(); + final var builder = new StringBuilder(nameLen + diff); + builder.append(newPrefix); + builder.append(name, oldLen, nameLen); + return builder.toString(); + } + + public static Iterator camelHumpsIterator(String str) { + return new Iterator() { + int idx; + + public boolean hasNext() { + return idx < str.length(); + } + + public String next() { + if (idx == str.length()) + throw new NoSuchElementException(); + // known mixed-case rule-breakers + if (str.startsWith("JBoss", idx)) { + idx += 5; + return "JBoss"; + } + final int start = idx; + int c = str.codePointAt(idx); + if (Character.isUpperCase(c)) { + // an uppercase-starting word + idx = str.offsetByCodePoints(idx, 1); + if (idx < str.length()) { + c = str.codePointAt(idx); + if (Character.isUpperCase(c)) { + // all-caps word; need one look-ahead + int nextIdx = str.offsetByCodePoints(idx, 1); + while (nextIdx < str.length()) { + c = str.codePointAt(nextIdx); + if (Character.isLowerCase(c)) { + // ended at idx + return str.substring(start, idx); + } + idx = nextIdx; + nextIdx = str.offsetByCodePoints(idx, 1); + } + // consumed the whole remainder, update idx to length + idx = str.length(); + return str.substring(start); + } else { + // initial caps, trailing lowercase + idx = str.offsetByCodePoints(idx, 1); + while (idx < str.length()) { + c = str.codePointAt(idx); + if (Character.isUpperCase(c)) { + // end + return str.substring(start, idx); + } + idx = str.offsetByCodePoints(idx, 1); + } + // consumed the whole remainder + return str.substring(start); + } + } else { + // one-letter word + return str.substring(start); + } + } else { + // a lowercase-starting word + idx = str.offsetByCodePoints(idx, 1); + while (idx < str.length()) { + c = str.codePointAt(idx); + if (Character.isUpperCase(c)) { + // end + return str.substring(start, idx); + } + idx = str.offsetByCodePoints(idx, 1); + } + // consumed the whole remainder + return str.substring(start); + } + } + }; + } + + public static Iterator lowerCase(Iterator orig) { + return new Iterator() { + public boolean hasNext() { + return orig.hasNext(); + } + + public String next() { + return orig.next().toLowerCase(Locale.ROOT); + } + }; + } + + /** + * @deprecated Use {@link String#join} instead. + * @param delim delimiter + * @param it iterator + * @return the joined string + */ + @Deprecated + public static String join(String delim, Iterator it) { + final StringBuilder b = new StringBuilder(); + if (it.hasNext()) { + b.append(it.next()); + while (it.hasNext()) { + b.append(delim); + b.append(it.next()); + } + } + return b.toString(); + } + + public static String join(Iterator it) { + final StringBuilder b = new StringBuilder(); + if (it.hasNext()) { + b.append(it.next()); + while (it.hasNext()) { + b.append(it.next()); + } + } + return b.toString(); + } + + public static Iterator lowerCaseFirst(Iterator orig) { + return new Iterator() { + boolean first = true; + + public boolean hasNext() { + return orig.hasNext(); + } + + public String next() { + final String next = orig.next(); + if (first) { + first = false; + return next.toLowerCase(Locale.ROOT); + } else { + return next; + } + } + }; + } + + public static Iterator withoutSuffix(Iterator orig, String... suffixes) { + return new Iterator() { + String next = null; + + public boolean hasNext() { + if (next == null) { + if (!orig.hasNext()) + return false; + final String next = orig.next(); + if (!orig.hasNext() && arrayContains(next, suffixes)) { + return false; + } + this.next = next; + } + return true; + } + + public String next() { + if (!hasNext()) + throw new NoSuchElementException(); + final String next = this.next; + this.next = null; + return next; + } + }; + } + + @SafeVarargs + public static List withoutSuffix(List list, T... segments) { + if (list.size() < segments.length) { + return list; + } + for (int i = 0; i < segments.length; i++) { + if (!list.get(list.size() - i - 1).equals(segments[segments.length - i - 1])) { + return list; + } + } + return list.subList(0, list.size() - segments.length); + } + + public static List toList(Iterator orig) { + return toList(orig, 0); + } + + private static List toList(Iterator orig, int idx) { + if (orig.hasNext()) { + final String item = orig.next(); + final List list = toList(orig, idx + 1); + list.set(idx, item); + return list; + } else { + return Arrays.asList(new String[idx]); + } + } + + @SafeVarargs + private static boolean arrayContains(final T item, final T... array) { + for (T arrayItem : array) { + if (Objects.equals(arrayItem, item)) + return true; + } + return false; + } + + public static String hyphenate(String orig) { + return join("-", lowerCase(camelHumpsIterator(orig))); + } + + public static boolean isNullOrEmpty(String s) { + return s == null || s.isEmpty(); + } +} \ No newline at end of file