1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.internal;
21
22 import static com.google.common.truth.Truth.assertWithMessage;
23
24 import java.io.File;
25 import java.net.URI;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.regex.Pattern;
34
35 import javax.xml.parsers.ParserConfigurationException;
36
37 import org.junit.jupiter.api.BeforeEach;
38 import org.junit.jupiter.api.Test;
39 import org.w3c.dom.Document;
40 import org.w3c.dom.NamedNodeMap;
41 import org.w3c.dom.Node;
42 import org.w3c.dom.NodeList;
43
44 import com.google.common.collect.ImmutableMap;
45 import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
46 import com.puppycrawl.tools.checkstyle.Checker;
47 import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
48 import com.puppycrawl.tools.checkstyle.ModuleFactory;
49 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
50 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
51 import com.puppycrawl.tools.checkstyle.api.DetailAST;
52 import com.puppycrawl.tools.checkstyle.api.Scope;
53 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
54 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
55 import com.puppycrawl.tools.checkstyle.checks.LineSeparatorOption;
56 import com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck;
57 import com.puppycrawl.tools.checkstyle.checks.blocks.BlockOption;
58 import com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyOption;
59 import com.puppycrawl.tools.checkstyle.checks.blocks.RightCurlyOption;
60 import com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderOption;
61 import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocContentLocationOption;
62 import com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocMethodCheck;
63 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
64 import com.puppycrawl.tools.checkstyle.checks.whitespace.PadOption;
65 import com.puppycrawl.tools.checkstyle.checks.whitespace.WrapOption;
66 import com.puppycrawl.tools.checkstyle.internal.utils.TestUtil;
67 import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
68 import com.puppycrawl.tools.checkstyle.internal.utils.XmlUtil;
69 import com.puppycrawl.tools.checkstyle.site.PropertiesMacro;
70 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
71 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
72 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
73
74 public class XdocsJavaDocsTest extends AbstractModuleTestSupport {
75 private static final Map<String, Class<?>> FULLY_QUALIFIED_CLASS_NAMES =
76 ImmutableMap.<String, Class<?>>builder()
77 .put("int", int.class)
78 .put("int[]", int[].class)
79 .put("boolean", boolean.class)
80 .put("double", double.class)
81 .put("double[]", double[].class)
82 .put("String", String.class)
83 .put("String[]", String[].class)
84 .put("Pattern", Pattern.class)
85 .put("Pattern[]", Pattern[].class)
86 .put("AccessModifierOption[]", AccessModifierOption[].class)
87 .put("BlockOption", BlockOption.class)
88 .put("ClosingParensOption", AnnotationUseStyleCheck.ClosingParensOption.class)
89 .put("ElementStyleOption", AnnotationUseStyleCheck.ElementStyleOption.class)
90 .put("File", File.class)
91 .put("ImportOrderOption", ImportOrderOption.class)
92 .put("JavadocContentLocationOption", JavadocContentLocationOption.class)
93 .put("LeftCurlyOption", LeftCurlyOption.class)
94 .put("LineSeparatorOption", LineSeparatorOption.class)
95 .put("PadOption", PadOption.class)
96 .put("RightCurlyOption", RightCurlyOption.class)
97 .put("Scope", Scope.class)
98 .put("SeverityLevel", SeverityLevel.class)
99 .put("TrailingArrayCommaOption", AnnotationUseStyleCheck.TrailingArrayCommaOption.class)
100 .put("URI", URI.class)
101 .put("WrapOption", WrapOption.class)
102 .put("PARAM_LITERAL", int[].class).build();
103
104 private static final List<List<Node>> CHECK_PROPERTIES = new ArrayList<>();
105 private static final Map<String, String> CHECK_PROPERTY_DOC = new HashMap<>();
106 private static final Map<String, String> CHECK_TEXT = new HashMap<>();
107
108 private static Checker checker;
109
110 private static String checkName;
111
112 private static Path currentXdocPath;
113
114 @Override
115 protected String getPackageLocation() {
116 return "com.puppycrawl.tools.checkstyle.internal";
117 }
118
119 @BeforeEach
120 public void setUp() throws Exception {
121 final DefaultConfiguration checkConfig = new DefaultConfiguration(
122 JavaDocCapture.class.getName());
123 checker = createChecker(checkConfig);
124 }
125
126
127
128
129
130
131
132
133
134 @Test
135 public void testAllCheckSectionJavaDocs() throws Exception {
136 final ModuleFactory moduleFactory = TestUtil.getPackageObjectFactory();
137
138 for (Path path : XdocUtil.getXdocsConfigFilePaths(XdocUtil.getXdocsFilePaths())) {
139 currentXdocPath = path;
140 final File file = path.toFile();
141 final String fileName = file.getName();
142
143 if ("config_system_properties.xml".equals(fileName)
144 || "index.xml".equals(fileName)) {
145 continue;
146 }
147
148 final String input = Files.readString(path);
149 final Document document = XmlUtil.getRawXml(fileName, input, input);
150 final NodeList sources = document.getElementsByTagName("section");
151
152 for (int position = 0; position < sources.getLength(); position++) {
153 final Node section = sources.item(position);
154 final String sectionName = XmlUtil.getNameAttributeOfNode(section);
155
156 if ("Content".equals(sectionName) || "Overview".equals(sectionName)) {
157 continue;
158 }
159
160 examineCheckSection(moduleFactory, fileName, sectionName, section);
161 }
162 }
163 }
164
165 private static void examineCheckSection(ModuleFactory moduleFactory, String fileName,
166 String sectionName, Node section) throws Exception {
167 final Object instance;
168
169 try {
170 instance = moduleFactory.createModule(sectionName);
171 }
172 catch (CheckstyleException ex) {
173 throw new CheckstyleException(fileName + " couldn't find class: " + sectionName, ex);
174 }
175
176 CHECK_TEXT.clear();
177 CHECK_PROPERTIES.clear();
178 CHECK_PROPERTY_DOC.clear();
179 checkName = sectionName;
180
181 examineCheckSectionChildren(section);
182
183 final List<File> files = new ArrayList<>();
184 files.add(new File("src/main/java/" + instance.getClass().getName().replace(".", "/")
185 + ".java"));
186
187 checker.process(files);
188 }
189
190 private static void examineCheckSectionChildren(Node section) {
191 for (Node subSection : XmlUtil.getChildrenElements(section)) {
192 if (!"subsection".equals(subSection.getNodeName())) {
193 final String text = getNodeText(subSection);
194 if (text.startsWith("Since Checkstyle")) {
195 CHECK_TEXT.put("since", text.substring(17));
196 }
197 continue;
198 }
199
200 final String subSectionName = XmlUtil.getNameAttributeOfNode(subSection);
201
202 examineCheckSubSection(subSection, subSectionName);
203 }
204 }
205
206 private static void examineCheckSubSection(Node subSection, String subSectionName) {
207 switch (subSectionName) {
208 case "Description":
209 case "Examples":
210 case "Notes":
211 case "Rule Description":
212 CHECK_TEXT.put(subSectionName, getNodeText(subSection).replace("\r", ""));
213 break;
214 case "Properties":
215 populateProperties(subSection);
216 CHECK_TEXT.put(subSectionName, createPropertiesText());
217 break;
218 case "Example of Usage":
219 case "Violation Messages":
220 CHECK_TEXT.put(subSectionName,
221 createViolationMessagesText(getViolationMessages(subSection)));
222 break;
223 case "Package":
224 case "Parent Module":
225 CHECK_TEXT.put(subSectionName, createParentText(subSection));
226 break;
227 default:
228 break;
229 }
230 }
231
232 private static List<String> getViolationMessages(Node subsection) {
233 final Node child = XmlUtil.getFirstChildElement(subsection);
234 final List<String> violationMessages = new ArrayList<>();
235 for (Node row : XmlUtil.getChildrenElements(child)) {
236 violationMessages.add(row.getTextContent().trim());
237 }
238 return violationMessages;
239 }
240
241 private static String createViolationMessagesText(List<String> violationMessages) {
242 final StringBuilder result = new StringBuilder(100);
243 result.append("\n<p>\nViolation Message Keys:\n</p>\n<ul>");
244
245 for (String msg : violationMessages) {
246 result.append("\n<li>\n{@code ").append(msg).append("}\n</li>");
247 }
248
249 result.append("\n</ul>");
250 return result.toString();
251 }
252
253 private static String createParentText(Node subsection) {
254 return "\n<p>\nParent is {@code com.puppycrawl.tools.checkstyle."
255 + XmlUtil.getFirstChildElement(subsection).getTextContent().trim() + "}\n</p>";
256 }
257
258 private static void populateProperties(Node subSection) {
259 boolean skip = true;
260
261
262
263
264 Node child = XmlUtil.getFirstChildElement(subSection);
265 if (child.hasAttributes() && child.getAttributes().getNamedItem("class") != null
266 && "wrapper".equals(child.getAttributes().getNamedItem("class")
267 .getTextContent())) {
268 child = XmlUtil.getFirstChildElement(child);
269 }
270 for (Node row : XmlUtil.getChildrenElements(child)) {
271 if (skip) {
272 skip = false;
273 continue;
274 }
275 CHECK_PROPERTIES.add(new ArrayList<>(XmlUtil.getChildrenElements(row)));
276 }
277 }
278
279 private static String createPropertiesText() {
280 final StringBuilder result = new StringBuilder(100);
281
282 result.append("\n<ul>");
283
284 for (List<Node> property : CHECK_PROPERTIES) {
285 final String propertyName = getNodeText(property.get(0));
286
287 result.append("\n<li>\nProperty {@code ");
288 result.append(propertyName);
289 result.append("} - ");
290
291 final String temp = getNodeText(property.get(1));
292
293 result.append(temp);
294 CHECK_PROPERTY_DOC.put(propertyName, temp);
295
296 String typeText = "java.lang.String[]";
297 final String propertyType = property.get(2).getTextContent();
298 final boolean isSpecialAllTokensType = propertyType.contains("set of any supported");
299 final boolean isPropertyTokenType = isSpecialAllTokensType
300 || propertyType.contains("subset of tokens")
301 || propertyType.contains("subset of javadoc tokens");
302 if (!isPropertyTokenType) {
303 final String typeName =
304 getCorrectNodeBasedOnPropertyType(property).getTextContent().trim();
305 typeText = FULLY_QUALIFIED_CLASS_NAMES.get(typeName).getTypeName();
306 }
307 if (isSpecialAllTokensType) {
308 typeText = "anyTokenTypesSet";
309 }
310 result.append(" Type is {@code ").append(typeText).append("}.");
311
312 if (!isSpecialAllTokensType) {
313 final String validationType = getValidationType(isPropertyTokenType, propertyName);
314 if (validationType != null) {
315 result.append(validationType);
316 }
317 }
318
319 result.append(getDefaultValueOfType(propertyName, isSpecialAllTokensType));
320
321 result.append(emptyStringArrayDefaultValue(property.get(3), isPropertyTokenType));
322
323 if (result.charAt(result.length() - 1) != '.') {
324 result.append('.');
325 }
326
327 result.append("\n</li>");
328 }
329
330 result.append("\n</ul>");
331
332 return result.toString();
333 }
334
335 private static Node getCorrectNodeBasedOnPropertyType(List<Node> property) {
336 final Node result;
337 if (property.get(2).getFirstChild().getFirstChild() == null) {
338 result = property.get(2).getFirstChild().getNextSibling();
339 }
340 else {
341 result = property.get(2).getFirstChild().getFirstChild();
342 }
343 return result;
344 }
345
346 private static String getDefaultValueOfType(String propertyName,
347 boolean isSpecialAllTokensType) {
348 final String result;
349 if (!isSpecialAllTokensType
350 && (propertyName.endsWith("token") || propertyName.endsWith(
351 "tokens"))) {
352 result = " Default value is: ";
353 }
354 else {
355 result = " Default value is ";
356 }
357 return result;
358 }
359
360 private static String getValidationType(boolean isPropertyTokenType, String propertyName) {
361 String result = null;
362 if (PropertiesMacro.NON_BASE_TOKEN_PROPERTIES.contains(checkName + " - " + propertyName)) {
363 result = " Validation type is {@code tokenTypesSet}.";
364 }
365 else if (isPropertyTokenType) {
366 result = " Validation type is {@code tokenSet}.";
367 }
368 return result;
369 }
370
371 private static String emptyStringArrayDefaultValue(Node defaultValueNode,
372 boolean isPropertyTokenType) {
373 String defaultValueText = getNodeText(defaultValueNode);
374 if ("{@code {}}".equals(defaultValueText)
375 || "{@code all files}".equals(defaultValueText)
376 || isPropertyTokenType && "{@code empty}".equals(defaultValueText)) {
377 defaultValueText = "{@code \"\"}";
378 }
379 return defaultValueText;
380 }
381
382 private static String getNodeText(Node node) {
383 final StringBuilder result = new StringBuilder(20);
384
385 for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) {
386 if (child.getNodeType() == Node.TEXT_NODE) {
387 for (String temp : child.getTextContent().split("\n")) {
388 final String text = temp.trim();
389
390 if (!text.isEmpty()) {
391 if (shouldAppendSpace(result, text.charAt(0))) {
392 result.append(' ');
393 }
394
395 result.append(text);
396 }
397 }
398 }
399 else {
400 if (child.hasAttributes() && child.getAttributes().getNamedItem("class") != null
401 && "wrapper".equals(child.getAttributes().getNamedItem("class")
402 .getNodeValue())) {
403 appendNodeText(result, XmlUtil.getFirstChildElement(child));
404 }
405 else {
406 appendNodeText(result, child);
407 }
408 }
409 }
410
411 return result.toString();
412 }
413
414
415 private static void appendNodeText(StringBuilder result, Node node) {
416 final String name = transformXmlToJavaDocName(node.getNodeName());
417 final boolean list = "ol".equals(name) || "ul".equals(name);
418 final boolean newLineOpenBefore = list || "p".equals(name) || "pre".equals(name)
419 || "li".equals(name);
420 final boolean newLineOpenAfter = newLineOpenBefore && !list;
421 final boolean newLineClose = newLineOpenAfter || list;
422 final boolean sanitize = "pre".equals(name);
423 final boolean changeToTag = "code".equals(name);
424
425 if (newLineOpenBefore) {
426 result.append('\n');
427 }
428 else if (shouldAppendSpace(result, '<')) {
429 result.append(' ');
430 }
431
432 if (changeToTag) {
433 result.append("{@");
434 result.append(name);
435 result.append(' ');
436 }
437 else {
438 result.append('<');
439 result.append(name);
440 result.append(getAttributeText(name, node.getAttributes()));
441 result.append('>');
442 }
443
444 if (newLineOpenAfter) {
445 result.append('\n');
446 }
447
448 if (sanitize) {
449 result.append(XmlUtil.sanitizeXml(node.getTextContent()));
450 }
451 else {
452 result.append(getNodeText(node));
453 }
454
455 if (newLineClose) {
456 result.append('\n');
457 }
458
459 if (changeToTag) {
460 result.append('}');
461 }
462 else {
463 result.append("</");
464 result.append(name);
465 result.append('>');
466 }
467 }
468
469 private static boolean shouldAppendSpace(StringBuilder text, char firstCharToAppend) {
470 final boolean result;
471
472 if (text.length() == 0) {
473 result = false;
474 }
475 else {
476 final char last = text.charAt(text.length() - 1);
477
478 result = (firstCharToAppend == '@'
479 || Character.getType(firstCharToAppend) == Character.DASH_PUNCTUATION
480 || Character.getType(last) == Character.OTHER_PUNCTUATION
481 || Character.isAlphabetic(last)
482 || Character.isAlphabetic(firstCharToAppend)) && !Character.isWhitespace(last);
483 }
484
485 return result;
486 }
487
488 private static String transformXmlToJavaDocName(String name) {
489 final String result;
490
491 if ("source".equals(name)) {
492 result = "pre";
493 }
494 else if ("h4".equals(name)) {
495 result = "p";
496 }
497 else {
498 result = name;
499 }
500
501 return result;
502 }
503
504 private static String getAttributeText(String nodeName, NamedNodeMap attributes) {
505 final StringBuilder result = new StringBuilder(20);
506
507 for (int i = 0; i < attributes.getLength(); i++) {
508 result.append(' ');
509
510 final Node attribute = attributes.item(i);
511 final String attrName = attribute.getNodeName();
512 final String attrValue;
513
514 if ("a".equals(nodeName) && "href".equals(attrName)) {
515 final String value = attribute.getNodeValue();
516
517 assertWithMessage("links starting with '#' aren't supported: " + value)
518 .that(value.charAt(0))
519 .isNotEqualTo('#');
520
521 attrValue = getLinkValue(value);
522 }
523 else {
524 attrValue = attribute.getNodeValue();
525 }
526
527 result.append(attrName);
528 result.append("=\"");
529 result.append(attrValue);
530 result.append('"');
531 }
532
533 return result.toString();
534 }
535
536 private static String getLinkValue(String initialValue) {
537 String value = initialValue;
538 final String attrValue;
539 if (value.contains("://")) {
540 attrValue = value;
541 }
542 else {
543 if (value.charAt(0) == '/') {
544 value = value.substring(1);
545 }
546
547
548 if (!initialValue.startsWith("/dtds")) {
549 value = currentXdocPath
550 .getParent()
551 .resolve(Paths.get(value))
552 .normalize()
553 .toString()
554 .replaceAll("src[\\\\/]xdocs[\\\\/]", "")
555 .replaceAll("\\\\", "/");
556 }
557
558 attrValue = "https://checkstyle.org/" + value;
559 }
560 return attrValue;
561 }
562
563 public static class JavaDocCapture extends AbstractCheck {
564 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
565
566 @Override
567 public boolean isCommentNodesRequired() {
568 return true;
569 }
570
571 @Override
572 public int[] getRequiredTokens() {
573 return new int[] {
574 TokenTypes.BLOCK_COMMENT_BEGIN,
575 };
576 }
577
578 @Override
579 public int[] getDefaultTokens() {
580 return getRequiredTokens();
581 }
582
583 @Override
584 public int[] getAcceptableTokens() {
585 return getRequiredTokens();
586 }
587
588 @Override
589 public void visitToken(DetailAST ast) {
590 if (JavadocUtil.isJavadocComment(ast)) {
591 final DetailAST parentNode = getParent(ast);
592
593 switch (parentNode.getType()) {
594 case TokenTypes.CLASS_DEF:
595 visitClass(ast);
596 break;
597 case TokenTypes.METHOD_DEF:
598 visitMethod(ast, parentNode);
599 break;
600 case TokenTypes.VARIABLE_DEF:
601 visitField(ast, parentNode);
602 break;
603 case TokenTypes.CTOR_DEF:
604 case TokenTypes.ENUM_DEF:
605 case TokenTypes.ENUM_CONSTANT_DEF:
606
607 break;
608 default:
609 assertWithMessage(
610 "Unknown token '" + TokenUtil.getTokenName(parentNode.getType())
611 + "': " + ast.getLineNo()).fail();
612 break;
613 }
614 }
615 }
616
617 private static DetailAST getParent(DetailAST node) {
618 DetailAST result = node.getParent();
619 int type = result.getType();
620
621 while (type == TokenTypes.MODIFIERS || type == TokenTypes.ANNOTATION) {
622 result = result.getParent();
623 type = result.getType();
624 }
625
626 return result;
627 }
628
629 private static void visitClass(DetailAST node) {
630 String violationMessagesText = CHECK_TEXT.get("Violation Messages");
631
632 if (checkName.endsWith("Filter") || "SuppressWarningsHolder".equals(checkName)) {
633 violationMessagesText = "";
634 }
635
636 if (ScopeUtil.isInScope(node, Scope.PUBLIC)) {
637 final String expected = CHECK_TEXT.get("Description")
638 + CHECK_TEXT.computeIfAbsent("Rule Description", unused -> "")
639 + CHECK_TEXT.computeIfAbsent("Notes", unused -> "")
640 + CHECK_TEXT.computeIfAbsent("Properties", unused -> "")
641 + CHECK_TEXT.get("Parent Module")
642 + violationMessagesText + " @since "
643 + CHECK_TEXT.get("since");
644
645 assertWithMessage(checkName + "'s class-level JavaDoc")
646 .that(getJavaDocText(node))
647 .isEqualTo(expected);
648 }
649 }
650
651 private static void visitField(DetailAST node, DetailAST parentNode) {
652 if (ScopeUtil.isInScope(parentNode, Scope.PUBLIC)) {
653 final String propertyName = parentNode.findFirstToken(TokenTypes.IDENT).getText();
654 final String propertyDoc = CHECK_PROPERTY_DOC.get(propertyName);
655
656 if (propertyDoc != null) {
657 assertWithMessage(checkName + "'s class field-level JavaDoc for "
658 + propertyName)
659 .that(getJavaDocText(node))
660 .isEqualTo(makeFirstUpper(propertyDoc));
661 }
662 }
663 }
664
665 private static void visitMethod(DetailAST node, DetailAST parentNode) {
666 if (ScopeUtil.isInScope(node, Scope.PUBLIC) && isSetterMethod(parentNode)) {
667 final String propertyUpper = parentNode.findFirstToken(TokenTypes.IDENT)
668 .getText().substring(3);
669 final String propertyName = makeFirstLower(propertyUpper);
670 final String propertyDoc = CHECK_PROPERTY_DOC.get(propertyName);
671
672 if (propertyDoc != null) {
673 final String javaDoc = getJavaDocText(node);
674
675 assertWithMessage(checkName + "'s class method-level JavaDoc for "
676 + propertyName)
677 .that(javaDoc.substring(0, javaDoc.indexOf(" @param")))
678 .isEqualTo("Setter to " + makeFirstLower(propertyDoc));
679 }
680 }
681 }
682
683
684
685
686
687
688
689
690
691 private static boolean isSetterMethod(DetailAST ast) {
692 boolean setterMethod = false;
693
694 if (ast.getType() == TokenTypes.METHOD_DEF) {
695 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
696 final String name = type.getNextSibling().getText();
697 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
698 final boolean voidReturnType = type.findFirstToken(TokenTypes.LITERAL_VOID) != null;
699
700 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
701 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
702
703 if (matchesSetterFormat && voidReturnType && singleParam) {
704 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
705
706 setterMethod = slist != null;
707 }
708 }
709 return setterMethod;
710 }
711
712 private static String getJavaDocText(DetailAST node) {
713 final String text = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document>\n"
714 + node.getFirstChild().getText().replaceAll("(^|\\r?\\n)\\s*\\* ?", "\n")
715 .replaceAll("\\n?@noinspection.*\\r?\\n[^@]*", "\n")
716 .trim() + "\n</document>";
717 String result = null;
718
719 try {
720 result = getNodeText(XmlUtil.getRawXml(checkName, text, text).getFirstChild())
721 .replace("\r", "");
722 }
723 catch (ParserConfigurationException ex) {
724 assertWithMessage("Exception: " + ex.getClass() + " - " + ex.getMessage()).fail();
725 }
726
727 return result;
728 }
729
730 private static String makeFirstUpper(String str) {
731 final char ch = str.charAt(0);
732 final String result;
733
734 if (Character.isLowerCase(ch)) {
735 result = Character.toUpperCase(ch) + str.substring(1);
736 }
737 else {
738 result = str;
739 }
740
741 return result;
742 }
743
744 private static String makeFirstLower(String str) {
745 return Character.toLowerCase(str.charAt(0)) + str.substring(1);
746 }
747 }
748 }