1/*
2 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.internal.jshell.tool;
27
28import java.io.InputStream;
29import java.io.PrintStream;
30import java.util.Locale;
31import java.util.Map;
32import java.util.Objects;
33import java.util.Set;
34import java.util.prefs.BackingStoreException;
35import java.util.prefs.Preferences;
36import jdk.jshell.tool.JavaShellToolBuilder;
37
38/**
39 * Builder for programmatically building the jshell tool.
40 */
41public class JShellToolBuilder implements JavaShellToolBuilder {
42
43    private static final String PREFERENCES_NODE = "tool/JShell";
44    private InputStream cmdIn = System.in;
45    private InputStream userIn = null;
46    private PrintStream cmdOut = System.out;
47    private PrintStream console = System.out;
48    private PrintStream userOut = System.out;
49    private PrintStream cmdErr = System.err;
50    private PrintStream userErr = System.err;
51    private PersistentStorage prefs = null;
52    private Map<String, String> vars = null;
53    private Locale locale = Locale.getDefault();
54    private boolean capturePrompt = false;
55
56    /**
57     * Set the input channels.
58     * Default, if not set, {@code in(System.in, null)}.
59     *
60     * @param cmdIn source of command input
61     * @param userIn source of input for running user code, or {@code null} to
62     * be extracted from cmdIn
63     * @return the {@code JavaShellToolBuilder} instance
64     */
65    @Override
66    public JavaShellToolBuilder in(InputStream cmdIn, InputStream userIn) {
67        this.cmdIn = cmdIn;
68        this.userIn = userIn;
69        return this;
70    }
71
72    /**
73     * Set the output channels. Same as {@code out(output, output, output)}.
74     * Default, if not set, {@code out(System.out)}.
75     *
76     * @param output destination of command feedback, console interaction, and
77     * user code output
78     * @return the {@code JavaShellToolBuilder} instance
79     */
80    @Override
81    public JavaShellToolBuilder out(PrintStream output) {
82        this.cmdOut = output;
83        this.console = output;
84        this.userOut = output;
85        return this;
86    }
87
88    /**
89     * Set the output channels.
90     * Default, if not set, {@code out(System.out, System.out, System.out)}.
91     *
92     * @param cmdOut destination of command feedback including error messages
93     * for users
94     * @param console destination of console interaction
95     * @param userOut destination of user code output.  For example, user snippet
96     * {@code System.out.println("Hello")} when executed {@code Hello} goes to
97     * userOut.
98     * @return the {@code JavaShellToolBuilder} instance
99     */
100    @Override
101    public JavaShellToolBuilder out(PrintStream cmdOut, PrintStream console, PrintStream userOut) {
102        this.cmdOut = cmdOut;
103        this.console = console;
104        this.userOut = userOut;
105        return this;
106    }
107
108    /**
109     * Set the error channels. Same as {@code err(error, error)}.
110     * Default, if not set, {@code err(System.err)}.
111     *
112     * @param error destination of tool errors, and
113     * user code errors
114     * @return the {@code JavaShellToolBuilder} instance
115     */
116    @Override
117    public JavaShellToolBuilder err(PrintStream error) {
118        this.cmdErr = error;
119        this.userErr = error;
120        return this;
121    }
122
123    /**
124     * Set the error channels.
125     * Default, if not set, {@code err(System.err, System.err, System.err)}.
126     *
127     * @param cmdErr destination of tool start-up and fatal errors
128     * @param userErr destination of user code error output.
129     * For example, user snippet  {@code System.err.println("Oops")}
130     * when executed {@code Oops} goes to userErr.
131     * @return the {@code JavaShellToolBuilder} instance
132     */
133    @Override
134    public JavaShellToolBuilder err(PrintStream cmdErr, PrintStream userErr) {
135        this.cmdErr = cmdErr;
136        this.userErr = userErr;
137        return this;
138    }
139
140    /**
141     * Set the storage mechanism for persistent information which includes
142     * input history and retained settings. Default if not set is the
143     * tool's standard persistence mechanism.
144     *
145     * @param prefs an instance of {@link java.util.prefs.Preferences} that
146     * is used to retrieve and store persistent information
147     * @return the {@code JavaShellToolBuilder} instance
148     */
149    @Override
150    public JavaShellToolBuilder persistence(Preferences prefs) {
151        this.prefs = new PreferencesStorage(prefs);
152        return this;
153    }
154
155    /**
156     * Set the storage mechanism for persistent information which includes
157     * input history and retained settings.   Default if not set is the
158     * tool's standard persistence mechanism.
159     *
160     * @param prefsMap  an instance of {@link java.util.Map} that
161     * is used to retrieve and store persistent information
162     * @return the {@code JavaShellToolBuilder} instance
163     */
164    @Override
165    public JavaShellToolBuilder persistence(Map<String, String> prefsMap) {
166        this.prefs = new MapStorage(prefsMap);
167        return this;
168    }
169
170    /**
171     * Set the source for environment variables.
172     * Default, if not set, {@code env(System.getenv())}.
173     *
174     * @param vars the Map of environment variable names to values
175     * @return the {@code JavaShellToolBuilder} instance
176     */
177    @Override
178    public JavaShellToolBuilder env(Map<String, String> vars) {
179        this.vars = vars;
180        return this;
181    }
182
183    /**
184     * Set the locale.
185     * Default, if not set, {@code locale(Locale.getDefault())}.
186     *
187     * @param locale the locale
188     * @return the {@code JavaShellToolBuilder} instance
189     */
190    @Override
191    public JavaShellToolBuilder locale(Locale locale) {
192        this.locale = locale;
193        return this;
194    }
195
196    /**
197     * Set if the special command capturing prompt override should be used.
198     * Default, if not set, {@code promptCapture(false)}.
199     *
200     * @param capture if {@code true}, basic prompt is the {@code ENQ}
201     * character and continuation prompt is the {@code ACK} character.
202     * If false, prompts are as set with set-up or user {@code /set} commands.
203     * @return the {@code JavaShellToolBuilder} instance
204     */
205    @Override
206    public JavaShellToolBuilder promptCapture(boolean capture) {
207        this.capturePrompt = capture;
208        return this;
209    }
210
211    /**
212     * Create a tool instance for testing. Not in JavaShellToolBuilder.
213     *
214     * @return the tool instance
215     */
216    public JShellTool rawTool() {
217        if (prefs == null) {
218            prefs = new PreferencesStorage(Preferences.userRoot().node(PREFERENCES_NODE));
219        }
220        if (vars == null) {
221            vars = System.getenv();
222        }
223        JShellTool sh = new JShellTool(cmdIn, cmdOut, cmdErr, console, userIn,
224                userOut, userErr, prefs, vars, locale);
225        sh.testPrompt = capturePrompt;
226        return sh;
227    }
228
229    /**
230     * Run an instance of the Java shell tool as configured by the other methods
231     * in this interface.  This call is not destructive, more than one call of
232     * this method may be made from a configured builder.
233     *
234     * @param arguments the command-line arguments (including options), if any
235     * @throws Exception an unexpected fatal exception
236     */
237    @Override
238    public void run(String... arguments) throws Exception {
239        rawTool().start(arguments);
240    }
241
242    /**
243     * Persistence stored in Preferences.
244     */
245    private static class PreferencesStorage implements PersistentStorage {
246
247        final Preferences p;
248
249        PreferencesStorage(Preferences p) {
250            this.p = p;
251        }
252
253        @Override
254        public void clear() {
255            try {
256                p.clear();
257            } catch (BackingStoreException ex) {
258                throw new IllegalStateException(ex);
259            }
260        }
261
262        @Override
263        public String[] keys() {
264            try {
265                return p.keys();
266            } catch (BackingStoreException ex) {
267                throw new IllegalStateException(ex);
268            }
269        }
270
271        @Override
272        public String get(String key) {
273            return p.get(key, null);
274        }
275
276        @Override
277        public void put(String key, String value) {
278            p.put(key, value);
279        }
280
281        @Override
282        public void remove(String key) {
283            p.remove(key);
284        }
285
286        @Override
287        public void flush() {
288            try {
289                p.flush();
290            } catch (BackingStoreException ex) {
291                throw new IllegalStateException(ex);
292            }
293        }
294    }
295
296    /**
297     * Persistence stored in a Map.
298     */
299    private static class MapStorage implements PersistentStorage {
300
301        final Map<String, String> map;
302
303        MapStorage(Map<String, String> map) {
304            this.map = map;
305        }
306
307        @Override
308        public void clear() {
309
310            try {
311                map.clear();
312            } catch (UnsupportedOperationException ex) {
313                throw new IllegalStateException(ex);
314            }
315        }
316
317        @Override
318        public String[] keys() {
319            Set<String> ks = map.keySet();
320            return ks.toArray(new String[ks.size()]);
321        }
322
323        @Override
324        public String get(String key) {
325            Objects.requireNonNull(key);
326            return map.get(key);
327        }
328
329        @Override
330        public void put(String key, String value) {
331            Objects.requireNonNull(key);
332            Objects.requireNonNull(value);
333            map.put(key, value);
334        }
335
336        @Override
337        public void remove(String key) {
338            Objects.requireNonNull(key);
339            map.remove(key);
340        }
341
342        @Override
343        public void flush() {
344            // no-op always up-to-date
345        }
346    }
347
348}
349