1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.jline;
20
21 import javax.annotation.Priority;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.concurrent.ConcurrentHashMap;
31
32 import org.apache.maven.api.Constants;
33 import org.apache.maven.api.annotations.Experimental;
34 import org.apache.maven.api.services.MessageBuilder;
35 import org.apache.maven.api.services.MessageBuilderFactory;
36 import org.codehaus.plexus.components.interactivity.InputHandler;
37 import org.codehaus.plexus.components.interactivity.OutputHandler;
38 import org.codehaus.plexus.components.interactivity.Prompter;
39 import org.codehaus.plexus.components.interactivity.PrompterException;
40 import org.jline.utils.AttributedStringBuilder;
41 import org.jline.utils.AttributedStyle;
42 import org.jline.utils.StyleResolver;
43
44 @Experimental
45 @Named
46 @Singleton
47 @Priority(10)
48 public class JLineMessageBuilderFactory implements MessageBuilderFactory, Prompter, InputHandler, OutputHandler {
49
50 private final StyleResolver resolver;
51
52 public JLineMessageBuilderFactory() {
53 this.resolver = new MavenStyleResolver();
54 }
55
56 @Override
57 public boolean isColorEnabled() {
58 return false;
59 }
60
61 @Override
62 public int getTerminalWidth() {
63 return MessageUtils.getTerminalWidth();
64 }
65
66 @Override
67 public MessageBuilder builder() {
68 return new JlineMessageBuilder();
69 }
70
71 @Override
72 public MessageBuilder builder(int size) {
73 return new JlineMessageBuilder(size);
74 }
75
76 @Override
77 public String readLine() throws IOException {
78 return doPrompt(null, false);
79 }
80
81 @Override
82 public String readPassword() throws IOException {
83 return doPrompt(null, true);
84 }
85
86 @Override
87 public List<String> readMultipleLines() throws IOException {
88 List<String> lines = new ArrayList<>();
89 for (String line = this.readLine(); line != null && !line.isEmpty(); line = readLine()) {
90 lines.add(line);
91 }
92 return lines;
93 }
94
95 @Override
96 public void write(String line) throws IOException {
97 doDisplay(line);
98 }
99
100 @Override
101 public void writeLine(String line) throws IOException {
102 doDisplay(line + System.lineSeparator());
103 }
104
105 @Override
106 public String prompt(String message) throws PrompterException {
107 return prompt(message, null, null);
108 }
109
110 @Override
111 public String prompt(String message, String defaultReply) throws PrompterException {
112 return prompt(message, null, defaultReply);
113 }
114
115 @Override
116 public String prompt(String message, List possibleValues) throws PrompterException {
117 return prompt(message, possibleValues, null);
118 }
119
120 @Override
121 public String prompt(String message, List possibleValues, String defaultReply) throws PrompterException {
122 return doPrompt(message, possibleValues, defaultReply, false);
123 }
124
125 @Override
126 public String promptForPassword(String message) throws PrompterException {
127 return doPrompt(message, null, null, true);
128 }
129
130 @Override
131 public void showMessage(String message) throws PrompterException {
132 try {
133 doDisplay(message);
134 } catch (IOException e) {
135 throw new PrompterException("Failed to present prompt", e);
136 }
137 }
138
139 String doPrompt(String message, List<Object> possibleValues, String defaultReply, boolean password)
140 throws PrompterException {
141 String formattedMessage = formatMessage(message, possibleValues, defaultReply);
142 String line;
143 do {
144 try {
145 line = doPrompt(formattedMessage, password);
146 if (line == null && defaultReply == null) {
147 throw new IOException("EOF");
148 }
149 } catch (IOException e) {
150 throw new PrompterException("Failed to prompt user", e);
151 }
152 if (line == null || line.isEmpty()) {
153 line = defaultReply;
154 }
155 if (line != null && (possibleValues != null && !possibleValues.contains(line))) {
156 try {
157 doDisplay("Invalid selection.\n");
158 } catch (IOException e) {
159 throw new PrompterException("Failed to present feedback", e);
160 }
161 }
162 } while (line == null || (possibleValues != null && !possibleValues.contains(line)));
163 return line;
164 }
165
166 private String formatMessage(String message, List<Object> possibleValues, String defaultReply) {
167 StringBuilder formatted = new StringBuilder(message.length() * 2);
168 formatted.append(message);
169 if (possibleValues != null && !possibleValues.isEmpty()) {
170 formatted.append(" (");
171 for (Iterator<?> it = possibleValues.iterator(); it.hasNext(); ) {
172 String possibleValue = String.valueOf(it.next());
173 formatted.append(possibleValue);
174 if (it.hasNext()) {
175 formatted.append('/');
176 }
177 }
178 formatted.append(')');
179 }
180 if (defaultReply != null) {
181 formatted.append(' ').append(defaultReply).append(": ");
182 }
183 return formatted.toString();
184 }
185
186 private void doDisplay(String message) throws IOException {
187 try {
188 MessageUtils.terminal.writer().print(message);
189 MessageUtils.terminal.flush();
190 } catch (Exception e) {
191 throw new IOException("Unable to display message", e);
192 }
193 }
194
195 private String doPrompt(String message, boolean password) throws IOException {
196 try {
197 if (message != null) {
198 if (!message.endsWith("\n")) {
199 if (message.endsWith(":")) {
200 message += " ";
201 } else if (!message.endsWith(": ")) {
202 message += ": ";
203 }
204 }
205 int lastNl = message.lastIndexOf('\n');
206 String begin = message.substring(0, lastNl + 1);
207 message = message.substring(lastNl + 1);
208 MessageUtils.terminal.writer().print(begin);
209 MessageUtils.terminal.flush();
210 }
211 return MessageUtils.reader.readLine(message, password ? '*' : null);
212 } catch (Exception e) {
213 throw new IOException("Unable to prompt user", e);
214 }
215 }
216
217 class JlineMessageBuilder implements MessageBuilder {
218
219 final AttributedStringBuilder builder;
220
221 JlineMessageBuilder() {
222 builder = new AttributedStringBuilder();
223 }
224
225 JlineMessageBuilder(int size) {
226 builder = new AttributedStringBuilder(size);
227 }
228
229 @Override
230 public MessageBuilder style(String style) {
231 if (MessageUtils.isColorEnabled()) {
232 builder.style(resolver.resolve(style));
233 }
234 return this;
235 }
236
237 @Override
238 public MessageBuilder resetStyle() {
239 builder.style(AttributedStyle.DEFAULT);
240 return this;
241 }
242
243 @Override
244 public MessageBuilder append(CharSequence cs) {
245 builder.append(cs);
246 return this;
247 }
248
249 @Override
250 public MessageBuilder append(CharSequence cs, int start, int end) {
251 builder.append(cs, start, end);
252 return this;
253 }
254
255 @Override
256 public MessageBuilder append(char c) {
257 builder.append(c);
258 return this;
259 }
260
261 @Override
262 public MessageBuilder setLength(int length) {
263 builder.setLength(length);
264 return this;
265 }
266
267 @Override
268 public String build() {
269 return builder.toAnsi(MessageUtils.terminal);
270 }
271
272 @Override
273 public String toString() {
274 return build();
275 }
276 }
277
278 static class MavenStyleResolver extends StyleResolver {
279
280 private final Map<String, AttributedStyle> styles = new ConcurrentHashMap<>();
281
282 MavenStyleResolver() {
283 super(key -> {
284 String v = System.getProperty(Constants.MAVEN_STYLE_PREFIX + key);
285 if (v == null) {
286 v = System.getProperty("style." + key);
287 }
288 return v;
289 });
290 }
291
292 @Override
293 public AttributedStyle resolve(String spec) {
294 return styles.computeIfAbsent(spec, this::doResolve);
295 }
296
297 @Override
298 public AttributedStyle resolve(String spec, String defaultSpec) {
299 return resolve(defaultSpec != null ? spec + ":-" + defaultSpec : spec);
300 }
301
302 private AttributedStyle doResolve(String spec) {
303 String def = null;
304 int i = spec.indexOf(":-");
305 if (i != -1) {
306 String[] parts = spec.split(":-");
307 spec = parts[0].trim();
308 def = parts[1].trim();
309 }
310 return super.resolve(spec, def);
311 }
312 }
313 }