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}