View Javadoc
1   /*
2    * MoneyWise: Finance Application
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.moneywise.data.validate;
18  
19  import io.github.tonywasher.joceanus.metis.field.MetisFieldRequired;
20  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseBasicDataType;
21  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseDepositInfoSet;
22  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseLoanInfoSet;
23  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseRegion;
24  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseRegion.MoneyWiseRegionList;
25  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity;
26  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurity.MoneyWiseSecurityList;
27  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityInfo;
28  import io.github.tonywasher.joceanus.moneywise.data.basic.MoneyWiseSecurityInfoSet;
29  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseAccountInfoClass;
30  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseCurrency;
31  import io.github.tonywasher.joceanus.moneywise.data.statics.MoneyWiseSecurityClass;
32  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
33  import io.github.tonywasher.joceanus.oceanus.decimal.OceanusPrice;
34  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataInfoClass;
35  import io.github.tonywasher.joceanus.prometheus.data.PrometheusDataItem;
36  import io.github.tonywasher.joceanus.prometheus.validate.PrometheusValidateInfoSet;
37  
38  import java.util.Currency;
39  import java.util.Iterator;
40  
41  /**
42   * Validate SecurityInfoSet.
43   */
44  public class MoneyWiseValidateSecurityInfoSet
45          extends PrometheusValidateInfoSet<MoneyWiseSecurityInfo> {
46      /**
47       * New Symbol name.
48       */
49      private static final String NAME_NEWSYMBOL = "SYMBOL";
50  
51      @Override
52      public MoneyWiseSecurity getOwner() {
53          return (MoneyWiseSecurity) super.getOwner();
54      }
55  
56      @Override
57      public MetisFieldRequired isClassRequired(final PrometheusDataInfoClass pClass) {
58          /* Access details about the Security */
59          final MoneyWiseSecurity mySec = getOwner();
60          final MoneyWiseSecurityClass myType = mySec.getCategoryClass();
61  
62          /* If we have no Type, no class is allowed */
63          if (myType == null) {
64              return MetisFieldRequired.NOTALLOWED;
65          }
66          /* Switch on class */
67          return switch ((MoneyWiseAccountInfoClass) pClass) {
68              /* Allowed set */
69              case NOTES -> MetisFieldRequired.CANEXIST;
70  
71              /* Symbol */
72              case SYMBOL -> myType.needsSymbol()
73                      ? MetisFieldRequired.MUSTEXIST
74                      : MetisFieldRequired.NOTALLOWED;
75  
76              /* Region */
77              case REGION -> myType.needsRegion()
78                      ? MetisFieldRequired.MUSTEXIST
79                      : MetisFieldRequired.NOTALLOWED;
80  
81              /* Options */
82              case UNDERLYINGSTOCK, OPTIONPRICE -> myType.isOption()
83                      ? MetisFieldRequired.MUSTEXIST
84                      : MetisFieldRequired.NOTALLOWED;
85  
86              /* Not Allowed */
87              default -> MetisFieldRequired.NOTALLOWED;
88          };
89      }
90  
91      @Override
92      public void validateClass(final MoneyWiseSecurityInfo pInfo,
93                                final PrometheusDataInfoClass pClass) {
94          /* Switch on class */
95          switch ((MoneyWiseAccountInfoClass) pClass) {
96              case NOTES:
97                  validateNotes(pInfo);
98                  break;
99              case SYMBOL:
100                 validateSymbol(pInfo);
101                 break;
102             case UNDERLYINGSTOCK:
103                 validateUnderlyingStock(pInfo);
104                 break;
105             case OPTIONPRICE:
106                 validateOptionPrice(pInfo);
107                 break;
108             default:
109                 break;
110         }
111     }
112 
113     /**
114      * Validate the Notes info.
115      *
116      * @param pInfo the info
117      */
118     private void validateNotes(final MoneyWiseSecurityInfo pInfo) {
119         final char[] myArray = pInfo.getValue(char[].class);
120         if (myArray.length > MoneyWiseAccountInfoClass.NOTES.getMaximumLength()) {
121             getOwner().addError(PrometheusDataItem.ERROR_LENGTH, MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.NOTES));
122         }
123     }
124 
125     /**
126      * Validate the Symbol info.
127      *
128      * @param pInfo the info
129      */
130     private void validateSymbol(final MoneyWiseSecurityInfo pInfo) {
131         final String mySymbol = pInfo.getValue(String.class);
132         if (mySymbol.length() > MoneyWiseAccountInfoClass.SYMBOL.getMaximumLength()) {
133             getOwner().addError(PrometheusDataItem.ERROR_LENGTH, MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.SYMBOL));
134         }
135     }
136 
137     /**
138      * Validate the UnderlyingStock info.
139      *
140      * @param pInfo the info
141      */
142     private void validateUnderlyingStock(final MoneyWiseSecurityInfo pInfo) {
143         final MoneyWiseSecurity myStock = pInfo.getValue(MoneyWiseSecurity.class);
144         if (!myStock.getCategoryClass().isShares()) {
145             getOwner().addError("Invalid underlying stock", MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.UNDERLYINGSTOCK));
146         }
147         if (!myStock.getCurrency().equals(getOwner().getCurrency())) {
148             getOwner().addError(MoneyWiseDepositInfoSet.ERROR_CURRENCY, MoneyWiseLoanInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.UNDERLYINGSTOCK));
149         }
150     }
151 
152     /**
153      * Validate the OptionPrice info.
154      *
155      * @param pInfo the info
156      */
157     private void validateOptionPrice(final MoneyWiseSecurityInfo pInfo) {
158         final OceanusPrice myPrice = pInfo.getValue(OceanusPrice.class);
159         if (myPrice.isZero()) {
160             getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.OPTIONPRICE));
161         } else if (!myPrice.isPositive()) {
162             getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.OPTIONPRICE));
163         }
164         if (!myPrice.getCurrency().equals(getOwner().getCurrency())) {
165             getOwner().addError(MoneyWiseDepositInfoSet.ERROR_CURRENCY, MoneyWiseLoanInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.OPTIONPRICE));
166         }
167     }
168 
169     @Override
170     protected void setDefault(final PrometheusDataInfoClass pClass) throws OceanusException {
171         /* Switch on the class */
172         switch ((MoneyWiseAccountInfoClass) pClass) {
173             case SYMBOL:
174                 getInfoSet().setValue(pClass, getUniqueSymbol());
175                 break;
176             case REGION:
177                 getInfoSet().setValue(pClass, getDefaultRegion());
178                 break;
179             case UNDERLYINGSTOCK:
180                 getInfoSet().setValue(pClass, getDefaultUnderlyingStock());
181                 break;
182             case OPTIONPRICE:
183                 getInfoSet().setValue(pClass, getDefaultOptionPrice());
184                 break;
185             default:
186                 break;
187         }
188     }
189 
190     /**
191      * Obtain unique symbol for new tag.
192      *
193      * @return The new symbol
194      */
195     private String getUniqueSymbol() {
196         /* Access the security list */
197         final MoneyWiseSecurityList mySecurities = getOwner().getList();
198 
199         /* Set up base constraints */
200         final String myBase = NAME_NEWSYMBOL;
201         int iNextId = 1;
202 
203         /* Loop until we found a symbol */
204         String mySymbol = myBase;
205         while (true) {
206             /* try out the symbol */
207             if (mySecurities.findItemBySymbol(mySymbol) == null) {
208                 return mySymbol;
209             }
210 
211             /* Build next symbol */
212             mySymbol = myBase.concat(Integer.toString(iNextId++));
213         }
214     }
215 
216     /**
217      * Obtain default region for security.
218      *
219      * @return the default region
220      */
221     private MoneyWiseRegion getDefaultRegion() {
222         /* Access the region list */
223         final MoneyWiseRegionList myRegions
224                 = getEditSet().getDataList(MoneyWiseBasicDataType.REGION, MoneyWiseRegionList.class);
225 
226         /* loop through the regions */
227         final Iterator<MoneyWiseRegion> myIterator = myRegions.iterator();
228         while (myIterator.hasNext()) {
229             final MoneyWiseRegion myRegion = myIterator.next();
230 
231             /* Return first non-deleted region */
232             if (!myRegion.isDeleted()) {
233                 return myRegion;
234             }
235         }
236 
237         /* Return no region */
238         return null;
239     }
240 
241     /**
242      * Obtain default underlying stock.
243      *
244      * @return the default underlying stock
245      */
246     private MoneyWiseSecurity getDefaultUnderlyingStock() {
247         /* Access the security list */
248         final MoneyWiseSecurityList mySecurities = getOwner().getList();
249 
250         /* loop through the securities */
251         final Iterator<MoneyWiseSecurity> myIterator = mySecurities.iterator();
252         while (myIterator.hasNext()) {
253             final MoneyWiseSecurity mySecurity = myIterator.next();
254 
255             /* Ignore deleted securities */
256             if (mySecurity.isDeleted()) {
257                 continue;
258             }
259 
260             /* Ignore securities that are the wrong class */
261             if (mySecurity.getCategoryClass().isShares()) {
262                 return mySecurity;
263             }
264         }
265 
266         /* Return no security */
267         return null;
268     }
269 
270     /**
271      * Obtain default option price.
272      *
273      * @return the default underlying stock
274      */
275     private OceanusPrice getDefaultOptionPrice() {
276         /* Obtain the underlying stock */
277         final MoneyWiseSecurity myUnderlying = getOwner().getUnderlyingStock();
278 
279         /* If there is no underlying stock, then there is no price */
280         if (myUnderlying == null) {
281             return null;
282         }
283 
284         final MoneyWiseCurrency myCurrency = myUnderlying.getAssetCurrency();
285         return OceanusPrice.getWholeUnits(1, myCurrency.getCurrency());
286     }
287 
288     @Override
289     protected void autoCorrect(final PrometheusDataInfoClass pClass) throws OceanusException {
290         /* Switch on class */
291         switch ((MoneyWiseAccountInfoClass) pClass) {
292             case UNDERLYINGSTOCK:
293                 autoCorrectUnderlyingStock();
294                 break;
295             case OPTIONPRICE:
296                 autoCorrectOptionPrice();
297                 break;
298             default:
299                 break;
300         }
301     }
302 
303     /**
304      * AutoCorrect underlying stock.
305      *
306      * @throws OceanusException on error
307      */
308     private void autoCorrectUnderlyingStock() throws OceanusException {
309         /* Obtain the existing value */
310         MoneyWiseSecurity myStock = getOwner().getUnderlyingStock();
311 
312         /* If the stock is not the correct currency */
313         if (!myStock.getCurrency().equals(getOwner().getCurrency())) {
314             /* Reset to new default */
315             myStock = getDefaultUnderlyingStock();
316             getInfoSet().setValue(MoneyWiseAccountInfoClass.UNDERLYINGSTOCK, myStock);
317         }
318     }
319 
320     /**
321      * AutoCorrect option price.
322      *
323      * @throws OceanusException on error
324      */
325     private void autoCorrectOptionPrice() throws OceanusException {
326         /* Obtain the existing value */
327         OceanusPrice myPrice = getOwner().getOptionPrice();
328         final MoneyWiseCurrency myAssetCurrency = getOwner().getAssetCurrency();
329         final Currency myCurrency = myAssetCurrency.getCurrency();
330 
331         /* If the price is not the correct currency */
332         if (!myPrice.getCurrency().equals(myCurrency)) {
333             myPrice = myPrice.changeCurrency(myCurrency);
334             getInfoSet().setValue(MoneyWiseAccountInfoClass.OPTIONPRICE, myPrice);
335         }
336     }
337 }