content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements. See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License. You may obtain a copy of the License at
   *
   *    http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.kafka.common.serialization;
  
  import org.apache.kafka.clients.CommonClientConfigs;
  import org.apache.kafka.common.KafkaException;
  import org.apache.kafka.common.config.ConfigException;
  import org.apache.kafka.common.utils.Utils;
  
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.io.ByteArrayOutputStream;
  import java.io.DataOutputStream;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  import static org.apache.kafka.common.serialization.Serdes.ListSerde.SerializationStrategy;
  
  public class ListSerializer<Inner> implements Serializer<List<Inner>> {
  
      final Logger log = LoggerFactory.getLogger(ListSerializer.class);
  
      private static final List<Class<? extends Serializer<?>>> FIXED_LENGTH_SERIALIZERS = Arrays.asList(
          ShortSerializer.class,
          IntegerSerializer.class,
          FloatSerializer.class,
          LongSerializer.class,
          DoubleSerializer.class,
          UUIDSerializer.class);
  
      private Serializer<Inner> inner;
      private SerializationStrategy serStrategy;
  
      public ListSerializer() {}
  
      public ListSerializer(Serializer<Inner> inner) {
          if (inner == null) {
              throw new IllegalArgumentException("ListSerializer requires \"serializer\" parameter to be provided during initialization");
          }
          this.inner = inner;
          this.serStrategy = FIXED_LENGTH_SERIALIZERS.contains(inner.getClass()) ? SerializationStrategy.CONSTANT_SIZE : SerializationStrategy.VARIABLE_SIZE;
      }
  
      public Serializer<Inner> getInnerSerializer() {
          return inner;
      }
  
      @SuppressWarnings("unchecked")
      @Override
      public void configure(Map<String, ?> configs, boolean isKey) {
          if (inner != null) {
              log.error("Could not configure ListSerializer as the parameter has already been set -- inner: {}", inner);
              throw new ConfigException("List serializer was already initialized using a non-default constructor");
          }
          final String innerSerdePropertyName = isKey ? CommonClientConfigs.DEFAULT_LIST_KEY_SERDE_INNER_CLASS : CommonClientConfigs.DEFAULT_LIST_VALUE_SERDE_INNER_CLASS;
          final Object innerSerdeClassOrName = configs.get(innerSerdePropertyName);
          if (innerSerdeClassOrName == null) {
              throw new ConfigException("Not able to determine the serializer class because it was neither passed via the constructor nor set in the config.");
          }
          try {
              if (innerSerdeClassOrName instanceof String) {
                  inner = Utils.newInstance((String) innerSerdeClassOrName, Serde.class).serializer();
              } else if (innerSerdeClassOrName instanceof Class) {
                  inner = (Serializer<Inner>) ((Serde) Utils.newInstance((Class) innerSerdeClassOrName)).serializer();
              } else {
                  throw new KafkaException("Could not create a serializer class instance using \"" + innerSerdePropertyName + "\" property.");
              }
              inner.configure(configs, isKey);
              serStrategy = FIXED_LENGTH_SERIALIZERS.contains(inner.getClass()) ? SerializationStrategy.CONSTANT_SIZE : SerializationStrategy.VARIABLE_SIZE;
          } catch (final ClassNotFoundException e) {
              throw new ConfigException(innerSerdePropertyName, innerSerdeClassOrName, "Serializer class " + innerSerdeClassOrName + " could not be found.");
          }
      }
  
      private void serializeNullIndexList(final DataOutputStream out, List<Inner> data) throws IOException {
          int i = 0;
          List<Integer> nullIndexList = new ArrayList<>();
          for (Iterator<Inner> it = data.listIterator(); it.hasNext(); i++) {
              if (it.next() == null) {
                  nullIndexList.add(i);
              }
          }
          out.writeInt(nullIndexList.size());
          for (int nullIndex : nullIndexList) {
              out.writeInt(nullIndex);
          }
      }
  
      @Override
      public byte[] serialize(String topic, List<Inner> data) {
          if (data == null) {
              return null;
          }
          try (final ByteArrayOutputStream baos = new ByteArrayOutputStream();
               final DataOutputStream out = new DataOutputStream(baos)) {
              out.writeByte(serStrategy.ordinal()); // write serialization strategy flag
              if (serStrategy == SerializationStrategy.CONSTANT_SIZE) {
                  // In CONSTANT_SIZE strategy, indexes of null entries are encoded in a null index list
                  serializeNullIndexList(out, data);
              }
              final int size = data.size();
              out.writeInt(size);
              for (Inner entry : data) {
                  if (entry == null) {
                      if (serStrategy == SerializationStrategy.VARIABLE_SIZE) {
                          out.writeInt(Serdes.ListSerde.NULL_ENTRY_VALUE);
                      }
                  } else {
                      final byte[] bytes = inner.serialize(topic, entry);
                      if (serStrategy == SerializationStrategy.VARIABLE_SIZE) {
                          out.writeInt(bytes.length);
                      }
                      out.write(bytes);
                  }
              }
              return baos.toByteArray();
          } catch (IOException e) {
              log.error("Failed to serialize list due to", e);
              log.trace("List that could not be serialized: {}", data); // avoid logging actual data above TRACE level since it may contain sensitive information
              throw new KafkaException("Failed to serialize List", e);
          }
      }
  
      @Override
      public void close() {
          if (inner != null) {
              inner.close();
          }
      }
  
  }
  