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                         @SuppressWarnings("deprecation")
  86                         Object o = Thread.currentThread().getContextClassLoader().loadClass(type).newInstance();
  87                         t = (Terminal) o;
  88                     }
  89                     catch (Exception e) {
  90                         throw new IllegalArgumentException(MessageFormat.format("Invalid terminal type: {0}", type), e);
  91                     }
  92                 }
  93             }
  94         }
  95         catch (Exception e) {
  96             Log.error("Failed to construct terminal; falling back to unsupported", e);
  97             t = new UnsupportedTerminal();
  98         }
  99 
 100         Log.debug("Created Terminal: ", t);
 101 
 102         try {
 103             t.init();
 104         }
 105         catch (Throwable e) {
 106             Log.error("Terminal initialization failed; falling back to unsupported", e);
 107             return new UnsupportedTerminal();
 108         }
 109 
 110         return t;
 111     }
 112 
 113     public static synchronized void reset() {
 114         term = null;
 115     }
 116 
 117     public static synchronized void resetIf(final Terminal t) {
 118         if(t == term) {
 119             reset();
 120         }
 121     }
 122 
 123     public static enum Type
 124     {
 125         AUTO,
 126         WINDOWS,
 127         UNIX,
 128         NONE
 129     }
 130 
 131     public static synchronized void configure(final String type) {
 132         checkNotNull(type);
 133         System.setProperty(JLINE_TERMINAL, type);
 134     }
 135 
 136     public static synchronized void configure(final Type type) {
 137         checkNotNull(type);
 138         configure(type.name().toLowerCase());
 139     }
 140 
 141     //
 142     // Flavor Support
 143     //
 144 
 145     public static enum Flavor
 146     {
 147         WINDOWS,
 148         UNIX
 149     }
 150 
 151     private static final Map<Flavor, Callable<? extends Terminal>> FLAVORS = new HashMap<>();
 152 
 153     static {
 154 //        registerFlavor(Flavor.WINDOWS, AnsiWindowsTerminal.class);
 155 //        registerFlavor(Flavor.UNIX, UnixTerminal.class);
 156         registerFlavor(Flavor.WINDOWS, WindowsTerminal :: new);
 157         registerFlavor(Flavor.UNIX, UnixTerminal :: new);
 158     }
 159 
 160     public static synchronized Terminal get() {
 161         if (term == null) {
 162             term = create();
 163         }
 164         return term;
 165     }
 166 
 167     public static Terminal getFlavor(final Flavor flavor) throws Exception {
 168         return FLAVORS.getOrDefault(flavor, () -> {throw new InternalError();}).call();
 169     }
 170 
 171     public static void registerFlavor(final Flavor flavor, final Callable<? extends Terminal> sup) {
 172         FLAVORS.put(flavor, sup);
 173     }
 174 
 175 }