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

com.anaptecs.jeaf.xfun.api.config.AnnotationBasedConfiguration Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2004 - 2019 anaptecs GmbH, Burgstr. 96, 72764 Reutlingen, Germany
 *
 * All rights reserved.
 */
package com.anaptecs.jeaf.xfun.api.config;

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import com.anaptecs.jeaf.xfun.api.XFun;
import com.anaptecs.jeaf.xfun.api.XFunRuntimeException;
import com.anaptecs.jeaf.xfun.api.trace.Trace;
import com.anaptecs.jeaf.xfun.bootstrap.Assert;
import com.anaptecs.jeaf.xfun.fallback.trace.FallbackTraceProviderImpl;

/**
 * Class implements a generic configuration provider that can be used to easily implement an configuration provider
 * based on annotations.
 */
public abstract class AnnotationBasedConfiguration {
  /**
   * Attribute controls behavior in case of exceptions. false means that when ever possible throwing an
   * exception is suppressed.
   */
  protected final boolean exceptionOnError;

  /**
   * Reference to default configuration. When ever the custom configuration does not contain any values then the default
   * configuration will be used.
   */
  protected final T defaultConfig;

  /**
   * Attribute defines if the default configuration is available.
   */
  protected final boolean isDefaultConfigAvailable;

  /**
   * Reference to custom configuration.
   */
  protected final T customConfig;

  /**
   * Reference to configuration that holds the values. This can either be the custom or the default configuration. The
   * reference is never null.
   */
  protected final T theConfig;

  /**
   * Attribute defines if a custom configuration is available.
   */
  protected final boolean isCustomConfigAvailable;

  /**
   * Name of the class that provided the configuration that is used. This is either the default configuration class or
   * some custom configuration class.
   */
  private Class configurationClass;

  /**
   * List with all configuration errors that were detected when the configuration was loaded.
   */
  private final List configErrors;

  /**
   * Initialize object. During initialization configuration will be loaded.
   * 
   * @param pCustomConfigurationResourceName Name of the file which contains the class name of the custom configuration
   * class. The parameter must not be null.
   * @param pCustomConfigurationBasePackagePath Path under which the file should be found in the classpath. The
   * parameter may be null.
   * @param pExceptionOnError If parameter is set to true then an exception will be thrown in case of configuration
   * errors.
   */
  protected AnnotationBasedConfiguration( String pCustomConfigurationResourceName,
      String pCustomConfigurationBasePackagePath, boolean pExceptionOnError ) {
    // It is required that the defined annotation has retention type runtime. Otherwise the configuration parameters
    // that are defined through the annotation are not visible at runtime.
    Class lAnnotationClass = this.getAnnotationClass();
    Retention lRetentionAnnotation = lAnnotationClass.getAnnotation(Retention.class);
    RetentionPolicy lRetentionPolicy;
    if (lRetentionAnnotation != null) {
      lRetentionPolicy = lRetentionAnnotation.value();
    }
    else {
      // As retention policy is not defined we fall back to default.
      lRetentionPolicy = RetentionPolicy.CLASS;
    }

    // Annotation that is used for configuration has the required retention policy, so we can just proceed.
    if (lRetentionPolicy == RetentionPolicy.RUNTIME) {
      // Configure exception handling
      exceptionOnError = pExceptionOnError;

      // Load default configuration
      defaultConfig = this.loadDefaultConfiguration();
      isDefaultConfigAvailable = this.isDefaultConfigurationAvailable();

      // Load custom configuration
      customConfig =
          this.loadCustomConfiguration(pCustomConfigurationResourceName, pCustomConfigurationBasePackagePath);
      isCustomConfigAvailable = this.isCustomConfigurationAvailable();

      // Try to detect configuration errors
      configErrors =
          this.detectConfigurationErrors(pCustomConfigurationBasePackagePath + "/" + pCustomConfigurationResourceName);

      // Make custom config to the one that should be used.
      if (isCustomConfigAvailable == true) {
        theConfig = customConfig;
      }
      // Make default config to the one that should be used.
      else {
        theConfig = defaultConfig;
      }
    }
    // The annotation type that is used for configuration does not have retention policy RUNTIME
    else {
      String lMessage = "The annotation " + lAnnotationClass.getName()
          + " does not have retention RUNTIME. Please correction the annotation by adding '@Retention(RetentionPolicy.RUNTIME)'.";
      FallbackTraceProviderImpl.EMERGENCY_TRACE.error(lMessage);
      throw new XFunRuntimeException(lMessage);
    }
  }

  /**
   * Method tries to detect configuration errors.
   * 
   * @param pCustomConfigurationResourceName Name of the resource from which the configuration was read. The parameter
   * may be null.
   * @param pCustomConfigurationBasePackagePath Path under which the file should be found in the classpath. The
   * parameter may be null.
   * @return {@link List} List with a description of all configuration errors that were detected. The method never
   * returns null.
   */
  private List detectConfigurationErrors( String pCustomConfigurationResourceName ) {
    // Start check of configuration
    List lConfigErrors;
    if (isDefaultConfigAvailable == false && isCustomConfigAvailable == false) {
      String lMessage = "Configuration error. Default configuration class '" + this.getDefaultConfigurationClass()
          + "' is not in classpath AND no custom configuration could be loaded from resource file '"
          + pCustomConfigurationResourceName + "'. Please check your custom configuration file and your classpath.";
      throw new XFunRuntimeException(lMessage);
    }
    // Check custom configuration only in case that there is no default configuration. It's an explicit feature the a
    // custom configuration is allowed to define only some values.
    else if (isCustomConfigAvailable == true && isDefaultConfigAvailable == false) {
      // We need to check that custom configuration contains only valid values.
      lConfigErrors = this.checkCustomConfiguration();
    }
    // We only have our default configuration, so no special checks are required as we believe in the overall
    // correctness of our default configuration ;-)
    else {
      lConfigErrors = Collections.emptyList();
    }
    return lConfigErrors;
  }

  /**
   * Method checks if a custom configuration is correct.
   * 
   * @return {@link List} List with a description of all configuration errors that were detected. The method never
   * returns null.
   */
  private List checkCustomConfiguration( ) {
    List lConfigErrors;
    lConfigErrors = this.checkCustomConfiguration(customConfig);
    this.traceConfigurationProblems(lConfigErrors);

    // Abort loading process if it was requested.
    if (exceptionOnError == true) {
      if (lConfigErrors != null && lConfigErrors.isEmpty() == false) {
        String lMessage = "Found " + lConfigErrors.size()
            + " error(s) during analysis of configuration. Please see error log for further details.";
        throw new XFunRuntimeException(lMessage);
      }
      else {
        lConfigErrors = Collections.emptyList();
      }
    }
    // Even if configuration errors were found we should continue
    else {
      // There are errors in the configuration
      if (lConfigErrors != null && lConfigErrors.isEmpty() == false) {
        lConfigErrors = Collections.unmodifiableList(lConfigErrors);
      }
      // No configuration errors detected.
      else {
        lConfigErrors = Collections.emptyList();
      }
    }
    return lConfigErrors;
  }

  /**
   * Method traces the passed configuration problems.
   * 
   * @param pConfigfurationProblems List with all configuration problems that were found. The list may be null or empty.
   * In this case it's assumed that there are no configuration problems.
   */
  private void traceConfigurationProblems( List pConfigfurationProblems ) {
    if (pConfigfurationProblems != null && pConfigfurationProblems.isEmpty() == false) {
      Trace lTrace = XFun.getTrace();
      lTrace.error("Found " + pConfigfurationProblems.size()
          + " configuration problems during analysis of configuration provided through class "
          + configurationClass.getName() + ":");

      for (String lMessage : pConfigfurationProblems) {
        lTrace.error(lMessage);
      }
    }
  }

  /**
   * Method returns the class object of the annotation that holds the configuration data. This method is only needed due
   * to limitations of Java generics ;-(
   * 
   * @return {@link Class} Class object of the annotation. The method must not return null.
   */
  protected abstract Class getAnnotationClass( );

  /**
   * Method returns the name of the class that contains the default configuration. In order to avoid direct compile time
   * dependencies to this class only its name should be returned.
   * 
   * @return Name of the class that hold the annotation with the default values. The method may return null in case that
   * there is no meaningful default.
   */
  protected abstract String getDefaultConfigurationClass( );

  /**
   * Method should return an empty custom configuration. Once again we have to fight with limitation in Java generics.
   * In order to simplify the further processing this method has to be implemented.
   * 
   * @return T Empty custom configuration. The method must not return null.
   */
  protected abstract T getEmptyConfiguration( );

  /**
   * Method needs to be implemented by subclasses in order to check the custom configuration.
   * 
   * @param pCustomConfiguration Custom configuration that should be checked. The parameter is never null.
   * @return {@link List} List of Strings describing the found problems during the check. For each found problem one
   * entry should be provided inside the list. If no problems were found the the method is supposed to return null or an
   * empty list.
   */
  protected abstract List checkCustomConfiguration( T pCustomConfiguration );

  /**
   * Method loads default configuration and returns it.
   * 
   * @return T Loaded default configuration. The method method never returns null.
   */
  private T loadDefaultConfiguration( ) {
    // Load default configuration values. In order to avoid ugly dependencies in client code it's getting a little ugly
    // here instead. We have to know the class name of the default configuration values and load them via reflection.
    Class lDefaultConfigurationClass = this.loadDefaultConfigurationClass();

    // If we found the default configuration class then we take the configuration values from the annotation.
    T lDefaultConfiguration;
    if (lDefaultConfigurationClass != null) {
      lDefaultConfiguration = lDefaultConfigurationClass.getAnnotation(this.getAnnotationClass());
      configurationClass = lDefaultConfigurationClass;
    }
    // No default configuration is available. This may be fine or not depending of a may be existing custom
    // configuration.
    else {
      lDefaultConfiguration = this.getEmptyConfiguration();
      configurationClass = null;
    }
    return lDefaultConfiguration;
  }

  /**
   * Method returns the class from which the configuration was loaded.
   * 
   * @return Class from which the configuration was actually read. The method never returns null.
   */
  public final Class getConfigurationClass( ) {
    return configurationClass;
  }

  /**
   * Method returns a list with all configuration errors that were detected when the configuration was loaded.
   * 
   * @return List with all detected configuration errors. The method never returns null.
   */
  public final List getConfigurationErrors( ) {
    return configErrors;
  }

  /**
   * Method checks if the default configuration is available.
   * 
   * @return boolean Method returns true if the default configuration is available and false
   * otherwise.
   */
  public final boolean isDefaultConfigurationAvailable( ) {
    boolean lDefaultConfigAvailable;
    if (defaultConfig == null) {
      lDefaultConfigAvailable = false;
    }
    else {
      lDefaultConfigAvailable = !this.getEmptyConfiguration().getClass().equals(defaultConfig.getClass());
    }
    return lDefaultConfigAvailable;
  }

  /**
   * Method loads the class that contains the default configuration.
   * 
   * @return {@link Class} Class that contains the default configuration. The method may return null.
   */
  private Class loadDefaultConfigurationClass( ) {
    Class lDefaultConfigurationClass;
    try {
      // Try to load default configuration class.
      String lClassName = this.getDefaultConfigurationClass();
      if (lClassName != null) {
        lDefaultConfigurationClass = Class.forName(lClassName);
      }
      // There may be case when there is no meaningful default.
      else {
        lDefaultConfigurationClass = null;
      }
    }
    // Configured default configuration could not be found in classpath. This is a regular situation.
    catch (ClassNotFoundException e) {
      lDefaultConfigurationClass = null;
    }
    return lDefaultConfigurationClass;
  }

  /**
   * Method returns the configured custom configuration.
   * 
   * @param pCustomConfigurationResourceName Name of the file which contains the class name of the custom configuration
   * class. The parameter must not be null.
   * @param pBasePackagePath Path under which the file should be found in the classpath. The parameter may be null.
   * @return T Custom configuration. The method never returns null. In case of no custom configuration an empty
   * configuration will be used {@link #getEmptyConfiguration()}.
   */
  private T loadCustomConfiguration( String pCustomConfigurationResourceName,
      String pCustomConfigurationBasePackagePath ) {

    // Determine class that contains the config annotation from resources.
    T lAnnotation;
    if (pCustomConfigurationResourceName != null) {
      ConfigurationReader lReader = new ConfigurationReader();
      Class lCustomConfigClass =
          lReader.readClassFromConfigFile(pCustomConfigurationResourceName, pCustomConfigurationBasePackagePath);

      // Custom configuration is present
      if (lCustomConfigClass != null) {
        // Get annotation
        lAnnotation = lCustomConfigClass.getAnnotation(this.getAnnotationClass());
        if (lAnnotation == null) {
          XFun.getTrace().error("Configured class " + lCustomConfigClass.getName() + " does not have annotation "
              + this.getAnnotationClass().getName());
          lAnnotation = this.getEmptyConfiguration();
        }
        else {
          configurationClass = lCustomConfigClass;
        }
      }
      // No custom configuration is available, so we use an empty default annotation.
      else {
        lAnnotation = this.getEmptyConfiguration();
      }
    }
    // Name of custom configuration is null.
    else {
      lAnnotation = this.getEmptyConfiguration();
    }

    // Return annotation.
    Assert.assertNotNull(lAnnotation, "lAnnotation (Custom Configuration Annotation)");
    return lAnnotation;
  }

  /**
   * Method checks if the default configuration is available.
   * 
   * @return boolean Method returns true if the default configuration is available and false
   * otherwise.
   */
  public final boolean isCustomConfigurationAvailable( ) {
    // Compare custom and default configuration
    boolean lCustomEqualsDefaultConfig;
    if (defaultConfig != null) {
      lCustomEqualsDefaultConfig = defaultConfig.equals(customConfig);
    }
    else {
      lCustomEqualsDefaultConfig = false;
    }

    // Compare custom and empty configuration
    Class lEmptyConfigClass = this.getEmptyConfiguration().getClass();
    boolean lCustomEqualsEmptyConfig = lEmptyConfigClass.equals(customConfig.getClass());

    // A custom configuration is available if the custom configuration is not the same as the default one and if the
    // custom is not the empty configuration.
    boolean lCustomConfigurationAvailable;
    if (lCustomEqualsDefaultConfig == false && lCustomEqualsEmptyConfig == false) {
      lCustomConfigurationAvailable = true;
    }
    else {
      lCustomConfigurationAvailable = false;
    }
    return lCustomConfigurationAvailable;
  }

  /**
   * Method checks the passed custom configuration. During check we check all params of the configuration as we assume
   * that there is no default configuration.
   * 
   * @param pImplClass Class object of which a new instance should be created. The parameter must not be null.
   * @param pInterface Interface that is supposed to be implemented by the passed class. The parameter is only needed to
   * generate some meaningful exception message.
   * @param pConfigErrors List of strings describing the found problems in the passed custom configuration. If this
   * methods identifies new problems then it will add an entry to the passed list. The parameter must not be null.
   */
  protected final  void tryNewInstance( Class pImplClass, Class pInterface,
      List pConfigErrors ) {

    // Check parameter
    Assert.assertNotNull(pConfigErrors, "pConfigErrors");

    // Try to create new instance of this class. If this is possible then everything is fine. That the right interface
    // is implemented is already ensured by the compiler.
    if (pImplClass != null && pImplClass.isInterface() == false) {
      try {
        pImplClass.newInstance();
      }
      // We have a configuration issue. We can not create a new instance of the passed class.
      catch (ReflectiveOperationException e) {
        String lConfigError = "Unable to create new instance of implementation for interface " + pInterface.getName()
            + ". Faulty implemetation class: " + pImplClass.getName();

        // In order to not loose exception in critical cases we have to do it in this ugly way.
        System.err.println(lConfigError);
        e.printStackTrace(System.err);
        pConfigErrors.add(lConfigError);
      }
    }
    else {
      // Implementation class is null.
      if (pImplClass == null) {
        String lConfigError = "Unable to create new instance of configured class. Configured class is 'null'.";
        pConfigErrors.add(lConfigError);
      }
      // Provided implementation class actually is just an interface.
      else {
        String lConfigError = "Unable to create new instance of configured class " + pImplClass.getName()
            + ". Configured class is an interface.";
        pConfigErrors.add(lConfigError);
      }
    }
  }

  /**
   * Method is intended to be used when creating new objects of configured classes. In case that a custom class is
   * provided it will be used if not the the default class will be used.
   * 
   * @param pCustomClass Custom class of which a new instance should be created. The parameter may be null. At least one
   * of the parameters pCustomClass and pDefaultClass must not be null.
   * @param pDefaultClass Default class of which an instance should be created. The parameter may be null. At least one
   * of the parameters pCustomClass and pDefaultClass must not be null.
   * @param pType Type parameter for Java generics.
   * @param pExceptionOnError If parameter is set to true then an exception will be thrown in case of an error else null
   * will be returned.
   * @return X Created instance of one of the passed classes. The method may return null in case that neither a custom
   * class nor a default class is configured or in case that no instance of the configured class could be created.
   */
  protected final  X newInstance( Class pCustomClass, Class pDefaultClass,
      boolean pExceptionOnError ) {
    // Resolve configured class in a 2-way approach. First we look at the custom configuration afterwards we do the
    // fallback to our default configuration.
    Class lConfiguredClass = pCustomClass;

    // Do fallback to default configuration
    if (lConfiguredClass == null || lConfiguredClass.isInterface()) {
      lConfiguredClass = pDefaultClass;
    }

    // Check if configured class is set.
    return this.newInstance(lConfiguredClass, pExceptionOnError);
  }

  /**
   * Method tries to create a new instance of the passed class using its default constructor.
   * 
   * @param pClass Class of which a new instance should be created. The parameter must not be null.
   * @param pExceptionOnError If parameter is set to true then an exception will be thrown in case of an error else null
   * will be returned.
   * @return X New instance of the passed class or null if the instance could not be created.
   */
  protected final  X newInstance( Class pClass, boolean pExceptionOnError ) {
    X lNewInstance;
    if (pClass != null) {
      lNewInstance = this.newInstanceOrNull(pClass, pExceptionOnError);
    }
    // No class configured, so we can not create an instance and will return null.
    else {
      if (pExceptionOnError == true) {
        throw new XFunRuntimeException("Unable to create new instance of class 'null'.");
      }
      else {
        lNewInstance = null;
      }
    }
    return lNewInstance;
  }

  /**
   * Method tries to create a new instance of the passed class.
   * 
   * @param 
   * @param pClass Class from which a new instance should be created. The parameter must not be null.
   * @param pExceptionOnError Parameter defines if in case of an error either null should be returned or an exception
   * should be thrown.
   * @return New instance that was created. Depending on parameter pExceptionOnError the method might return null.
   */
  private  X newInstanceOrNull( Class pClass, boolean pExceptionOnError ) {
    // Create instance of class.
    X lNewInstance;
    try {
      lNewInstance = pClass.newInstance();
    }
    catch (ReflectiveOperationException e) {
      String lMessage = "Unable to create new instance of class " + pClass.getName() + ".";
      FallbackTraceProviderImpl.EMERGENCY_TRACE.error(lMessage, e);
      if (pExceptionOnError == true) {
        throw new XFunRuntimeException(lMessage, e);
      }
      else {
        lNewInstance = null;
      }
    }
    catch (RuntimeException | Error e) {
      String lMessage = "Unable to create new instance of class " + pClass.getName() + ".";
      FallbackTraceProviderImpl.EMERGENCY_TRACE.error(lMessage, e);
      if (pExceptionOnError == true) {
        throw e;
      }
      else {
        lNewInstance = null;
      }
    }
    return lNewInstance;
  }

  /**
   * 
   * @param 
   * @param pClasses
   * @param pExceptionOnError If parameter is set to true then an exception will be thrown in case of an error else null
   * will be returned.
   * @return
   */
  protected final  List newInstances( Collection> pClasses, boolean pExceptionOnError ) {
    // Try to create new instance of each class.
    List lNewInstances;
    if (pClasses != null) {
      lNewInstances = new ArrayList<>(pClasses.size());
      for (Class lNextClass : pClasses) {
        X lNewInstance = this.newInstance(lNextClass, pExceptionOnError);
        if (lNewInstance != null) {
          lNewInstances.add(lNewInstance);
        }
      }
    }
    else {
      lNewInstances = Collections.emptyList();
    }
    return lNewInstances;
  }

  /**
   * Method converts that passed array of objects to a list of objects.
   * 
   * @param pBooleans Array of objects. The parameter may be null.
   * @return {@link List} List of objects. The method returns null if null is passed. If the array is empty then an
   * empty list will be returned.
   */
  protected final  List asList( X[] pObjects ) {
    List lList;
    if (pObjects != null) {
      lList = Arrays.asList(pObjects);
    }
    else {
      lList = null;
    }
    return lList;
  }

  /**
   * Method converts that passed array of primitive booleans to a list of Boolean objects.
   * 
   * @param pBooleans Array of primitive booleans. The parameter may be null.
   * @return {@link List} List of Boolean objects. The method returns null if null is passed. If the array is empty then
   * an empty list will be returned.
   */
  protected final List asList( boolean[] pBooleans ) {
    List lList;
    if (pBooleans != null) {
      lList = new ArrayList<>(pBooleans.length);
      for (int i = 0; i < pBooleans.length; i++) {
        lList.add(pBooleans[i]);
      }
    }
    else {
      lList = null;
    }
    return lList;
  }

  /**
   * Method converts that passed array of primitive bytes to a list of Byte objects.
   * 
   * @param pBytes Array of primitive byte. The parameter may be null.
   * @return {@link List} List of Byte objects. The method returns null if null is passed. If the array is empty then an
   * empty list will be returned.
   */
  protected final List asList( byte[] pBytes ) {
    List lList;
    if (pBytes != null) {
      lList = new ArrayList<>(pBytes.length);
      for (int i = 0; i < pBytes.length; i++) {
        lList.add(pBytes[i]);
      }
    }
    else {
      lList = null;
    }
    return lList;
  }

  /**
   * Method converts that passed array of primitive shorts to a list of Short objects.
   * 
   * @param pShorts Array of primitive shorts. The parameter may be null.
   * @return {@link List} List of Short objects. The method returns null if null is passed. If the array is empty then
   * an empty list will be returned.
   */
  protected final List asList( short[] pShorts ) {
    List lList;
    if (pShorts != null) {
      lList = new ArrayList<>(pShorts.length);
      for (int i = 0; i < pShorts.length; i++) {
        lList.add(pShorts[i]);
      }
    }
    else {
      lList = null;
    }
    return lList;
  }

  /**
   * Method converts that passed array of primitive ints to a list of Integer objects.
   * 
   * @param pInts Array of primitive ints. The parameter may be null.
   * @return {@link List} List of Integer objects. The method returns null if null is passed. If the array is empty then
   * an empty list will be returned.
   */
  protected final List asList( int[] pInts ) {
    List lList;
    if (pInts != null) {
      lList = new ArrayList<>(pInts.length);
      for (int i = 0; i < pInts.length; i++) {
        lList.add(pInts[i]);
      }
    }
    else {
      lList = null;
    }
    return lList;
  }

  /**
   * Method converts that passed array of primitive longs to a list of Long objects.
   * 
   * @param pLongs Array of primitive longs. The parameter may be null.
   * @return {@link List} List of Long objects. The method returns null if null is passed. If the array is empty then an
   * empty list will be returned.
   */
  protected final List asList( long[] pLongs ) {
    List lList;
    if (pLongs != null) {
      lList = new ArrayList<>(pLongs.length);
      for (int i = 0; i < pLongs.length; i++) {
        lList.add(pLongs[i]);
      }
    }
    else {
      lList = null;
    }
    return lList;
  }

  /**
   * Method converts that passed array of primitive floats to a list of Float objects.
   * 
   * @param pFloats Array of primitive floats. The parameter may be null.
   * @return {@link List} List of Float objects. The method returns null if null is passed. If the array is empty then
   * an empty list will be returned.
   */
  protected final List asList( float[] pFloats ) {
    List lList;
    if (pFloats != null) {
      lList = new ArrayList<>(pFloats.length);
      for (int i = 0; i < pFloats.length; i++) {
        lList.add(pFloats[i]);
      }
    }
    else {
      lList = null;
    }
    return lList;
  }

  /**
   * Method converts that passed array of primitive doubles to a list of Double objects.
   * 
   * @param pDoubles Array of primitive doubles. The parameter may be null.
   * @return {@link List} List of Double objects. The method returns null if null is passed. If the array is empty then
   * an empty list will be returned.
   */
  protected final List asList( double[] pDoubles ) {
    List lList;
    if (pDoubles != null) {
      lList = new ArrayList<>(pDoubles.length);
      for (int i = 0; i < pDoubles.length; i++) {
        lList.add(pDoubles[i]);
      }
    }
    else {
      lList = null;
    }
    return lList;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy