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