All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.numdata.oss.junit.FieldTester Maven / Gradle / Ivy

There is a newer version: 1.22
Show newest version
/*
 * Copyright (c) 2017, Numdata BV, The Netherlands.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Numdata nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL NUMDATA BV BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.numdata.oss.junit;

import java.lang.reflect.*;
import java.util.*;

import com.numdata.oss.*;
import org.jetbrains.annotations.*;
import org.junit.*;

/**
 * JUnit unit tool class to help with testing class field definitions.
 *
 * @author Peter S. Heijnen
 */
public class FieldTester
{
	/**
	 * Utility class is not supposed to be instantiated.
	 */
	private FieldTester()
	{
	}

	/**
	 * Get list of constant values from the specified class.
	 *
	 * @param forClass         Class to get the constants from.
	 * @param includeAncestors Include fields of ancestor classes.
	 * @param publicOnly       Return only public fields.
	 * @param typeFilter       Return constants of this type.
	 * @param ignoredFields    Names of fields to ignore.
	 *
	 * @return List of constant values.
	 */
	@NotNull
	public static  List getConstants( @NotNull final Class forClass, final boolean includeAncestors, final boolean publicOnly, @NotNull final Class typeFilter, @NotNull final String... ignoredFields )
	{
		return getConstants( forClass, includeAncestors, publicOnly, typeFilter, Arrays.asList( ignoredFields ) );
	}

	/**
	 * Get list of constant values from the specified class.
	 *
	 * @param forClass         Class to get the constants from.
	 * @param includeAncestors Include fields of ancestor classes.
	 * @param publicOnly       Return only public fields.
	 * @param typeFilter       Return constants of this type.
	 * @param ignoredFields    Names of fields to ignore.
	 *
	 * @return List of constant values.
	 */
	@NotNull
	public static  List getConstants( @NotNull final Class forClass, final boolean includeAncestors, final boolean publicOnly, @NotNull final Class typeFilter, @NotNull final Collection ignoredFields )
	{
		final List fields = getFields( forClass, includeAncestors, true, false, publicOnly, typeFilter, ignoredFields );
		final List result = new ArrayList( fields.size() );
		for ( final Field field : fields )
		{
			try
			{
				field.setAccessible( true );
				result.add( (T)field.get( null ) );
			}
			catch ( final IllegalAccessException e )
			{
				throw new AssertionError( e );
			}
		}
		return result;
	}

	/**
	 * Get list of constant fields from the specified class.
	 *
	 * @param forClass      Class to get the constants from.
	 * @param publicOnly    Return only public fields.
	 * @param ignoredFields Names of fields to ignore.
	 *
	 * @return List of constants.
	 */
	@NotNull
	public static List getConstantFields( @NotNull final Class forClass, final boolean publicOnly, @NotNull final String... ignoredFields )
	{
		return getConstantFields( forClass, publicOnly, Arrays.asList( ignoredFields ) );
	}

	/**
	 * Get list of constant fields from the specified class.
	 *
	 * @param forClass      Class to get the constants from.
	 * @param publicOnly    Return only public fields.
	 * @param ignoredFields Names of fields to ignore.
	 *
	 * @return List of constants.
	 */
	@NotNull
	public static List getConstantFields( @NotNull final Class forClass, final boolean publicOnly, @NotNull final Collection ignoredFields )
	{
		return getFields( forClass, true, true, false, publicOnly, null, ignoredFields );
	}

	/**
	 * Get list of constant fields by their value. This is useful when trying to
	 * find constants without using explicit names.
	 *
	 * @param forClass Class to get the constants from.
	 * @param values   Constant values.
	 *
	 * @return List of constant values.
	 */
	@NotNull
	public static List getConstantFieldsByValue( @NotNull final Class forClass, @NotNull final Collection values )
	{
		final List result = getFields( forClass, true, true, false, false, null );
		for ( final Iterator it = result.iterator(); it.hasNext(); )
		{
			final Field field = it.next();
			try
			{
				field.setAccessible( true );
				final Object value = field.get( null );

				boolean found = false;
				for ( final Object o : values )
				{
					if ( o.equals( value ) )
					{
						found = true;
						break;
					}
				}

				if ( !found )
				{
					it.remove();
				}
			}
			catch ( final IllegalAccessException e )
			{
				throw new AssertionError( e );
			}
		}

		if ( result.size() != values.size() )
		{
			throw new IllegalArgumentException( "Not all values were found" );

		}

		return result;
	}

	/**
	 * Get list of instance fields from the specified class.
	 *
	 * @param forClass      Class to get the instances from.
	 * @param publicOnly    Return only public fields.
	 * @param ignoredFields Names of fields to ignore.
	 *
	 * @return List of instances.
	 */
	@NotNull
	public static List getInstanceFields( @NotNull final Class forClass, final boolean publicOnly, @NotNull final String... ignoredFields )
	{
		return getFields( forClass, true, false, true, publicOnly, null, ignoredFields );
	}

	/**
	 * Get list of fields from the specified class.
	 *
	 * @param forClass         Class to get the constants from.
	 * @param includeAncestors Include fields of ancestor classes.
	 * @param includeStatic    If set, include static fields.
	 * @param includeInstance  If set, include non-static fields.
	 * @param publicOnly       Return only public fields.
	 * @param typeFilter       Return only fields of this type ({@code null} =
	 *                         any type).
	 * @param ignoredFields    Names of fields to ignore.
	 *
	 * @return List of constants.
	 */
	@NotNull
	public static List getFields( @NotNull final Class forClass, final boolean includeAncestors, final boolean includeStatic, final boolean includeInstance, final boolean publicOnly, @Nullable final Class typeFilter, @NotNull final String... ignoredFields )
	{
		return getFields( forClass, includeAncestors, includeStatic, includeInstance, publicOnly, typeFilter, Arrays.asList( ignoredFields ) );
	}

	/**
	 * Get list of fields from the specified class.
	 *
	 * @param forClass         Class to get the constants from.
	 * @param includeStatic    If set, include static fields.
	 * @param includeInstance  If set, include non-static fields.
	 * @param publicOnly       Return only public fields.
	 * @param includeAncestors Include fields of ancestor classes.
	 * @param typeFilter       Return only fields of this type ({@code null} =
	 *                         any type).
	 * @param ignoredFields    Names of fields to ignore.
	 *
	 * @return List of constants.
	 */
	@NotNull
	public static List getFields( @NotNull final Class forClass, final boolean includeAncestors, final boolean includeStatic, final boolean includeInstance, final boolean publicOnly, @Nullable final Class typeFilter, @NotNull final Collection ignoredFields )
	{
		int ignoreCount = 0;

		List fields = Arrays.asList( forClass.getDeclaredFields() );
		if ( includeAncestors )
		{
			fields = new ArrayList( fields );
			for ( Class ancestor = forClass.getSuperclass(); ancestor != null; ancestor = ancestor.getSuperclass() )
			{
				fields.addAll( Arrays.asList( ancestor.getDeclaredFields() ) );
			}
		}

		final List result = new ArrayList( fields.size() - ignoredFields.size() );

		for ( final Field field : fields )
		{
			//noinspection ObjectEquality
			if ( !includeAncestors && ( forClass != field.getDeclaringClass() ) )
			{
				continue;
			}

			final String name = field.getName();
			if ( ignoredFields.contains( name ) )
			{
				ignoreCount++;
			}
			else
			{
				final int modifiers = field.getModifiers();

				if ( ( Modifier.isStatic( modifiers ) ? includeStatic : includeInstance ) &&
				     ( !publicOnly || Modifier.isPublic( modifiers ) ) &&
				     ( ( typeFilter == null ) || typeFilter.isAssignableFrom( field.getType() ) ) )
				{
					result.add( field );
				}
			}
		}

		if ( ignoredFields.size() != ignoreCount )
		{
			throw new IllegalArgumentException( "Not all ignored fields were found: " + ignoredFields );
		}

		return result;
	}

	/**
	 * Get fields names from a collection of fields.
	 *
	 * @param fields Fields to get names from.
	 *
	 * @return Field names.
	 */
	public static Set getFieldNames( final Iterable fields )
	{
		final Set result = new HashSet();

		for ( final Field field : fields )
		{
			result.add( field.getName() );
		}

		return result;
	}

	/**
	 * Test constants defined by the specified class. String constants should
	 * always have the same value as their name. Public static fields should
	 * always be final (we hate globals!) and non-transient (which makes no
	 * sense for static fields).
	 *
	 * @param forClass       Class to test the constants for.
	 * @param allowNonString If set, allow constants of other types than {@code
	 *                       String}.
	 * @param ignoredFields  Names of fields to ignore.
	 */
	public static void testConstants( @NotNull final Class forClass, final boolean allowNonString, @NotNull final String... ignoredFields )
	{
		testConstants( forClass, allowNonString, Arrays.asList( ignoredFields ) );
	}

	/**
	 * Test constants defined by the specified class. String constants should
	 * always have the same value as their name. Public static fields should
	 * always be final (we hate globals!) and non-transient (which makes no
	 * sense for static fields).
	 *
	 * @param forClass       Class to test the constants for.
	 * @param allowNonString If set, allow constants of other types than {@code
	 *                       String}.
	 * @param ignoredFields  Names of fields to ignore.
	 */
	public static void testConstants( @NotNull final Class forClass, final boolean allowNonString, @NotNull final Collection ignoredFields )
	{
		final List fields = getConstantFields( forClass, true, ignoredFields );
		for ( final Field field : fields )
		{
			final String name = field.getName();
			final int modifiers = field.getModifiers();
			final Class type = field.getType();

			if ( Modifier.isVolatile( modifiers ) || Modifier.isTransient( modifiers ) || !Modifier.isFinal( modifiers ) )
			{
				Assert.fail( "Invalid modifiers '" + Modifier.toString( modifiers ) + "' for constant: " + forClass.getName() + '.' + name );
			}

			if ( String.class.equals( type ) )
			{
				try
				{
					final CharSequence value = (CharSequence)field.get( null );
					if ( !value.equals( name ) )
					{
						final String constantNameForValue = TextTools.camelToUpperCase( value );
						if ( !constantNameForValue.equals( name ) )
						{
							Assert.fail( "String constant " + forClass.getName() + '.' + name + "' does not match value '" + value + '\'' + ( !constantNameForValue.equals( value ) ? " (could also have been '" + constantNameForValue + "')" : "" ) );
						}
					}
				}
				catch ( final Exception e )
				{
					Assert.fail( "failed to get value of '" + forClass.getName() + '.' + name + "' constant (" + e + ')' );
				}
			}
			else
			{
				if ( !allowNonString )
				{
					Assert.fail( "non-String constant '" + forClass.getName() + '.' + name + "' detected" );
				}
			}
		}
	}

	/**
	 * Get list of public non-transient fields of the specified class.
	 *
	 * @param forClass      Class to test the fields for.
	 * @param ignoredFields Names of fields to ignore.
	 *
	 * @return List of public non-transient fields of the specified class.
	 *
	 * @deprecated This method is does some magic exceptions. Invoker should do
	 * this.
	 */
	@Deprecated
	public static List getFieldList( final Class forClass, @NotNull final String... ignoredFields )
	{
		final List fields = getFields( forClass, true, true, true, true, null, ignoredFields );

		final List result = new ArrayList( fields.size() );

		for ( final Field field : fields )
		{
			final int modifiers = field.getModifiers();
			final String name = field.getName();
			final Class type = field.getType();

			try
			{
				if ( !Modifier.isTransient( modifiers ) )
				{
					if ( Modifier.isStatic( modifiers ) )
					{
						if ( String.class.equals( type ) && !"TABLE_NAME".equals( name ) && !name.endsWith( "_CREATE_STATEMENT" ) && !"LIGHTWEIGHT_FIELDS".equals( name ) )
						{
							result.add( (String)field.get( null ) );
						}
					}
					else
					{
						result.add( name );
					}
				}
			}
			catch ( final Exception e )
			{ /* ignore invalid fields */ }
		}

		return result;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy