- /*
- * @(#)ProcessEnvironment.java 1.6 04/04/05
- *
- * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
- * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
- /* We use APIs that access a so-called Windows "Environment Block",
- * which looks like an array of jchars like this:
- *
- * FOO=BAR\u0000 ... GORP=QUUX\u0000\u0000
- *
- * This data structure has a number of peculiarities we must contend with:
- * - The NUL jchar separators, and a double NUL jchar terminator.
- * It appears that the Windows implementation requires double NUL
- * termination even if the environment is empty. We should always
- * generate environments with double NUL termination, while accepting
- * empty environments consisting of a single NUL.
- * - on Windows9x, this is actually an array of 8-bit chars, not jchars,
- * encoded in the system default encoding.
- * - The block must be sorted by Unicode value, case-insensitively.
- * - There are magic environment variables maintained by Windows
- * that start with a `=' (!) character. These are used for
- * Windows drive current directory (e.g. "=C:=C:\WINNT") or the
- * exit code of the last command (e.g. "=ExitCode=0000001").
- *
- * Since Java and non-9x Windows speak the same character set, and
- * even the same encoding, we don't have to deal with unreliable
- * conversion to byte streams. Just add a few NUL terminators.
- *
- * System.getenv(String) is case-insensitive, while System.getenv()
- * returns a map that is case-sensitive, which is consistent with
- * native Windows APIs.
- *
- * The non-private methods in this class are not for general use even
- * within this package. Instead, they are the system-dependent parts
- * of the system-independent method of the same name. Don't even
- * think of using this class unless your method's name appears below.
- *
- * @author Martin Buchholz
- * @version 1.6, 04/04/05
- * @since 1.5
- */
-
- package java.lang;
-
- import java.io.*;
- import java.util.*;
-
- final class ProcessEnvironment extends HashMap<String,String>
- {
- private static String validateName(String name) {
- // An initial `=' indicates a magic Windows variable name -- OK
- if (name.indexOf('=', 1) != -1 ||
- name.indexOf('\u0000') != -1)
- throw new IllegalArgumentException
- ("Invalid environment variable name: \"" + name + "\"");
- return name;
- }
-
- private static String validateValue(String value) {
- if (value.indexOf('\u0000') != -1)
- throw new IllegalArgumentException
- ("Invalid environment variable value: \"" + value + "\"");
- return value;
- }
-
- private static String nonNullString(Object o) {
- if (o == null)
- throw new NullPointerException();
- return (String) o;
- }
-
- public String put(String key, String value) {
- return super.put(validateName(key), validateValue(value));
- }
-
- public String get(Object key) {
- return super.get(nonNullString(key));
- }
-
- public boolean containsKey(Object key) {
- return super.containsKey(nonNullString(key));
- }
-
- public boolean containsValue(Object value) {
- return super.containsValue(nonNullString(value));
- }
-
- public String remove(Object key) {
- return super.remove(nonNullString(key));
- }
-
- private static class CheckedEntry
- implements Map.Entry<String,String>
- {
- private final Map.Entry<String,String> e;
- public CheckedEntry(Map.Entry<String,String> e) {this.e = e;}
- public String getKey() { return e.getKey();}
- public String getValue() { return e.getValue();}
- public String setValue(String value) {
- return e.setValue(validateValue(value));
- }
- public String toString() { return getKey() + "=" + getValue();}
- public boolean equals(Object o) {return e.equals(o);}
- public int hashCode() {return e.hashCode();}
- }
-
- private static class CheckedEntrySet
- extends AbstractSet<Map.Entry<String,String>>
- {
- private final Set<Map.Entry<String,String>> s;
- public CheckedEntrySet(Set<Map.Entry<String,String>> s) {this.s = s;}
- public int size() {return s.size();}
- public boolean isEmpty() {return s.isEmpty();}
- public void clear() { s.clear();}
- public Iterator<Map.Entry<String,String>> iterator() {
- return new Iterator<Map.Entry<String,String>>() {
- Iterator<Map.Entry<String,String>> i = s.iterator();
- public boolean hasNext() { return i.hasNext();}
- public Map.Entry<String,String> next() {
- return new CheckedEntry(i.next());
- }
- public void remove() { i.remove();}
- };
- }
- private static Map.Entry<String,String> checkedEntry (Object o) {
- Map.Entry<String,String> e = (Map.Entry<String,String>) o;
- nonNullString(e.getKey());
- nonNullString(e.getValue());
- return e;
- }
- public boolean contains(Object o) {return s.contains(checkedEntry(o));}
- public boolean remove(Object o) {return s.remove(checkedEntry(o));}
- }
-
- private static class CheckedValues extends AbstractCollection<String> {
- private final Collection<String> c;
- public CheckedValues(Collection<String> c) {this.c = c;}
- public int size() {return c.size();}
- public boolean isEmpty() {return c.isEmpty();}
- public void clear() { c.clear();}
- public Iterator<String> iterator() {return c.iterator();}
- public boolean contains(Object o) {return c.contains(nonNullString(o));}
- public boolean remove(Object o) {return c.remove(nonNullString(o));}
- }
-
- private static class CheckedKeySet extends AbstractSet<String> {
- private final Set<String> s;
- public CheckedKeySet(Set<String> s) {this.s = s;}
- public int size() {return s.size();}
- public boolean isEmpty() {return s.isEmpty();}
- public void clear() { s.clear();}
- public Iterator<String> iterator() {return s.iterator();}
- public boolean contains(Object o) {return s.contains(nonNullString(o));}
- public boolean remove(Object o) {return s.remove(nonNullString(o));}
- }
-
- public Set<String> keySet() {
- return new CheckedKeySet(super.keySet());
- }
-
- public Collection<String> values() {
- return new CheckedValues(super.values());
- }
-
- public Set<Map.Entry<String,String>> entrySet() {
- return new CheckedEntrySet(super.entrySet());
- }
-
-
- private static final class NameComparator
- implements Comparator<String> {
- public int compare(String x, String y) {
- return x.compareToIgnoreCase(y);
- }
- }
-
- private static final class EntryComparator
- implements Comparator<Map.Entry<String,String>> {
- public int compare(Map.Entry<String,String> e1,
- Map.Entry<String,String> e2) {
- return e1.getKey().compareToIgnoreCase(e2.getKey());
- }
- }
-
- // Allow `=' as first char in name, e.g. =C:=C:\DIR
- static final int MIN_NAME_LENGTH = 1;
-
- private static final NameComparator nameComparator;
- private static final EntryComparator entryComparator;
- private static final ProcessEnvironment theEnvironment;
- private static final Map<String,String> theUnmodifiableEnvironment;
- private static final Map<String,String> theCaseInsensitiveEnvironment;
-
- static {
- nameComparator = new NameComparator();
- entryComparator = new EntryComparator();
- theEnvironment = new ProcessEnvironment();
- theUnmodifiableEnvironment
- = Collections.unmodifiableMap(theEnvironment);
-
- String envblock = environmentBlock();
- int beg, end, eql;
- for (beg = 0;
- ((end = envblock.indexOf('\u0000', beg )) != -1 &&
- // An initial `=' indicates a magic Windows variable name -- OK
- (eql = envblock.indexOf('=' , beg+1)) != -1);
- beg = end + 1) {
- // Ignore corrupted environment strings.
- if (eql < end)
- theEnvironment.put(envblock.substring(beg, eql),
- envblock.substring(eql+1,end));
- }
-
- theCaseInsensitiveEnvironment
- = new TreeMap<String,String>(nameComparator);
- theCaseInsensitiveEnvironment.putAll(theEnvironment);
- }
-
- private ProcessEnvironment() {
- super();
- }
-
- private ProcessEnvironment(int capacity) {
- super(capacity);
- }
-
- // Only for use by System.getenv(String)
- static String getenv(String name) {
- // The original implementation used a native call to _wgetenv,
- // but it turns out that _wgetenv is only consistent with
- // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used
- // instead of `main', even in a process created using
- // CREATE_UNICODE_ENVIRONMENT. Instead we perform the
- // case-insensitive comparison ourselves. At least this
- // guarantees that System.getenv().get(String) will be
- // consistent with System.getenv(String).
- return theCaseInsensitiveEnvironment.get(name);
- }
-
- // Only for use by System.getenv()
- static Map<String,String> getenv() {
- return theUnmodifiableEnvironment;
- }
-
- // Only for use by ProcessBuilder.environment()
- static Map<String,String> environment() {
- return (Map<String,String>) theEnvironment.clone();
- }
-
- // Only for use by Runtime.exec(...String[]envp...)
- static Map<String,String> emptyEnvironment(int capacity) {
- return new ProcessEnvironment(capacity);
- }
-
- private static native String environmentBlock();
-
- // Only for use by ProcessImpl.start()
- String toEnvironmentBlock() {
- // Sort Unicode-case-insensitively by name
- List<Map.Entry<String,String>> list
- = new ArrayList<Map.Entry<String,String>>(entrySet());
- Collections.sort(list, entryComparator);
-
- StringBuilder sb = new StringBuilder(size()*30);
- for (Map.Entry<String,String> e : list)
- sb.append(e.getKey())
- .append('=')
- .append(e.getValue())
- .append('\u0000');
- // Ensure double NUL termination,
- // even if environment is empty.
- if (sb.length() == 0)
- sb.append('\u0000');
- sb.append('\u0000');
- return sb.toString();
- }
-
- static String toEnvironmentBlock(Map<String,String> map) {
- return map == null ? null :
- ((ProcessEnvironment)map).toEnvironmentBlock();
- }
- }