/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.core.internal.expressions;

import org.eclipse.core.expressions.IPropertyTester;
import org.eclipse.core.expressions.PropertyTester;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;

public class TypeExtension {

	private static final TypeExtension[] EMPTY_TYPE_EXTENSION_ARRAY= new TypeExtension[0];

	/* a special property tester instance that is used to signal that method searching has to continue */
	/* package */ static final IPropertyTester CONTINUE= new IPropertyTester() {
		@Override
		public boolean handles(String namespace, String method) {
			return false;
		}
		@Override
		public boolean isInstantiated() {
			return true;
		}
		@Override
		public boolean isDeclaringPluginActive() {
			return true;
		}
		@Override
		public IPropertyTester instantiate() {
			return this;
		}
		@Override
		public boolean test(Object receiver, String method, Object[] args, Object expectedValue) {
			return false;
		}
	};

	/* a special type extension instance that marks the end of an evaluation chain */
	private static final TypeExtension END_POINT= new TypeExtension() {
		@Override
		IPropertyTester findTypeExtender(TypeExtensionManager manager, String namespace, String name, boolean staticMethod, boolean forcePluginActivation) throws CoreException {
			return CONTINUE;
		}
	};

	/* the type this extension is extending */
	private Class<?> fType;
	/* the list of associated extenders */
	private IPropertyTester[] fExtenders;

	/* the extension associated with <code>fType</code>'s super class */
	private TypeExtension fExtends;
	/* the extensions associated with <code>fTypes</code>'s interfaces */
	private TypeExtension[] fImplements;

	private TypeExtension() {
		// special constructor to create the CONTINUE instance
	}

	/* package */ TypeExtension(Class<?> type) {
		Assert.isNotNull(type);
		fType= type;
	}

	/* package */ IPropertyTester findTypeExtender(TypeExtensionManager manager, String namespace, String method, boolean staticMethod, boolean forcePluginActivation) throws CoreException {
		if (fExtenders == null) {
			fExtenders= manager.loadTesters(fType);
		}
		IPropertyTester result;

		// handle extenders associated with this type extender
		for (int i= 0; i < fExtenders.length; i++) {
			IPropertyTester extender= fExtenders[i];
			if (extender == null || !extender.handles(namespace, method))
				continue;
			if (extender.isInstantiated()) {
				// There is no need to check for an active plug-in here. If a plug-in
				// gets uninstalled we receive an registry event which will flush the whole
				// type extender cache and will reinstantiate the testers. However Bundle#stop
				// isn't handled by this. According to bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=130338
				// we don't have to support stop in 3.2. If we have to in the future we have to
				// reactivate the stopped plug-in if we are in forcePluginActivation mode.
				return extender;
			} else {
				if (extender.isDeclaringPluginActive() || forcePluginActivation) {
					try {
						PropertyTesterDescriptor descriptor= (PropertyTesterDescriptor)extender;
						IPropertyTester inst= descriptor.instantiate();
						((PropertyTester)inst).internalInitialize(descriptor);
						fExtenders[i]= extender= inst;
						return extender;
					} catch (CoreException e) {
						fExtenders[i]= null;
						throw e;
					} catch (ClassCastException e) {
						fExtenders[i]= null;
						throw new CoreException(new ExpressionStatus(
							ExpressionStatus.TYPE_EXTENDER_INCORRECT_TYPE,
							ExpressionMessages.TypeExtender_incorrectType,
							e));
					}
				} else {
					return extender;
				}
			}
		}

		// there is no inheritance for static methods
		if (staticMethod)
			return CONTINUE;

		// handle extends chain
		if (fExtends == null) {
			Class<?> superClass= fType.getSuperclass();
			if (superClass != null) {
				fExtends= manager.get(superClass);
			} else {
				fExtends= END_POINT;
			}
		}
		result= fExtends.findTypeExtender(manager, namespace, method, staticMethod, forcePluginActivation);
		if (result != CONTINUE)
			return result;

		// handle implements chain
		if (fImplements == null) {
			Class<?>[] interfaces= fType.getInterfaces();
			if (interfaces.length == 0) {
				fImplements= EMPTY_TYPE_EXTENSION_ARRAY;
			} else {
				fImplements= new TypeExtension[interfaces.length];
				for (int i= 0; i < interfaces.length; i++) {
					fImplements[i]= manager.get(interfaces[i]);
				}
			}
		}
		for (TypeExtension fImplement : fImplements) {
			result= fImplement.findTypeExtender(manager, namespace, method, staticMethod, forcePluginActivation);
			if (result != CONTINUE)
				return result;
		}
		return CONTINUE;
	}
}
