package io.tracee.contextlogger.outputgenerator.functions;

import io.tracee.contextlogger.outputgenerator.RecursiveOutputElementTreeBuilder;
import io.tracee.contextlogger.outputgenerator.RecursiveOutputElementTreeBuilderState;
import io.tracee.contextlogger.outputgenerator.outputelements.ComplexOutputElement;
import io.tracee.contextlogger.outputgenerator.outputelements.NullValueOutputElement;
import io.tracee.contextlogger.outputgenerator.outputelements.OutputElement;
import io.tracee.contextlogger.outputgenerator.utility.BeanUtility;
import io.tracee.contextlogger.utility.FieldUtilities;
import io.tracee.contextlogger.utility.GetterUtilities;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Transforms a given bean to an {@link ComplexOutputElement}.
 */
public class BeanToOutputElementTransformerFunction extends ToComplexOutputElementTransformerFunction<Object> {

	private static final BeanToOutputElementTransformerFunction instance = new BeanToOutputElementTransformerFunction();

	public static BeanToOutputElementTransformerFunction getInstance() {
		return instance;
	}

	@Override
	public OutputElement apply(final RecursiveOutputElementTreeBuilder recursiveOutputElementTreeBuilder,
							   final RecursiveOutputElementTreeBuilderState state, final Object instance) {

		if (instance == null) {
			return NullValueOutputElement.INSTANCE;
		}
		ComplexOutputElement complexOutputElement = new ComplexOutputElement(instance.getClass(), instance);

		if (recursiveOutputElementTreeBuilder.checkIfInstanceIsAlreadyRegistered(complexOutputElement)) {
			return recursiveOutputElementTreeBuilder.getRegisteredOutputElementAndMarkItAsMultipleRegistered(complexOutputElement);
		}

		// must register output element before processing children to prevent problems with alreadyprocessed references
		recursiveOutputElementTreeBuilder.registerOutputElement(complexOutputElement);

		// get internal fields with matching getter
		for (Method method : BeanUtility.getGetterMethodsRecursively(instance.getClass())) {

			complexOutputElement.addOutputElement(GetterUtilities.getFieldName(method),
					recursiveOutputElementTreeBuilder.convertInstanceRecursively(state, invokeField(instance, method)));

		}

		return complexOutputElement;

	}

	/**
	 * Invokes the getter and return the return values
	 *
	 * @param instance the instance to invoke the getter method on.
	 * @param method   the method to invoke
	 * @return the return value of the getter invocation
	 */
	protected Object invokeGetter(final Object instance, final Method method) {
		try {
			return method.invoke(instance);
		} catch (IllegalAccessException e) {
			// shouldn't occur
		} catch (InvocationTargetException e) {
			// shouldn't occur
		}

		return null;
	}

	/**
	 * Invokes the getter and return the return values
	 *
	 * @param instance the instance to invoke the getter method on.
	 * @param method   the method to invoke
	 * @return the return value of the getter invocation
	 */
	protected Object invokeField(final Object instance, final Method method) {
		if (instance == null || method == null) {
			return null;
		}

		try {
			Field field = FieldUtilities.getField(instance.getClass(), GetterUtilities.getFieldName(method));
			field.setAccessible(true);
			return field.get(instance);
		} catch (IllegalAccessException e) {
			// shouldn't occur
		} catch (IllegalArgumentException e) {
			// ignore
		}

		return null;
	}

}


package io.tracee.contextlogger.contextprovider.aspectj;

import io.tracee.contextlogger.MessagePrefixProvider;
import io.tracee.contextlogger.TraceeContextLogger;
import io.tracee.contextlogger.api.ErrorMessage;
import io.tracee.contextlogger.api.internal.MessageLogLevel;
import io.tracee.contextlogger.contextprovider.aspectj.contextprovider.WatchdogDataWrapper;
import io.tracee.contextlogger.contextprovider.aspectj.util.WatchdogUtils;
import io.tracee.contextlogger.contextprovider.core.CoreImplicitContextProviders;
import io.tracee.contextlogger.contextprovider.core.tracee.TraceeMessage;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Watchdog Assert class.
 * This aspect logs method calls of Watchdog annotated classes and methods in case of an exception is thrown during the execution of the method.
 * <p>
 * Created by Tobias Gindler, holisticon AG on 16.02.14.
 */

@Aspect
public class WatchdogAspect {

	private static final Logger logger = LoggerFactory.getLogger(WatchdogAspect.class);

	private final boolean active;

	public WatchdogAspect() {
		this(Boolean.valueOf(System.getProperty(Constants.SYSTEM_PROPERTY_IS_ACTIVE, "true")));
	}

	WatchdogAspect(boolean active) {
		this.active = active;
	}

	@SuppressWarnings("unused")
	@Pointcut("(execution(* *(..)) && @annotation(io.tracee.contextlogger.contextprovider.aspectj.Watchdog))")
	void withinWatchdogAnnotatedMethods() {
	}

	@SuppressWarnings("unused")
	@Pointcut("within(@io.tracee.contextlogger.contextprovider.aspectj.Watchdog *)")
	void withinClassWithWatchdogAnnotation() {

	}

	@SuppressWarnings("unused")
	@Pointcut("execution(public * *(..))")
	void publicMethods() {

	}

	@SuppressWarnings("unused")
	@Around("withinWatchdogAnnotatedMethods() || (publicMethods() && withinClassWithWatchdogAnnotation()) ")
	public Object guard(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

		try {
			return proceedingJoinPoint.proceed();
		} catch (final Throwable e) {

			// check if watchdog processing is flagged as active
			if (active) {

				// make sure that original exception will be passed through
				try {

					// get watchdog annotation
					Watchdog watchdog = WatchdogUtils.getWatchdogAnnotation(proceedingJoinPoint);

					// check if watchdog aspect processing is deactivated by annotation
					if (WatchdogUtils.checkProcessWatchdog(watchdog, proceedingJoinPoint, e)) {

						String annotatedId = watchdog.id().isEmpty() ? null : watchdog.id();
						sendErrorReportToConnectors(proceedingJoinPoint, annotatedId, e);

					}

				} catch (Throwable error) {
					// will be ignored
					logger.error("error", error);
				}
			}
			// rethrow exception
			throw e;
		}

	}

	/**
	 * Sends the error reports to all connectors.
	 *
	 * @param proceedingJoinPoint the aspectj calling context
	 * @param annotatedId         the id defined in the watchdog annotation
	 */
	void sendErrorReportToConnectors(ProceedingJoinPoint proceedingJoinPoint, String annotatedId, Throwable e) {

		// try to get error message annotation
		ErrorMessage errorMessage = WatchdogUtils.getErrorMessageAnnotation(proceedingJoinPoint);

		if (errorMessage == null) {
			TraceeContextLogger
					.create()
					.enforceOrder()
					.apply()
					.logWithPrefixedMessage(MessagePrefixProvider.provideLogMessagePrefix(MessageLogLevel.ERROR, Watchdog.class), CoreImplicitContextProviders.COMMON,
							CoreImplicitContextProviders.TRACEE, WatchdogDataWrapper.wrap(annotatedId, proceedingJoinPoint), e);
		} else {
			TraceeContextLogger
					.create()
					.enforceOrder()
					.apply()
					.logWithPrefixedMessage(MessagePrefixProvider.provideLogMessagePrefix(MessageLogLevel.ERROR, Watchdog.class),
							TraceeMessage.wrap(errorMessage.value()), CoreImplicitContextProviders.COMMON, CoreImplicitContextProviders.TRACEE,
							WatchdogDataWrapper.wrap(annotatedId, proceedingJoinPoint), e);
		}
	}

}


package io.tracee.contextlogger.outputgenerator.functions;

import io.tracee.contextlogger.contextprovider.TypeToWrapper;
import io.tracee.contextlogger.contextprovider.api.ImplicitContext;
import io.tracee.contextlogger.contextprovider.api.WrappedContextData;
import io.tracee.contextlogger.impl.ContextLoggerConfiguration;
import io.tracee.contextlogger.outputgenerator.predicates.IsImplicitContextEnumValuePredicate;

/**
 * Wrapper function that wraps a given instance in a matching Tracee context logging provider.
 */
public class TraceeContextProviderWrapperFunction {

	private static final TraceeContextProviderWrapperFunction instance = new TraceeContextProviderWrapperFunction();

	/**
	 * Hide constructor.
	 */
	private TraceeContextProviderWrapperFunction() {

	}

	public static TraceeContextProviderWrapperFunction getInstance() {
		return instance;
	}

	/**
	 * Wraps the passed instance inside a matching tracee context logging provider instance.
	 * Will return the passed instance, if no matching tracee context logging provider exists.
	 *
	 * @param contextLoggerConfiguration the configuration to use
	 * @param instanceToWrap             the instance to wrap
	 * @return either a tracee context provider that encapsulates the passed instance or the instance itself if no matching context provider exists
	 */
	public Object apply(ContextLoggerConfiguration contextLoggerConfiguration, Object instanceToWrap) {

		// check for implicit context
		if (IsImplicitContextEnumValuePredicate.getInstance().apply(instanceToWrap)) {
			return createInstance((Class) contextLoggerConfiguration.getImplicitContextProviderClass((ImplicitContext) instanceToWrap));
		}

		// now try to find instance type in known wrapper types map
		Class matchingWrapperType = contextLoggerConfiguration.getContextProviderClass(instanceToWrap.getClass());
		if (matchingWrapperType == null) {

			// now try to find instance type in TypeToWrapper List
			for (TypeToWrapper wrapper : contextLoggerConfiguration.getWrapperList()) {
				if (wrapper.getWrappedInstanceType().isAssignableFrom(instanceToWrap.getClass())) {
					matchingWrapperType = wrapper.getWrapperType();
					break;
				}
			}

		}

		if (matchingWrapperType != null) {
			// now create wrapper instance
			try {
				WrappedContextData wrapperInstance = (WrappedContextData) createInstance(matchingWrapperType);
				wrapperInstance.setContextData(instanceToWrap);

				return wrapperInstance;
			} catch (Exception e) {
				// continue

			}
		}

		return instanceToWrap;
	}

	/**
	 * Creates a new instance of the passed type via reflection.
	 *
	 * @param type the type of the new instance
	 * @return a new instance of the passed type or null if an exception occurred during the creation of the instance of if the passed type is null.
	 */
	protected Object createInstance(final Class type) {
		if (type != null) {
			try {
				return type.newInstance();
			} catch (Exception e) {
				// should not occur
			}
		}
		return null;
	}

}


package io.tracee.contextlogger.outputgenerator.predicates;

import io.tracee.contextlogger.contextprovider.api.TraceeContextProviderPrimitiveType;
import io.tracee.contextlogger.contextprovider.api.WrappedPrimitiveTypeContextData;
import io.tracee.contextlogger.utility.TraceeContextLogAnnotationUtilities;

/**
 * Determines if the instance is a tracee context provider.
 */
public class IsTraceeContextProviderPrimitiveTypePredicate implements OutputElementTypePredicate {

    private static final IsTraceeContextProviderPrimitiveTypePredicate instance = new IsTraceeContextProviderPrimitiveTypePredicate();

    @Override
    public boolean apply(final Object instance) {
        return hasTraceeContextProviderPrimitiveTypeAnnotation(instance) && isExtendingTraceeContextProvider(instance);
    }

    /**
     * Checks whether the passed instance type is annotated with the {@link io.tracee.contextlogger.contextprovider.api.TraceeContextProvider} annotation.
     *
     * @param instance the instance to check
     * @return true, if the passed instance's type is annotated with the {@link io.tracee.contextlogger.contextprovider.api.TraceeContextProvider} annotation,
     * otherwise false
     */
    protected boolean hasTraceeContextProviderPrimitiveTypeAnnotation(final Object instance) {

        if (instance == null) {
            return false;
        }

        TraceeContextProviderPrimitiveType annotation = TraceeContextLogAnnotationUtilities.getAnnotationFromType(instance, TraceeContextProviderPrimitiveType.class);
        return annotation != null;

    }

    /**
     * Checks whether the passed instance extends the {@link io.tracee.contextlogger.contextprovider.api.ImplicitContextData} or
     * {@link io.tracee.contextlogger.contextprovider.api.WrappedContextData}types.
     *
     * @param instance the instance to check
     * @return true, if the passed instance's type extends the ImplicitContextData or WrappedContextData class
     */
    protected boolean isExtendingTraceeContextProvider(final Object instance) {

        if (instance == null) {
            return false;
        }

        return WrappedPrimitiveTypeContextData.class.isAssignableFrom(instance.getClass());

    }

    public static IsTraceeContextProviderPrimitiveTypePredicate getInstance() {
        return instance;
    }

}


package io.tracee.contextlogger.profile;

import io.tracee.contextlogger.contextprovider.api.Profile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * Class that offers helper methods for getting the configured Profile
 */
public class ProfileLookup {

	private static final Logger logger = LoggerFactory.getLogger(Profile.class);

	/**
	 * Gets the current profile.
	 * Uses the following algorithm to determine the profile.
	 * 1. If System Property is set force it as profile
	 * 2. If Custom Profile resource can be located in classpath use that file as profile
	 * 3. Use default profile
	 *
	 * @return
	 */
	public static Profile getCurrentProfile() {

		// First get profile from system properties
		Profile profile = getProfileFromSystemProperties();

		// check if profile has been found otherwise try getting profile from file in classpath
		if (profile == null) {
			profile = getProfileFromFileInClasspath(ProfilePropertyNames.PROFILE_SET_BY_FILE_IN_CLASSPATH_FILENAME);
		}

		// use DEFAULT profile, if profile has not been found
		if (profile == null) {
			profile = ProfilePropertyNames.DEFAULT_PROFILE;
		}

		return profile;

	}

	/**
	 * Gets the profile defined via the system properties.
	 *
	 * @return returns the profile defined via the system properties. Returns null if property is not set or set to an invalid value
	 */
	static Profile getProfileFromSystemProperties() {

		Profile result = null;

		// try to get system property
		String systemPropertyProfileName = System.getProperty(ProfilePropertyNames.PROFILE_SET_GLOBALLY_VIA_SYSTEM_PROPERTIES);

		if (systemPropertyProfileName != null) {

			// try to convert String to enum value
			try {
				result = Profile.valueOf(systemPropertyProfileName);
			} catch (IllegalArgumentException e) {
				logger.warn("Tracee ContextLoggerBuilder profile property ('" + ProfilePropertyNames.PROFILE_SET_GLOBALLY_VIA_SYSTEM_PROPERTIES
						+ "') is set to the invalid value '" + systemPropertyProfileName + "' ==> Use default profile");
			}
		}

		return result;
	}

	/**
	 * Gets the profile defined via the system properties.
	 *
	 * @return returns the profile defined via the system properties or {@code null} if property is not set or set to an invalid value.
	 */
	static Profile getProfileFromFileInClasspath(final String filename) {

		Profile result = null;

		try {
			Properties properties = openProperties(filename);
			if (properties != null) {
				String profileFromProperties = properties.getProperty(ProfilePropertyNames.PROFILE_SET_BY_FILE_IN_CLASSPATH_PROPERTY);
				if (profileFromProperties != null) {
					// try to convert String to enum value
					try {
						result = Profile.valueOf(profileFromProperties);
					} catch (IllegalArgumentException e) {
						logger.warn("Tracee custom ContextLoggerBuilder profile property ('"
								+ ProfilePropertyNames.PROFILE_SET_BY_FILE_IN_CLASSPATH_PROPERTY + "') is set to the invalid value '"
								+ profileFromProperties + "' and will be ignored");
					}
				}
			}
		} catch (IOException e) {
			// ignore it
		}

		return result;
	}

	/**
	 * Loads properties from resources.
	 *
	 * @param propertyFileName the property file name to load
	 * @return the properties, or null if the properties can't be found
	 * @throws IOException if property file can't be opened
	 */
	public static Properties openProperties(final String propertyFileName) throws IOException {

		if (propertyFileName == null) {
			return null;
		}

		InputStream inputStream = null;
		try {
			inputStream = Profile.class.getResourceAsStream(propertyFileName);
			if (inputStream != null) {
				// file could be opened
				Properties properties = new Properties();
				properties.load(inputStream);
				return properties;
			} else {
				// file doesn't exist
				return null;
			}
		} finally {
			if (inputStream != null) {
				inputStream.close();
			}
		}
	}

}


package io.tracee.contextlogger.impl;

import io.tracee.contextlogger.api.ConfigBuilder;
import io.tracee.contextlogger.api.ToStringBuilder;
import io.tracee.contextlogger.api.internal.Configuration;
import io.tracee.contextlogger.api.internal.ContextLoggerBuilderAccessible;
import io.tracee.contextlogger.contextprovider.api.Profile;
import io.tracee.contextlogger.outputgenerator.TraceeContextStringRepresentationBuilderImpl;
import io.tracee.contextlogger.outputgenerator.writer.BasicOutputWriterConfiguration;
import io.tracee.contextlogger.outputgenerator.writer.OutputWriterConfiguration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Implementation class to create a configuration by using the fluent api.
 */
public class ConfigBuilderImpl<T extends ToStringBuilder> implements Configuration<T> {

	private final ContextLoggerBuilderAccessible contextLogger;
	private final ContextLoggerConfiguration contextLoggerConfiguration;

	private Profile profile = null;

	private boolean enforceOrder = false;

	private Map<String, Boolean> manualContextOverrides = new HashMap<String, Boolean>();

	private OutputWriterConfiguration outputWriterConfiguration = BasicOutputWriterConfiguration.JSON_INTENDED;

	public ConfigBuilderImpl(ContextLoggerBuilderAccessible traceeContextLoggerBuilderAccessible) {

		this.contextLogger = traceeContextLoggerBuilderAccessible;
		this.contextLoggerConfiguration = traceeContextLoggerBuilderAccessible.getContextLoggerConfiguration();

	}

	@Override
	public final ConfigBuilderImpl enforceProfile(Profile profile) {
		this.profile = profile;
		return this;
	}

	@Override
	public ConfigBuilder<T> enable(String... contexts) {
		fillManualContextOverrideMap(contexts, true);
		return this;
	}

	@Override
	public ConfigBuilder<T> disableTypes(final Class... types) {
		List<String> classNames = new ArrayList<String>();
		if (types != null) {

			for (Class type : types) {
				if (type != null) {
					this.manualContextOverrides.put(type.getCanonicalName(), Boolean.FALSE);
				}
			}

		}
		return this;
	}

	@Override
	public ConfigBuilder<T> disable(String... contexts) {
		fillManualContextOverrideMap(contexts, false);
		return this;
	}

	@Override
	public ConfigBuilder<T> enforceOrder() {
		this.enforceOrder = true;
		return this;
	}

	@Override
	public ConfigBuilder<T> enforceOutputWriterConfiguration(final BasicOutputWriterConfiguration outputWriterConfiguration) {
		this.outputWriterConfiguration = outputWriterConfiguration;
		return this;
	}

	@Override
	public T apply() {
		contextLogger.setStringRepresentationBuilder(createContextStringRepresentationLogBuilder());
		return (T) contextLogger;
	}

	public Map<String, Boolean> getManualContextOverrides() {
		return manualContextOverrides;
	}

	public Profile getProfile() {
		return profile;
	}

	public boolean getEnforceOrder() {
		return enforceOrder;
	}

	@Override
	public OutputWriterConfiguration getOutputWriterConfiguration() {
		return this.outputWriterConfiguration;
	}

	/**
	 * Adds passed contexts value pairs to manualContextOverrides.
	 *
	 * @param contexts The property name of the context data.
	 * @param value    the value which should be set.
	 */
	private void fillManualContextOverrideMap(final String[] contexts, final boolean value) {
		if (contexts != null) {

			for (String context : contexts) {

				if (!context.isEmpty()) {
					this.manualContextOverrides.put(context, value);
				}

			}

		}
	}

	/**
	 * Creates a TraceeGsonContextStringRepresentationBuilder instance which can be used for creating the createStringRepresentation message.
	 *
	 * @return An instance of TraceeGsonContextStringRepresentationBuilder
	 */
	private TraceeContextStringRepresentationBuilderImpl createContextStringRepresentationLogBuilder() {

		TraceeContextStringRepresentationBuilderImpl traceeContextStringRepresentationBuilderImpl = new TraceeContextStringRepresentationBuilderImpl();
		traceeContextStringRepresentationBuilderImpl.setManualContextOverrides(this.getManualContextOverrides());
		traceeContextStringRepresentationBuilderImpl.setProfile(this.getProfile());
		traceeContextStringRepresentationBuilderImpl.setEnforceOrder(this.getEnforceOrder());
		traceeContextStringRepresentationBuilderImpl.setOutputWriterConfiguration(this.getOutputWriterConfiguration());

		return traceeContextStringRepresentationBuilderImpl;
	}
}


package io.tracee.contextlogger.contextprovider.aspectj.util;

import io.tracee.contextlogger.contextprovider.aspectj.Watchdog;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import io.tracee.contextlogger.api.ErrorMessage;

/**
 * Utility class for watchdog aspect
 * Created by Tobias Gindler, holisticon AG on 03.04.14.
 */
public final class WatchdogUtils {

    private WatchdogUtils() {

    }

    /**
     * Tries to get the watchdog annotation for the called method or the methods class.
     *
     * @param proceedingJoinPoint The aspectj join point
     * @return The watchdog annotation of the method, the class or null if neither the method or class aren't annotated with the watchdog annotation
     */
    public static Watchdog getWatchdogAnnotation(final ProceedingJoinPoint proceedingJoinPoint) {

        // get watchdog annotation from method
        MethodSignature methodSignature = (MethodSignature)proceedingJoinPoint.getSignature();
        Watchdog methodAnnotation = methodSignature.getMethod().getAnnotation(Watchdog.class);

        if (methodAnnotation != null) {
            return methodAnnotation;
        }

        // get watchdog annotation at class
        return (Watchdog)proceedingJoinPoint.getSignature().getDeclaringType().getAnnotation(Watchdog.class);

    }

    /**
     * Tries to get the watchdog annotation for the called method or the methods class.
     *
     * @param proceedingJoinPoint The aspectj join point
     * @return The watchdog annotation of the method, the class or null if neither the method or class aren't annotated with the watchdog annotation
     */
    public static ErrorMessage getErrorMessageAnnotation(final ProceedingJoinPoint proceedingJoinPoint) {

        // get watchdog annotation from method
        MethodSignature methodSignature = (MethodSignature)proceedingJoinPoint.getSignature();
        ErrorMessage methodAnnotation = methodSignature.getMethod().getAnnotation(ErrorMessage.class);

        if (methodAnnotation != null) {
            return methodAnnotation;
        }
        else {
            return null;
        }

    }

    /**
     * Checks whether the passed Throwable is contained in methods throws part.
     *
     * @param proceedingJoinPoint The aspectj join point
     * @param thrownException The Throwable which must be looked for
     * @return true if Throwable was found and must be suppressed, otherwise false
     */
    public static boolean checkIfMethodThrowsContainsPassedException(final ProceedingJoinPoint proceedingJoinPoint, Throwable thrownException) {

        if (proceedingJoinPoint == null || thrownException == null) {
            return false;
        }

        Class[] throwsClassesFromMethodSignature = getDefinedThrowsFromMethodSignature(proceedingJoinPoint);
        return checkClassIsDefinedInThrowsException(throwsClassesFromMethodSignature, thrownException);
    }

    /**
     * Checks whether the passed Throwable is defined included in passed classes array or is subtype of one of the included classes.
     *
     * @param classes the classes to search
     * @param thrownException the Throwable which must be searched for
     * @return true if Throwable was found, otherwise false
     */
    public static boolean checkClassIsDefinedInThrowsException(Class[] classes, Throwable thrownException) {

        // return false if either passed classes array or thrownException are null.
        if (classes == null || thrownException == null) {
            return false;
        }

        // loop through classes array to check for matching type
        for (Class clazz : classes) {

            if (clazz.isInstance(thrownException)) {
                return true;
            }

        }

        return false;
    }

    /**
     * Gets all Exceptions declared at the throws part of the method signature.
     *
     * @param proceedingJoinPoint the proceeding join point to get the method signature from.
     * @return All defined exceptions that must be caught (are defined in the method signature in the throws part)
     */
    public static Class[] getDefinedThrowsFromMethodSignature(final ProceedingJoinPoint proceedingJoinPoint) {

        if (proceedingJoinPoint == null) {
            return new Class[0];
        }

        // get watchdog annotation from method
        MethodSignature methodSignature = (MethodSignature)proceedingJoinPoint.getSignature();

        return methodSignature.getMethod().getExceptionTypes();
    }

    /**
     * Checks whether the exception should be processed or not.
     *
     * @param watchdogAnnotation the watchdog annotation to check.
     * @return true, if passed watchdogAnnotation is not null and not disabled vie system properties, otherwise false.
     */
    public static boolean checkProcessWatchdog(final Watchdog watchdogAnnotation, final ProceedingJoinPoint proceedingJoinPoint, final Throwable throwable) {
        // check if watchdog aspect processing is deactivated by annotation
        if (watchdogAnnotation != null && watchdogAnnotation.isActive()) {

            // checks if throws annotations must be suppressed
            boolean throwableIsPartOfThrowsDeclaration = WatchdogUtils.checkIfMethodThrowsContainsPassedException(proceedingJoinPoint, throwable);

            if (!watchdogAnnotation.suppressThrowsExceptions() || (watchdogAnnotation.suppressThrowsExceptions() && !throwableIsPartOfThrowsDeclaration)) {

                return true;

            }

        }

        return false;
    }

}


/*
 * Created on Feb 9, 2008
 *
 * 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.
 *
 * Copyright @2008-2013 the original author or authors.
 */
package org.fest.assertions;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.util.List;

import static java.lang.String.format;
import static org.fest.assertions.FileContentComparator.LineDiff.lineDiff;
import static org.fest.util.Closeables.closeQuietly;
import static org.fest.util.Lists.newArrayList;
import static org.fest.util.Objects.*;
import static org.fest.util.Strings.quote;

/**
 * Compares the contents of two files.
 *
 * @author David DIDIER
 * @author Alex Ruiz
 * @author Yvonne Wang
 */
class FileContentComparator {
  private static final String EOF = "EOF";

  /*
   * Verifies that the given files have the same content. Adapted from FileAssert (from JUnit-addons.)
   */
  @NotNull LineDiff[] compareContents(@NotNull File actual, @NotNull File expected) throws IOException {
    InputStream ais = null;
    InputStream eis = null;
    try {
      ais = new FileInputStream(actual);
      eis = new FileInputStream(expected);
      List<LineDiff> diffs = verifyEqualContent(readerFor(ais), readerFor(eis));
      return diffs.toArray(new LineDiff[diffs.size()]);
    } finally {
      closeQuietly(eis);
      closeQuietly(ais);
    }
  }

  private @NotNull LineNumberReader readerFor(@NotNull InputStream inputStream) {
    return new LineNumberReader(new BufferedReader(new InputStreamReader(inputStream)));
  }

  private @NotNull List<LineDiff> verifyEqualContent(
      @NotNull LineNumberReader actual, @NotNull LineNumberReader expected) throws IOException {
    List<LineDiff> diffs = newArrayList();
    while (true) {
      if (!expected.ready() && !actual.ready()) {
        return diffs;
      }
      int lineNumber = expected.getLineNumber();
      String actualLine = actual.readLine();
      String expectedLine = expected.readLine();
      if (areEqual(actualLine, expectedLine)) {
        continue;
      }
      diffs.add(lineDiff(lineNumber, actualLine, expectedLine));
      if (!actual.ready() && expected.ready()) {
        diffs.add(lineDiff(lineNumber, EOF, expectedLine));
        return diffs;
      }
      if (actual.ready() && !expected.ready()) {
        diffs.add(lineDiff(lineNumber, actualLine, EOF));
        return diffs;
      }
    }
  }

  static class LineDiff {
    final int lineNumber;
    final String actual;
    final String expected;

    private LineDiff(int lineNumber, @Nullable String actual, @Nullable String expected) {
      this.lineNumber = lineNumber;
      this.actual = actual;
      this.expected = expected;
    }

    static LineDiff lineDiff(int lineNumber, @Nullable String actual, @Nullable String expected) {
      return new LineDiff(lineNumber, actual, expected);
    }

    @Override
    public int hashCode() {
      final int prime = HASH_CODE_PRIME;
      int result = 1;
      result = prime * result + hashCodeFor(actual);
      result = prime * result + hashCodeFor(expected);
      result = prime * result + lineNumber;
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null) {
        return false;
      }
      if (getClass() != obj.getClass()) {
        return false;
      }
      LineDiff other = (LineDiff) obj;
      if (!areEqual(actual, other.actual)) {
        return false;
      }
      if (!areEqual(expected, other.expected)) {
        return false;
      }
      return lineNumber == other.lineNumber;
    }

    @Override
    public String toString() {
      return format("LineDiff [actual=%s, expected=%s, lineNumber=%d]", quote(actual), quote(expected), lineNumber);
    }
  }
}


/*
 * Created on Oct 4, 2009
 *
 * 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.
 *
 * Copyright @2009-2013 the original author or authors.
 */
package org.fest.assertions;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static org.fest.assertions.ErrorMessages.*;
import static org.fest.assertions.Fail.comparisonFailed;

/**
 * Template for assertions applicable to {@link Comparable}s.
 *
 * @param <S> used to simulate "self types." For more information please read "Emulating 'self types' using Java Generics to simplify fluent API implementation."
 * @param <T> the type of the "actual" value.
 * @author Alex Ruiz
 * @author Ted M. Young
 */
public abstract class ComparableAssert<S, T extends Comparable<T>> extends GenericAssert<S, T> {
  /**
   * Creates a new {@link ComparableAssert}.
   *
   * @param selfType the "self type."
   * @param actual   the target to verify.
   */
  protected ComparableAssert(@NotNull Class<S> selfType, @Nullable T actual) {
    super(selfType, actual);
  }

  /**
   * Verifies that the actual {@link Comparable} is equal to the given one.
   *
   * @param expected the given {@code Comparable} to compare the actual {@code Comparable} to.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Comparable} is {@code null}.
   * @throws AssertionError if the actual {@code Comparable} is not equal to the given one.
   */
  public final @NotNull S isEqualByComparingTo(T expected) {
    isNotNull();
    if (actual.compareTo(expected) == 0) {
      return myself();
    }
    failIfCustomMessageIsSet();
    throw comparisonFailed(rawDescription(), actual, expected);
  }

  /**
   * Verifies that the actual {@link Comparable} is not equal to the given one.
   *
   * @param expected the given {@code Comparable} to use to compare to the actual {@code Comparable}.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Comparable} is {@code null}.
   * @throws AssertionError if the actual {@code Comparable} is equal to the given one.
   */
  public final @NotNull S isNotEqualByComparingTo(T expected) {
    isNotNull();
    if (actual.compareTo(expected) != 0) {
      return myself();
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedEqual(actual, expected));
  }

  /**
   * Verifies that the actual {@link Comparable} is less than the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Comparable} is {@code null}.
   * @throws AssertionError if the actual {@code Comparable} is not less than the given one.
   */
  public final @NotNull S isLessThan(T other) {
    isNotNull();
    if (actual.compareTo(other) < 0) {
      return myself();
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedGreaterThanOrEqualTo(actual, other));
  }

  /**
   * Verifies that the actual {@link Comparable} is greater than the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Comparable} is {@code null}.
   * @throws AssertionError if the actual {@code Comparable} is not greater than the given one.
   */
  public final @NotNull S isGreaterThan(@Nullable T other) {
    isNotNull();
    if (actual.compareTo(other) > 0) {
      return myself();
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedLessThanOrEqualTo(actual, other));
  }

  /**
   * Verifies that the actual {@link Comparable} is less than or equal to the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Comparable} is {@code null}.
   * @throws AssertionError if the actual {@code Comparable} is not less than or equal to the given one.
   */
  public final @NotNull S isLessThanOrEqualTo(@Nullable T other) {
    isNotNull();
    if (actual.compareTo(other) <= 0) {
      return myself();
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedGreaterThan(actual, other));
  }

  /**
   * Verifies that the actual {@link Comparable} is greater than or equal to the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Comparable} is {@code null}.
   * @throws AssertionError if the actual {@code Comparable} is not greater than or equal to the given one.
   */
  public final @NotNull S isGreaterThanOrEqualTo(@Nullable T other) {
    isNotNull();
    if (actual.compareTo(other) >= 0) {
      return myself();
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedLessThan(actual, other));
  }
}


/*
 * Created on Jun 7, 2007
 *
 * 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.
 *
 * Copyright @2007-2013 the original author or authors.
 */
package org.fest.assertions;

import org.fest.util.VisibleForTesting;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import static org.fest.assertions.ErrorMessages.unexpectedEqual;
import static org.fest.assertions.ErrorMessages.unexpectedNotEqual;
import static org.fest.assertions.Fail.comparisonFailed;
import static org.fest.assertions.Fail.failWithMessage;
import static org.fest.assertions.Formatting.format;
import static org.fest.assertions.Threshold.threshold;
import static org.fest.util.Objects.areEqual;
import static org.fest.util.Preconditions.checkNotNull;

/**
 * Assertions for {@code BufferedImage}s.
 *

 * To create a new instance of this class invoke {@link Assertions#assertThat(BufferedImage)}.
 *
 * @author Yvonne Wang
 * @author Alex Ruiz
 * @author Ansgar Konermann
 */
public class ImageAssert extends GenericAssert<ImageAssert, BufferedImage> {
  private static final Threshold ZERO_THRESHOLD = threshold(0);
  private static ImageReader imageReader = new ImageReader();

  /**
   * Creates a new {@link ImageAssert}.
   *
   * @param actual the target to verify.
   */
  protected ImageAssert(@Nullable BufferedImage actual) {
    super(ImageAssert.class, actual);
  }

  /**
   * Reads the image in the specified path.
   *
   * @param imageFilePath the path of the image to read.
   * @return the read image.
   * @throws NullPointerException     if the given path is {@code null}.
   * @throws IllegalArgumentException if the given path does not belong to a file.
   * @throws IOException              if any I/O error occurred while reading the image.
   */
  public static @Nullable BufferedImage read(@NotNull String imageFilePath) throws IOException {
    checkNotNull(imageFilePath);
    File imageFile = new File(imageFilePath);
    if (!imageFile.isFile()) {
      throw new IllegalArgumentException(format("The path <%s> does not belong to a file", imageFilePath));
    }
    return imageReader.read(imageFile);
  }

  private static @Nullable Dimension sizeOf(@Nullable BufferedImage image) {
    if (image == null) {
      return null;
    }
    return new Dimension(image.getWidth(), image.getHeight());
  }

  @VisibleForTesting
  static void imageReader(@NotNull ImageReader newImageReader) {
    imageReader = newImageReader;
  }

  /**
   * Verifies that the actual image is equal to the given one. Two images are equal if they have the same size and the
   * pixels at the same coordinates have the same color.
   *
   * @param expected the given image to compare the actual image to.
   * @return this assertion object.
   * @throws AssertionError if the actual image is not equal to the given one.
   */
  @Override
  public @NotNull ImageAssert isEqualTo(@Nullable BufferedImage expected) {
    return isEqualTo(expected, ZERO_THRESHOLD);
  }

  /**
   * Verifies that the actual image is equal to the given one. Two images are equal if:
   *

   *
they have the same size

   *
the difference between the RGB values of the color of each pixel is less than or equal to the given threshold

   *

   *
   * @param expected  the given image to compare the actual image to.
   * @param threshold the threshold to use to decide if the color of two pixels are similar: two pixels that are
   *                  identical to the human eye may still have slightly different color values. For example, by using a
   *                  threshold of 1 we can indicate that a blue value of 60 is similar to a blue value of 61.
   * @return this assertion object.
   * @throws NullPointerException if {@code threshold} is {@code null}.
   * @throws AssertionError       if the actual image is not equal to the given one.
   * @since 1.1
   */
  public @NotNull ImageAssert isEqualTo(@Nullable BufferedImage expected, @NotNull Threshold threshold) {
    checkNotNull(threshold);
    if (areEqual(actual, expected)) {
      return this;
    }
    failIfNull(expected);
    failIfNotEqual(sizeOf(actual), sizeOf(expected));
    failIfNotEqualColor(checkNotNull(expected), threshold);
    return this;
  }

  private void failIfNull(@Nullable BufferedImage expected) {
    if (expected != null) {
      return;
    }
    failIfCustomMessageIsSet();
    fail(unexpectedNotEqual(actual, null));
  }

  private void failIfNotEqual(@Nullable Dimension a, @Nullable Dimension e) {
    if (areEqual(a, e)) {
      return;
    }
    failIfCustomMessageIsSet();
    fail(format("image size: expected:<%s> but was:<%s>", new DimensionFormatter(e), new DimensionFormatter(a)));
  }

  private void failIfNotEqualColor(@NotNull BufferedImage expected, @NotNull Threshold threshold) {
    int w = actual.getWidth();
    int h = actual.getHeight();
    for (int x = 0; x < w; x++) {
      for (int y = 0; y < h; y++) {
        failIfNotEqual(new RGBColor(actual.getRGB(x, y)), new RGBColor(expected.getRGB(x, y)), threshold, x, y);
      }
    }
  }

  private void failIfNotEqual(@NotNull RGBColor a, @NotNull RGBColor e, @NotNull Threshold threshold, int x, int y) {
    if (a.isEqualTo(e, threshold.value())) {
      return;
    }
    failIfCustomMessageIsSet();
    fail(String.format("expected:<%s> but was:<%s> at pixel [%d,%d]", a, e, x, y));
  }

  /**
   * Verifies that the actual image is not equal to the given one. Two images are equal if they have the same size and
   * the pixels at the same coordinates have the same color.
   *
   * @param image the given image to compare the actual image to.
   * @return this assertion object.
   * @throws AssertionError if the actual image is equal to the given one.
   */
  @Override
  public @NotNull ImageAssert isNotEqualTo(@Nullable BufferedImage image) {
    if (areEqual(actual, image)) {
      failIfCustomMessageIsSet();
      throw failure(unexpectedEqual(actual, image));
    }
    if (image == null) {
      return this;
    }
    if (areEqual(sizeOf(actual), sizeOf(image)) && hasEqualColor(image)) {
      failIfCustomMessageIsSet();
      throw failure("images are equal");
    }
    return this;
  }

  private boolean hasEqualColor(@NotNull BufferedImage expected) {
    int w = actual.getWidth();
    int h = actual.getHeight();
    for (int x = 0; x < w; x++) {
      for (int y = 0; y < h; y++) {
        if (actual.getRGB(x, y) != expected.getRGB(x, y)) {
          return false;
        }
      }
    }
    return true;
  }

  /**
   * Verifies that the size of the actual image is equal to the given one.
   *
   * @param expected the expected size of the actual image.
   * @return this assertion object.
   * @throws AssertionError       if the actual image is {@code null}.
   * @throws NullPointerException if the given size is {@code null}.
   * @throws AssertionError       if the size of the actual image is not equal to the given one.
   */
  public @NotNull ImageAssert hasSize(@NotNull Dimension expected) {
    isNotNull();
    checkNotNull(expected);
    Dimension actualDimension = new Dimension(actual.getWidth(), actual.getHeight());
    if (areEqual(actualDimension, expected)) {
      return this;
    }
    failWithMessage(customErrorMessage());
    throw comparisonFailed(rawDescription(), new DimensionFormatter(actualDimension), new DimensionFormatter(expected));
  }

  private static class DimensionFormatter {
    private final Dimension dimension;

    DimensionFormatter(@Nullable Dimension dimension) {
      this.dimension = dimension;
    }

    @Override
    public String toString() {
      if (dimension == null) {
        return null;
      }
      String format = "(w=%d, h=%d)";
      return String.format(format, dimension.width, dimension.height);
    }
  }
}


/*
 * Created on Mar 31, 2008
 *
 * 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.
 *
 * Copyright @2008-2013 the original author or authors.
 */
package org.fest.assertions;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;

import static org.fest.util.Lists.emptyList;
import static org.fest.util.Lists.newArrayList;

/**
 * Utility methods for {@code Collection}s.
 *
 * @author Alex Ruiz
 */
public final class Collections {
  private Collections() {
  }

  /**
   * Attempts to find the given objects in the given {@code Collection}, returning the ones that were not found.
   *
   * @param target  the given {@code Collection}.
   * @param objects the elements to find in the given {@code Collection}.
   * @return the elements that were not found in the given {@code Collection}.
   */
  public static @NotNull Collection<Object> notFound(@Nullable Collection<?> target, @NotNull Object... objects) {
    if (target == null) {
      return emptyList();
    }
    List<Object> notFound = newArrayList();
    for (Object o : objects) {
      if (!target.contains(o)) {
        notFound.add(o);
      }
    }
    return notFound;
  }

  /**
   * Attempts to find the given objects in the given {@code Collection}, returning the ones that were found.
   *
   * @param target  the given {@code Collection}.
   * @param objects the elements to find in the given {@code Collection}.
   * @return the elements that were found in the given {@code Collection}.
   */
  public static @NotNull Collection<Object> found(@Nullable Collection<?> target, @NotNull Object... objects) {
    if (target == null) {
      return emptyList();
    }
    List<Object> found = newArrayList();
    for (Object o : objects) {
      if (target.contains(o)) {
        found.add(o);
      }
    }
    return found;
  }
}


/*
 * Created on May 21, 2007
 *
 * 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.
 *
 * Copyright @2007-2013 the original author or authors.
 */
package org.fest.assertions;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static org.fest.assertions.Formatting.format;

/**
 * Template for assertions for classes representing groups of values.
 *
 * @param <S> used to simulate "self types." For more information please read "Emulating 'self types' using Java Generics to simplify fluent API
 *            implementation."
 * @param  the type the "actual" value.
 * @author Yvonne Wang
 * @author Alex Ruiz
 */
public abstract class GroupAssert<S, A> extends GenericAssert<S, A> {
  /**
   * Creates a new {@link GroupAssert}.
   *
   * @param selfType the "self type."
   * @param actual   the target to verify.
   */
  protected GroupAssert(@NotNull Class<S> selfType, @Nullable A actual) {
    super(selfType, actual);
  }

  /**
   * Verifies that the actual group of values is {@code null} or empty.
   *
   * @throws AssertionError if the actual group of values is not {@code null} or not empty.
   */
  public final void isNullOrEmpty() {
    if (actual == null || !hasElements()) {
      return;
    }
    failIfCustomMessageIsSet();
    fail(format("expecting null or empty, but was:<%s>", actual));
  }

  /**
   * Verifies that the actual group of values is empty.
   *
   * @throws AssertionError if the actual group of values is {@code null} or not empty.
   */
  public final void isEmpty() {
    isNotNull();
    if (!hasElements()) {
      return;
    }
    failIfCustomMessageIsSet();
    fail(format("expecting empty, but was:<%s>", actual));
  }

  private boolean hasElements() {
    return actualGroupSize() > 0;
  }

  /**
   * Verifies that the actual group contains at least on value.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual group is {@code null} or empty.
   */
  public final @NotNull S isNotEmpty() {
    isNotNull();
    if (hasElements()) {
      return myself();
    }
    failIfCustomMessageIsSet();
    throw failure("expecting non-empty, but it was empty");
  }

  /**
   * Verifies that the number of values in the actual group is equal to the given one.
   *
   * @param expected the expected number of values in the actual group.
   * @return this assertion object.
   * @throws AssertionError if the number of values of the actual group is not equal to the given one.
   */
  public final @NotNull S hasSize(int expected) {
    isNotNull();
    int size = actualGroupSize();
    if (size == expected) {
      return myself();
    }
    failIfCustomMessageIsSet();
    throw failure(format("expected size:<%s> but was:<%s> for <%s>", expected, size, actual));
  }

  /**
   * Returns the size of the actual group of values (array, collection, etc.)
   *
   * @return the size of the actual group of values.
   * @throws AssertionError if the actual group of values is {@code null}.
   */
  protected abstract int actualGroupSize();
}


/*
 * Created on Dec 23, 2007
 *
 * 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.
 *
 * Copyright @2007-2013 the original author or authors.
 */
package org.fest.assertions;

import org.fest.assertions.FileContentComparator.LineDiff;
import org.fest.util.VisibleForTesting;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;

import static org.fest.assertions.Formatting.format;
import static org.fest.assertions.Formatting.inBrackets;
import static org.fest.util.Arrays.isNullOrEmpty;
import static org.fest.util.Preconditions.checkNotNull;

/**
 * Assertions for {@code File}.
 *

 * To create a new instance of this class invoke {@link Assertions#assertThat(File)}.
 *
 * @author David DIDIER
 * @author Yvonne Wang
 * @author Alex Ruiz
 */
public class FileAssert extends GenericAssert<FileAssert, File> {
  private final FileContentComparator comparator;

  /**
   * Creates a new {@link FileAssert}.
   *
   * @param actual the target to verify.
   */
  protected FileAssert(@Nullable File actual) {
    this(actual, new FileContentComparator());
  }

  @VisibleForTesting FileAssert(@Nullable File actual, @NotNull FileContentComparator comparator) {
    super(FileAssert.class, actual);
    this.comparator = comparator;
  }

  /**
   * Verifies that the actual {@code File} does not exist.
   *
   * @return this assertion object.
   * @throws AssertionError if the the actual {@code File} is {@code null}.
   * @throws AssertionError if the actual {@code File} exists.
   */
  public @NotNull FileAssert doesNotExist() {
    isNotNull();
    if (!actual.exists()) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(format("file:<%s> should not exist", actual));
  }

  /**
   * Verifies that the actual {@code File} does exist.
   *
   * @return this assertion object.
   * @throws AssertionError if the the actual {@code File} is {@code null}.
   * @throws AssertionError if the actual {@code File} does not exist.
   */
  public @NotNull FileAssert exists() {
    isNotNull();
    assertExists(actual);
    return this;
  }

  /**
   * Verifies that the size of the actual {@code File} is equal to the given one.
   *
   * @param expected the expected size of the actual {@code File}.
   * @return this assertion object.
   * @throws AssertionError if the the actual {@code File} is {@code null}.
   * @throws AssertionError if the size of the actual {@code File} is not equal to the given one.
   */
  public @NotNull FileAssert hasSize(long expected) {
    isNotNull();
    long size = actual.length();
    if (size == expected) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(format("size of file:<%s> expected:<%s> but was:<%s>", actual, expected, size));
  }

  /**
   * Verifies that the actual {@code File} is a directory.
   *
   * @return this assertion object.
   * @throws AssertionError if the the actual {@code File} is {@code null}.
   * @throws AssertionError if the actual {@code File} is not a directory.
   */
  public @NotNull FileAssert isDirectory() {
    isNotNull();
    if (actual.isDirectory()) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(format("file:<%s> should be a directory", actual));
  }

  /**
   * Verifies that the actual {@code File} is a regular file.
   *
   * @return this assertion object.
   * @throws AssertionError if the the actual {@code File} is {@code null}.
   * @throws AssertionError if the actual {@code File} is not a regular file.
   */
  public @NotNull FileAssert isFile() {
    isNotNull();
    if (actual.isFile()) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(format("file:<%s> should be a file", actual));
  }

  /**
   * Verifies that the content of the actual {@code File} is equal to the content of the given one. Adapted from FileAssert (from JUnit-addons.)
   *
   * @param expected the given {@code File} to compare the actual {@code File} to.
   * @return this assertion object.
   * @throws NullPointerException if the file to compare to is {@code null}.
   * @throws AssertionError       if the the actual {@code File} is {@code null}.
   * @throws AssertionError       if the content of the actual {@code File} is not equal to the content of the given one.
   */
  public @NotNull FileAssert hasSameContentAs(@NotNull File expected) {
    checkNotNull(expected);
    isNotNull();
    assertExists(actual).assertExists(expected);
    try {
      LineDiff[] diffs = comparator.compareContents(actual, expected);
      if (!isNullOrEmpty(diffs)) {
        fail(expected, diffs);
      }
    } catch (IOException e) {
      cannotCompareToExpectedFile(expected, e);
    }
    return this;
  }

  private void fail(@NotNull File expected, @NotNull LineDiff[] diffs) {
    failIfCustomMessageIsSet();
    StringBuilder b = new StringBuilder();
    b.append(String.format("file:%s and file:%s do not have same contents:", inBrackets(actual), inBrackets(expected)));
    for (LineDiff diff : diffs) {
      String line = String.format("%nline:<%d>, expected:%s but was:%s", diff.lineNumber, inBrackets(diff.expected),
          inBrackets(diff.actual));
      b.append(line);
    }
    fail(b.toString());
  }

  private void cannotCompareToExpectedFile(@NotNull File expected, @NotNull Exception e) {
    failIfCustomMessageIsSet(e);
    String message = format("unable to compare contents of files:<%s> and <%s>", actual, expected);
    fail(message, e);
  }

  private FileAssert assertExists(@NotNull File file) {
    if (file.exists()) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(format("file:<%s> should exist", file));
  }

  /**
   * Verifies that the actual {@code File} is a relative path.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual {@code File} is not a relative path.
   */
  public @NotNull FileAssert isRelative() {
    isNotNull();
    if (!actual.isAbsolute()) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(format("file:<%s> should be a relative path", actual));
  }

  /**
   * Verifies that the actual {@code File} is an absolute path.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual {@code File} is not an absolute path.
   */
  public @NotNull FileAssert isAbsolute() {
    isNotNull();
    if (actual.isAbsolute()) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(format("file:<%s> should be an absolute path", actual));
  }
}


/*
 * Created on Jan 11, 2011
 *
 * 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.
 *
 * Copyright @2011-2013 the original author or authors.
 */
package org.fest.assertions;

import org.fest.util.VisibleForTesting;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import static org.fest.util.Lists.newArrayList;
import static org.fest.util.Preconditions.checkNotNull;
import static org.fest.util.Sets.newLinkedHashSet;
import static org.fest.util.ToString.toStringOf;

/**
 * Assertions for {@code Iterator}s.
 * <p/>
 * To create a new instance of this class invoke {@link Assertions#assertThat(Iterator)}.
 *
 * @author Alex Ruiz
 * @since 1.4
 */
public class IteratorAssert extends ObjectGroupAssert<IteratorAssert, Iterator<?>> {
  /**
   * Creates a new {@link IteratorAssert}.
   *
   * @param actual the target to verify.
   */
  protected IteratorAssert(@Nullable Iterator<?> actual) {
    super(IteratorAssert.class, wrap(actual));
  }

  private static @Nullable Iterator<?> wrap(@Nullable Iterator<?> actual) {
    if (actual == null) {
      return null;
    }
    return new PrettyPrintIterator(actual);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected @NotNull IteratorAssert onProperty(@NotNull String propertyName) {
    checkNotNull(propertyName);
    isNotNull();
    List<Object> subset = PropertySupport.instance().propertyValues(propertyName, newArrayList(contentOfActual()));
    return new IteratorAssert(subset.iterator());
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected @Nullable Set<Object> actualAsSet() {
    if (actual == null) {
      return null;
    }
    return newLinkedHashSet(contentOfActual());
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected @Nullable List<Object> actualAsList() {
    if (actual == null) {
      return null;
    }
    return newArrayList(contentOfActual());
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected int actualGroupSize() {
    isNotNull();
    return contentOfActual().length;
  }

  private @NotNull Object[] contentOfActual() {
    PrettyPrintIterator wrapped = (PrettyPrintIterator) actual;
    return wrapped.contents().toArray();
  }

  @VisibleForTesting
  static class PrettyPrintIterator implements Iterator<Object> {
    private final Iterator<?> wrapped;
    boolean wrappedWasConsumed;
    List<Object> wrappedContents;
    Iterator<Object> iterator;

    PrettyPrintIterator(@NotNull Iterator<?> wrapped) {
      this.wrapped = wrapped;
    }

    @NotNull List<Object> contents() {
      consumeIterator();
      return wrappedContents;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasNext() {
      consumeIterator();
      return iterator.hasNext();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public @Nullable Object next() {
      consumeIterator();
      return iterator.next();
    }

    private synchronized void consumeIterator() {
      if (wrappedWasConsumed) {
        return;
      }
      wrappedContents = newArrayList(wrapped);
      wrappedWasConsumed = true;
      iterator = wrappedContents.iterator();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
      consumeIterator();
      return toStringOf(wrappedContents);
    }
  }
}


/*
 * Created on Jun 26, 2010
 *
 * 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.
 *
 * Copyright @2010-2013 the original author or authors.
 */
package org.fest.assertions;

import org.fest.util.IntrospectionError;
import org.fest.util.Preconditions;
import org.fest.util.VisibleForTesting;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.beans.PropertyDescriptor;
import java.util.Collection;
import java.util.List;

import static org.fest.util.Collections.isNullOrEmpty;
import static org.fest.util.Collections.nonNullElementsIn;
import static org.fest.util.Introspection.getProperty;
import static org.fest.util.Lists.emptyList;
import static org.fest.util.Lists.newArrayList;
import static org.fest.util.Preconditions.checkNotNull;
import static org.fest.util.Strings.quote;

/**
 * Utility methods for properties access.
 *
 * @author Joel Costigliola
 * @author Alex Ruiz
 * @since 1.3
 */
final class PropertySupport {
  private static final String SEPARATOR = ".";
  private static final PropertySupport INSTANCE = new PropertySupport();
  private final JavaBeanDescriptor javaBeanDescriptor;

  private PropertySupport() {
    this(new JavaBeanDescriptor());
  }

  PropertySupport(@NotNull JavaBeanDescriptor javaBeanDescriptor) {
    this.javaBeanDescriptor = javaBeanDescriptor;
  }

  static @NotNull PropertySupport instance() {
    return INSTANCE;
  }

  /**
   * Returns a list containing the values of the given property name, from the elements of the given collection. If the
   * given collection is empty or {@code null}, this method will return an empty collection.
   *

   * For example, given the nested property "address.street.number", this method will:
   *

   *
extract a collection of "address" from the given collection (remaining property is 'street.number')

   *
extract a collection of "street" from the "address" collection (remaining property is 'number')

   *
extract a collection of "number" from the "street" collection

   *

   *
   * @param propertyName the name of the property. It may be a nested property.
   * @param target       the given collection.
   * @return a list containing the values of the given property name, from the elements of the given collection.
   * @throws NullPointerException if given property name is {@code null}.
   * @throws IntrospectionError   if an element in the given collection does not have a matching property.
   */
  @NotNull List<Object> propertyValues(@NotNull String propertyName, @Nullable Collection<?> target) {
    if (isNullOrEmpty(target)) {
      return emptyList();
    }
    // ignore null elements as we can't extract a property from a null object
    Collection<?> nonNullElements = nonNullElementsIn(target);
    if (isNestedProperty(propertyName)) {
      String firstProperty = firstPropertyIfNested(propertyName);
      List<Object> firstPropertyValues = propertyValues(firstProperty, nonNullElements);
      // extract next sub property values until reaching a last sub property
      return propertyValues(removeFirstPropertyIfNested(propertyName), firstPropertyValues);
    }
    return simplePropertyValues(propertyName, nonNullElements);
  }

  private @NotNull List<Object> simplePropertyValues(@NotNull String propertyName, @NotNull Collection<?> target) {
    List<Object> propertyValues = newArrayList();
    for (Object e : target) {
      if (e != null) {
        propertyValues.add(propertyValue(propertyName, e));
      }
    }
    return propertyValues;
  }

  /**
   * Returns {@code true} if property is nested, {@code false} otherwise.
   *

   * Examples:
   *

   * isNestedProperty("address.street"); // true
   * isNestedProperty("address.street.name"); // true
   * isNestedProperty("person"); // false
   * isNestedProperty(".name"); // false
   * isNestedProperty("person."); // false
   * isNestedProperty("person.name."); // false
   * isNestedProperty(".person.name"); // false
   * isNestedProperty("."); // false
   * isNestedProperty(""); // false
   *

   *
   * @param propertyName the given property name.
   * @return {@code true} if property is nested, {@code false} otherwise.
   * @throws NullPointerException if given property name is {@code null}.
   */
  boolean isNestedProperty(String propertyName) {
    Preconditions.checkNotNull(propertyName);
    return propertyName.contains(SEPARATOR) && !propertyName.startsWith(SEPARATOR) && !propertyName.endsWith(SEPARATOR);
  }

  /**
   * Removes the first property from the given property name only if the given property name belongs to a nested
   * property. For example, given the nested property "address.street.name", this method will return "street.name". This
   * method returns an empty {@code String} if the given property name does not belong to a nested property.
   *
   * @param propertyName the given property name.
   * @return the given property name without its first property, if the property name belongs to a nested property;
   *         otherwise, it will return an empty {@code String}.
   * @throws NullPointerException if given property name is {@code null}.
   */
  @NotNull String removeFirstPropertyIfNested(@NotNull String propertyName) {
    if (!isNestedProperty(propertyName)) {
      return "";
    }
    return checkNotNull(propertyName.substring(propertyName.indexOf(SEPARATOR) + 1));
  }

  /**
   * Returns the first property from the given property name only if the given property name belongs to a nested
   * property. For example, given the nested property "address.street.name", this method will return "address". This
   * method returns the given property name unchanged if it does not belong to a nested property.
   *
   * @param propertyName the given property name.
   * @return the first property from the given property name, if the property name belongs to a nested property;
   *         otherwise, it will return the given property name unchanged.
   * @throws NullPointerException if given property name is {@code null}.
   */
  @NotNull String firstPropertyIfNested(@NotNull String propertyName) {
    if (!isNestedProperty(propertyName)) {
      return propertyName;
    }
    return checkNotNull(propertyName.substring(0, propertyName.indexOf(SEPARATOR)));
  }

  private @Nullable Object propertyValue(@NotNull String propertyName, @NotNull Object target) {
    PropertyDescriptor descriptor = getProperty(propertyName, target);
    return propertyValue(descriptor, propertyName, target);
  }

  @VisibleForTesting
  @Nullable Object propertyValue(
      @NotNull PropertyDescriptor descriptor, @NotNull String propertyName, @NotNull Object target) {
    try {
      return javaBeanDescriptor.invokeReadMethod(descriptor, target);
    } catch (Exception e) {
      throw new IntrospectionError("Unable to obtain the value in property " + quote(propertyName), e);
    }
  }
}


/*
 * Created on Oct 10, 2007
 *
 * 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.
 *
 * Copyright @2007-2013 the original author or authors.
 */
package org.fest.assertions;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static org.fest.util.Preconditions.checkNotNull;
import static org.fest.util.Strings.isNullOrEmpty;
import static org.fest.util.Strings.quote;
import static org.fest.util.ToString.toStringOf;

/**
 * Creates instances of JUnit's {@code ComparisonFailure}.
 *
 * @author Yvonne Wang
 * @author Alex Ruiz
 */
public final class ComparisonFailureFactory {
  private static final String EMPTY_MESSAGE = "";
  private static ConstructorInvoker constructorInvoker = new ConstructorInvoker();

  private ComparisonFailureFactory() {
  }

  static void constructorInvoker(@NotNull ConstructorInvoker newConstructorInvoker) {
    constructorInvoker = checkNotNull(newConstructorInvoker);
  }

  /**
   * Creates a new instance of JUnit's {@code ComparisonFailure} only if JUnit 4+ is in the classpath.
   *
   * @param message  the identifying message or {@code null}.
   * @param expected the expected value.
   * @param actual   the actual value.
   * @return the created {@code ComparisonFailure}, or {@code null} if JUnit 4+ is not in the classpath.
   */
  public static @Nullable AssertionError comparisonFailure(
      @Nullable String message, @Nullable Object expected, @Nullable Object actual) {
    try {
      return newComparisonFailure(clean(message), expected, actual);
    } catch (Exception e) {
      return null;
    }
  }

  private static @NotNull String clean(@Nullable String message) {
    return message == null ? "" : message;
  }

  private static @Nullable AssertionError newComparisonFailure(
      @Nullable String message, @Nullable Object expected, @Nullable Object actual) throws Exception {
    String className = "org.junit.ComparisonFailure";
    Class<?>[] parameterTypes = new Class<?>[]{String.class, String.class, String.class};
    Object[] parameterValues = new Object[]{format(message), asString(expected), asString(actual)};
    Object o = constructorInvoker.newInstance(className, parameterTypes, parameterValues);
    if (o instanceof AssertionError) {
      return (AssertionError) o;
    }
    return null;
  }

  private static @Nullable String asString(@Nullable Object o) {
    if (o instanceof String) {
      return quote((String) o);
    }
    if (o == null) {
      return null;
    }
    return toStringOf(o);
  }

  private static @NotNull String format(@Nullable String message) {
    if (isNullOrEmpty(message)) {
      return EMPTY_MESSAGE;
    }
    return String.format("[%s]", message);
  }
}


/*
 * Created on Mar 29, 2009
 *
 * 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.
 *
 * Copyright @2009-2013 the original author or authors.
 */
package org.fest.assertions;

import org.fest.util.IntrospectionError;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Set;

import static java.util.Collections.emptyList;
import static org.fest.assertions.Formatting.format;
import static org.fest.util.Lists.newArrayList;
import static org.fest.util.Objects.areEqual;
import static org.fest.util.Preconditions.checkNotNull;
import static org.fest.util.Sets.newLinkedHashSet;

/**
 * Assertions for {@code List}s.
 *

 * To create a new instance of this class invoke {@link Assertions#assertThat(List)}.
 *
 * @author Alex Ruiz
 * @author Yvonne Wang
 * @since 1.1
 */
public class ListAssert extends ObjectGroupAssert<ListAssert, List<?>> {
  /**
   * Creates a new {@link ListAssert}.
   *
   * @param actual the target to verify.
   */
  protected ListAssert(@Nullable List<?> actual) {
    super(ListAssert.class, actual);
  }

  /**
   * Verifies that the actual {@code List} contains the given object at the given index.
   *
   * @param o     the object to look for.
   * @param index the index where the object should be stored in the actual {@code List}.
   * @return this assertion object.
   * @throws NullPointerException      if the given {@code Index} is {@code null}.
   * @throws IndexOutOfBoundsException if the value of the given {@code Index} is negative, or equal to or greater than
   *                                   the size of the actual {@code List}.
   * @throws AssertionError            if the given {@code List} does not contain the given object at the given index.
   */
  public @NotNull ListAssert contains(@Nullable Object o, @NotNull Index index) {
    checkNotNull(index);
    isNotNull().isNotEmpty();
    int indexValue = index.value();
    int listSize = actualGroupSize();
    if (indexValue < 0 || indexValue >= listSize) {
      failIndexOutOfBounds(indexValue);
    }
    Object actualElement = actual.get(indexValue);
    if (!areEqual(actualElement, o)) {
      failElementNotFound(o, actualElement, indexValue);
    }
    return this;
  }

  private void failElementNotFound(@Nullable Object e, @Nullable Object a, int index) {
    failIfCustomMessageIsSet();
    fail(format("expecting <%s> at index <%s> but found <%s>", e, index, a));
  }

  private void failIndexOutOfBounds(int index) {
    throw new IndexOutOfBoundsException(formattedErrorMessage(format(
        "The index <%s> should be greater than or equal to zero and less than %s", index, actualGroupSize())));
  }

  /**
   * Verifies that the actual {@code List} contains the given sequence of objects, without any other objects between
   * them.
   *
   * @param sequence the sequence of objects to look for.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code List} is {@code null}.
   * @throws AssertionError if the given array is {@code null}.
   * @throws AssertionError if the actual {@code List} does not contain the given sequence of objects.
   */
  public @NotNull ListAssert containsSequence(@NotNull Object... sequence) {
    isNotNull();
    checkNotNull(sequence);
    int sequenceSize = sequence.length;
    if (sequenceSize == 0) {
      return this;
    }
    int indexOfFirst = actual.indexOf(sequence[0]);
    if (indexOfFirst == -1) {
      failIfSequenceNotFound(sequence);
    }
    int listSize = actualGroupSize();
    for (int i = 0; i < sequenceSize; i++) {
      int actualIndex = indexOfFirst + i;
      if (actualIndex > listSize - 1) {
        failIfSequenceNotFound(sequence);
      }
      if (!areEqual(sequence[i], actual.get(actualIndex))) {
        failIfSequenceNotFound(sequence);
      }
    }
    return this;
  }

  private void failIfSequenceNotFound(@NotNull Object[] notFound) {
    failIfCustomMessageIsSet();
    fail(format("list:<%s> does not contain the sequence:<%s>", actual, notFound));
  }

  /**
   * Verifies that the actual {@code List} starts with the given sequence of objects, without any other objects between
   * them. Same as {@link #containsSequence}, but verifies also that first given object is also first element of
   * {@code List}.
   *
   * @param sequence the sequence of objects to look for.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code List} is {@code null}.
   * @throws AssertionError if the given array is {@code null}.
   * @throws AssertionError if the actual {@code List} is not empty and with the given sequence of objects is empty.
   * @throws AssertionError if the actual {@code List} does not start with the given sequence of objects.
   */
  public @NotNull ListAssert startsWith(@NotNull Object... sequence) {
    isNotNull();
    checkNotNull(sequence);
    int sequenceSize = sequence.length;
    int listSize = actualGroupSize();
    if (sequenceSize == 0 && listSize == 0) {
      return this;
    }
    if (sequenceSize == 0 && listSize != 0) {
      failIfNotStartingWithSequence(sequence);
    }
    if (listSize < sequenceSize) {
      failIfNotStartingWithSequence(sequence);
    }
    for (int i = 0; i < sequenceSize; i++) {
      if (!areEqual(sequence[i], actual.get(i))) {
        failIfNotStartingWithSequence(sequence);
      }
    }
    return this;
  }

  private void failIfNotStartingWithSequence(@NotNull Object[] notFound) {
    failIfCustomMessageIsSet();
    fail(format("list:<%s> does not start with the sequence:<%s>", actual, notFound));
  }

  /**
   * Verifies that the actual {@code List} ends with the given sequence of objects, without any other objects between
   * them. Same as {@link #containsSequence}, but verifies also that last given object is also last element of
   * {@code List}.
   *
   * @param sequence the sequence of objects to look for.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code List} is {@code null}.
   * @throws AssertionError if the given array is {@code null}.
   * @throws AssertionError if the actual {@code List} is not empty and with the given sequence of objects is empty.
   * @throws AssertionError if the actual {@code List} does not end with the given sequence of objects.
   */
  public @NotNull ListAssert endsWith(@NotNull Object... sequence) {
    isNotNull();
    checkNotNull(sequence);
    int sequenceSize = sequence.length;
    int listSize = actualGroupSize();
    if (sequenceSize == 0 && listSize == 0) {
      return this;
    }
    if (sequenceSize == 0 && listSize != 0) {
      failIfNotEndingWithSequence(sequence);
    }
    if (listSize < sequenceSize) {
      failIfNotEndingWithSequence(sequence);
    }
    for (int i = 0; i < sequenceSize; i++) {
      int sequenceIndex = sequenceSize - 1 - i;
      int listIndex = listSize - 1 - i;
      if (!areEqual(sequence[sequenceIndex], actual.get(listIndex))) {
        failIfNotEndingWithSequence(sequence);
      }
    }
    return this;
  }

  private void failIfNotEndingWithSequence(@NotNull Object[] notFound) {
    failIfCustomMessageIsSet();
    fail(format("list:<%s> does not end with the sequence:<%s>", actual, notFound));
  }

  /**
   * Returns the number of elements in the actual {@code List}.
   *
   * @return the number of elements in the actual {@code List}.
   * @throws AssertionError if the actual {@code List} is {@code null}.
   */
  @Override
  protected int actualGroupSize() {
    isNotNull();
    return actual.size();
  }

  /**
   * Verifies that the actual {@code List} contains the given objects, in the same order. This method works just like
   * {@code isEqualTo(List)}, with the difference that internally the given array is converted to a {@code List}.
   *
   * @param objects the objects to look for.
   * @return this assertion object.
   * @throws AssertionError       if the actual {@code List} is {@code null}.
   * @throws NullPointerException if the given array is {@code null}.
   * @throws AssertionError       if the actual {@code List} does not contain the given objects.
   */
  public @NotNull ListAssert containsExactly(@NotNull Object... objects) {
    checkNotNull(objects);
    return isNotNull().isEqualTo(newArrayList(objects));
  }

  /**
   * Creates a new instance of {@link ListAssert} whose target list contains the values of the given
   * property name from the elements of this {@code ListAssert}'s list. Property access works with both simple
   * properties like {@code Person.age} and nested properties {@code Person.father.age}.
   *

   * For example, let's say we have a list of {@code Person} objects and you want to verify their age:
   *

   * assertThat(persons).onProperty("age").containsOnly(25, 16, 44, 37); // simple property
   * assertThat(persons).onProperty("father.age").containsOnly(55, 46, 74, 62); // nested property
   *

   *
   * @param propertyName the name of the property to extract values from the actual list to build a new
   *                     {@code ListAssert}.
   * @return a new {@code ListAssert} containing the values of the given property name from the elements of this
   *         {@code ListAssert}'s list.
   * @throws AssertionError     if the actual list is {@code null}.
   * @throws IntrospectionError if an element in the given list does not have a matching property.
   * @since 1.3
   */
  @Override
  public @NotNull ListAssert onProperty(@NotNull String propertyName) {
    isNotNull();
    if (actual.isEmpty()) {
      return new ListAssert(emptyList());
    }
    return new ListAssert(PropertySupport.instance().propertyValues(propertyName, actual));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected @Nullable Set<Object> actualAsSet() {
    return actual == null ? null : newLinkedHashSet(actual.toArray());
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected @Nullable List<Object> actualAsList() {
    return actual == null ? null : newArrayList(actual.toArray());
  }
}


/*
 * Created on Feb 16, 2008
 *
 * 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.
 *
 * Copyright @2008-2013 the original author or authors.
 */
package org.fest.assertions;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static java.lang.Double.valueOf;
import static java.lang.Math.abs;
import static org.fest.assertions.ErrorMessages.*;
import static org.fest.assertions.Formatting.format;

/**
 * Assertions for {@code Double}s and {@code double}s.
 * <p/>
 * To create a new instance of this class invoke {@link Assertions#assertThat(Double)} or
 * {@link Assertions#assertThat(double)}.
 *
 * @author Yvonne Wang
 * @author David DIDIER
 * @author Alex Ruiz
 * @author Ansgar Konermann
 */
public class DoubleAssert extends GenericAssert<DoubleAssert, Double> implements NumberAssert {
  private static final double ZERO = 0.0;

  /**
   * Creates a new {@link DoubleAssert}.
   *
   * @param actual the actual value to verify.
   */
  protected DoubleAssert(double actual) {
    super(DoubleAssert.class, actual);
  }

  /**
   * Creates a new {@link DoubleAssert}.
   *
   * @param actual the actual value to verify.
   */
  protected DoubleAssert(@Nullable Double actual) {
    super(DoubleAssert.class, actual);
  }

  /**
   * Creates a new holder for a delta value to be used in
   * {@link DoubleAssert#isEqualTo(double, org.fest.assertions.DoubleAssert.Delta)}.
   *
   * @param d the delta value.
   * @return a new delta value holder.
   * @deprecated use method {@link org.fest.assertions.Delta#delta(double)} instead. This method will be removed in
   *             version 2.0.
   */
  @Deprecated
  public static @NotNull Delta delta(double d) {
    return new Delta(d);
  }

  /**
   * Verifies that the actual {@code Double} is equal to the given one.
   *
   * @param expected the value to compare the actual one to.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not equal to the given one.
   */
  public @NotNull DoubleAssert isEqualTo(double expected) {
    return isEqualTo(valueOf(expected));
  }

  /**
   * Verifies that the actual {@code Double} is equal to the given one, within a positive delta.
   *
   * @param expected the value to compare the actual one to.
   * @param delta    the given delta.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not equal to the given one.
   * @deprecated use method {@link #isEqualTo(double, org.fest.assertions.Delta)} instead. This method will be removed
   *             in version 2.0.
   */
  @Deprecated
  public @NotNull DoubleAssert isEqualTo(double expected, Delta delta) {
    return isEqualTo(expected, delta.value);
  }

  /**
   * Verifies that the actual {@code Double} is equal to the given one, within a positive delta.
   *
   * @param expected the value to compare the actual one to.
   * @param delta    the given delta.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not equal to the given one.
   * @since 1.1
   */
  public @NotNull DoubleAssert isEqualTo(double expected, org.fest.assertions.Delta delta) {
    return isEqualTo(expected, delta.doubleValue());
  }

  private @NotNull DoubleAssert isEqualTo(double expected, double deltaValue) {
    return isEqualTo(valueOf(expected), deltaValue);
  }

  /**
   * Verifies that the actual {@code Double} is equal to the given one, within a positive delta.
   *
   * @param expected the value to compare the actual one to.
   * @param delta    the given delta.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not equal to the given one.
   * @since 1.3
   */
  public @NotNull DoubleAssert isEqualTo(@Nullable Double expected, @NotNull org.fest.assertions.Delta delta) {
    return isEqualTo(expected, delta.doubleValue());
  }

  private @NotNull DoubleAssert isEqualTo(@Nullable Double expected, double deltaValue) {
    if (actual == null || expected == null) {
      return isEqualTo(expected);
    }
    if (actual.compareTo(expected) == 0) {
      return this;
    }
    if (abs(expected - actual) <= deltaValue) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedNotEqual(actual, expected) + format(" using delta:<%s>", deltaValue));
  }

  /**
   * Verifies that the actual {@code Double} is not equal to the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is equal to the given one.
   */
  public @NotNull DoubleAssert isNotEqualTo(double other) {
    if (compareTo(other) != 0) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedEqual(actual, other));
  }

  /**
   * Verifies that the actual {@code Double} is greater than the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not greater than the given one.
   */
  public @NotNull DoubleAssert isGreaterThan(double other) {
    if (compareTo(other) > 0) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedLessThanOrEqualTo(actual, other));
  }

  /**
   * Verifies that the actual {@code Double} is less than the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not less than the given one.
   */
  public @NotNull DoubleAssert isLessThan(double other) {
    if (compareTo(other) < 0) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedGreaterThanOrEqualTo(actual, other));
  }

  /**
   * Verifies that the actual {@code Double} is greater or equal to the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not greater than or equal to the given one.
   */
  public @NotNull DoubleAssert isGreaterThanOrEqualTo(double other) {
    if (compareTo(other) >= 0) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedLessThan(actual, other));
  }

  /**
   * Verifies that the actual {@code Double} is less or equal to the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not less than or equal to the given one.
   */
  public @NotNull DoubleAssert isLessThanOrEqualTo(double other) {
    if (compareTo(other) <= 0) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedGreaterThan(actual, other));
  }

  private int compareTo(double other) {
    return Double.compare(actual, other);
  }

  /**
   * Verifies that the actual {@code Double} is equal to zero.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not equal to zero.
   */
  @Override
  public @NotNull DoubleAssert isZero() {
    return isEqualTo(ZERO);
  }

  /**
   * Verifies that the actual {@code Double} is positive.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not positive.
   */
  @Override
  public @NotNull DoubleAssert isPositive() {
    return isGreaterThan(ZERO);
  }

  /**
   * Verifies that the actual {@code Double} is negative.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not negative.
   */
  @Override
  public @NotNull DoubleAssert isNegative() {
    return isLessThan(ZERO);
  }

  /**
   * Verifies that the actual {@code Double} is equal to {@link Double#NaN}.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Double} is not equal to {@code NAN}.
   */
  public @NotNull DoubleAssert isNaN() {
    return isEqualTo(Double.NaN);
  }

  /**
   * Holds a delta value to be used in {@link DoubleAssert#isEqualTo(double, org.fest.assertions.DoubleAssert.Delta)}.
   *
   * @deprecated use top-level class {@link org.fest.assertions.Delta} instead. This class will be removed in version
   *             2.0.
   */
  @Deprecated
  public static class Delta {
    final double value;

    private Delta(double value) {
      this.value = value;
    }
  }
}


/*
 * Created on Jun 18, 2007
 *
 * 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.
 *
 * Copyright @2007-2013 the original author or authors.
 */
package org.fest.assertions;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static java.lang.Long.valueOf;
import static org.fest.assertions.ErrorMessages.*;

/**
 * Assertions for {@code Long}s and {@code long}s.
 * <p/>
 * To create a new instance of this class invoke either {@link Assertions#assertThat(Long)} or
 * {@link Assertions#assertThat(long)}.
 *
 * @author Yvonne Wang
 * @author David DIDIER
 * @author Ansgar Konermann
 * @author Alex Ruiz
 */
public class LongAssert extends GenericAssert<LongAssert, Long> implements NumberAssert {
  private static final long ZERO = 0L;

  /**
   * Creates a new {@link LongAssert}.
   *
   * @param actual the actual value to verify.
   */
  protected LongAssert(long actual) {
    super(LongAssert.class, actual);
  }

  /**
   * Creates a new {@link LongAssert}.
   *
   * @param actual the actual value to verify.
   */
  protected LongAssert(@Nullable Long actual) {
    super(LongAssert.class, actual);
  }

  /**
   * Verifies that the actual {@code Long} is equal to the given one.
   *
   * @param expected the value to compare the actual one to.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Long} is not equal to the given one.
   */
  public @NotNull LongAssert isEqualTo(long expected) {
    return isEqualTo(valueOf(expected));
  }

  /**
   * Verifies that the actual {@code Long} is not equal to the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Long} is equal to the given one.
   */
  public @NotNull LongAssert isNotEqualTo(long other) {
    return isNotEqualTo(valueOf(other));
  }

  /**
   * Verifies that the actual {@code Long} is greater than the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Long} is not greater than the given one.
   */
  public @NotNull LongAssert isGreaterThan(long other) {
    if (actual > other) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedLessThanOrEqualTo(actual, other));
  }

  /**
   * Verifies that the actual {@code Long} is less than the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Long} is not less than the given one.
   */
  public @NotNull LongAssert isLessThan(long other) {
    if (actual < other) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedGreaterThanOrEqualTo(actual, other));
  }

  /**
   * Verifies that the actual {@code Long} is greater or equal to the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Long} is not greater than or equal to the given one.
   */
  public @NotNull LongAssert isGreaterThanOrEqualTo(long other) {
    if (actual >= other) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedLessThan(actual, other));
  }

  /**
   * Verifies that the actual {@code Long} is less or equal to the given one.
   *
   * @param other the given value.
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Long} is not less than or equal to the given one.
   */
  public @NotNull LongAssert isLessThanOrEqualTo(long other) {
    if (actual <= other) {
      return this;
    }
    failIfCustomMessageIsSet();
    throw failure(unexpectedGreaterThan(actual, other));
  }

  /**
   * Verifies that the actual {@code Long} is equal to zero.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Long} is not equal to zero.
   */
  @Override
  public @NotNull LongAssert isZero() {
    return isEqualTo(ZERO);
  }

  /**
   * Verifies that the actual {@code Long} is positive.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Long} is not positive.
   */
  @Override
  public @NotNull LongAssert isPositive() {
    return isGreaterThan(ZERO);
  }

  /**
   * Verifies that the actual {@code Long} is negative.
   *
   * @return this assertion object.
   * @throws AssertionError if the actual {@code Long} is not negative.
   */
  @Override
  public @NotNull LongAssert isNegative() {
    return isLessThan(ZERO);
  }
}