package name.abuchen.portfolio.datatransfer; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import name.abuchen.portfolio.Messages; import name.abuchen.portfolio.datatransfer.Extractor.Item; import name.abuchen.portfolio.datatransfer.Extractor.SecurityItem; import name.abuchen.portfolio.model.Client; import name.abuchen.portfolio.model.Security; public class SecurityCache { private static final Security DUPLICATE_SECURITY_MARKER = new Security(); private static final List<String> MESSAGES = Arrays.asList(Messages.MsgErrorDuplicateISIN, Messages.MsgErrorDuplicateTicker, Messages.MsgErrorDuplicateWKN, Messages.MsgErrorDuplicateName); private final Client client; private final List<Map<String, Security>> localMaps = new ArrayList<>(); public SecurityCache(Client client) { this.client = client; this.localMaps.add(client.getSecurities().stream().filter(s -> s.getIsin() != null && !s.getIsin().isEmpty()) .collect(Collectors.toMap(Security::getIsin, s -> s, (l, r) -> DUPLICATE_SECURITY_MARKER))); this.localMaps.add(client.getSecurities().stream() .filter(s -> s.getTickerSymbol() != null && !s.getTickerSymbol().isEmpty()) // .collect(Collectors.toMap(Security::getTickerSymbol, s -> s, (l, r) -> DUPLICATE_SECURITY_MARKER))); this.localMaps.add(client.getSecurities().stream().filter(s -> s.getWkn() != null && !s.getWkn().isEmpty()) .collect(Collectors.toMap(Security::getWkn, s -> s, (l, r) -> DUPLICATE_SECURITY_MARKER))); this.localMaps.add(client.getSecurities().stream().filter(s -> s.getName() != null && !s.getName().isEmpty()) .collect(Collectors.toMap(Security::getName, s -> s, (l, r) -> DUPLICATE_SECURITY_MARKER))); } public Security lookup(String isin, String tickerSymbol, String wkn, String name, Supplier<Security> creationFunction) { List<String> attributes = Arrays.asList(isin, tickerSymbol, wkn, name); // first: check the identifying attributes (ISIN, Ticker, WKN) for (int ii = 0; ii < 3; ii++) { String attribute = attributes.get(ii); Security security = localMaps.get(ii).get(attribute); if (security == DUPLICATE_SECURITY_MARKER) throw new IllegalArgumentException(MessageFormat.format(MESSAGES.get(ii), attribute)); if (security != null) return security; } // second: check the name. But: even if the name matches, we also must // check that the identifying attributes do not differ. Why? Investment // instruments could have the same name but different ISINs. Security security = lookupSecurityByName(isin, tickerSymbol, wkn, name); if (security != null) return security; security = creationFunction.get(); security.setIsin(isin); security.setWkn(wkn); security.setTickerSymbol(tickerSymbol); security.setName(name); for (int ii = 0; ii < localMaps.size(); ii++) { String attribute = attributes.get(ii); if (attribute != null) localMaps.get(ii).put(attribute, security); } return security; } private Security lookupSecurityByName(String isin, String tickerSymbol, String wkn, String name) { Security security = localMaps.get(3).get(name); // allow imports by duplicate name if (security == null || security == DUPLICATE_SECURITY_MARKER) return null; if (doNotMatchIfGiven(isin, security.getIsin())) return null; if (doNotMatchIfGiven(tickerSymbol, security.getTickerSymbol())) return null; if (doNotMatchIfGiven(wkn, security.getWkn())) return null; return security; } private boolean doNotMatchIfGiven(String attribute1, String attribute2) { return attribute1 != null && attribute2 != null && !attribute1.equalsIgnoreCase(attribute2); } /** * Returns a list of {@link SecurityItem} that are implicitly created when * extracting transactions. Do not add all newly created securities as they * might be created out of erroneous transactions. */ public Collection<Item> createMissingSecurityItems(List<Item> items) { List<Item> answer = new ArrayList<>(); Set<Security> available = new HashSet<>(); available.addAll(client.getSecurities()); items.stream().filter(i -> i instanceof SecurityItem).map(Item::getSecurity).forEach(available::add); for (Item item : items) { if (item instanceof SecurityItem || item.getSecurity() == null) continue; if (!available.contains(item.getSecurity())) { answer.add(new SecurityItem(item.getSecurity())); available.add(item.getSecurity()); } } return answer; } }