1 /*
   2  * Copyright (c) 2002-2012, the original author or authors.
   3  *
   4  * This software is distributable under the BSD license. See the terms of the
   5  * BSD license in the documentation provided with this software.
   6  *
   7  * http://www.opensource.org/licenses/bsd-license.php
   8  */
   9 package jdk.internal.jline;
  10 
  11 import java.text.MessageFormat;
  12 import java.util.HashMap;
  13 import java.util.Map;
  14 import java.util.concurrent.Callable;
  15 
  16 import jdk.internal.jline.internal.Configuration;
  17 import jdk.internal.jline.internal.Log;
  18 import jdk.internal.jline.internal.Preconditions;
  19 import static jdk.internal.jline.internal.Preconditions.checkNotNull;
  20 
  21 /**
  22  * Creates terminal instances.
  23  *
  24  * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
  25  * @since 2.0
  26  */
  27 public class TerminalFactory
  28 {
  29     public static final String JLINE_TERMINAL = "jline.terminal";
  30 
  31     public static final String AUTO = "auto";
  32 
  33     public static final String UNIX = "unix";
  34 
  35     public static final String WIN = "win";
  36 
  37     public static final String WINDOWS = "windows";
  38 
  39     public static final String NONE = "none";
  40 
  41     public static final String OFF = "off";
  42 
  43     public static final String FALSE = "false";
  44 
  45     private static Terminal term = null;
  46 
  47     public static synchronized Terminal create() {
  48         if (Log.TRACE) {
  49             //noinspection ThrowableInstanceNeverThrown
  50             Log.trace(new Throwable("CREATE MARKER"));
  51         }
  52 
  53         String type = Configuration.getString(JLINE_TERMINAL, AUTO);
  54         if ("dumb".equals(System.getenv("TERM"))) {
  55             type = "none";
  56             Log.debug("$TERM=dumb; setting type=", type);
  57         }
  58 
  59         Log.debug("Creating terminal; type=", type);
  60 
  61         Terminal t;
  62         try {
  63             String tmp = type.toLowerCase();
  64 
  65             if (tmp.equals(UNIX)) {
  66                 t = getFlavor(Flavor.UNIX);
  67             }
  68             else if (tmp.equals(WIN) | tmp.equals(WINDOWS)) {
  69                 t = getFlavor(Flavor.WINDOWS);
  70             }
  71             else if (tmp.equals(NONE) || tmp.equals(OFF) || tmp.equals(FALSE)) {
  72                 t = new UnsupportedTerminal();
  73             }
  74             else {
  75                 if (tmp.equals(AUTO)) {
  76                     String os = Configuration.getOsName();
  77                     Flavor flavor = Flavor.UNIX;
  78                     if (os.contains(WINDOWS)) {
  79                         flavor = Flavor.WINDOWS;
  80                     }
  81                     t = getFlavor(flavor);
  82                 }
  83                 else {
  84                     try {
  85                         t = (Terminal) Thread.currentThread().getContextClassLoader().loadClass(type).newInstance();
  86                     }
  87                     catch (Exception e) {
  88                         throw new IllegalArgumentException(MessageFormat.format("Invalid terminal type: {0}", type), e);
  89                     }
  90                 }
  91             }
  92         }
  93         catch (Exception e) {
  94             Log.error("Failed to construct terminal; falling back to unsupported", e);
  95             t = new UnsupportedTerminal();
  96         }
  97 
  98         Log.debug("Created Terminal: ", t);
  99 
 100         try {
 101             t.init();
 102         }
 103         catch (Throwable e) {
 104             Log.error("Terminal initialization failed; falling back to unsupported", e);
 105             return new UnsupportedTerminal();
 106         }
 107 
 108         return t;
 109     }
 110 
 111     public static synchronized void reset() {
 112         term = null;
 113     }
 114 
 115     public static synchronized void resetIf(final Terminal t) {
 116         if(t == term) {
 117             reset();
 118         }
 119     }
 120 
 121     public static enum Type
 122     {
 123         AUTO,
 124         WINDOWS,
 125         UNIX,
 126         NONE
 127     }
 128 
 129     public static synchronized void configure(final String type) {
 130         checkNotNull(type);
 131         System.setProperty(JLINE_TERMINAL, type);
 132     }
 133 
 134     public static synchronized void configure(final Type type) {
 135         checkNotNull(type);
 136         configure(type.name().toLowerCase());
 137     }
 138 
 139     //
 140     // Flavor Support
 141     //
 142 
 143     public static enum Flavor
 144     {
 145         WINDOWS,
 146         UNIX
 147     }
 148 
 149     private static final Map<Flavor, Callable<? extends Terminal>> FLAVORS = new HashMap<>();
 150 
 151     static {
 152 //        registerFlavor(Flavor.WINDOWS, AnsiWindowsTerminal.class);
 153 //        registerFlavor(Flavor.UNIX, UnixTerminal.class);
 154         registerFlavor(Flavor.WINDOWS, WindowsTerminal :: new);
 155         registerFlavor(Flavor.UNIX, UnixTerminal :: new);
 156     }
 157 
 158     public static synchronized Terminal get() {
 159         if (term == null) {
 160             term = create();
 161         }
 162         return term;
 163     }
 164 
 165     public static Terminal getFlavor(final Flavor flavor) throws Exception {
 166         return FLAVORS.getOrDefault(flavor, () -> {throw new InternalError();}).call();
 167     }
 168 
 169     public static void registerFlavor(final Flavor flavor, final Callable<? extends Terminal> sup) {
 170         FLAVORS.put(flavor, sup);
 171     }
 172 
 173 }