All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.lang.ProcessEnvironment Maven / Gradle / Ivy

/*
 * This code is based on OpenJDK source file(s) which contain the following copyright notice:
 *
 * ------
 * 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.
 * ------
 *
 * This file may contain additional modifications which are Copyright (c) Red Hat and other
 * contributors.
 */
package java.lang;

import static java.lang.Math.min;
import static org.qbicc.runtime.CNative.*;
import static org.qbicc.runtime.posix.Unistd.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.qbicc.rt.annotation.Tracking;
import org.qbicc.runtime.Build;

@Tracking("src/java.base/unix/classes/java/lang/ProcessEnvironment.java")
@Tracking("src/java.base/unix/native/libjava/ProcessEnvironment_md.c")
@Tracking("src/java.base/windows/classes/java/lang/ProcessEnvironment.java")
@Tracking("src/java.base/windows/native/libjava/ProcessEnvironment_md.c")
final class ProcessEnvironment {
    private ProcessEnvironment() {}

    static final EnvironmentMap theEnvironment;
    static final Map theUnmodifiableEnvironment;

    static {
        assert Build.isHost();
        String[] hostEnvironment = getHostEnvironment();
        EnvironmentMap env = new EnvironmentMap();
        for (int i = 0; i < hostEnvironment.length; i += 2) {
            String key = hostEnvironment[i];
            String value = hostEnvironment[i + 1];
            if (isValidEnvString(key) && isValidEnvString(value)) {
                env.put(key, value);
            }
        }
        theEnvironment = env;
        theUnmodifiableEnvironment = Collections.unmodifiableMap(env);
    }

    private static Key makeKey(final String str) {
        // this is the only place where key subclasses are constructed
        // thus key instance methods should always be devirtualized completely
        if (str == null) {
            return null;
        } else if (Build.Target.isWindows()) {
            return new WindowsKey(str);
        } else {
            return new PosixKey(str);
        }
    }

    // API used by other components

    /* ProcessBuilder.environment(java.lang.String[]) */
    static final int MIN_NAME_LENGTH = Build.Target.isWindows() ? 1 : 0;

    /* System.getenv(String) */
    static String getenv(String name) {
        return theEnvironment.get(name);
    }

    /* System.getenv() */
    static Map getenv() {
        return theUnmodifiableEnvironment;
    }

    /* ProcessBuilder.environment() */
    static Map environment() {
        return theEnvironment.clone();
    }

    /* Runtime.exec(*) / ProcessBuilder */
    static Map emptyEnvironment(int capacity) {
        return new EnvironmentMap(capacity);
    }

    // ProcessImpl.start() (Windows)
    static String toEnvironmentBlock(Map map) {
        if (! Build.Target.isWindows()) {
            throw new UnsupportedOperationException();
        } else {
            return makeWindowsEnvironmentBlock(map);
        }
    }

    // ProcessImpl.start() (UNIX)
    static byte[] toEnvironmentBlock(Map map, int[] envc) {
        if (Build.Target.isWindows()) {
            throw new UnsupportedOperationException();
        } else {
            return makeUnixEnvironmentBlock(map, envc);
        }
    }

    // Shared

    static boolean isValidEnvString(final String string) {
        return string.indexOf('=') == -1 && string.indexOf('\0') == -1;
    }

    static String validateEnvString(final String string) {
        if (! isValidEnvString(string)) {
            throw new IllegalArgumentException("Invalid environment string");
        }
        return string;
    }

    abstract static class Key implements Comparable {
        private final String string;
        private int hashCode;

        Key(final String string) {
            validateEnvString(string);
            this.string = string;
        }

        public final boolean equals(final Object other) {
            return other instanceof Key key && equals(key);
        }

        public abstract int compareTo(final Key o);

        abstract boolean equals(final Key other);

        public final int hashCode() {
            int hashCode = this.hashCode;
            if (hashCode == 0) {
                hashCode = computeHashCode();
                if (hashCode == 0) {
                    hashCode |= 1 << 31;
                }
                this.hashCode = hashCode;
            }
            return hashCode;
        }

        abstract int computeHashCode();

        public final String toString() {
            return string;
        }
    }

    /**
     * A read-only map view of an environment, using the target platform's sorting rules.
     */
    static final class EnvironmentMap extends AbstractMap {
        private final Map env;
        private EntrySet entrySet;
        private KeySet keySet;

        EnvironmentMap(final Map env) {
            this.env = env;
        }

        EnvironmentMap(final int capacity) {
            this(new HashMap<>(capacity));
        }

        EnvironmentMap() {
            this(new HashMap<>());
        }

        public Set> entrySet() {
            EntrySet entrySet = this.entrySet;
            if (entrySet == null) {
                this.entrySet = entrySet = new EntrySet();
            }
            return entrySet;
        }

        public Set keySet() {
            KeySet keySet = this.keySet;
            if (keySet == null) {
                this.keySet = keySet = new KeySet();
            }
            return keySet;
        }

        public Collection values() {
            return env.values();
        }

        public boolean containsKey(final Object key) {
            Objects.requireNonNull(key, "key");
            return key instanceof String keyStr && env.containsKey(makeKey(keyStr));
        }

        public boolean containsValue(final Object value) {
            Objects.requireNonNull(value, "value");
            return env.containsValue(value);
        }

        public String get(final Object key) {
            Objects.requireNonNull(key, "key");
            return key instanceof String keyStr ? env.get(makeKey(keyStr)) : null;
        }

        public String getOrDefault(final Object key, final String defaultValue) {
            Objects.requireNonNull(key, "key");
            return key instanceof String keyStr ? Objects.requireNonNullElse(env.get(makeKey(keyStr)), defaultValue) : defaultValue;
        }

        public String put(final String key, final String value) {
            Objects.requireNonNull(key, "key");
            Objects.requireNonNull(value, "value");
            return env.put(makeKey(validateEnvString(key)), validateEnvString(value));
        }

        public String putIfAbsent(final String key, final String value) {
            Objects.requireNonNull(key, "key");
            Objects.requireNonNull(value, "value");
            return env.putIfAbsent(makeKey(validateEnvString(key)), validateEnvString(value));
        }

        public String remove(final Object key) {
            Objects.requireNonNull(key, "key");
            return key instanceof String keyStr ? env.remove(makeKey(keyStr)) : null;
        }

        public boolean remove(final Object key, final Object value) {
            Objects.requireNonNull(key, "key");
            Objects.requireNonNull(value, "value");
            return key instanceof String keyStr && env.remove(makeKey(keyStr), value);
        }

        public String replace(final String key, final String value) {
            Objects.requireNonNull(key, "key");
            Objects.requireNonNull(value, "value");
            return env.replace(makeKey(key), validateEnvString(value));
        }

        public boolean replace(final String key, final String oldValue, final String newValue) {
            Objects.requireNonNull(key, "key");
            Objects.requireNonNull(oldValue, "oldValue");
            Objects.requireNonNull(newValue, "newValue");
            return env.replace(makeKey(key), oldValue, validateEnvString(newValue));
        }

        public void clear() {
            env.clear();
        }

        public EnvironmentMap clone() {
            return new EnvironmentMap(new HashMap<>(env));
        }

        public int size() {
            return env.size();
        }

        final class EntrySet extends AbstractSet> {
            public Iterator> iterator() {
                Iterator> iterator = env.entrySet().iterator();
                return new Iterator>() {
                    public boolean hasNext() {
                        return iterator.hasNext();
                    }

                    public Entry next() {
                        Entry next = iterator.next();
                        return Map.entry(next.getKey().toString(), next.getValue());
                    }

                    public void remove() {
                        iterator.remove();
                    }
                };
            }

            public boolean contains(final Object o) {
                return o instanceof Map.Entry me
                    && me.getKey() instanceof String key
                    && me.getValue() instanceof String value
                    && env.entrySet().contains(Map.entry(makeKey(key), value));
            }

            public boolean remove(final Object o) {
                return o instanceof Map.Entry me
                    && me.getKey() instanceof String key
                    && me.getValue() instanceof String value
                    && env.remove(makeKey(key), value);
            }

            public int size() {
                return env.size();
            }
        }

        final class KeySet extends AbstractSet {
            public Iterator iterator() {
                Iterator iterator = env.keySet().iterator();
                return new Iterator() {
                    public boolean hasNext() {
                        return iterator.hasNext();
                    }

                    public String next() {
                        return iterator.next().toString();
                    }

                    public void remove() {
                        iterator.remove();
                    }
                };
            }

            public int size() {
                return env.size();
            }

            public boolean contains(final Object o) {
                return o instanceof String str && env.containsKey(makeKey(str));
            }

            public boolean remove(final Object o) {
                return o instanceof String str && env.remove(makeKey(str)) != null;
            }
        }
    }

    // OS-dependent: Host

    static native String[] getHostEnvironment();

    // OS-dependent: UNIX

    static final class PosixKey extends Key {
        PosixKey(final String string) {
            super(string);
        }

        public int compareTo(final Key o) {
            return toString().compareTo(o.toString());
        }

        boolean equals(final Key other) {
            return other instanceof PosixKey pk && toString().equals(pk.toString());
        }

        int computeHashCode() {
            return toString().hashCode();
        }
    }

    static byte[] makeUnixEnvironmentBlock(final Map map, final int[] envCnt) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        OutputStreamWriter w = new OutputStreamWriter(baos, StandardCharsets.UTF_8);
        int cnt = 0;
        for (Map.Entry entry : map.entrySet()) try {
            String key = entry.getKey();
            String value = entry.getValue();
            if (! isValidEnvString(key) || ! isValidEnvString(value)) {
                // skip the key for safety
                continue;
            }
            cnt++;
            w.write(key);
            w.write('\0');
            w.write(value);
            w.write('\0');
            w.flush();
        } catch (IOException e) {
            // impossible
            throw new IllegalStateException();
        }
        envCnt[0] = cnt;
        return baos.toByteArray();
    }

    static void getPosixEnv(final Map env) {
        char_ptr_ptr env_ptr = environ;
        StringBuilder b = new StringBuilder();
        char_ptr entry_ptr;
        for (;;) {
            entry_ptr = env_ptr.loadUnshared();
            env_ptr = env_ptr.plus(1);
            String key = makeString(b, entry_ptr);
            if (key == null) {
                return;
            }
            entry_ptr = env_ptr.loadUnshared();
            env_ptr = env_ptr.plus(1);
            String value = makeString(b, entry_ptr);
            if (value == null) {
                return;
            }
            env.put(key, value);
        }
    }

    /**
     * Make a string from a utf8z pointer.
     *
     * @param sb the string builder to (re)use
     * @param ptr the character pointer
     * @return the string, or {@code null} if {@code ptr} is {@code null}
     */
    private static String makeString(StringBuilder sb, char_ptr ptr) {
        if (ptr.isNull()) {
            return null;
        }
        sb.setLength(0);
        for (;;) {
            int a = ptr.loadUnshared().byteValue() & 0xff;
            ptr = ptr.plus(1);
            if (a == 0) {
                // end of string
                return sb.toString();
            }
            if (a < 0x80) {
                sb.appendCodePoint(a);
            } else if (a < 0xC0 || a >= 0xF8) {
                sb.append('�');
            } else {
                // at least two bytes
                int b = ptr.loadUnshared().byteValue() & 0xff;
                ptr = ptr.plus(1);
                if (b == 0) {
                    // end of string after partial
                    sb.append('�');
                    return sb.toString();
                }
                if (b < 0x80 || b >= 0xC0) {
                    sb.append('�').append('�');
                } else if (a < 0xE0) {
                    sb.appendCodePoint((a & 0b11111) << 6 | b & 0b111111);
                } else {
                    // at least three bytes
                    int c = ptr.loadUnshared().byteValue() & 0xff;
                    ptr = ptr.plus(1);
                    if (c == 0) {
                        // end of string after partial
                        sb.append('�').append('�');
                        return sb.toString();
                    }
                    if (c < 0x80 || c >= 0xC0) {
                        sb.append('�').append('�').append('�');
                    } else if (a < 0xF0) {
                        sb.appendCodePoint((a & 0b1111) << 12 | (b & 0b111111) << 6 | c & 0b111111);
                    } else {
                        // at least four bytes
                        int d = ptr.loadUnshared().byteValue() & 0xff;
                        ptr = ptr.plus(1);
                        if (d == 0) {
                            // end of string after partial
                            sb.append('�').append('�').append('�');
                            return sb.toString();
                        }
                        if (d < 0x80 || d >= 0xC0) {
                            sb.append('�').append('�').append('�').append('�');
                        } else {
                            // a < 0xF8
                            sb.appendCodePoint((a & 0b111) << 18 | (b & 0b111111) << 12 | (c & 0b111111) << 6 | d & 0b111111);
                        }
                    }
                }
            }
        }
    }

    // OS-dependent: Windows

    static final class WindowsKey extends Key {
        WindowsKey(final String string) {
            super(string);
        }

        public int compareTo(final Key other) {
            WindowsKey key = (WindowsKey) other;
            String a = toString();
            String b = key.toString();
            int lenA = a.length();
            int lenB = b.length();
            int minLen = min(lenA, lenB);
            int cmp;
            for (int i = 0; i < minLen; i ++) {
                char ac = a.charAt(i);
                char bc = b.charAt(i);
                if (ac != bc) {
                    cmp = Character.compare(Character.toUpperCase(ac), Character.toUpperCase(bc));
                    if (cmp != 0) {
                        return cmp;
                    }
                }
            }
            return Integer.compare(lenA, lenB);
        }

        boolean equals(final Key other) {
            WindowsKey key = (WindowsKey) other;
            String a = toString();
            String b = key.toString();
            int len = a.length();
            if (len != b.length()) {
                return false;
            }
            for (int i = 0; i < len; i ++) {
                char ac = a.charAt(i);
                char bc = b.charAt(i);
                if (ac != bc && Character.toUpperCase(ac) != Character.toUpperCase(bc)) {
                    return false;
                }
            }
            return true;
        }

        int computeHashCode() {
            int hc = 0;
            String str = toString();
            int len = str.length();
            for (int i = 0; i < len; i ++) {
                hc = 31 * hc + Character.toUpperCase(str.charAt(i));
            }
            return hc;
        }
    }

    static native String environmentBlock();

    static int compareWindowsStyle(String a, String b) {
        int lenA = a.length();
        int lenB = b.length();
        int minLen = min(lenA, lenB);
        int cmp;
        for (int i = 0; i < minLen; i ++) {
            char ac = a.charAt(i);
            char bc = b.charAt(i);
            if (ac != bc) {
                cmp = Character.compare(Character.toUpperCase(ac), Character.toUpperCase(bc));
                if (cmp != 0) {
                    return cmp;
                }
            }
        }
        return Integer.compare(lenA, lenB);
    }

    private static String makeWindowsEnvironmentBlock(final Map map) {
        List list = new ArrayList<>(map.keySet());
        list.sort(ProcessEnvironment::compareWindowsStyle);

        StringBuilder sb = new StringBuilder(map.size()*30);
        int cmp = -1;

        // Some versions of MSVCRT.DLL require SystemRoot to be set.
        // So, we make sure that it is always set, even if not provided
        // by the caller.
        final String systemRoot = "SystemRoot";

        for (String key : list) {
            String value = map.get(key);
            if (cmp < 0 && (cmp = compareWindowsStyle(key, systemRoot)) > 0) {
                addToEnvIfSet(sb, systemRoot);
            }
            addToEnv(sb, key, value);
        }
        if (cmp < 0) {
            addToEnvIfSet(sb, systemRoot);
        }
        if (sb.length() == 0) {
            // Environment was empty and SystemRoot not set in parent
            sb.append('\u0000');
        }
        // Block is double NUL terminated
        sb.append('\u0000');
        return sb.toString();
    }

    private static void addToEnvIfSet(final StringBuilder sb, final String key) {
        String val = getenv(key);
        if (val != null) {
            addToEnv(sb, key, val);
        }
    }

    private static void addToEnv(final StringBuilder sb, final String key, final String val) {
        sb.append(key).append('=').append(val).appendCodePoint(0);
    }

    static void parseWindowsEnvBlock(String block, Map map) {
        if (! Build.Target.isWindows()) {
            throw new UnsupportedOperationException();
        }
        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)
                map.put(envblock.substring(beg, eql),
                                   envblock.substring(eql+1,end));
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy