package to.etc.domui.injector;

import org.eclipse.jdt.annotation.Nullable;
import to.etc.domui.dom.html.AbstractPage;
import to.etc.util.PropertyInfo;
import to.etc.webapp.ProgrammerErrorException;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Access checker which uses the type of the value to check access to it.
 *
 * @author <a href="mailto:jal@etc.to">Frits Jalvingh</a>
 * Created on 20-12-18.
 */
public class TypedPropertyAccessChecker implements IInjectedPropertyAccessChecker {
	private Map<Class<?>, ITypedValueAccessChecker<?>> m_checkerMap = Collections.emptyMap();

	private List<ITypedValueAccessChecker<Object>> m_anyCheckerList = new CopyOnWriteArrayList<>();

	public synchronized <T> void register(Class<T> clz, ITypedValueAccessChecker<T> check) {
		Map<Class<?>, ITypedValueAccessChecker<?>> map = new HashMap<>(m_checkerMap);
		if(map.put(clz, check) != null)
			throw new ProgrammerErrorException("Duplicate typed access checker for class " + clz.getName());
		m_checkerMap = Collections.unmodifiableMap(map);
	}

	public void registerAny(ITypedValueAccessChecker<Object> checker) {
		m_anyCheckerList.add(checker);
	}

	private synchronized Map<Class<?>, ITypedValueAccessChecker<?>> getCheckerMap() {
		return m_checkerMap;
	}

	@Override
	public void checkAccessAllowed(PropertyInfo info, AbstractPage page, @Nullable Object value) throws Exception {
		checkAccessForAnyChecker(info, page, value);
	}

	/**
	 * Checks if any of registered access checkers can grant the access. If none can grant it, we abort with first access check exception that was delivered by checkers.
	 */
	private <T> void checkAccessForAnyChecker(PropertyInfo info, AbstractPage page, @Nullable Object value) throws Exception {
		if(null == value)
			return;

		AccessCheckException firstException = null;
		ITypedValueAccessChecker<T> checker = findClassChecker(value.getClass());
		if(null != checker) {
			try {
				checker.checkAccessAllowed(info, page, (T) value);
				return;
			}catch (AccessCheckException x) {
				if(null == firstException) {
					firstException = x;
				}
			}
		}
		for(ITypedValueAccessChecker<Object> any : m_anyCheckerList) {
			try{
				any.checkAccessAllowed(info, page, value);
				return;
			}catch (AccessCheckException x) {
				if(null == firstException) {
					firstException = x;
				}
			}
		}
		if(null != firstException) {
			throw firstException;
		}
	}

	@Nullable
	private <T> ITypedValueAccessChecker<T> findClassChecker(Class<?> clz) {
		Map<Class<?>, ITypedValueAccessChecker<?>> map = getCheckerMap();
		Class<?> curr = clz;
		for(;;) {
			if(curr == Object.class || curr == null)
				return null;
			ITypedValueAccessChecker<?> checker = map.get(curr);
			if(null != checker) {
				return (ITypedValueAccessChecker<T>) checker;
			}

			curr = curr.getSuperclass();
		}
	}
}

@@ -1498,29 +1498,24 @@
 ckAccess
-ForAnyChecker
+Internal
 (info, p
@@ -1535,185 +1535,8 @@
 %09%7D%0A%0A
-%09/**%0A%09 * Checks if any of registered access checkers can grant the access. If none can grant it, we abort with first access check exception that was delivered by checkers.%0A%09 */%0A
 %09pri
@@ -1564,21 +1564,16 @@
 cess
-ForAnyChecker
+Internal
 (Pro
@@ -1686,54 +1686,8 @@
 n;%0A%0A
-%09%09AccessCheckException firstException = null;%0A
 %09%09IT
@@ -1786,18 +1786,8 @@
 ) %7B%0A
-%09%09%09try %7B%0A%09
 %09%09%09c
@@ -1831,33 +1831,32 @@
  (T) value);%0A%09%09%09
-%09
 return;%0A%09%09%09%7Dcatc
@@ -1849,37 +1849,144 @@
 turn;%0A%09%09
-%09%7Dcatch (A
+%7D%0A%09%09//Checks if any of registered access checkers can grant the access. If none can grant it, we abort with first a
 ccess
-C
+ c
 heck
-E
+ e
 xception
@@ -1990,85 +1990,85 @@
 ion 
-x) %7B%0A%09%09%09%09if(null == firstException) %7B%0A%09%09%09%09%09firstException = x;%0A%09%09%09%09%7D%0A%09%09%09%7D%0A%09%09%7D
+that was delivered by checkers.%0A%09%09AccessCheckException firstException = null;
 %0A%09%09f

Fix in internal access check logic to mimic old behavior