1 /*
2 * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 /* We use APIs that access a so-called Windows "Environment Block",
27 * which looks like an array of jchars like this:
28 *
29 * FOO=BAR\u0000 ... GORP=QUUX\u0000\u0000
30 *
31 * This data structure has a number of peculiarities we must contend with:
32 * (see: http://windowssdk.msdn.microsoft.com/en-us/library/ms682009.aspx)
33 * - The NUL jchar separators, and a double NUL jchar terminator.
34 * It appears that the Windows implementation requires double NUL
35 * termination even if the environment is empty. We should always
36 * generate environments with double NUL termination, while accepting
37 * empty environments consisting of a single NUL.
38 * - on Windows9x, this is actually an array of 8-bit chars, not jchars,
39 * encoded in the system default encoding.
40 * - The block must be sorted by Unicode value, case-insensitively,
41 * as if folded to upper case.
42 * - There are magic environment variables maintained by Windows
43 * that start with a `=' (!) character. These are used for
44 * Windows drive current directory (e.g. "=C:=C:\WINNT") or the
45 * exit code of the last command (e.g. "=ExitCode=0000001").
46 *
47 * Since Java and non-9x Windows speak the same character set, and
48 * even the same encoding, we don't have to deal with unreliable
49 * conversion to byte streams. Just add a few NUL terminators.
50 *
51 * System.getenv(String) is case-insensitive, while System.getenv()
52 * returns a map that is case-sensitive, which is consistent with
53 * native Windows APIs.
54 *
55 * The non-private methods in this class are not for general use even
56 * within this package. Instead, they are the system-dependent parts
57 * of the system-independent method of the same name. Don't even
58 * think of using this class unless your method's name appears below.
59 *
60 * @author Martin Buchholz
61 * @since 1.5
62 */
63
64 package java.lang;
65
66 import java.io;
67 import java.util;
68
69 final class ProcessEnvironment extends HashMap<String,String>
70 {
71 private static String validateName(String name) {
72 // An initial `=' indicates a magic Windows variable name -- OK
73 if (name.indexOf('=', 1) != -1 ||
74 name.indexOf('\u0000') != -1)
75 throw new IllegalArgumentException
76 ("Invalid environment variable name: \"" + name + "\"");
77 return name;
78 }
79
80 private static String validateValue(String value) {
81 if (value.indexOf('\u0000') != -1)
82 throw new IllegalArgumentException
83 ("Invalid environment variable value: \"" + value + "\"");
84 return value;
85 }
86
87 private static String nonNullString(Object o) {
88 if (o == null)
89 throw new NullPointerException();
90 return (String) o;
91 }
92
93 public String put(String key, String value) {
94 return super.put(validateName(key), validateValue(value));
95 }
96
97 public String get(Object key) {
98 return super.get(nonNullString(key));
99 }
100
101 public boolean containsKey(Object key) {
102 return super.containsKey(nonNullString(key));
103 }
104
105 public boolean containsValue(Object value) {
106 return super.containsValue(nonNullString(value));
107 }
108
109 public String remove(Object key) {
110 return super.remove(nonNullString(key));
111 }
112
113 private static class CheckedEntry
114 implements Map.Entry<String,String>
115 {
116 private final Map.Entry<String,String> e;
117 public CheckedEntry(Map.Entry<String,String> e) {this.e = e;}
118 public String getKey() { return e.getKey();}
119 public String getValue() { return e.getValue();}
120 public String setValue(String value) {
121 return e.setValue(validateValue(value));
122 }
123 public String toString() { return getKey() + "=" + getValue();}
124 public boolean equals(Object o) {return e.equals(o);}
125 public int hashCode() {return e.hashCode();}
126 }
127
128 private static class CheckedEntrySet
129 extends AbstractSet<Map.Entry<String,String>>
130 {
131 private final Set<Map.Entry<String,String>> s;
132 public CheckedEntrySet(Set<Map.Entry<String,String>> s) {this.s = s;}
133 public int size() {return s.size();}
134 public boolean isEmpty() {return s.isEmpty();}
135 public void clear() { s.clear();}
136 public Iterator<Map.Entry<String,String>> iterator() {
137 return new Iterator<Map.Entry<String,String>>() {
138 Iterator<Map.Entry<String,String>> i = s.iterator();
139 public boolean hasNext() { return i.hasNext();}
140 public Map.Entry<String,String> next() {
141 return new CheckedEntry(i.next());
142 }
143 public void remove() { i.remove();}
144 };
145 }
146 private static Map.Entry<String,String> checkedEntry (Object o) {
147 Map.Entry<String,String> e = (Map.Entry<String,String>) o;
148 nonNullString(e.getKey());
149 nonNullString(e.getValue());
150 return e;
151 }
152 public boolean contains(Object o) {return s.contains(checkedEntry(o));}
153 public boolean remove(Object o) {return s.remove(checkedEntry(o));}
154 }
155
156 private static class CheckedValues extends AbstractCollection<String> {
157 private final Collection<String> c;
158 public CheckedValues(Collection<String> c) {this.c = c;}
159 public int size() {return c.size();}
160 public boolean isEmpty() {return c.isEmpty();}
161 public void clear() { c.clear();}
162 public Iterator<String> iterator() {return c.iterator();}
163 public boolean contains(Object o) {return c.contains(nonNullString(o));}
164 public boolean remove(Object o) {return c.remove(nonNullString(o));}
165 }
166
167 private static class CheckedKeySet extends AbstractSet<String> {
168 private final Set<String> s;
169 public CheckedKeySet(Set<String> s) {this.s = s;}
170 public int size() {return s.size();}
171 public boolean isEmpty() {return s.isEmpty();}
172 public void clear() { s.clear();}
173 public Iterator<String> iterator() {return s.iterator();}
174 public boolean contains(Object o) {return s.contains(nonNullString(o));}
175 public boolean remove(Object o) {return s.remove(nonNullString(o));}
176 }
177
178 public Set<String> keySet() {
179 return new CheckedKeySet(super.keySet());
180 }
181
182 public Collection<String> values() {
183 return new CheckedValues(super.values());
184 }
185
186 public Set<Map.Entry<String,String>> entrySet() {
187 return new CheckedEntrySet(super.entrySet());
188 }
189
190
191 private static final class NameComparator
192 implements Comparator<String> {
193 public int compare(String s1, String s2) {
194 // We can't use String.compareToIgnoreCase since it
195 // canonicalizes to lower case, while Windows
196 // canonicalizes to upper case! For example, "_" should
197 // sort *after* "Z", not before.
198 int n1 = s1.length();
199 int n2 = s2.length();
200 int min = Math.min(n1, n2);
201 for (int i = 0; i < min; i++) {
202 char c1 = s1.charAt(i);
203 char c2 = s2.charAt(i);
204 if (c1 != c2) {
205 c1 = Character.toUpperCase(c1);
206 c2 = Character.toUpperCase(c2);
207 if (c1 != c2)
208 // No overflow because of numeric promotion
209 return c1 - c2;
210 }
211 }
212 return n1 - n2;
213 }
214 }
215
216 private static final class EntryComparator
217 implements Comparator<Map.Entry<String,String>> {
218 public int compare(Map.Entry<String,String> e1,
219 Map.Entry<String,String> e2) {
220 return nameComparator.compare(e1.getKey(), e2.getKey());
221 }
222 }
223
224 // Allow `=' as first char in name, e.g. =C:=C:\DIR
225 static final int MIN_NAME_LENGTH = 1;
226
227 private static final NameComparator nameComparator;
228 private static final EntryComparator entryComparator;
229 private static final ProcessEnvironment theEnvironment;
230 private static final Map<String,String> theUnmodifiableEnvironment;
231 private static final Map<String,String> theCaseInsensitiveEnvironment;
232
233 static {
234 nameComparator = new NameComparator();
235 entryComparator = new EntryComparator();
236 theEnvironment = new ProcessEnvironment();
237 theUnmodifiableEnvironment
238 = Collections.unmodifiableMap(theEnvironment);
239
240 String envblock = environmentBlock();
241 int beg, end, eql;
242 for (beg = 0;
243 ((end = envblock.indexOf('\u0000', beg )) != -1 &&
244 // An initial `=' indicates a magic Windows variable name -- OK
245 (eql = envblock.indexOf('=' , beg+1)) != -1);
246 beg = end + 1) {
247 // Ignore corrupted environment strings.
248 if (eql < end)
249 theEnvironment.put(envblock.substring(beg, eql),
250 envblock.substring(eql+1,end));
251 }
252
253 theCaseInsensitiveEnvironment
254 = new TreeMap<String,String>(nameComparator);
255 theCaseInsensitiveEnvironment.putAll(theEnvironment);
256 }
257
258 private ProcessEnvironment() {
259 super();
260 }
261
262 private ProcessEnvironment(int capacity) {
263 super(capacity);
264 }
265
266 // Only for use by System.getenv(String)
267 static String getenv(String name) {
268 // The original implementation used a native call to _wgetenv,
269 // but it turns out that _wgetenv is only consistent with
270 // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used
271 // instead of `main', even in a process created using
272 // CREATE_UNICODE_ENVIRONMENT. Instead we perform the
273 // case-insensitive comparison ourselves. At least this
274 // guarantees that System.getenv().get(String) will be
275 // consistent with System.getenv(String).
276 return theCaseInsensitiveEnvironment.get(name);
277 }
278
279 // Only for use by System.getenv()
280 static Map<String,String> getenv() {
281 return theUnmodifiableEnvironment;
282 }
283
284 // Only for use by ProcessBuilder.environment()
285 static Map<String,String> environment() {
286 return (Map<String,String>) theEnvironment.clone();
287 }
288
289 // Only for use by Runtime.exec(...String[]envp...)
290 static Map<String,String> emptyEnvironment(int capacity) {
291 return new ProcessEnvironment(capacity);
292 }
293
294 private static native String environmentBlock();
295
296 // Only for use by ProcessImpl.start()
297 String toEnvironmentBlock() {
298 // Sort Unicode-case-insensitively by name
299 List<Map.Entry<String,String>> list
300 = new ArrayList<Map.Entry<String,String>>(entrySet());
301 Collections.sort(list, entryComparator);
302
303 StringBuilder sb = new StringBuilder(size()*30);
304 for (Map.Entry<String,String> e : list)
305 sb.append(e.getKey())
306 .append('=')
307 .append(e.getValue())
308 .append('\u0000');
309 // Ensure double NUL termination,
310 // even if environment is empty.
311 if (sb.length() == 0)
312 sb.append('\u0000');
313 sb.append('\u0000');
314 return sb.toString();
315 }
316
317 static String toEnvironmentBlock(Map<String,String> map) {
318 return map == null ? null :
319 ((ProcessEnvironment)map).toEnvironmentBlock();
320 }
321 }