001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  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 implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.settings;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021
022import java.io.*;
023import java.net.*;
024import java.nio.charset.*;
025import java.nio.file.*;
026import java.util.*;
027import java.util.function.*;
028
029/**
030 * A specialized {@link Setting} for string values that provides convenience methods for type conversion.
031 *
032 * <p>
033 * This class extends {@link Setting} with type-specific conversion methods such as {@link #asInteger()},
034 * {@link #asBoolean()}, {@link #asCharset()}, etc.
035 */
036@SuppressWarnings({
037   "java:S1192" // Duplicated literals (argument/property names) read more clearly inline than as constants
038})
039public class StringSetting extends Setting<String> {
040
041   /**
042    * Creates a new StringSetting from a Settings instance and a Supplier.
043    *
044    * @param settings The Settings instance that created this setting. Must not be <jk>null</jk>.
045    * @param supplier The supplier that provides the string value. Must not be <jk>null</jk>.
046    */
047   public StringSetting(Settings settings, Supplier<String> supplier) {
048      super(settings, supplier);
049   }
050
051   /**
052    * If a value is present, applies the provided mapping function to it and returns a StringSetting describing the result.
053    *
054    * <p>
055    * This method is specifically for String-to-String mappings. For mappings to other types, use the inherited {@link #map(Function)} method.
056    *
057    * <p>
058    * The returned StringSetting maintains its own cache, independent of this supplier.
059    * Resetting the mapped supplier does not affect this supplier, and vice versa.
060    *
061    * @param mapper A mapping function to apply to the value, if present. Must not be <jk>null</jk>.
062    * @return A StringSetting describing the result of applying a mapping function to the value of this StringSetting, if a value is present, otherwise an empty StringSetting.
063    */
064   public StringSetting mapString(UnaryOperator<String> mapper) {
065      reqnn("mapper", mapper);
066      return new StringSetting(getSettings(), () -> {
067         String value = get();
068         return nn(value) ? mapper.apply(value) : null;
069      });
070   }
071
072   /**
073    * If a value is present, and the value matches the given predicate, returns a StringSetting describing the value, otherwise returns an empty StringSetting.
074    *
075    * <p>
076    * The returned StringSetting maintains its own cache, independent of this supplier.
077    * Resetting the filtered supplier does not affect this supplier, and vice versa.
078    *
079    * @param predicate A predicate to apply to the value, if present. Must not be <jk>null</jk>.
080    * @return A StringSetting describing the value of this StringSetting if a value is present and the value matches the given predicate, otherwise an empty StringSetting.
081    */
082   @Override
083   public StringSetting filter(Predicate<? super String> predicate) {
084      reqnn("predicate", predicate);
085      return new StringSetting(getSettings(), () -> {
086         String value = get();
087         return (nn(value) && predicate.test(value)) ? value : null;
088      });
089   }
090
091   /**
092    * Converts the string value to an Integer.
093    *
094    * <p>
095    * The property value is parsed using {@link Integer#valueOf(String)}. If the property is not found
096    * or cannot be parsed as an integer, returns {@link Optional#empty()}.
097    *
098    * @return The property value as an Integer, or {@link Optional#empty()} if not found or not a valid integer.
099    */
100   public Setting<Integer> asInteger() {
101      return map(v -> safeOrNull(() -> Integer.valueOf(v))).filter(Objects::nonNull);
102   }
103
104   /**
105    * Converts the string value to a Long.
106    *
107    * <p>
108    * The property value is parsed using {@link Long#valueOf(String)}. If the property is not found
109    * or cannot be parsed as a long, returns {@link Optional#empty()}.
110    *
111    * @return The property value as a Long, or {@link Optional#empty()} if not found or not a valid long.
112    */
113   public Setting<Long> asLong() {
114      return map(v -> safeOrNull(() -> Long.valueOf(v))).filter(Objects::nonNull);
115   }
116
117   /**
118    * Converts the string value to a Boolean.
119    *
120    * <p>
121    * The property value is parsed using {@link Boolean#parseBoolean(String)}, which returns <c>true</c>
122    * if the value is (case-insensitive) "true", otherwise <c>false</c>. Note that this method will
123    * return <c>Optional.of(false)</c> for any non-empty value that is not "true", and
124    * {@link Optional#empty()} only if the property is not set.
125    *
126    * @return The property value as a Boolean, or {@link Optional#empty()} if not found.
127    */
128   public Setting<Boolean> asBoolean() {
129      return map(Boolean::parseBoolean);
130   }
131
132   /**
133    * Converts the string value to a Double.
134    *
135    * <p>
136    * The property value is parsed using {@link Double#valueOf(String)}. If the property is not found
137    * or cannot be parsed as a double, returns {@link Optional#empty()}.
138    *
139    * @return The property value as a Double, or {@link Optional#empty()} if not found or not a valid double.
140    */
141   public Setting<Double> asDouble() {
142      return map(v -> safeOrNull(() -> Double.valueOf(v))).filter(Objects::nonNull);
143   }
144
145   /**
146    * Converts the string value to a Float.
147    *
148    * <p>
149    * The property value is parsed using {@link Float#valueOf(String)}. If the property is not found
150    * or cannot be parsed as a float, returns {@link Optional#empty()}.
151    *
152    * @return The property value as a Float, or {@link Optional#empty()} if not found or not a valid float.
153    */
154   public Setting<Float> asFloat() {
155      return map(v -> safeOrNull(() -> Float.valueOf(v))).filter(Objects::nonNull);
156   }
157
158   /**
159    * Converts the string value to a File.
160    *
161    * <p>
162    * The property value is converted to a {@link File} using the {@link File#File(String)} constructor.
163    * If the property is not found, returns {@link Optional#empty()}. Note that this method does not
164    * validate that the file path is valid or that the file exists.
165    *
166    * @return The property value as a File, or {@link Optional#empty()} if not found.
167    */
168   public Setting<File> asFile() {
169      return map(File::new);
170   }
171
172   /**
173    * Converts the string value to a Path.
174    *
175    * <p>
176    * The property value is converted to a {@link Path} using {@link Paths#get(String, String...)}.
177    * If the property is not found or the path string is invalid, returns {@link Optional#empty()}.
178    *
179    * @return The property value as a Path, or {@link Optional#empty()} if not found or not a valid path.
180    */
181   public Setting<Path> asPath() {
182      return map(v -> safeOrNull(() -> Paths.get(v))).filter(Objects::nonNull);
183   }
184
185   /**
186    * Converts the string value to a URI.
187    *
188    * <p>
189    * The property value is converted to a {@link URI} using {@link URI#create(String)}.
190    * If the property is not found or the URI string is invalid, returns {@link Optional#empty()}.
191    *
192    * @return The property value as a URI, or {@link Optional#empty()} if not found or not a valid URI.
193    */
194   public Setting<URI> asURI() {
195      return map(v -> safeOrNull(() -> URI.create(v))).filter(Objects::nonNull);
196   }
197
198   /**
199    * Converts the string value to a Charset.
200    *
201    * <p>
202    * The property value is converted to a {@link Charset} using {@link Charset#forName(String)}.
203    * If the property is not found or the charset name is not supported, returns {@link Optional#empty()}.
204    *
205    * @return The property value as a Charset, or {@link Optional#empty()} if not found or not a valid charset.
206    */
207   public Setting<Charset> asCharset() {
208      return map(v -> safeOrNull(() -> Charset.forName(v))).filter(Objects::nonNull);
209   }
210
211   /**
212    * Converts the string value to the specified type using the Settings type conversion functions.
213    *
214    * <p>
215    * The property value is converted using {@link Settings#toType(String, Class)}. If the property is not found
216    * or cannot be converted to the specified type, returns {@link Optional#empty()}.
217    *
218    * @param <T> The target type.
219    * @param c The target class. Must not be <jk>null</jk>.
220    * @return The property value as the specified type, or {@link Optional#empty()} if not found or not a valid conversion.
221    */
222   public <T> Setting<T> asType(Class<T> c) {
223      reqnn("c", c);
224      return map(v -> getSettings().toType(v, c)).filter(Objects::nonNull);
225   }
226}