/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket.util.lang;

/**
 * 
 * @author igor.vaynberg
 */
public class Exceptions
{
	private Exceptions()
	{
	}

	/**
	 * Gets root cause of the throwable
	 * 
	 * @param throwable
	 * @return root cause
	 */
	public Throwable getRootCause(final Throwable throwable)
	{
		Throwable cursor = throwable;
		while (cursor.getCause() != null)
		{
			cursor = cursor.getCause();
		}
		return cursor;
	}

	/**
	 * Looks for a cause of the specified type in throwable's chain
	 * 
	 * @param <T>
	 * @param throwable
	 * @param causeType
	 * @return matched {@link Throwable} in the chain or {@code null} if none
	 */
	public static <T extends Throwable> T findCause(final Throwable throwable,
		final Class<T> causeType)
	{
		return visit(throwable, new IThrowableVisitor<T>()
		{
			@Override
			@SuppressWarnings("unchecked")
			public void visit(final Throwable throwable, final Visit<T> visit)
			{
				if (causeType.isAssignableFrom(throwable.getClass()))
				{
					visit.stop((T)throwable);
				}

			}
		});
	}

	/**
	 * Represents a visit
	 * 
	 * @author igor
	 * @param <T>
	 */
	public static class Visit<T>
	{
		private T result;
		private boolean stopped;

		/**
		 * Stops visit with specified resut
		 * 
		 * @param result
		 */
		public void stop(final T result)
		{
			this.result = result;
			stop();
		}

		/**
		 * Stops visit
		 */
		public void stop()
		{
			stopped = true;
		}
	}

	/**
	 * Visitor used to visit {@link Throwable} chains
	 * 
	 * @param <T>
	 */
	public static interface IThrowableVisitor<T>
	{
		/**
		 * Visit a throwable
		 * 
		 * @param throwable
		 * @param visit
		 */
		void visit(Throwable throwable, Visit<T> visit);
	}

	/**
	 * Visits the {@link Throwable}'s chain
	 * 
	 * @param <T>
	 * @param throwable
	 * @param visitor
	 * @return result set on visitor or {@code null} if none
	 */
	public static <T> T visit(final Throwable throwable, final IThrowableVisitor<T> visitor)
	{
		Visit<T> visit = new Visit<>();
		Throwable cursor = throwable;
		while (cursor != null)
		{
			visitor.visit(cursor, visit);
			if (visit.stopped)
			{
				return visit.result;
			}
			cursor = cursor.getCause();
		}
		return null;
	}
}
