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 
  26 package jdk.internal.jshell.tool;
  27 
  28 import java.io.InputStream;
  29 import java.io.PrintStream;
  30 import java.util.Locale;
  31 import java.util.Map;
  32 import java.util.Objects;
  33 import java.util.Set;
  34 import java.util.prefs.BackingStoreException;
  35 import java.util.prefs.Preferences;
  36 import jdk.jshell.tool.JavaShellToolBuilder;
  37 
  38 /**
  39  * Builder for programmatically building the jshell tool.
  40  */
  41 public 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 }