blob: 08d260cf4a3cd3a66a11d435d99ae404b68e9d8e [file] [log] [blame]
/*
* Copyright (c) 2003, 2011, Oracle and/or its affiliates. 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* We use APIs that access the standard Unix environ array, which
* is defined by UNIX98 to look like:
*
* char **environ;
*
* These are unsorted, case-sensitive, null-terminated arrays of bytes
* of the form FOO=BAR\000 which are usually encoded in the user's
* default encoding (file.encoding is an excellent choice for
* encoding/decoding these). However, even though the user cannot
* directly access the underlying byte representation, we take pains
* to pass on the child the exact byte representation we inherit from
* the parent process for any environment name or value not created by
* Javaland. So we keep track of all the byte representations.
*
* Internally, we define the types Variable and Value that exhibit
* String/byteArray duality. The internal representation of the
* environment then looks like a Map<Variable,Value>. But we don't
* expose this to the user -- we only provide a Map<String,String>
* view, although we could also provide a Map<byte[],byte[]> view.
*
* 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
{
private static final HashMap<Variable,Value> theEnvironment;
private static final Map<String,String> theUnmodifiableEnvironment;
static final int MIN_NAME_LENGTH = 0;
static {
// We cache the C environment. This means that subsequent calls
// to putenv/setenv from C will not be visible from Java code.
byte[][] environ = environ();
theEnvironment = new HashMap<>(environ.length/2 + 3);
// Read environment variables back to front,
// so that earlier variables override later ones.
for (int i = environ.length-1; i > 0; i-=2)
theEnvironment.put(Variable.valueOf(environ[i-1]),
Value.valueOf(environ[i]));
theUnmodifiableEnvironment
= Collections.unmodifiableMap
(new StringEnvironment(theEnvironment));
}
/* Only for use by System.getenv(String) */
static String getenv(String name) {
return theUnmodifiableEnvironment.get(name);
}
/* Only for use by System.getenv() */
static Map<String,String> getenv() {
return theUnmodifiableEnvironment;
}
/* Only for use by ProcessBuilder.environment() */
@SuppressWarnings("unchecked")
static Map<String,String> environment() {
return new StringEnvironment
((Map<Variable,Value>)(theEnvironment.clone()));
}
/* Only for use by Runtime.exec(...String[]envp...) */
static Map<String,String> emptyEnvironment(int capacity) {
return new StringEnvironment(new HashMap<Variable,Value>(capacity));
}
private static native byte[][] environ();
// This class is not instantiable.
private ProcessEnvironment() {}
// Check that name is suitable for insertion into Environment map
private static void validateVariable(String name) {
if (name.indexOf('=') != -1 ||
name.indexOf('\u0000') != -1)
throw new IllegalArgumentException
("Invalid environment variable name: \"" + name + "\"");
}
// Check that value is suitable for insertion into Environment map
private static void validateValue(String value) {
if (value.indexOf('\u0000') != -1)
throw new IllegalArgumentException
("Invalid environment variable value: \"" + value + "\"");
}
// A class hiding the byteArray-String duality of
// text data on Unixoid operating systems.
private static abstract class ExternalData {
protected final String str;
protected final byte[] bytes;
protected ExternalData(String str, byte[] bytes) {
this.str = str;
this.bytes = bytes;
}
public byte[] getBytes() {
return bytes;
}
public String toString() {
return str;
}
public boolean equals(Object o) {
return o instanceof ExternalData
&& arrayEquals(getBytes(), ((ExternalData) o).getBytes());
}
public int hashCode() {
return arrayHash(getBytes());
}
}
private static class Variable
extends ExternalData implements Comparable<Variable>
{
protected Variable(String str, byte[] bytes) {
super(str, bytes);
}
public static Variable valueOfQueryOnly(Object str) {
return valueOfQueryOnly((String) str);
}
public static Variable valueOfQueryOnly(String str) {
return new Variable(str, str.getBytes());
}
public static Variable valueOf(String str) {
validateVariable(str);
return valueOfQueryOnly(str);
}
public static Variable valueOf(byte[] bytes) {
return new Variable(new String(bytes), bytes);
}
public int compareTo(Variable variable) {
return arrayCompare(getBytes(), variable.getBytes());
}
public boolean equals(Object o) {
return o instanceof Variable && super.equals(o);
}
}
private static class Value
extends ExternalData implements Comparable<Value>
{
protected Value(String str, byte[] bytes) {
super(str, bytes);
}
public static Value valueOfQueryOnly(Object str) {
return valueOfQueryOnly((String) str);
}
public static Value valueOfQueryOnly(String str) {
return new Value(str, str.getBytes());
}
public static Value valueOf(String str) {
validateValue(str);
return valueOfQueryOnly(str);
}
public static Value valueOf(byte[] bytes) {
return new Value(new String(bytes), bytes);
}
public int compareTo(Value value) {
return arrayCompare(getBytes(), value.getBytes());
}
public boolean equals(Object o) {
return o instanceof Value && super.equals(o);
}
}
// This implements the String map view the user sees.
private static class StringEnvironment
extends AbstractMap<String,String>
{
private Map<Variable,Value> m;
private static String toString(Value v) {
return v == null ? null : v.toString();
}
public StringEnvironment(Map<Variable,Value> m) {this.m = m;}
public int size() {return m.size();}
public boolean isEmpty() {return m.isEmpty();}
public void clear() { m.clear();}
public boolean containsKey(Object key) {
return m.containsKey(Variable.valueOfQueryOnly(key));
}
public boolean containsValue(Object value) {
return m.containsValue(Value.valueOfQueryOnly(value));
}
public String get(Object key) {
return toString(m.get(Variable.valueOfQueryOnly(key)));
}
public String put(String key, String value) {
return toString(m.put(Variable.valueOf(key),
Value.valueOf(value)));
}
public String remove(Object key) {
return toString(m.remove(Variable.valueOfQueryOnly(key)));
}
public Set<String> keySet() {
return new StringKeySet(m.keySet());
}
public Set<Map.Entry<String,String>> entrySet() {
return new StringEntrySet(m.entrySet());
}
public Collection<String> values() {
return new StringValues(m.values());
}
// It is technically feasible to provide a byte-oriented view
// as follows:
// public Map<byte[],byte[]> asByteArrayMap() {
// return new ByteArrayEnvironment(m);
// }
// Convert to Unix style environ as a monolithic byte array
// inspired by the Windows Environment Block, except we work
// exclusively with bytes instead of chars, and we need only
// one trailing NUL on Unix.
// This keeps the JNI as simple and efficient as possible.
public byte[] toEnvironmentBlock(int[]envc) {
int count = m.size() * 2; // For added '=' and NUL
for (Map.Entry<Variable,Value> entry : m.entrySet()) {
count += entry.getKey().getBytes().length;
count += entry.getValue().getBytes().length;
}
byte[] block = new byte[count];
int i = 0;
for (Map.Entry<Variable,Value> entry : m.entrySet()) {
byte[] key = entry.getKey ().getBytes();
byte[] value = entry.getValue().getBytes();
System.arraycopy(key, 0, block, i, key.length);
i+=key.length;
block[i++] = (byte) '=';
System.arraycopy(value, 0, block, i, value.length);
i+=value.length + 1;
// No need to write NUL byte explicitly
//block[i++] = (byte) '\u0000';
}
envc[0] = m.size();
return block;
}
}
static byte[] toEnvironmentBlock(Map<String,String> map, int[]envc) {
return map == null ? null :
((StringEnvironment)map).toEnvironmentBlock(envc);
}
private static class StringEntry
implements Map.Entry<String,String>
{
private final Map.Entry<Variable,Value> e;
public StringEntry(Map.Entry<Variable,Value> e) {this.e = e;}
public String getKey() {return e.getKey().toString();}
public String getValue() {return e.getValue().toString();}
public String setValue(String newValue) {
return e.setValue(Value.valueOf(newValue)).toString();
}
public String toString() {return getKey() + "=" + getValue();}
public boolean equals(Object o) {
return o instanceof StringEntry
&& e.equals(((StringEntry)o).e);
}
public int hashCode() {return e.hashCode();}
}
private static class StringEntrySet
extends AbstractSet<Map.Entry<String,String>>
{
private final Set<Map.Entry<Variable,Value>> s;
public StringEntrySet(Set<Map.Entry<Variable,Value>> 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<Variable,Value>> i = s.iterator();
public boolean hasNext() {return i.hasNext();}
public Map.Entry<String,String> next() {
return new StringEntry(i.next());
}
public void remove() {i.remove();}
};
}
private static Map.Entry<Variable,Value> vvEntry(final Object o) {
if (o instanceof StringEntry)
return ((StringEntry)o).e;
return new Map.Entry<Variable,Value>() {
public Variable getKey() {
return Variable.valueOfQueryOnly(((Map.Entry)o).getKey());
}
public Value getValue() {
return Value.valueOfQueryOnly(((Map.Entry)o).getValue());
}
public Value setValue(Value value) {
throw new UnsupportedOperationException();
}
};
}
public boolean contains(Object o) { return s.contains(vvEntry(o)); }
public boolean remove(Object o) { return s.remove(vvEntry(o)); }
public boolean equals(Object o) {
return o instanceof StringEntrySet
&& s.equals(((StringEntrySet) o).s);
}
public int hashCode() {return s.hashCode();}
}
private static class StringValues
extends AbstractCollection<String>
{
private final Collection<Value> c;
public StringValues(Collection<Value> 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 new Iterator<String>() {
Iterator<Value> i = c.iterator();
public boolean hasNext() {return i.hasNext();}
public String next() {return i.next().toString();}
public void remove() {i.remove();}
};
}
public boolean contains(Object o) {
return c.contains(Value.valueOfQueryOnly(o));
}
public boolean remove(Object o) {
return c.remove(Value.valueOfQueryOnly(o));
}
public boolean equals(Object o) {
return o instanceof StringValues
&& c.equals(((StringValues)o).c);
}
public int hashCode() {return c.hashCode();}
}
private static class StringKeySet extends AbstractSet<String> {
private final Set<Variable> s;
public StringKeySet(Set<Variable> 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 new Iterator<String>() {
Iterator<Variable> i = s.iterator();
public boolean hasNext() {return i.hasNext();}
public String next() {return i.next().toString();}
public void remove() { i.remove();}
};
}
public boolean contains(Object o) {
return s.contains(Variable.valueOfQueryOnly(o));
}
public boolean remove(Object o) {
return s.remove(Variable.valueOfQueryOnly(o));
}
}
// Replace with general purpose method someday
private static int arrayCompare(byte[]x, byte[] y) {
int min = x.length < y.length ? x.length : y.length;
for (int i = 0; i < min; i++)
if (x[i] != y[i])
return x[i] - y[i];
return x.length - y.length;
}
// Replace with general purpose method someday
private static boolean arrayEquals(byte[] x, byte[] y) {
if (x.length != y.length)
return false;
for (int i = 0; i < x.length; i++)
if (x[i] != y[i])
return false;
return true;
}
// Replace with general purpose method someday
private static int arrayHash(byte[] x) {
int hash = 0;
for (int i = 0; i < x.length; i++)
hash = 31 * hash + x[i];
return hash;
}
}