001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2022 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.loader; 018 019import java.lang.reflect.Type; 020 021import java.util.function.Supplier; 022 023import org.microbean.invoke.FixedValueSupplier; 024 025import org.microbean.loader.api.Loader; 026 027import org.microbean.loader.spi.AbstractProvider; 028import org.microbean.loader.spi.Value; 029 030import org.microbean.path.Path; 031import org.microbean.path.Path.Element; 032 033/** 034 * An {@link AbstractProvider} that provides access to {@linkplain 035 * System#getenv(String) environment variables}. 036 * 037 * @author <a href="https://about.me/lairdnelson" 038 * target="_parent">Laird Nelson</a> 039 */ 040public class EnvironmentVariableProvider extends AbstractProvider { 041 042 043 044 /* 045 * Instance fields. 046 */ 047 048 049 private final boolean flatKeys; 050 051 052 /* 053 * Constructors. 054 */ 055 056 057 /** 058 * Creates a new {@link EnvironmentVariableProvider} that uses flat 059 * keys. 060 * 061 * @see #EnvironmentVariableProvider(boolean) 062 */ 063 public EnvironmentVariableProvider() { 064 this(true); 065 } 066 067 /** 068 * Creates a new {@link EnvironmentVariableProvider}. 069 * 070 * @param flatKeys whether the key for an environment variable is 071 * derived from a {@link Path}'s {@linkplain Path#lastElement() last 072 * element}'s {@linkplain Element#name() name} only 073 */ 074 public EnvironmentVariableProvider(final boolean flatKeys) { 075 super(String.class); 076 this.flatKeys = flatKeys; 077 } 078 079 080 /* 081 * Instance methods. 082 */ 083 084 085 /** 086 * If the supplied {@code absolutePath} has a {@linkplain 087 * Path#size() size} of {@code 2} (the {@linkplain Path#root() root} 088 * plus a single name), returns a {@linkplain FixedValueSupplier 089 * deterministic <code>Supplier</code>} whose {@link Supplier#get()} 090 * method returns the {@linkplain System#getenv(String) environment 091 * variable} with that name, or {@code null} in all other cases. 092 * 093 * @param requestor the {@link Loader} requesting a {@link Value}; 094 * must not be {@code null} 095 * 096 * @param absolutePath an {@linkplain Path#absolute() absolute} 097 * {@link Path}; must not be {@code null} 098 * 099 * @return a {@linkplain FixedValueSupplier deterministic} {@link 100 * Supplier} whose {@link Supplier#get()} method returns the 101 * appropriate {@linkplain System#getenv(String) environment 102 * variable} if the supplied {@code absolutePath} has a {@linkplain 103 * Path#size() size} of {@code 2} (the {@linkplain Path#root() root} 104 * plus a single name) and there actually is a {@linkplain 105 * System#getenv(String) corresponding environment variable}; {@code 106 * null} in all other cases 107 * 108 * @nullability This method may return {@code null}. 109 * 110 * @idempotency This method is idempotent and deterministic during 111 * the lifetime of a Java virtual machine instance. 112 * 113 * @threadsafety This method is safe for concurrent use by multiple 114 * threads. 115 * 116 * @exception NullPointerException if {@code requestor} or {@code 117 * absolutePath} is {@code null} 118 * 119 * @see AbstractProvider#find(Loader, Path) 120 * 121 * @see System#getenv(String) 122 */ 123 @Override // AbstractProvider 124 protected Supplier<?> find(final Loader<?> requestor, final Path<? extends Type> absolutePath) { 125 assert absolutePath.absolute(); 126 assert absolutePath.startsWith(requestor.path()); 127 assert !absolutePath.equals(requestor.path()); 128 129 // On Unix systems, there is absolutely no question that the 130 // environment is entirely immutable, even when probed via 131 // System#getenv(String). See 132 // https://github.com/openjdk/jdk/blob/dfacda488bfbe2e11e8d607a6d08527710286982/src/java.base/unix/classes/java/lang/ProcessEnvironment.java#L67-L91. 133 // 134 // Things are ever so slightly more murky in Windows land. As of 135 // JDK 17, the environment there is also entirely immutable: 136 // https://github.com/openjdk/jdk/blob/dfacda488bfbe2e11e8d607a6d08527710286982/src/java.base/windows/classes/java/lang/ProcessEnvironment.java#L257-L258 137 // but the class is not as "immutable looking" as the Unix one and 138 // it seems to be designed for updating in some cases. 139 // Nevertheless, for the System#getenv(String) case, the 140 // environment is immutable. 141 // 142 // TL;DR: System.getenv("foo") will always return a value for 143 // "foo" if ever there was one, and will always return null if 144 // there wasn't. 145 146 final String value = System.getenv(key(absolutePath, this.flatKeys)); 147 if (value == null) { 148 // Environment variables conflate null with absence. 149 return null; 150 } 151 return FixedValueSupplier.of(value); 152 } 153 154 /** 155 * Overrides the {@link AbstractProvider#path(Loader, Path)} method 156 * to return a (relative) {@link Path} consisting solely of the 157 * {@linkplain Path#lastElement() last element} of the supplied 158 * {@code absolutePath}. 159 * 160 * @param requestor the {@link Loader} seeking a {@link Value}; must 161 * not be {@code null} 162 * 163 * @param absolutePath an {@linkplain Path#absolute() absolute 164 * <code>Path</code>} for which a {@link Value} is being sought; 165 * must not be {@code null} 166 * 167 * @return a {@link Path} that will be {@linkplain 168 * Value#Value(Supplier, Path) used to build a <code>Value</code>} 169 * to be returned by the {@link #get(Loader, Path)} method 170 * 171 * @nullability This method does not, but overrides may, return 172 * {@code null}. 173 * 174 * @idempotency This method is, and its overrides must be, 175 * idempotent and deterministic. 176 * 177 * @threadsafety This method is, and its overrides must be, safe for 178 * concurrent use by multiple threads. 179 * 180 * @see AbstractProvider#path(Loader, Path) 181 */ 182 @Override // AbstractProvider 183 protected <T extends Type> Path<T> path(final Loader<?> requestor, final Path<T> absolutePath) { 184 return Path.of(absolutePath.lastElement()); 185 } 186 187 188 /* 189 * Static methods. 190 */ 191 192 193 /** 194 * Returns a {@link String} representation of the supplied {@link Path}. 195 * 196 * @param path the {@link Path} in question; must not be {@code 197 * null} 198 * 199 * @param flat whether the key is derived from the supplied {@link 200 * Path}'s {@linkplain Path#lastElement() last element}'s 201 * {@linkplain Element#name() name} only 202 * 203 * @return a {@link String} representation of the supplied {@link 204 * Path} 205 * 206 * @exception NullPointerException if {@code path} is {@code null} 207 * 208 * @nullability This method never returns {@code null}. 209 * 210 * @idempotency This method is idempotent and deterministic. 211 * 212 * @threadsafety This method is safe for concurrent use by multiple 213 * threads. 214 */ 215 protected static final String key(final Path<?> path, final boolean flat) { 216 if (flat) { 217 return path.lastElement().name(); 218 } else { 219 return path.stream() 220 .map(Element::name) 221 .filter(s1 -> !s1.isEmpty()) 222 .reduce((s1, s2) -> String.join(".", s1, s2)) 223 .orElse(""); 224 } 225 } 226 227}