Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 64 additions & 36 deletions src/main/java/org/apache/maven/plugins/help/DescribeMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
Expand Down Expand Up @@ -733,48 +731,78 @@ private boolean describeCommand(StringBuilder descriptionBuffer) throws MojoExec
}

/**
* Invoke the following private method
* <code>HelpMojo#toLines(String, int, int, int)</code>
* Splits and wraps display text without depending on the generated help mojo implementation.
*
* @param text The text to split into lines, must not be <code>null</code>.
* @param indent The base indentation level of each line, must not be negative.
* @param indentSize The size of each indentation, must not be negative.
* @param lineLength The length of the line, must not be negative.
* @return The sequence of display lines, never <code>null</code>.
* @throws MojoFailureException if any can not invoke the method
* @throws MojoExecutionException if no line was found for <code>text</code>
* @param text the text to split
* @param indent the base indentation level of each line
* @param indentSize the size of each indentation
* @param lineLength the maximum line length
* @return the display lines
* @throws MojoFailureException if the requested indentation cannot be represented safely
*/
private static List<String> toLines(String text, int indent, int indentSize, int lineLength)
throws MojoFailureException, MojoExecutionException {
try {
Method m =
HelpMojo.class.getDeclaredMethod("toLines", String.class, Integer.TYPE, Integer.TYPE, Integer.TYPE);
m.setAccessible(true);
@SuppressWarnings("unchecked")
List<String> output = (List<String>) m.invoke(HelpMojo.class, text, indent, indentSize, lineLength);

if (output == null) {
throw new MojoExecutionException("No output was specified.");
throws MojoFailureException {
List<String> lines = new ArrayList<>();
String ind = repeat("\t", indent);

for (String plainLine : text.split("(\r\n)|(\r)|(\n)")) {
addLines(lines, ind + plainLine, indentSize, lineLength);
}
return lines;
}

private static void addLines(List<String> lines, String line, int indentSize, int lineLength)
throws MojoFailureException {
int lineIndent = getIndentLevel(line);
StringBuilder buf = new StringBuilder(256);

for (String token : line.split(" +")) {
if (buf.length() > 0) {
if (buf.length() + token.length() >= lineLength) {
lines.add(buf.toString());
buf.setLength(0);
buf.append(repeat(" ", Math.multiplyExact(lineIndent, indentSize)));
} else {
buf.append(' ');
}
}
for (int j = 0; j < token.length(); j++) {
char c = token.charAt(j);
if (c == '\t') {
buf.append(repeat(" ", indentSize - buf.length() % indentSize));
} else if (c == '\u00A0') {
buf.append(' ');
} else {
buf.append(c);
}
}
}
lines.add(buf.toString());
}

return output;
} catch (SecurityException e) {
throw new MojoFailureException("SecurityException: " + e.getMessage());
} catch (IllegalArgumentException e) {
throw new MojoFailureException("IllegalArgumentException: " + e.getMessage());
} catch (NoSuchMethodException e) {
throw new MojoFailureException("NoSuchMethodException: " + e.getMessage());
} catch (IllegalAccessException e) {
throw new MojoFailureException("IllegalAccessException: " + e.getMessage());
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();

if (cause instanceof NegativeArraySizeException) {
throw new MojoFailureException("NegativeArraySizeException: " + cause.getMessage());
private static int getIndentLevel(String line) {
int level = 0;
for (int i = 0; i < line.length() && line.charAt(i) == '\t'; i++) {
level++;
}
for (int i = level + 1; i <= level + 4 && i < line.length(); i++) {
if (line.charAt(i) == '\t') {
level++;
break;
}
}
return level;
}

throw new MojoFailureException("InvocationTargetException: " + e.getMessage());
private static String repeat(String str, int repeat) throws MojoFailureException {
if (repeat < 0 || ((long) repeat * str.length()) >= Integer.MAX_VALUE) {
throw new MojoFailureException("Requested indentation is too large");
}
StringBuilder result = new StringBuilder((int) ((long) repeat * str.length()));
for (int i = 0; i < repeat; i++) {
result.append(str);
}
return result.toString();
}

/**
Expand Down
13 changes: 13 additions & 0 deletions src/test/java/org/apache/maven/plugins/help/DescribeMojoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.MavenPluginManager;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
Expand All @@ -51,6 +52,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
Expand All @@ -76,6 +78,17 @@ void testGetExpressionsRoot()
toLines.invoke(null, "", 2, 2, 80);
}

@Test
void testToLinesRejectsOverflowingIndentation() throws Exception {
Method toLines = DescribeMojo.class.getDeclaredMethod("toLines", String.class, int.class, int.class, int.class);
toLines.setAccessible(true);

InvocationTargetException exception = assertThrows(
InvocationTargetException.class, () -> toLines.invoke(null, "text", Integer.MAX_VALUE, 2, 80));

assertTrue(exception.getCause() instanceof MojoFailureException);
}

@Test
void testValidExpression() throws Exception {
StringBuilder sb = new StringBuilder();
Expand Down