001: /*
002: * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: /* We use APIs that access a so-called Windows "Environment Block",
027: * which looks like an array of jchars like this:
028: *
029: * FOO=BAR\u0000 ... GORP=QUUX\u0000\u0000
030: *
031: * This data structure has a number of peculiarities we must contend with:
032: * (see: http://windowssdk.msdn.microsoft.com/en-us/library/ms682009.aspx)
033: * - The NUL jchar separators, and a double NUL jchar terminator.
034: * It appears that the Windows implementation requires double NUL
035: * termination even if the environment is empty. We should always
036: * generate environments with double NUL termination, while accepting
037: * empty environments consisting of a single NUL.
038: * - on Windows9x, this is actually an array of 8-bit chars, not jchars,
039: * encoded in the system default encoding.
040: * - The block must be sorted by Unicode value, case-insensitively,
041: * as if folded to upper case.
042: * - There are magic environment variables maintained by Windows
043: * that start with a `=' (!) character. These are used for
044: * Windows drive current directory (e.g. "=C:=C:\WINNT") or the
045: * exit code of the last command (e.g. "=ExitCode=0000001").
046: *
047: * Since Java and non-9x Windows speak the same character set, and
048: * even the same encoding, we don't have to deal with unreliable
049: * conversion to byte streams. Just add a few NUL terminators.
050: *
051: * System.getenv(String) is case-insensitive, while System.getenv()
052: * returns a map that is case-sensitive, which is consistent with
053: * native Windows APIs.
054: *
055: * The non-private methods in this class are not for general use even
056: * within this package. Instead, they are the system-dependent parts
057: * of the system-independent method of the same name. Don't even
058: * think of using this class unless your method's name appears below.
059: *
060: * @author Martin Buchholz
061: * @version 1.13, 07/05/05
062: * @since 1.5
063: */
064:
065: package java.lang;
066:
067: import java.io.*;
068: import java.util.*;
069:
070: final class ProcessEnvironment extends HashMap<String, String> {
071: private static String validateName(String name) {
072: // An initial `=' indicates a magic Windows variable name -- OK
073: if (name.indexOf('=', 1) != -1 || name.indexOf('\u0000') != -1)
074: throw new IllegalArgumentException(
075: "Invalid environment variable name: \"" + name
076: + "\"");
077: return name;
078: }
079:
080: private static String validateValue(String value) {
081: if (value.indexOf('\u0000') != -1)
082: throw new IllegalArgumentException(
083: "Invalid environment variable value: \"" + value
084: + "\"");
085: return value;
086: }
087:
088: private static String nonNullString(Object o) {
089: if (o == null)
090: throw new NullPointerException();
091: return (String) o;
092: }
093:
094: public String put(String key, String value) {
095: return super .put(validateName(key), validateValue(value));
096: }
097:
098: public String get(Object key) {
099: return super .get(nonNullString(key));
100: }
101:
102: public boolean containsKey(Object key) {
103: return super .containsKey(nonNullString(key));
104: }
105:
106: public boolean containsValue(Object value) {
107: return super .containsValue(nonNullString(value));
108: }
109:
110: public String remove(Object key) {
111: return super .remove(nonNullString(key));
112: }
113:
114: private static class CheckedEntry implements
115: Map.Entry<String, String> {
116: private final Map.Entry<String, String> e;
117:
118: public CheckedEntry(Map.Entry<String, String> e) {
119: this .e = e;
120: }
121:
122: public String getKey() {
123: return e.getKey();
124: }
125:
126: public String getValue() {
127: return e.getValue();
128: }
129:
130: public String setValue(String value) {
131: return e.setValue(validateValue(value));
132: }
133:
134: public String toString() {
135: return getKey() + "=" + getValue();
136: }
137:
138: public boolean equals(Object o) {
139: return e.equals(o);
140: }
141:
142: public int hashCode() {
143: return e.hashCode();
144: }
145: }
146:
147: private static class CheckedEntrySet extends
148: AbstractSet<Map.Entry<String, String>> {
149: private final Set<Map.Entry<String, String>> s;
150:
151: public CheckedEntrySet(Set<Map.Entry<String, String>> s) {
152: this .s = s;
153: }
154:
155: public int size() {
156: return s.size();
157: }
158:
159: public boolean isEmpty() {
160: return s.isEmpty();
161: }
162:
163: public void clear() {
164: s.clear();
165: }
166:
167: public Iterator<Map.Entry<String, String>> iterator() {
168: return new Iterator<Map.Entry<String, String>>() {
169: Iterator<Map.Entry<String, String>> i = s.iterator();
170:
171: public boolean hasNext() {
172: return i.hasNext();
173: }
174:
175: public Map.Entry<String, String> next() {
176: return new CheckedEntry(i.next());
177: }
178:
179: public void remove() {
180: i.remove();
181: }
182: };
183: }
184:
185: private static Map.Entry<String, String> checkedEntry(Object o) {
186: Map.Entry<String, String> e = (Map.Entry<String, String>) o;
187: nonNullString(e.getKey());
188: nonNullString(e.getValue());
189: return e;
190: }
191:
192: public boolean contains(Object o) {
193: return s.contains(checkedEntry(o));
194: }
195:
196: public boolean remove(Object o) {
197: return s.remove(checkedEntry(o));
198: }
199: }
200:
201: private static class CheckedValues extends
202: AbstractCollection<String> {
203: private final Collection<String> c;
204:
205: public CheckedValues(Collection<String> c) {
206: this .c = c;
207: }
208:
209: public int size() {
210: return c.size();
211: }
212:
213: public boolean isEmpty() {
214: return c.isEmpty();
215: }
216:
217: public void clear() {
218: c.clear();
219: }
220:
221: public Iterator<String> iterator() {
222: return c.iterator();
223: }
224:
225: public boolean contains(Object o) {
226: return c.contains(nonNullString(o));
227: }
228:
229: public boolean remove(Object o) {
230: return c.remove(nonNullString(o));
231: }
232: }
233:
234: private static class CheckedKeySet extends AbstractSet<String> {
235: private final Set<String> s;
236:
237: public CheckedKeySet(Set<String> s) {
238: this .s = s;
239: }
240:
241: public int size() {
242: return s.size();
243: }
244:
245: public boolean isEmpty() {
246: return s.isEmpty();
247: }
248:
249: public void clear() {
250: s.clear();
251: }
252:
253: public Iterator<String> iterator() {
254: return s.iterator();
255: }
256:
257: public boolean contains(Object o) {
258: return s.contains(nonNullString(o));
259: }
260:
261: public boolean remove(Object o) {
262: return s.remove(nonNullString(o));
263: }
264: }
265:
266: public Set<String> keySet() {
267: return new CheckedKeySet(super .keySet());
268: }
269:
270: public Collection<String> values() {
271: return new CheckedValues(super .values());
272: }
273:
274: public Set<Map.Entry<String, String>> entrySet() {
275: return new CheckedEntrySet(super .entrySet());
276: }
277:
278: private static final class NameComparator implements
279: Comparator<String> {
280: public int compare(String s1, String s2) {
281: // We can't use String.compareToIgnoreCase since it
282: // canonicalizes to lower case, while Windows
283: // canonicalizes to upper case! For example, "_" should
284: // sort *after* "Z", not before.
285: int n1 = s1.length();
286: int n2 = s2.length();
287: int min = Math.min(n1, n2);
288: for (int i = 0; i < min; i++) {
289: char c1 = s1.charAt(i);
290: char c2 = s2.charAt(i);
291: if (c1 != c2) {
292: c1 = Character.toUpperCase(c1);
293: c2 = Character.toUpperCase(c2);
294: if (c1 != c2)
295: // No overflow because of numeric promotion
296: return c1 - c2;
297: }
298: }
299: return n1 - n2;
300: }
301: }
302:
303: private static final class EntryComparator implements
304: Comparator<Map.Entry<String, String>> {
305: public int compare(Map.Entry<String, String> e1,
306: Map.Entry<String, String> e2) {
307: return nameComparator.compare(e1.getKey(), e2.getKey());
308: }
309: }
310:
311: // Allow `=' as first char in name, e.g. =C:=C:\DIR
312: static final int MIN_NAME_LENGTH = 1;
313:
314: private static final NameComparator nameComparator;
315: private static final EntryComparator entryComparator;
316: private static final ProcessEnvironment theEnvironment;
317: private static final Map<String, String> theUnmodifiableEnvironment;
318: private static final Map<String, String> theCaseInsensitiveEnvironment;
319:
320: static {
321: nameComparator = new NameComparator();
322: entryComparator = new EntryComparator();
323: theEnvironment = new ProcessEnvironment();
324: theUnmodifiableEnvironment = Collections
325: .unmodifiableMap(theEnvironment);
326:
327: String envblock = environmentBlock();
328: int beg, end, eql;
329: for (beg = 0; ((end = envblock.indexOf('\u0000', beg)) != -1 &&
330: // An initial `=' indicates a magic Windows variable name -- OK
331: (eql = envblock.indexOf('=', beg + 1)) != -1); beg = end + 1) {
332: // Ignore corrupted environment strings.
333: if (eql < end)
334: theEnvironment.put(envblock.substring(beg, eql),
335: envblock.substring(eql + 1, end));
336: }
337:
338: theCaseInsensitiveEnvironment = new TreeMap<String, String>(
339: nameComparator);
340: theCaseInsensitiveEnvironment.putAll(theEnvironment);
341: }
342:
343: private ProcessEnvironment() {
344: super ();
345: }
346:
347: private ProcessEnvironment(int capacity) {
348: super (capacity);
349: }
350:
351: // Only for use by System.getenv(String)
352: static String getenv(String name) {
353: // The original implementation used a native call to _wgetenv,
354: // but it turns out that _wgetenv is only consistent with
355: // GetEnvironmentStringsW (for non-ASCII) if `wmain' is used
356: // instead of `main', even in a process created using
357: // CREATE_UNICODE_ENVIRONMENT. Instead we perform the
358: // case-insensitive comparison ourselves. At least this
359: // guarantees that System.getenv().get(String) will be
360: // consistent with System.getenv(String).
361: return theCaseInsensitiveEnvironment.get(name);
362: }
363:
364: // Only for use by System.getenv()
365: static Map<String, String> getenv() {
366: return theUnmodifiableEnvironment;
367: }
368:
369: // Only for use by ProcessBuilder.environment()
370: static Map<String, String> environment() {
371: return (Map<String, String>) theEnvironment.clone();
372: }
373:
374: // Only for use by Runtime.exec(...String[]envp...)
375: static Map<String, String> emptyEnvironment(int capacity) {
376: return new ProcessEnvironment(capacity);
377: }
378:
379: private static native String environmentBlock();
380:
381: // Only for use by ProcessImpl.start()
382: String toEnvironmentBlock() {
383: // Sort Unicode-case-insensitively by name
384: List<Map.Entry<String, String>> list = new ArrayList<Map.Entry<String, String>>(
385: entrySet());
386: Collections.sort(list, entryComparator);
387:
388: StringBuilder sb = new StringBuilder(size() * 30);
389: for (Map.Entry<String, String> e : list)
390: sb.append(e.getKey()).append('=').append(e.getValue())
391: .append('\u0000');
392: // Ensure double NUL termination,
393: // even if environment is empty.
394: if (sb.length() == 0)
395: sb.append('\u0000');
396: sb.append('\u0000');
397: return sb.toString();
398: }
399:
400: static String toEnvironmentBlock(Map<String, String> map) {
401: return map == null ? null : ((ProcessEnvironment) map)
402: .toEnvironmentBlock();
403: }
404: }
|