content: /**
   * Licensed 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.aurora.scheduler.storage.testing;
  
  import java.lang.reflect.Field;
  import java.lang.reflect.Modifier;
  import java.util.Collection;
  import java.util.Map;
  import java.util.Set;
  
  import com.google.common.base.Defaults;
  import com.google.common.base.Throwables;
  import com.google.common.collect.ImmutableSet;
  import com.google.gson.internal.Primitives;
  
  import org.apache.thrift.TUnion;
  
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertNotEquals;
  import static org.junit.Assert.assertNotNull;
  
  /**
   * Utility for validating objects used in storage testing.
   */
  public final class StorageEntityUtil {
  
    private StorageEntityUtil() {
      // Utility class.
    }
  
    private static void assertFullyPopulated(String name, Object object, Set<Field> ignoredFields) {
      if (object instanceof Collection) {
        Object[] values = ((Collection<?>) object).toArray();
        assertFalse("Collection is empty: " + name, values.length == 0);
        for (int i = 0; i < values.length; i++) {
          assertFullyPopulated(name + "[" + i + "]", values[i], ignoredFields);
        }
      } else if (object instanceof Map) {
        Map<?, ?> map = (Map<?, ?>) object;
        assertFalse("Map is empty: " + name, map.isEmpty());
        for (Map.Entry<?, ?> entry : map.entrySet()) {
          assertFullyPopulated(name + " key", entry.getKey(), ignoredFields);
          assertFullyPopulated(name + "[" + entry.getKey() + "]", entry.getValue(), ignoredFields);
        }
      } else if (object instanceof TUnion) {
        TUnion<?, ?> union = (TUnion<?, ?>) object;
        assertFullyPopulated(
            name + "." + union.getSetField().getFieldName(),
            union.getFieldValue(),
            ignoredFields);
      } else if (!(object instanceof String) && !(object instanceof Enum)) {
        for (Field field : object.getClass().getDeclaredFields()) {
          if (!Modifier.isStatic(field.getModifiers())) {
            validateField(name, object, field, ignoredFields);
          }
        }
      }
    }
  
    private static void validateField(
        String name,
        Object object,
        Field field,
        Set<Field> ignoredFields) {
  
      try {
        field.setAccessible(true);
        String fullName = name + "." + field.getName();
        Object fieldValue = field.get(object);
        boolean mustBeSet = !ignoredFields.contains(field);
        if (mustBeSet) {
          assertNotNull(fullName + " is null", fieldValue);
        }
        if (fieldValue != null) {
          if (Primitives.isWrapperType(fieldValue.getClass())) {
            // Special-case the mutable hash code field.
            if (mustBeSet && !fullName.endsWith("cachedHashCode")) {
              assertNotEquals(
                  "Primitive value must not be default: " + fullName,
                  Defaults.defaultValue(Primitives.unwrap(fieldValue.getClass())),
                  fieldValue);
            }
          } else {
            assertFullyPopulated(fullName, fieldValue, ignoredFields);
          }
        }
      } catch (IllegalAccessException e) {
        throw Throwables.propagate(e);
      }
    }
  
    /**
     * Ensures that an object tree is fully-populated.  This is useful when testing store
     * implementations to validate that all fields are mapped during a round-trip into and out of
     * a store implementation.
     *
     * @param object Object to ensure is fully populated.
     * @param <T> Object type.
     * @return The original {@code object}.
     */
    public static <T> T assertFullyPopulated(T object, Field... ignoredFields) {
      assertFullyPopulated(
          object.getClass().getSimpleName(),
          object,
          ImmutableSet.copyOf(ignoredFields));
      return object;
    }
  
    /**
     * Convenience method to get a field by name from a class, to pass as an ignored field to
     * {@link #assertFullyPopulated(Object, Field...)}.
     *
     * @param clazz Class to get a field from.
     * @param field Field name.
     * @return Field with the given {@code name}.
     */
    public static Field getField(Class<?> clazz, String field) {
      for (Field f : clazz.getDeclaredFields()) {
        if (f.getName().equals(field)) {
          return f;
        }
      }
      throw new IllegalArgumentException("Field not found: " + field);
    }
  }
  