gt:         for (Security security : client.getSecurities())
context: package name.abuchen.portfolio.model;
  
  import java.io.BufferedInputStream;
  import java.io.BufferedOutputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.io.OutputStreamWriter;
  import java.io.Reader;
  import java.io.Writer;
  import java.lang.reflect.Field;
  import java.nio.ByteBuffer;
  import java.nio.charset.StandardCharsets;
  import java.security.AlgorithmParameters;
  import java.security.GeneralSecurityException;
  import java.security.NoSuchAlgorithmException;
  import java.security.spec.InvalidKeySpecException;
  import java.security.spec.KeySpec;
  import java.text.MessageFormat;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.function.Function;
  import java.util.stream.Collectors;
  import java.util.zip.Deflater;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipInputStream;
  import java.util.zip.ZipOutputStream;
  
  import javax.crypto.BadPaddingException;
  import javax.crypto.Cipher;
  import javax.crypto.CipherInputStream;
  import javax.crypto.CipherOutputStream;
  import javax.crypto.SecretKey;
  import javax.crypto.SecretKeyFactory;
  import javax.crypto.spec.IvParameterSpec;
  import javax.crypto.spec.PBEKeySpec;
  import javax.crypto.spec.SecretKeySpec;
  
  import org.eclipse.core.runtime.IProgressMonitor;
  
  import com.thoughtworks.xstream.XStream;
  import com.thoughtworks.xstream.XStreamException;
  import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
  import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
  import com.thoughtworks.xstream.mapper.Mapper;
  
  import name.abuchen.portfolio.Messages;
  import name.abuchen.portfolio.model.AttributeType.ImageConverter;
  import name.abuchen.portfolio.model.Classification.Assignment;
  import name.abuchen.portfolio.model.PortfolioTransaction.Type;
  import name.abuchen.portfolio.money.CurrencyUnit;
  import name.abuchen.portfolio.money.Money;
  import name.abuchen.portfolio.money.Values;
  import name.abuchen.portfolio.online.impl.YahooFinanceQuoteFeed;
  import name.abuchen.portfolio.util.ProgressMonitorInputStream;
  import name.abuchen.portfolio.util.XStreamLocalDateConverter;
  import name.abuchen.portfolio.util.XStreamLocalDateTimeConverter;
  import name.abuchen.portfolio.util.XStreamSecurityPriceConverter;
  
  @SuppressWarnings("deprecation")
  public class ClientFactory
  {
      private static class PortfolioTransactionConverter extends ReflectionConverter
      {
          public PortfolioTransactionConverter(Mapper mapper, ReflectionProvider reflectionProvider)
          {
              super(mapper, reflectionProvider);
          }
  
          @Override
          public boolean canConvert(@SuppressWarnings("rawtypes") Class type)
          {
              return type == PortfolioTransaction.class;
          }
  
          @Override
          protected boolean shouldUnmarshalField(Field field)
          {
              if ("fees".equals(field.getName()) || "taxes".equals(field.getName())) //$NON-NLS-1$ //$NON-NLS-2$
                  return true;
              return super.shouldUnmarshalField(field);
          }
  
      }
  
      private static class XmlSerialization
      {
          public Client load(Reader input) throws IOException
          {
              try
              {
                  Client client = (Client) xstream().fromXML(input);
  
                  if (client.getVersion() > Client.CURRENT_VERSION)
                      throw new IOException(MessageFormat.format(Messages.MsgUnsupportedVersionClientFiled,
                                      client.getVersion()));
  
                  upgradeModel(client);
  
                  return client;
              }
              catch (XStreamException e)
              {
                  throw new IOException(MessageFormat.format(Messages.MsgXMLFormatInvalid, e.getMessage()), e);
              }
          }
  
          void save(Client client, OutputStream output) throws IOException
          {
              Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8);
  
              xstream().toXML(client, writer);
  
              writer.flush();
          }
      }
  
      private interface ClientPersister
      {
          Client load(InputStream input) throws IOException;
  
          void save(Client client, OutputStream output) throws IOException;
      }
  
      private static class PlainWriter implements ClientPersister
      {
          @Override
          public Client load(InputStream input) throws IOException
          {
              return new XmlSerialization().load(new InputStreamReader(input, StandardCharsets.UTF_8));
          }
  
          @Override
          public void save(Client client, OutputStream output) throws IOException
          {
              try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8))
              {
                  xstream().toXML(client, writer);
                  writer.flush();
              }
          }
      }
  
      private static class PlainWriterZIP implements ClientPersister
      {
          @Override
          public Client load(InputStream input) throws IOException
          {
              // wrap with zip input stream
              try (ZipInputStream zipin = new ZipInputStream(input))
              {
                  ZipEntry entry = zipin.getNextEntry();
  
                  if (!ZIP_DATA_FILE.equals(entry.getName()))
                      throw new IOException(MessageFormat.format(Messages.MsgErrorUnexpectedZipEntry, ZIP_DATA_FILE,
                                      entry.getName()));
  
                  return new XmlSerialization().load(new InputStreamReader(zipin, StandardCharsets.UTF_8));
              }
          }
  
          @Override
          public void save(Client client, OutputStream output) throws IOException
          {
              // wrap with zip output stream
              try (ZipOutputStream zipout = new ZipOutputStream(output))
              {
                  zipout.setLevel(Deflater.BEST_COMPRESSION);
  
                  zipout.putNextEntry(new ZipEntry(ZIP_DATA_FILE));
                  new XmlSerialization().save(client, zipout);
                  zipout.closeEntry();
              }
          }
      }
  
      private static class Decryptor implements ClientPersister
      {
          private static final byte[] SIGNATURE = new byte[] { 'P', 'O', 'R', 'T', 'F', 'O', 'L', 'I', 'O' };
  
          private static final byte[] SALT = new byte[] { 112, 67, 103, 107, -92, -125, -112, -95, //
                          -97, -114, 117, -56, -53, -69, -25, -28 };
  
          private static final String AES = "AES"; //$NON-NLS-1$
          private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; //$NON-NLS-1$
          private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA1"; //$NON-NLS-1$
          private static final int ITERATION_COUNT = 65536;
          private static final int IV_LENGTH = 16;
  
          private static final int AES128_KEYLENGTH = 128;
          private static final int AES256_KEYLENGTH = 256;
  
          private char[] password;
          private int keyLength;
  
          public Decryptor(String method, char[] password)
          {
              this.password = password;
              this.keyLength = "AES256".equals(method) ? AES256_KEYLENGTH : AES128_KEYLENGTH; //$NON-NLS-1$
          }
  
          @Override
          public Client load(final InputStream input) throws IOException
          {
              try
              {
                  // check signature
                  byte[] signature = new byte[SIGNATURE.length];
                  int read = input.read(signature);
                  if (read != SIGNATURE.length)
                      throw new IOException();
                  if (!Arrays.equals(signature, SIGNATURE))
                      throw new IOException(Messages.MsgNotAPortflioFile);
  
                  // read encryption method
                  int method = input.read();
                  this.keyLength = method == 1 ? AES256_KEYLENGTH : AES128_KEYLENGTH;
  
                  // check if key length is supported
                  if (!isKeyLengthSupported(this.keyLength))
                      throw new IOException(Messages.MsgKeyLengthNotSupported);
  
                  // build secret key
                  SecretKey secret = buildSecretKey();
  
                  // read initialization vector
                  byte[] iv = new byte[IV_LENGTH];
                  read = input.read(iv);
                  if (read != IV_LENGTH)
                      throw new IOException();
  
                  Client client;
                  // build cipher and stream
                  Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
                  cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
                  try (InputStream decrypted = new CipherInputStream(input, cipher))
                  {
                      // read version information
                      byte[] bytes = new byte[4];
                      read = decrypted.read(bytes); // major version number
                      if (read != bytes.length)
                          throw new IOException();
  
                      int majorVersion = ByteBuffer.wrap(bytes).getInt();
                      read = decrypted.read(bytes); // version number
                      if (read != bytes.length)
                          throw new IOException();
  
                      int version = ByteBuffer.wrap(bytes).getInt();
  
                      // sanity check if the file was properly decrypted
                      if (majorVersion < 1 || majorVersion > 10 || version < 1 || version > 100)
                          throw new IOException(Messages.MsgIncorrectPassword);
                      if (majorVersion > Client.MAJOR_VERSION || version > Client.CURRENT_VERSION)
                          throw new IOException(MessageFormat.format(Messages.MsgUnsupportedVersionClientFiled, version));
  
                      // wrap with zip input stream
                      try (ZipInputStream zipin = new ZipInputStream(decrypted))
                      {
                          zipin.getNextEntry();
  
                          client = new XmlSerialization().load(new InputStreamReader(zipin, StandardCharsets.UTF_8));
  
                          try // NOSONAR
                          {
                              // explicitly close the stream to force bad padding
                              // exceptions to occur inside this try-catch-block
                              decrypted.close(); // NOSONAR
                          }
                          catch (IOException ex)
                          {
                              // starting with a later jdk 1.8.0 (for example
                              // 1.8.0_25), a javax.crypto.BadPaddingException
                              // "Given final block not properly padded" is thrown
                              // if we do not read the stream - so ignore that
                              // kind of exception
                              if (!(ex.getCause() instanceof BadPaddingException))
                                  throw ex;
                          }
                      }
                  }
  
                  // save secret key for next save
                  client.setSecret(secret);
  
                  return client;
              }
              catch (GeneralSecurityException e)
              {
                  throw new IOException(MessageFormat.format(Messages.MsgErrorDecrypting, e.getMessage()), e);
              }
          }
  
          @Override
          public void save(Client client, final OutputStream output) throws IOException
          {
              try
              {
                  // check if key length is supported
                  if (!isKeyLengthSupported(this.keyLength))
                      throw new IOException(Messages.MsgKeyLengthNotSupported);
  
                  // get or build secret key
                  // if password is given, it is used (when the user chooses
                  // "save as" from the menu)
                  SecretKey secret = password != null ? buildSecretKey() : client.getSecret();
                  if (secret == null)
                      throw new IOException(Messages.MsgPasswordMissing);
  
                  // save secret key for next save
                  client.setSecret(secret);
  
                  // write signature
                  output.write(SIGNATURE);
  
                  // write method
                  output.write(secret.getEncoded().length * 8 == AES256_KEYLENGTH ? 1 : 0);
  
                  // build cipher and stream
                  Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
                  cipher.init(Cipher.ENCRYPT_MODE, secret);
  
                  // write initialization vector
                  AlgorithmParameters params = cipher.getParameters();
                  byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
                  output.write(iv);
  
                  // encrypted stream
                  try (OutputStream encrypted = new CipherOutputStream(output, cipher))
                  {
                      // write version information
                      encrypted.write(ByteBuffer.allocate(4).putInt(Client.MAJOR_VERSION).array());
                      encrypted.write(ByteBuffer.allocate(4).putInt(client.getVersion()).array());
  
                      // wrap with zip output stream
                      try (ZipOutputStream zipout = new ZipOutputStream(encrypted))
                      {
                          zipout.setLevel(Deflater.BEST_COMPRESSION);
                          zipout.putNextEntry(new ZipEntry(ZIP_DATA_FILE));
  
                          new XmlSerialization().save(client, zipout);
                          zipout.closeEntry();
                      }
                  }
              }
              catch (GeneralSecurityException e)
              {
                  throw new IOException(MessageFormat.format(Messages.MsgErrorEncrypting, e.getMessage()), e);
              }
          }
  
          private SecretKey buildSecretKey() throws NoSuchAlgorithmException, InvalidKeySpecException
          {
              SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_ALGORITHM);
              KeySpec spec = new PBEKeySpec(password, SALT, ITERATION_COUNT, keyLength);
              SecretKey tmp = factory.generateSecret(spec);
              return new SecretKeySpec(tmp.getEncoded(), AES);
          }
      }
  
      private static final String ZIP_DATA_FILE = "data.xml"; //$NON-NLS-1$
  
      private static XStream xstream;
  
      public static boolean isEncrypted(File file)
      {
          return file.getName().endsWith(".portfolio"); //$NON-NLS-1$
      }
  
      public static boolean isCompressed(File file)
      {
          return file.getName().endsWith(".zip"); //$NON-NLS-1$
      }
  
      public static boolean isKeyLengthSupported(int keyLength)
      {
          try
          {
              return keyLength <= Cipher.getMaxAllowedKeyLength(Decryptor.CIPHER_ALGORITHM);
          }
          catch (NoSuchAlgorithmException e)
          {
              throw new IllegalArgumentException(MessageFormat.format(Messages.MsgErrorEncrypting, e.getMessage()), e);
          }
      }
  
      public static Client load(File file, char[] password, IProgressMonitor monitor) throws IOException
      {
          if (isEncrypted(file) && password == null)
              throw new IOException(Messages.MsgPasswordMissing);
  
          try
          {
              // progress monitor
              long bytesTotal = file.length();
              int increment = (int) Math.min(bytesTotal / 20L, Integer.MAX_VALUE);
              monitor.beginTask(MessageFormat.format(Messages.MsgReadingFile, file.getName()), 20);
              // open an input stream for the file using a 64 KB buffer to speed
              // up reading
              try (InputStream input = new ProgressMonitorInputStream(
                              new BufferedInputStream(new FileInputStream(file), 65536), increment, monitor))
              {
                  return buildPersister(file, null, password).load(input);
              }
          }
          catch (FileNotFoundException e)
          {
              FileNotFoundException fnf = new FileNotFoundException(
                              MessageFormat.format(Messages.MsgFileNotFound, file.getAbsolutePath()));
              fnf.initCause(e);
              throw fnf;
          }
      }
  
      public static Client load(Reader input) throws IOException
      {
          try
          {
              return new XmlSerialization().load(input);
          }
          finally
          {
              if (input != null)
                  input.close();
          }
      }
  
      public static Client load(InputStream input) throws IOException
      {
          return load(new InputStreamReader(input, StandardCharsets.UTF_8));
      }
  
      public static void save(final Client client, final File file, String method, char[] password) throws IOException
      {
          if (isEncrypted(file) && password == null && client.getSecret() == null)
              throw new IOException(Messages.MsgPasswordMissing);
          // open an output stream for the file using a 64 KB buffer to speed up
          // writing
          try (OutputStream output = new BufferedOutputStream(new FileOutputStream(file), 65536))
          {
              buildPersister(file, method, password).save(client, output);
          }
      }
  
      private static ClientPersister buildPersister(File file, String method, char[] password)
      {
          if (file != null && isEncrypted(file))
              return new Decryptor(method, password);
          else if (file != null && isCompressed(file))
              return new PlainWriterZIP();
          else
              return new PlainWriter();
      }
  
      private static void upgradeModel(Client client)
      {
          client.doPostLoadInitialization();
  
          client.setFileVersionAfterRead(client.getVersion());
  
          switch (client.getVersion())
          {
              case 1: // NOSONAR
                  fixAssetClassTypes(client);
                  addFeedAndExchange(client);
              case 2: // NOSONAR
                  addDecimalPlaces(client);
              case 3:
                  // do nothing --> added industry classification
              case 4: // NOSONAR
                  for (Security s : client.getSecurities())
                      s.generateUUID();
              case 5:
                  // do nothing --> save industry taxonomy in client
              case 6:
                  // do nothing --> added WKN attribute to security
              case 7: // NOSONAR
                  // new portfolio transaction types:
                  // DELIVERY_INBOUND, DELIVERY_OUTBOUND
                  changePortfolioTransactionTypeToDelivery(client);
              case 8:
                  // do nothing --> added 'retired' property to securities
              case 9:
                  // do nothing --> added 'cross entries' to transactions
              case 10: // NOSONAR
                  generateUUIDs(client);
              case 11:
                  // do nothing --> added 'properties' to client
              case 12: // NOSONAR
                  // added investment plans
                  // added security on chart as benchmark *and* performance
                  fixStoredBenchmarkChartConfigurations(client);
              case 13: // NOSONAR
                  // introduce arbitrary taxonomies
                  addAssetClassesAsTaxonomy(client);
                  addIndustryClassificationAsTaxonomy(client);
                  addAssetAllocationAsTaxonomy(client);
                  fixStoredClassificationChartConfiguration(client);
                  setDeprecatedFieldsToNull(client);
              case 14: // NOSONAR
                  // added shares to track dividends per share
                  assignSharesToDividendTransactions(client);
              case 15:
                  // do nothing --> added 'isRetired' property to account
              case 16:
                  // do nothing --> added 'feedURL' property to account
              case 17:
                  // do nothing --> added notes attribute
              case 18:
                  // do nothing --> added events (stock split) to securities
              case 19:
                  // do nothing --> added attribute types
              case 20:
                  // do nothing --> added note to investment plan
              case 21:
                  // do nothing --> added taxes to portfolio transaction
              case 22:
                  // do nothing --> added 'isRetired' property to portfolio
              case 23:
                  // do nothing --> added 'latestFeed' and 'latestFeedURL'
                  // property to security
              case 24:
                  // do nothing --> added 'TAX_REFUND' as account transaction
              case 25: // NOSONAR
                  // incremented precision of shares to 6 digits after the decimal
                  // sign
                  incrementSharesPrecisionFromFiveToSixDigitsAfterDecimalSign(client);
              case 26:
                  // do nothing --> added client settings
              case 27: // NOSONAR
                  // client settings include attribute types
                  fixStoredChartConfigurationToSupportMultipleViews(client);
              case 28: // NOSONAR
                  // added currency support --> designate a default currency (user
                  // will get a dialog to change)
                  setAllCurrencies(client, CurrencyUnit.EUR);
                  bumpUpCPIMonthValue(client);
                  convertFeesAndTaxesToTransactionUnits(client);
              case 29: // NOSONAR
                  // added decimal places to stock quotes
                  addDecimalPlacesToQuotes(client);
              case 30: // NOSONAR
                  // added dashboards to model
                  fixStoredChartConfigurationWithNewPerformanceSeriesKeys(client);
                  migrateToConfigurationSets(client);
              case 31:
                  // added INTEREST_CHARGE transaction type
              case 32:
                  // added AED currency
              case 33:
                  // added FEES_REFUND transaction type
              case 34:
                  // SecurityEvent has no more details
                  convertSecurityEventDetails(client);
                  // add optional security to FEES, FEES_REFUND, TAXES
              case 35:
                  // added flag to auto-generate tx from investment plan
              case 36:
                  // converted from LocalDate to LocalDateTime
                  // SecurityEvent has no more details
                  convertSecurityEventDetails(client);
              case 37:
                  // added boolean attribute type
                  // Portfoliotransfer has Quotesuggestion
                  introduceQuoteSuggestion4Transfer(client);
              case 38:
                  // added security exchange calendar
                  // added onlineId to security
                  // added DIVIDEND_CHARGE transaction type
              case 39:
                  // removed consumer price indices
                  // Introducing Peers
                  introducePeers(client);
              case 40:
                  // added attributes to account and portfolio
              case 41:
                  // added tax units to interest transaction
              case 42:
                  // added data map to classification and assignment
              case 43:
                  // added LimitPrice as attribute type
              case 44:
                  // added weights to dashboard columns
                  fixDashboardColumnWeights(client);
              case 45:
                  // added custom security type NOTE
              case 46:
                  // added dividend payment security event
                  addDefaultLogoAttributes(client);
              case 47:
                  // added fees to dividend transactions
              case 48:
                  incrementSharesPrecisionFromSixToEightDigitsAfterDecimalSign(client);
                  // add 4 more decimal places to the quote to make it 8
                  addDecimalPlacesToQuotes(client);
                  addDecimalPlacesToQuotes(client);
              case 49:
                  fixLimitQuotesWith4AdditionalDecimalPlaces(client);
  
                  client.setVersion(Client.CURRENT_VERSION);
                  break;
              case Client.CURRENT_VERSION:
                  // 
                  break;
              default:
                  break;
          }
      }
  
      private static void fixAssetClassTypes(Client client)
      {
          for (Security security : client.getSecurities())
          {
              if ("STOCK".equals(security.getType())) //$NON-NLS-1$ // NOSONAR
                  security.setType("EQUITY"); //$NON-NLS-1$ // NOSONAR
              else if ("BOND".equals(security.getType())) // NOSONAR //$NON-NLS-1$
                  security.setType("DEBT"); //$NON-NLS-1$ // NOSONAR
          }
      }
  
      private static void addFeedAndExchange(Client client)
      {
          for (Security s : client.getSecurities())
              s.setFeed(YahooFinanceQuoteFeed.ID);
      }
  
      private static void addDecimalPlaces(Client client)
      {
          for (Portfolio p : client.getPortfolios())
              for (PortfolioTransaction t : p.getTransactions())
                  t.setShares(t.getShares() * 100000);
      }
  
      private static void changePortfolioTransactionTypeToDelivery(Client client)
      {
          for (Portfolio p : client.getPortfolios())
          {
              for (PortfolioTransaction t : p.getTransactions())
              {
                  if (t.getType() == Type.TRANSFER_IN)
                      t.setType(Type.DELIVERY_INBOUND);
                  else if (t.getType() == Type.TRANSFER_OUT)
                      t.setType(Type.DELIVERY_OUTBOUND);
              }
          }
      }
  
      private static void generateUUIDs(Client client)
      {
          for (Account a : client.getAccounts())
              a.generateUUID();
          for (Portfolio p : client.getPortfolios())
              p.generateUUID();
          for (Category c : client.getRootCategory().flatten()) // NOSONAR
              c.generateUUID();
      }
  
      @SuppressWarnings("nls")
      private static void fixStoredBenchmarkChartConfigurations(Client client)
      {
          // Until now, the performance chart was showing *only* the benchmark
          // series, not the actual performance series. Change keys as benchmark
          // values are prefixed with '[b]'
  
          replace(client, "PerformanceChartView-PICKER", //
                          "Security", "[b]Security", //
                          "ConsumerPriceIndex", "[b]ConsumerPriceIndex");
      }
  
      private static void addAssetClassesAsTaxonomy(Client client)
      {
          TaxonomyTemplate template = TaxonomyTemplate.byId("assetclasses"); //$NON-NLS-1$
          Taxonomy taxonomy = template.buildFromTemplate();
          taxonomy.setId("assetclasses"); //$NON-NLS-1$
  
          int rank = 1;
  
          Classification cash = taxonomy.getClassificationById("CASH"); //$NON-NLS-1$
          for (Account account : client.getAccounts())
          {
              Assignment assignment = new Assignment(account);
              assignment.setRank(rank++);
              cash.addAssignment(assignment);
          }
  
          for (Security security : client.getSecurities())
          {
              Classification classification = taxonomy.getClassificationById(security.getType()); // NOSONAR
  
              if (classification != null)
              {
                  Assignment assignment = new Assignment(security);
                  assignment.setRank(rank++);
                  classification.addAssignment(assignment);
              }
          }
  
          client.addTaxonomy(taxonomy);
      }
  
      private static void addIndustryClassificationAsTaxonomy(Client client)
      {
          String oldIndustryId = client.getIndustryTaxonomy(); // NOSONAR
  
          Taxonomy taxonomy = null;
  
          if ("simple2level".equals(oldIndustryId)) //$NON-NLS-1$
              taxonomy = TaxonomyTemplate.byId(TaxonomyTemplate.INDUSTRY_SIMPLE2LEVEL).buildFromTemplate();
          else
              taxonomy = TaxonomyTemplate.byId(TaxonomyTemplate.INDUSTRY_GICS).buildFromTemplate();
  
          taxonomy.setId("industries"); //$NON-NLS-1$
  
          // add industry taxonomy only if at least one security has been assigned
          if (assignSecurities(client, taxonomy))
              client.addTaxonomy(taxonomy);
      }
  
      private static boolean assignSecurities(Client client, Taxonomy taxonomy)
      {
          boolean hasAssignments = false;
  
          int rank = 0;
          for (Security security : client.getSecurities())
          {
              Classification classification = taxonomy.getClassificationById(security.getIndustryClassification()); // NOSONAR
  
              if (classification != null)
              {
                  Assignment assignment = new Assignment(security);
                  assignment.setRank(rank++);
                  classification.addAssignment(assignment);
  
                  hasAssignments = true;
              }
          }
  
          return hasAssignments;
      }
  
      private static void addAssetAllocationAsTaxonomy(Client client)
      {
          Category category = client.getRootCategory(); // NOSONAR
  
          Taxonomy taxonomy = new Taxonomy("assetallocation", Messages.LabelAssetAllocation); //$NON-NLS-1$
          Classification root = new Classification(category.getUUID(), Messages.LabelAssetAllocation);
          taxonomy.setRootNode(root);
  
          buildTree(root, category);
  
          root.assignRandomColors();
  
          client.addTaxonomy(taxonomy);
      }
  
      private static void buildTree(Classification node, Category category) // NOSONAR
      {
          int rank = 0;
  
          for (Category child : category.getChildren()) // NOSONAR
          {
              Classification classification = new Classification(node, child.getUUID(), child.getName());
              classification.setWeight(child.getPercentage() * Values.Weight.factor());
              classification.setRank(rank++);
              node.addChild(classification);
  
              buildTree(classification, child);
          }
  
          for (Object element : category.getElements())
          {
              Assignment assignment = element instanceof Account ? new Assignment((Account) element)
                              : new Assignment((Security) element);
              assignment.setRank(rank++);
  
              node.addAssignment(assignment);
          }
      }
  
      @SuppressWarnings("nls")
      private static void fixStoredClassificationChartConfiguration(Client client)
      {
          String name = Classification.class.getSimpleName();
          replace(client, "PerformanceChartView-PICKER", //
                          "AssetClass", name, //
                          "Category", name);
  
          replace(client, "StatementOfAssetsHistoryView-PICKER", //
                          "AssetClass", name, //
                          "Category", name);
      }
  
      private static void replace(Client client, String property, String... replacements)
      {
          if (replacements.length % 2 != 0)
              throw new UnsupportedOperationException();
  
          String value = client.getProperty(property);
          if (value != null)
              replaceAll(client, property, value, replacements);
  
          int index = 0;
          while (true)
          {
              String key = property + '$' + index;
              value = client.getProperty(key);
              if (value != null)
                  replaceAll(client, key, value, replacements);
              else
                  break;
  
              index++;
          }
      }
  
      private static void replaceAll(Client client, String key, String value, String[] replacements)
      {
          String newValue = value;
          for (int ii = 0; ii < replacements.length; ii += 2)
              newValue = newValue.replaceAll(replacements[ii], replacements[ii + 1]);
          client.setProperty(key, newValue);
      }
  
      private static void setDeprecatedFieldsToNull(Client client)
      {
          client.setRootCategory(null); // NOSONAR
          client.setIndustryTaxonomy(null); // NOSONAR
  
          for (Security security : client.getSecurities())
          {
              security.setIndustryClassification(null); // NOSONAR
              security.setType(null); // NOSONAR
          }
      }
  
      private static void assignSharesToDividendTransactions(Client client)
      {
  