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          switch ((MoneyWiseAccountInfoClass) pClass) {
68              /* Allowed set */
69              case NOTES:
70                  return MetisFieldRequired.CANEXIST;
71  
72              /* Symbol */
73              case SYMBOL:
74                  return myType.needsSymbol()
75                          ? MetisFieldRequired.MUSTEXIST
76                          : MetisFieldRequired.NOTALLOWED;
77  
78              /* Region */
79              case REGION:
80                  return myType.needsRegion()
81                          ? MetisFieldRequired.MUSTEXIST
82                          : MetisFieldRequired.NOTALLOWED;
83  
84              /* Options */
85              case UNDERLYINGSTOCK:
86              case OPTIONPRICE:
87                  return myType.isOption()
88                          ? MetisFieldRequired.MUSTEXIST
89                          : MetisFieldRequired.NOTALLOWED;
90  
91              /* Not Allowed */
92              case SORTCODE:
93              case ACCOUNT:
94              case REFERENCE:
95              case WEBSITE:
96              case CUSTOMERNO:
97              case USERID:
98              case PASSWORD:
99              case MATURITY:
100             case OPENINGBALANCE:
101             case AUTOEXPENSE:
102             case AUTOPAYEE:
103             default:
104                 return MetisFieldRequired.NOTALLOWED;
105         }
106     }
107 
108     @Override
109     public void validateClass(final MoneyWiseSecurityInfo pInfo,
110                               final PrometheusDataInfoClass pClass) {
111         /* Switch on class */
112         switch ((MoneyWiseAccountInfoClass) pClass) {
113             case NOTES:
114                 validateNotes(pInfo);
115                 break;
116             case SYMBOL:
117                 validateSymbol(pInfo);
118                 break;
119             case UNDERLYINGSTOCK:
120                 validateUnderlyingStock(pInfo);
121                 break;
122             case OPTIONPRICE:
123                 validateOptionPrice(pInfo);
124                 break;
125             default:
126                 break;
127         }
128     }
129 
130     /**
131      * Validate the Notes info.
132      *
133      * @param pInfo the info
134      */
135     private void validateNotes(final MoneyWiseSecurityInfo pInfo) {
136         final char[] myArray = pInfo.getValue(char[].class);
137         if (myArray.length > MoneyWiseAccountInfoClass.NOTES.getMaximumLength()) {
138             getOwner().addError(PrometheusDataItem.ERROR_LENGTH, MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.NOTES));
139         }
140     }
141 
142     /**
143      * Validate the Symbol info.
144      *
145      * @param pInfo the info
146      */
147     private void validateSymbol(final MoneyWiseSecurityInfo pInfo) {
148         final String mySymbol = pInfo.getValue(String.class);
149         if (mySymbol.length() > MoneyWiseAccountInfoClass.SYMBOL.getMaximumLength()) {
150             getOwner().addError(PrometheusDataItem.ERROR_LENGTH, MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.SYMBOL));
151         }
152     }
153 
154     /**
155      * Validate the UnderlyingStock info.
156      *
157      * @param pInfo the info
158      */
159     private void validateUnderlyingStock(final MoneyWiseSecurityInfo pInfo) {
160         final MoneyWiseSecurity myStock = pInfo.getValue(MoneyWiseSecurity.class);
161         if (!myStock.getCategoryClass().isShares()) {
162             getOwner().addError("Invalid underlying stock", MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.UNDERLYINGSTOCK));
163         }
164         if (!myStock.getCurrency().equals(getOwner().getCurrency())) {
165             getOwner().addError(MoneyWiseDepositInfoSet.ERROR_CURRENCY, MoneyWiseLoanInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.UNDERLYINGSTOCK));
166         }
167     }
168 
169     /**
170      * Validate the OptionPrice info.
171      *
172      * @param pInfo the info
173      */
174     private void validateOptionPrice(final MoneyWiseSecurityInfo pInfo) {
175         final OceanusPrice myPrice = pInfo.getValue(OceanusPrice.class);
176         if (myPrice.isZero()) {
177             getOwner().addError(PrometheusDataItem.ERROR_ZERO, MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.OPTIONPRICE));
178         } else if (!myPrice.isPositive()) {
179             getOwner().addError(PrometheusDataItem.ERROR_NEGATIVE, MoneyWiseSecurityInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.OPTIONPRICE));
180         }
181         if (!myPrice.getCurrency().equals(getOwner().getCurrency())) {
182             getOwner().addError(MoneyWiseDepositInfoSet.ERROR_CURRENCY, MoneyWiseLoanInfoSet.getFieldForClass(MoneyWiseAccountInfoClass.OPTIONPRICE));
183         }
184     }
185 
186     @Override
187     protected void setDefault(final PrometheusDataInfoClass pClass) throws OceanusException {
188         /* Switch on the class */
189         switch ((MoneyWiseAccountInfoClass) pClass) {
190             case SYMBOL:
191                 getInfoSet().setValue(pClass, getUniqueSymbol());
192                 break;
193             case REGION:
194                 getInfoSet().setValue(pClass, getDefaultRegion());
195                 break;
196             case UNDERLYINGSTOCK:
197                 getInfoSet().setValue(pClass, getDefaultUnderlyingStock());
198                 break;
199             case OPTIONPRICE:
200                 getInfoSet().setValue(pClass, getDefaultOptionPrice());
201                 break;
202             default:
203                 break;
204         }
205     }
206 
207     /**
208      * Obtain unique symbol for new tag.
209      *
210      * @return The new symbol
211      */
212     private String getUniqueSymbol() {
213         /* Access the security list */
214         final MoneyWiseSecurityList mySecurities = getOwner().getList();
215 
216         /* Set up base constraints */
217         final String myBase = NAME_NEWSYMBOL;
218         int iNextId = 1;
219 
220         /* Loop until we found a symbol */
221         String mySymbol = myBase;
222         while (true) {
223             /* try out the symbol */
224             if (mySecurities.findItemBySymbol(mySymbol) == null) {
225                 return mySymbol;
226             }
227 
228             /* Build next symbol */
229             mySymbol = myBase.concat(Integer.toString(iNextId++));
230         }
231     }
232 
233     /**
234      * Obtain default region for security.
235      *
236      * @return the default region
237      */
238     private MoneyWiseRegion getDefaultRegion() {
239         /* Access the region list */
240         final MoneyWiseRegionList myRegions
241                 = getEditSet().getDataList(MoneyWiseBasicDataType.REGION, MoneyWiseRegionList.class);
242 
243         /* loop through the regions */
244         final Iterator<MoneyWiseRegion> myIterator = myRegions.iterator();
245         while (myIterator.hasNext()) {
246             final MoneyWiseRegion myRegion = myIterator.next();
247 
248             /* Return first non-deleted region */
249             if (!myRegion.isDeleted()) {
250                 return myRegion;
251             }
252         }
253 
254         /* Return no region */
255         return null;
256     }
257 
258     /**
259      * Obtain default underlying stock.
260      *
261      * @return the default underlying stock
262      */
263     private MoneyWiseSecurity getDefaultUnderlyingStock() {
264         /* Access the security list */
265         final MoneyWiseSecurityList mySecurities = getOwner().getList();
266 
267         /* loop through the securities */
268         final Iterator<MoneyWiseSecurity> myIterator = mySecurities.iterator();
269         while (myIterator.hasNext()) {
270             final MoneyWiseSecurity mySecurity = myIterator.next();
271 
272             /* Ignore deleted securities */
273             if (mySecurity.isDeleted()) {
274                 continue;
275             }
276 
277             /* Ignore securities that are the wrong class */
278             if (mySecurity.getCategoryClass().isShares()) {
279                 return mySecurity;
280             }
281         }
282 
283         /* Return no security */
284         return null;
285     }
286 
287     /**
288      * Obtain default option price.
289      *
290      * @return the default underlying stock
291      */
292     private OceanusPrice getDefaultOptionPrice() {
293         /* Obtain the underlying stock */
294         final MoneyWiseSecurity myUnderlying = getOwner().getUnderlyingStock();
295 
296         /* If there is no underlying stock, then there is no price */
297         if (myUnderlying == null) {
298             return null;
299         }
300 
301         final MoneyWiseCurrency myCurrency = myUnderlying.getAssetCurrency();
302         return OceanusPrice.getWholeUnits(1, myCurrency.getCurrency());
303     }
304 
305     @Override
306     protected void autoCorrect(final PrometheusDataInfoClass pClass) throws OceanusException {
307         /* Switch on class */
308         switch ((MoneyWiseAccountInfoClass) pClass) {
309             case UNDERLYINGSTOCK:
310                 autoCorrectUnderlyingStock();
311                 break;
312             case OPTIONPRICE:
313                 autoCorrectOptionPrice();
314                 break;
315             default:
316                 break;
317         }
318     }
319 
320     /**
321      * AutoCorrect underlying stock.
322      *
323      * @throws OceanusException on error
324      */
325     private void autoCorrectUnderlyingStock() throws OceanusException {
326         /* Obtain the existing value */
327         MoneyWiseSecurity myStock = getOwner().getUnderlyingStock();
328 
329         /* If the stock is not the correct currency */
330         if (!myStock.getCurrency().equals(getOwner().getCurrency())) {
331             /* Reset to new default */
332             myStock = getDefaultUnderlyingStock();
333             getInfoSet().setValue(MoneyWiseAccountInfoClass.UNDERLYINGSTOCK, myStock);
334         }
335     }
336 
337     /**
338      * AutoCorrect option price.
339      *
340      * @throws OceanusException on error
341      */
342     private void autoCorrectOptionPrice() throws OceanusException {
343         /* Obtain the existing value */
344         OceanusPrice myPrice = getOwner().getOptionPrice();
345         final MoneyWiseCurrency myAssetCurrency = getOwner().getAssetCurrency();
346         final Currency myCurrency = myAssetCurrency.getCurrency();
347 
348         /* If the price is not the correct currency */
349         if (!myPrice.getCurrency().equals(myCurrency)) {
350             myPrice = myPrice.changeCurrency(myCurrency);
351             getInfoSet().setValue(MoneyWiseAccountInfoClass.OPTIONPRICE, myPrice);
352         }
353     }
354 }