--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/windows/classes/java/lang/ProcessEnvironment.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/* 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:
+ * (see: http://windowssdk.msdn.microsoft.com/en-us/library/ms682009.aspx)
+ * - 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,
+ * as if folded to upper case.
+ * - 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
+ * @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 s1, String s2) {
+ // We can't use String.compareToIgnoreCase since it
+ // canonicalizes to lower case, while Windows
+ // canonicalizes to upper case! For example, "_" should
+ // sort *after* "Z", not before.
+ int n1 = s1.length();
+ int n2 = s2.length();
+ int min = Math.min(n1, n2);
+ for (int i = 0; i < min; i++) {
+ char c1 = s1.charAt(i);
+ char c2 = s2.charAt(i);
+ if (c1 != c2) {
+ c1 = Character.toUpperCase(c1);
+ c2 = Character.toUpperCase(c2);
+ if (c1 != c2)
+ // No overflow because of numeric promotion
+ return c1 - c2;
+ }
+ }
+ return n1 - n2;
+ }
+ }
+
+ 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 nameComparator.compare(e1.getKey(), 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();
+ }
+}