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.*;
020
021import java.util.concurrent.atomic.*;
022import java.util.function.*;
023
024import org.apache.juneau.commons.runtime.*;
025
026/**
027 * Property source backed by {@link Args}.
028 */
029public class ArgsPropertySource implements PropertySource {
030
031   private final Supplier<Args> argsSupplier;
032
033   /**
034    * Constructor.
035    *
036    * @param argsSupplier Supplier for args.
037    */
038   public ArgsPropertySource(Supplier<Args> argsSupplier) {
039      this.argsSupplier = argsSupplier;
040   }
041
042   /**
043    * Creates a source using the default command-line discovery.
044    *
045    * @return A new source.
046    */
047   public static ArgsPropertySource createDefault() {
048      return new ArgsPropertySource(ArgsPropertySource::getDefaultArgs);
049   }
050
051   /**
052    * Returns the default command-line args, discovered and parsed once per JVM.
053    *
054    * @return The cached default args.
055    */
056   public static Args getDefaultArgs() {
057      return DefaultArgsHolder.INSTANCE;
058   }
059
060   /**
061    * Wraps a supplier so that it is invoked at most once, with the result reused on later calls.
062    *
063    * @param supplier The supplier to wrap.
064    * @return A supplier that caches the first result.
065    */
066   static Supplier<Args> memoize(Supplier<Args> supplier) {
067      var cache = new AtomicReference<Args>();
068      return () -> {
069         var v = cache.get();
070         if (v == null) {
071            cache.compareAndSet(null, supplier.get());
072            v = cache.get();
073         }
074         return v;
075      };
076   }
077
078   private static final class DefaultArgsHolder {
079      static final Args INSTANCE = createDefaultArgs();
080   }
081
082   /**
083    * Discovers and parses the default command-line args without caching.
084    *
085    * @return A new args object.
086    */
087   public static Args createDefaultArgs() {
088      var s = System.getProperty("sun.java.command");
089      if (ine(s)) {
090         var i = s.indexOf(' ');
091         return new Args(i == -1 ? "" : s.substring(i + 1));
092      }
093      return new Args(System.getProperty("juneau.args", ""));
094   }
095
096   @Override
097   public PropertyLookupResult get(String name) {
098      var args = argsSupplier.get();
099      if (args == null)
100         return PropertyLookupResult.missing();
101      if (isIndex(name)) {
102         try {
103            var v = args.get(Integer.parseInt(name));
104            return v.isPresent() ? PropertyLookupResult.present(v) : PropertyLookupResult.missing();
105         } catch (@SuppressWarnings("unused") NumberFormatException e) {
106            // Out of int range; fall through.
107         }
108      }
109      var values = args.getAll(name);
110      if (! values.isEmpty())
111         return PropertyLookupResult.present(o(String.join(",", values)));
112      var v = args.get(name);
113      return v.isPresent() ? PropertyLookupResult.present(v) : PropertyLookupResult.missing();
114   }
115
116   /**
117    * Returns <jk>true</jk> if the name has the shape of an integer (optional sign followed by digits).
118    *
119    * <p>
120    * Guards {@link Integer#parseInt(String)} so that ordinary non-numeric keys do not pay for a thrown exception.
121    */
122   static boolean isIndex(String name) {
123      if (name == null)
124         return false;
125      var n = name.length();
126      if (n == 0)
127         return false;
128      var i = 0;
129      var c = name.charAt(0);
130      if (c == '-' || c == '+') {
131         if (n == 1)
132            return false;
133         i = 1;
134      }
135      for (; i < n; i++) {
136         c = name.charAt(i);
137         if (c < '0' || c > '9')
138            return false;
139      }
140      return true;
141   }
142}