1. /*
  2. * @(#)ProcessEnvironment.java 1.6 04/04/05
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /* We use APIs that access a so-called Windows "Environment Block",
  8. * which looks like an array of jchars like this:
  9. *
  10. * FOO=BAR\u0000 ... GORP=QUUX\u0000\u0000
  11. *
  12. * This data structure has a number of peculiarities we must contend with:
  13. * - The NUL jchar separators, and a double NUL jchar terminator.
  14. * It appears that the Windows implementation requires double NUL
  15. * termination even if the environment is empty. We should always
  16. * generate environments with double NUL termination, while accepting
  17. * empty environments consisting of a single NUL.
  18. * - on Windows9x, this is actually an array of 8-bit chars, not jchars,
  19. * encoded in the system default encoding.
  20. * - The block must be sorted by Unicode value, case-insensitively.
  21. * - There are magic environment variables maintained by Windows
  22. * that start with a `=' (!) character. These are used for
  23. * Windows drive current directory (e.g. "=C:=C:\WINNT") or the
  24. * exit code of the last command (e.g. "=ExitCode=0000001").
  25. *
  26. * Since Java and non-9x Windows speak the same character set, and
  27. * even the same encoding, we don't have to deal with unreliable
  28. * conversion to byte streams. Just add a few NUL terminators.
  29. *
  30. * System.getenv(String) is case-insensitive, while System.getenv()
  31. * returns a map that is case-sensitive, which is consistent with
  32. * native Windows APIs.
  33. *
  34. * The non-private methods in this class are not for general use even
  35. * within this package. Instead, they are the system-dependent parts
  36. * of the system-independent method of the same name. Don't even
  37. * think of using this class unless your method's name appears below.
  38. *
  39. * @author Martin Buchholz
  40. * @version 1.6, 04/04/05
  41. * @since 1.5
  42. */
  43. package java.lang;
  44. import java.io.*;
  45. import java.util.*;
  46. final class ProcessEnvironment extends HashMap<String,String>
  47. {
  48. private static String validateName(String name) {
  49. // An initial `=' indicates a magic Windows variable name -- OK
  50. if (name.indexOf('=', 1) != -1 ||
  51. name.indexOf('\u0000') != -1)
  52. throw new IllegalArgumentException
  53. ("Invalid environment variable name: \"" + name + "\"");
  54. return name;
  55. }
  56. private static String validateValue(String value) {
  57. if (value.indexOf('\u0000') != -1)
  58. throw new IllegalArgumentException
  59. ("Invalid environment variable value: \"" + value + "\"");
  60. return value;
  61. }
  62. private static String nonNullString(Object o) {
  63. if (o == null)
  64. throw new NullPointerException();
  65. return (String) o;
  66. }
  67. public String put(String key, String value) {
  68. return super.put(validateName(key), validateValue(value));
  69. }
  70. public String get(Object key) {
  71. return super.get(nonNullString(key));
  72. }
  73. public boolean containsKey(Object key) {
  74. return super.containsKey(nonNullString(key));
  75. }
  76. public boolean containsValue(Object value) {
  77. return super.containsValue(nonNullString(value));
  78. }
  79. public String remove(Object key) {
  80. return super.remove(nonNullString(key));
  81. }
  82. private static class CheckedEntry
  83. implements Map.Entry<String,String>
  84. {
  85. private final Map.Entry<String,String> e;
  86. public CheckedEntry(Map.Entry<String,String> e) {this.e = e;}
  87. public String getKey() { return e.getKey();}
  88. public String getValue() { return e.getValue();}
  89. public String setValue(String value) {
  90. return e.setValue(validateValue(value));
  91. }
  92. public String toString() { return getKey() + "=" + getValue();}
  93. public boolean equals(Object o) {return e.equals(o);}
  94. public int hashCode() {return e.hashCode();}
  95. }
  96. private static class CheckedEntrySet
  97. extends AbstractSet<Map.Entry<String,String>>
  98. {
  99. private final Set<Map.Entry<String,String>> s;
  100. public CheckedEntrySet(Set<Map.Entry<String,String>> s) {this.s = s;}
  101. public int size() {return s.size();}
  102. public boolean isEmpty() {return s.isEmpty();}
  103. public void clear() { s.clear();}
  104. public Iterator<Map.Entry<String,String>> iterator() {
  105. return new Iterator<Map.Entry<String,String>>() {
  106. Iterator<Map.Entry<String,String>> i = s.iterator();
  107. public boolean hasNext() { return i.hasNext();}
  108. public Map.Entry<String,String> next() {
  109. return new CheckedEntry(i.next());
  110. }
  111. public void remove() { i.remove();}
  112. };
  113. }
  114. private static Map.Entry<String,String> checkedEntry (Object o) {
  115. Map.Entry<String,String> e = (Map.Entry<String,String>) o;
  116. nonNullString(e.getKey());
  117. nonNullString(e.getValue());
  118. return e;
  119. }
  120. public boolean contains(Object o) {return s.contains(checkedEntry(o));}
  121. public boolean remove(Object o) {return s.remove(checkedEntry(o));}
  122. }
  123. private static class CheckedValues extends AbstractCollection<String> {
  124. private final Collection<String> c;
  125. public CheckedValues(Collection<String> c) {this.c = c;}
  126. public int size() {return c.size();}
  127. public boolean isEmpty() {return c.isEmpty();}
  128. public void clear() { c.clear();}
  129. public Iterator<String> iterator() {return c.iterator();}
  130. public boolean contains(Object o) {return c.contains(nonNullString(o));}
  131. public boolean remove(Object o) {return c.remove(nonNullString(o));}
  132. }
  133. private static class CheckedKeySet extends AbstractSet<String> {
  134. private final Set<String> s;
  135. public CheckedKeySet(Set<String> s) {this.s = s;}
  136. public int size() {return s.size();}
  137. public boolean isEmpty() {return s.isEmpty();}
  138. public void clear() { s.clear();}
  139. public Iterator<String> iterator() {return s.iterator();}
  140. public boolean contains(Object o) {return s.contains(nonNullString(o));}
  141. public boolean remove(Object o) {return s.remove(nonNullString(o));}
  142. }
  143. public Set<String> keySet() {
  144. return new CheckedKeySet(super.keySet());
  145. }
  146. public Collection<String> values() {
  147. return new CheckedValues(super.values());
  148. }
  149. public Set<Map.Entry<String,String>> entrySet() {
  150. return new CheckedEntrySet(super.entrySet());
  151. }
  152. private static final class NameComparator
  153. implements Comparator<String> {
  154. public int compare(String x, String y) {
  155. return x.compareToIgnoreCase(y);
  156. }
  157. }
  158. private static final class EntryComparator
  159. implements Comparator<Map.Entry<String,String>> {
  160. public int compare(Map.Entry<String,String> e1,
  161. Map.Entry<String,String> e2) {
  162. return e1.getKey().compareToIgnoreCase(e2.getKey());
  163. }
  164. }
  165. // Allow `=' as first char in name, e.g. =C:=C:\DIR
  166. static final int MIN_NAME_LENGTH = 1;
  167. private static final NameComparator nameComparator;
  168. private static final EntryComparator entryComparator;
  169. private static final ProcessEnvironment theEnvironment;
  170. private static final Map<String,String> theUnmodifiableEnvironment;
  171. private static final Map<String,String> theCaseInsensitiveEnvironment;
  172. static {
  173. nameComparator = new NameComparator();
  174. entryComparator = new EntryComparator();
  175. theEnvironment = new ProcessEnvironment();
  176. theUnmodifiableEnvironment
  177. = Collections.unmodifiableMap(theEnvironment);
  178. String envblock = environmentBlock();
  179. int beg, end, eql;
  180. for (beg = 0;
  181. ((end = envblock.indexOf('\u0000', beg )) != -1 &&
  182. // An initial `=' indicates a magic Windows variable name -- OK
  183. (eql = envblock.indexOf('=' , beg+1)) != -1);
  184. beg = end + 1) {
  185. // Ignore corrupted environment strings.
  186. if (eql < end)
  187. theEnvironment.put(envblock.substring(beg, eql),
  188. envblock.substring(eql+1,end));
  189. }
  190. theCaseInsensitiveEnvironment
  191. = new TreeMap<String,String>(nameComparator);
  192. theCaseInsensitiveEnvironment.putAll(theEnvironment);
  193. }
  194. private ProcessEnvironment() {
  195. super();
  196. }
  197. private ProcessEnvironment(int capacity) {
  198. super(capacity);
  199. }
  200. // Only for use by System.getenv(String)
  201. static String getenv(String name) {
  202. // The original implementation used a native call to _wgetenv,
  203. // but it turns out that _wgetenv is only consistent with
  204. // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used
  205. // instead of `main', even in a process created using
  206. // CREATE_UNICODE_ENVIRONMENT. Instead we perform the
  207. // case-insensitive comparison ourselves. At least this
  208. // guarantees that System.getenv().get(String) will be
  209. // consistent with System.getenv(String).
  210. return theCaseInsensitiveEnvironment.get(name);
  211. }
  212. // Only for use by System.getenv()
  213. static Map<String,String> getenv() {
  214. return theUnmodifiableEnvironment;
  215. }
  216. // Only for use by ProcessBuilder.environment()
  217. static Map<String,String> environment() {
  218. return (Map<String,String>) theEnvironment.clone();
  219. }
  220. // Only for use by Runtime.exec(...String[]envp...)
  221. static Map<String,String> emptyEnvironment(int capacity) {
  222. return new ProcessEnvironment(capacity);
  223. }
  224. private static native String environmentBlock();
  225. // Only for use by ProcessImpl.start()
  226. String toEnvironmentBlock() {
  227. // Sort Unicode-case-insensitively by name
  228. List<Map.Entry<String,String>> list
  229. = new ArrayList<Map.Entry<String,String>>(entrySet());
  230. Collections.sort(list, entryComparator);
  231. StringBuilder sb = new StringBuilder(size()*30);
  232. for (Map.Entry<String,String> e : list)
  233. sb.append(e.getKey())
  234. .append('=')
  235. .append(e.getValue())
  236. .append('\u0000');
  237. // Ensure double NUL termination,
  238. // even if environment is empty.
  239. if (sb.length() == 0)
  240. sb.append('\u0000');
  241. sb.append('\u0000');
  242. return sb.toString();
  243. }
  244. static String toEnvironmentBlock(Map<String,String> map) {
  245. return map == null ? null :
  246. ((ProcessEnvironment)map).toEnvironmentBlock();
  247. }
  248. }