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