/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * 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.springframework.security.util;

import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;

/**
 * Offers static methods for directly manipulating fields.
 *
 * @author Ben Alex
 */
public final class FieldUtils {

	// ~ Methods
	// ========================================================================================================
	/**
	 * Attempts to locate the specified field on the class.
	 *
	 * @param clazz the class definition containing the field
	 * @param fieldName the name of the field to locate
	 *
	 * @return the Field (never null)
	 *
	 * @throws IllegalStateException if field could not be found
	 */
	public static Field getField(Class<?> clazz, String fieldName)
			throws IllegalStateException {
		Assert.notNull(clazz, "Class required");
		Assert.hasText(fieldName, "Field name required");

		try {
			return clazz.getDeclaredField(fieldName);
		}
		catch (NoSuchFieldException nsf) {
			// Try superclass
			if (clazz.getSuperclass() != null) {
				return getField(clazz.getSuperclass(), fieldName);
			}

			throw new IllegalStateException("Could not locate field '" + fieldName
					+ "' on class " + clazz);
		}
	}

	/**
	 * Returns the value of a (nested) field on a bean. Intended for testing.
	 * @param bean the object
	 * @param fieldName the field name, with "." separating nested properties
	 * @return the value of the nested field
	 */
	public static Object getFieldValue(Object bean, String fieldName)
			throws IllegalAccessException {
		Assert.notNull(bean, "Bean cannot be null");
		Assert.hasText(fieldName, "Field name required");
		String[] nestedFields = StringUtils.tokenizeToStringArray(fieldName, ".");
		Class<?> componentClass = bean.getClass();
		Object value = bean;

		for (String nestedField : nestedFields) {
			Field field = getField(componentClass, nestedField);
			field.setAccessible(true);
			value = field.get(value);
			if (value != null) {
				componentClass = value.getClass();
			}
		}

		return value;

	}

	public static Object getProtectedFieldValue(String protectedField, Object object) {
		Field field = FieldUtils.getField(object.getClass(), protectedField);

		try {
			field.setAccessible(true);

			return field.get(object);
		}
		catch (Exception ex) {
			ReflectionUtils.handleReflectionException(ex);

			return null; // unreachable - previous line throws exception
		}
	}

	public static void setProtectedFieldValue(String protectedField, Object object,
			Object newValue) {
		Field field = FieldUtils.getField(object.getClass(), protectedField);

		try {
			field.setAccessible(true);
			field.set(object, newValue);
		}
		catch (Exception ex) {
			ReflectionUtils.handleReflectionException(ex);
		}
	}
}
