Repository: opengamma/strata
Issue: MultiCurrencyAmountArray.of(size, fn) doesn't work with empty MultiCurrencyAmounts 

The following code produces a `MultiCurrencyAmountArray` of size zero even though it is created from two `MultiCurrencyAmounts`

```
MultiCurrencyAmountArray array = MultiCurrencyAmountArray.of(
    MultiCurrencyAmount.empty(),
    MultiCurrencyAmount.empty());
```

The following code always produces a `MultiCurrencyAmountArray` with a size of zero regardless of the requested size:

```
MultiCurrencyAmountArray.of(size, () -> MultiCurrencyAmount.empty());
```

The size of the array is derived from the size of the internal arrays holding the currency values. If there are no values a size of zero is incorrectly returned.


Changed files:
modules/basics/src/main/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArray.java
modules/basics/src/test/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArrayTest.java
modules/data/src/test/java/com/opengamma/strata/data/scenario/MultiCurrencyScenarioArrayTest.java

diff --git a/modules/basics/src/main/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArray.java b/modules/basics/src/main/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArray.java
index c9ec298a4..129fd33a9 100644
--- a/modules/basics/src/main/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArray.java
+++ b/modules/basics/src/main/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArray.java
@@ -55,15 +55,17 @@ import com.opengamma.strata.collect.array.DoubleArray;
 public final class MultiCurrencyAmountArray
     implements FxConvertible<CurrencyAmountArray>, ImmutableBean, Serializable {
 
+  /**
+   * The size of this array.
+   */
+  @PropertyDefinition(validate = "notNegative")
+  private final int size;
+
   /**
    * The currency values, keyed by currency.
    */
   @PropertyDefinition(validate = "notNull")
   private final ImmutableSortedMap<Currency, DoubleArray> values;
-  /**
-   * The number of values for each currency.
-   */
-  private final int size;  // derived
 
   //-------------------------------------------------------------------------
   /**
@@ -93,7 +95,7 @@ public final class MultiCurrencyAmountArray
       }
     }
     Map<Currency, DoubleArray> doubleArrayMap = MapStream.of(valueMap).mapValues(v -> DoubleArray.ofUnsafe(v)).toMap();
-    return new MultiCurrencyAmountArray(doubleArrayMap);
+    return new MultiCurrencyAmountArray(size, doubleArrayMap);
   }
 
   /**
@@ -115,21 +117,26 @@ public final class MultiCurrencyAmountArray
         array[i] = ca.getAmount();
       }
     }
-    return new MultiCurrencyAmountArray(MapStream.of(map).mapValues(array -> DoubleArray.ofUnsafe(array)).toMap());
+    return new MultiCurrencyAmountArray(size, MapStream.of(map).mapValues(array -> DoubleArray.ofUnsafe(array)).toMap());
   }
 
   /**
    * Obtains an instance from a map of amounts.
    * <p>
    * Each currency is associated with an array of amounts.
-   * All the arrays must have the same number of elements in each array.
+   * All the arrays must have the same number of elements.
+   * <p>
+   * If the map is empty the returned array will have a size of zero. To create an empty array
+   * with a non-zero size use one of the other {@code of} methods.
    *
    * @param values  map of currencies to values
    * @return an instance containing the values from the map
    */
   public static MultiCurrencyAmountArray of(Map<Currency, DoubleArray> values) {
     values.values().stream().reduce((a1, a2) -> checkSize(a1, a2));
-    return new MultiCurrencyAmountArray(values);
+    // All of the values must have the same size so use the size of the first
+    int size = values.isEmpty() ? 0 : values.values().iterator().next().size();
+    return new MultiCurrencyAmountArray(size, values);
   }
 
   /**
@@ -152,14 +159,9 @@ public final class MultiCurrencyAmountArray
   }
 
   @ImmutableConstructor
-  private MultiCurrencyAmountArray(Map<Currency, DoubleArray> values) {
+  private MultiCurrencyAmountArray(int size, Map<Currency, DoubleArray> values) {
     this.values = ImmutableSortedMap.copyOf(values);
-    if (values.isEmpty()) {
-      size = 0;
-    } else {
-      // All currencies must have the same number of values so we can just take the size of the first
-      size = values.values().iterator().next().size();
-    }
+    this.size = size;
   }
 
   // validate when deserializing
@@ -405,6 +407,15 @@ public final class MultiCurrencyAmountArray
     return metaBean().metaPropertyMap().keySet();
   }
 
+  //-----------------------------------------------------------------------
+  /**
+   * Gets the size of this array.
+   * @return the value of the property
+   */
+  public int getSize() {
+    return size;
+  }
+
   //-----------------------------------------------------------------------
   /**
    * Gets the currency values, keyed by currency.
@@ -422,7 +433,8 @@ public final class MultiCurrencyAmountArray
     }
     if (obj != null && obj.getClass() == this.getClass()) {
       MultiCurrencyAmountArray other = (MultiCurrencyAmountArray) obj;
-      return JodaBeanUtils.equal(values, other.values);
+      return (size == other.size) &&
+          JodaBeanUtils.equal(values, other.values);
     }
     return false;
   }
@@ -430,14 +442,16 @@ public final class MultiCurrencyAmountArray
   @Override
   public int hashCode() {
     int hash = getClass().hashCode();
+    hash = hash * 31 + JodaBeanUtils.hashCode(size);
     hash = hash * 31 + JodaBeanUtils.hashCode(values);
     return hash;
   }
 
   @Override
   public String toString() {
-    StringBuilder buf = new StringBuilder(64);
+    StringBuilder buf = new StringBuilder(96);
     buf.append("MultiCurrencyAmountArray{");
+    buf.append("size").append('=').append(size).append(',').append(' ');
     buf.append("values").append('=').append(JodaBeanUtils.toString(values));
     buf.append('}');
     return buf.toString();
@@ -453,6 +467,11 @@ public final class MultiCurrencyAmountArray
      */
     static final Meta INSTANCE = new Meta();
 
+    /**
+     * The meta-property for the {@code size} property.
+     */
+    private final MetaProperty<Integer> size = DirectMetaProperty.ofImmutable(
+        this, "size", MultiCurrencyAmountArray.class, Integer.TYPE);
     /**
      * The meta-property for the {@code values} property.
      */
@@ -464,6 +483,7 @@ public final class MultiCurrencyAmountArray
      */
     private final Map<String, MetaProperty<?>> metaPropertyMap$ = new DirectMetaPropertyMap(
         this, null,
+        "size",
         "values");
 
     /**
@@ -475,6 +495,8 @@ public final class MultiCurrencyAmountArray
     @Override
     protected MetaProperty<?> metaPropertyGet(String propertyName) {
       switch (propertyName.hashCode()) {
+        case 3530753:  // size
+          return size;
         case -823812830:  // values
           return values;
       }
@@ -497,6 +519,14 @@ public final class MultiCurrencyAmountArray
     }
 
     //-----------------------------------------------------------------------
+    /**
+     * The meta-property for the {@code size} property.
+     * @return the meta-property, not null
+     */
+    public MetaProperty<Integer> size() {
+      return size;
+    }
+
     /**
      * The meta-property for the {@code values} property.
      * @return the meta-property, not null
@@ -509,6 +539,8 @@ public final class MultiCurrencyAmountArray
     @Override
     protected Object propertyGet(Bean bean, String propertyName, boolean quiet) {
       switch (propertyName.hashCode()) {
+        case 3530753:  // size
+          return ((MultiCurrencyAmountArray) bean).getSize();
         case -823812830:  // values
           return ((MultiCurrencyAmountArray) bean).getValues();
       }
@@ -532,6 +564,7 @@ public final class MultiCurrencyAmountArray
    */
   private static final class Builder extends DirectFieldsBeanBuilder<MultiCurrencyAmountArray> {
 
+    private int size;
     private SortedMap<Currency, DoubleArray> values = ImmutableSortedMap.of();
 
     /**
@@ -544,6 +577,8 @@ public final class MultiCurrencyAmountArray
     @Override
     public Object get(String propertyName) {
       switch (propertyName.hashCode()) {
+        case 3530753:  // size
+          return size;
         case -823812830:  // values
           return values;
         default:
@@ -555,6 +590,9 @@ public final class MultiCurrencyAmountArray
     @Override
     public Builder set(String propertyName, Object newValue) {
       switch (propertyName.hashCode()) {
+        case 3530753:  // size
+          this.size = (Integer) newValue;
+          break;
         case -823812830:  // values
           this.values = (SortedMap<Currency, DoubleArray>) newValue;
           break;
@@ -591,14 +629,16 @@ public final class MultiCurrencyAmountArray
     @Override
     public MultiCurrencyAmountArray build() {
       return new MultiCurrencyAmountArray(
+          size,
           values);
     }
 
     //-----------------------------------------------------------------------
     @Override
     public String toString() {
-      StringBuilder buf = new StringBuilder(64);
+      StringBuilder buf = new StringBuilder(96);
       buf.append("MultiCurrencyAmountArray.Builder{");
+      buf.append("size").append('=').append(JodaBeanUtils.toString(size)).append(',').append(' ');
       buf.append("values").append('=').append(JodaBeanUtils.toString(values));
       buf.append('}');
       return buf.toString();
diff --git a/modules/basics/src/test/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArrayTest.java b/modules/basics/src/test/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArrayTest.java
index 790322d1f..f8764f6bc 100644
--- a/modules/basics/src/test/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArrayTest.java
+++ b/modules/basics/src/test/java/com/opengamma/strata/basics/currency/MultiCurrencyAmountArrayTest.java
@@ -75,6 +75,15 @@ public class MultiCurrencyAmountArrayTest {
     assertThrowsIllegalArg(() -> raggedArray.getValues(Currency.AUD));
   }
 
+  public void test_empty_amounts() {
+    MultiCurrencyAmountArray array = MultiCurrencyAmountArray.of(
+        MultiCurrencyAmount.empty(),
+        MultiCurrencyAmount.empty());
+    assertThat(array.size()).isEqualTo(2);
+    assertThat(array.get(0)).isEqualTo(MultiCurrencyAmount.empty());
+    assertThat(array.get(1)).isEqualTo(MultiCurrencyAmount.empty());
+  }
+
   public void test_of_function() {
     MultiCurrencyAmount mca1 = MultiCurrencyAmount.of(CurrencyAmount.of(Currency.GBP, 10), CurrencyAmount.of(Currency.USD, 20));
     MultiCurrencyAmount mca2 = MultiCurrencyAmount.of(CurrencyAmount.of(Currency.GBP, 10), CurrencyAmount.of(Currency.EUR, 30));
@@ -87,14 +96,31 @@ public class MultiCurrencyAmountArrayTest {
     assertThat(test.get(2)).isEqualTo(mca3.plus(Currency.GBP, 0).plus(Currency.EUR, 0));
   }
 
+  public void test_of_function_empty_amounts() {
+    MultiCurrencyAmountArray test = MultiCurrencyAmountArray.of(3, i -> MultiCurrencyAmount.empty());
+    assertThat(test.size()).isEqualTo(3);
+  }
+
   public void test_of_map() {
     MultiCurrencyAmountArray array = MultiCurrencyAmountArray.of(
         ImmutableMap.of(
             Currency.GBP, DoubleArray.of(20, 21, 22),
-            Currency.USD, DoubleArray.of(30, 32, 33),
             Currency.EUR, DoubleArray.of(40, 43, 44)));
 
-    assertThat(array).isEqualTo(VALUES_ARRAY);
+    MultiCurrencyAmountArray expected = MultiCurrencyAmountArray.of(
+        ImmutableList.of(
+            MultiCurrencyAmount.of(
+                CurrencyAmount.of(Currency.GBP, 20),
+                CurrencyAmount.of(Currency.EUR, 40)),
+            MultiCurrencyAmount.of(
+                CurrencyAmount.of(Currency.GBP, 21),
+                CurrencyAmount.of(Currency.EUR, 43)),
+            MultiCurrencyAmount.of(
+                CurrencyAmount.of(Currency.GBP, 22),
+                CurrencyAmount.of(Currency.EUR, 44))));
+
+    assertThat(array.size()).isEqualTo(3);
+    assertThat(array).isEqualTo(expected);
 
     assertThrowsIllegalArg(
         () -> MultiCurrencyAmountArray.of(
@@ -102,6 +128,9 @@ public class MultiCurrencyAmountArrayTest {
                 Currency.GBP, DoubleArray.of(20, 21),
                 Currency.EUR, DoubleArray.of(40, 43, 44))),
         "Arrays must have the same size.*");
+
+    MultiCurrencyAmountArray empty = MultiCurrencyAmountArray.of(ImmutableMap.of());
+    assertThat(empty.size()).isEqualTo(0);
   }
 
   public void test_getValues() {
diff --git a/modules/data/src/test/java/com/opengamma/strata/data/scenario/MultiCurrencyScenarioArrayTest.java b/modules/data/src/test/java/com/opengamma/strata/data/scenario/MultiCurrencyScenarioArrayTest.java
index 79b2e1a5b..b0c108b0a 100644
--- a/modules/data/src/test/java/com/opengamma/strata/data/scenario/MultiCurrencyScenarioArrayTest.java
+++ b/modules/data/src/test/java/com/opengamma/strata/data/scenario/MultiCurrencyScenarioArrayTest.java
@@ -73,6 +73,15 @@ public class MultiCurrencyScenarioArrayTest {
     assertThrowsIllegalArg(() -> raggedArray.getValues(Currency.AUD));
   }
 
+  public void emptyAmounts() {
+    MultiCurrencyScenarioArray array = MultiCurrencyScenarioArray.of(
+        MultiCurrencyAmount.empty(),
+        MultiCurrencyAmount.empty());
+    assertThat(array.getScenarioCount()).isEqualTo(2);
+    assertThat(array.get(0)).isEqualTo(MultiCurrencyAmount.empty());
+    assertThat(array.get(1)).isEqualTo(MultiCurrencyAmount.empty());
+  }
+
   public void createByFunction() {
     MultiCurrencyAmount mca1 = MultiCurrencyAmount.of(CurrencyAmount.of(Currency.GBP, 10), CurrencyAmount.of(Currency.USD, 20));
     MultiCurrencyAmount mca2 = MultiCurrencyAmount.of(CurrencyAmount.of(Currency.GBP, 10), CurrencyAmount.of(Currency.EUR, 30));
@@ -85,6 +94,11 @@ public class MultiCurrencyScenarioArrayTest {
     assertThat(test.get(2)).isEqualTo(mca3.plus(Currency.GBP, 0).plus(Currency.EUR, 0));
   }
 
+  public void createByFunctionEmptyAmounts() {
+    MultiCurrencyScenarioArray test = MultiCurrencyScenarioArray.of(3, i -> MultiCurrencyAmount.empty());
+    assertThat(test.getScenarioCount()).isEqualTo(3);
+  }
+
   public void mapFactoryMethod() {
     MultiCurrencyScenarioArray array = MultiCurrencyScenarioArray.of(
         ImmutableMap.of(