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. The  exit code from
 233      * the Java shell tool is ignored.
 234      *
 235      * @param arguments the command-line arguments (including options), if any
 236      * @throws Exception an unexpected fatal exception
 237      */
 238     @Override
 239     public void run(String... arguments) throws Exception {
 240         rawTool().start(arguments);
 241     }
 242 
 243     /**
 244      * Run an instance of the Java shell tool as configured by the other methods
 245      * in this interface.  This call is not destructive, more than one call of
 246      * this method may be made from a configured builder.
 247      *
 248      * @param arguments the command-line arguments (including options), if any
 249      * @throws Exception an unexpected fatal exception
 250      * @return the exit code
 251      */
 252     @Override
 253     public int start(String... arguments) throws Exception {
 254         return rawTool().start(arguments);
 255     }
 256 
 257     /**
 258      * Persistence stored in Preferences.
 259      */
 260     private static class PreferencesStorage implements PersistentStorage {
 261 
 262         final Preferences p;
 263 
 264         PreferencesStorage(Preferences p) {
 265             this.p = p;
 266         }
 267 
 268         @Override
 269         public void clear() {
 270             try {
 271                 p.clear();
 272             } catch (BackingStoreException ex) {
 273                 throw new IllegalStateException(ex);
 274             }
 275         }
 276 
 277         @Override
 278         public String[] keys() {
 279             try {
 280                 return p.keys();
 281             } catch (BackingStoreException ex) {
 282                 throw new IllegalStateException(ex);
 283             }
 284         }
 285 
 286         @Override
 287         public String get(String key) {
 288             return p.get(key, null);
 289         }
 290 
 291         @Override
 292         public void put(String key, String value) {
 293             p.put(key, value);
 294         }
 295 
 296         @Override
 297         public void remove(String key) {
 298             p.remove(key);
 299         }
 300 
 301         @Override
 302         public void flush() {
 303             try {
 304                 p.flush();
 305             } catch (BackingStoreException ex) {
 306                 throw new IllegalStateException(ex);
 307             }
 308         }
 309     }
 310 
 311     /**
 312      * Persistence stored in a Map.
 313      */
 314     private static class MapStorage implements PersistentStorage {
 315 
 316         final Map<String, String> map;
 317 
 318         MapStorage(Map<String, String> map) {
 319             this.map = map;
 320         }
 321 
 322         @Override
 323         public void clear() {
 324 
 325             try {
 326                 map.clear();
 327             } catch (UnsupportedOperationException ex) {
 328                 throw new IllegalStateException(ex);
 329             }
 330         }
 331 
 332         @Override
 333         public String[] keys() {
 334             Set<String> ks = map.keySet();
 335             return ks.toArray(new String[ks.size()]);
 336         }
 337 
 338         @Override
 339         public String get(String key) {
 340             Objects.requireNonNull(key);
 341             return map.get(key);
 342         }
 343 
 344         @Override
 345         public void put(String key, String value) {
 346             Objects.requireNonNull(key);
 347             Objects.requireNonNull(value);
 348             map.put(key, value);
 349         }
 350 
 351         @Override
 352         public void remove(String key) {
 353             Objects.requireNonNull(key);
 354             map.remove(key);
 355         }
 356 
 357         @Override
 358         public void flush() {
 359             // no-op always up-to-date
 360         }
 361     }
 362 
 363 }