/*
 * 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.markup.html.form;

import java.io.Serializable;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IConverterLocator;
import org.apache.wicket.IGenericComponent;
import org.apache.wicket.Localizer;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.core.util.lang.WicketObjects;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.form.AutoLabelResolver.AutoLabelMarker;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.IObjectClassAwareModel;
import org.apache.wicket.model.IPropertyReflectionAwareModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.util.convert.ConversionException;
import org.apache.wicket.util.convert.IConverter;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.lang.Classes;
import org.apache.wicket.util.string.StringList;
import org.apache.wicket.util.string.StringValue;
import org.apache.wicket.util.string.Strings;
import org.apache.wicket.util.string.interpolator.VariableInterpolator;
import org.apache.wicket.util.visit.IVisit;
import org.apache.wicket.util.visit.IVisitFilter;
import org.apache.wicket.util.visit.IVisitor;
import org.apache.wicket.util.visit.Visits;
import org.apache.wicket.validation.IErrorMessageSource;
import org.apache.wicket.validation.INullAcceptingValidator;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.IValidationError;
import org.apache.wicket.validation.IValidator;
import org.apache.wicket.validation.ValidationError;
import org.apache.wicket.validation.ValidatorAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * An HTML form component knows how to validate itself. Validators that implement IValidator can be
 * added to the component. They will be evaluated in the order they were added and the first
 * Validator that returns an error message determines the error message returned by the component.
 * <p>
 * FormComponents are not versioned by default. If you need versioning for your FormComponents, you
 * will need to call Form.setVersioned(true), which will set versioning on for the form and all form
 * component children.
 * <p>
 * If this component is required and that fails, the error key that is used is the "Required"; if
 * the type conversion fails, it will use the key "IConverter" if the conversion failed in a
 * converter, or "ConversionError" if type was explicitly specified via {@link #setType(Class)} or a
 * {@link IPropertyReflectionAwareModel} was used. Notice that both "IConverter" and
 * "ConversionError" have a more specific variant of "key.classname" where classname is the type
 * that we failed to convert to. Classname is not full qualified, so only the actual name of the
 * class is used.
 * 
 * Property expressions that can be used in error messages are:
 * <ul>
 * <li>${input}: the input the user did give</li>
 * <li>${name}: the name of the component that failed</li>
 * <li>${label}: the label of the component</li>
 * </ul>
 * 
 * @author Jonathan Locke
 * @author Eelco Hillenius
 * @author Johan Compagner
 * @author Igor Vaynberg (ivaynberg)
 * 
 * @param <T>
 *            The model object type
 * 
 */
public abstract class FormComponent<T> extends LabeledWebMarkupContainer implements
	IFormVisitorParticipant, IFormModelUpdateListener, IGenericComponent<T, FormComponent<T>>
{
	private static final Logger logger = LoggerFactory.getLogger(FormComponent.class);

	/**
	 * {@link IErrorMessageSource} used for error messages against this form components.
	 * 
	 * @author ivaynberg
	 */
	private class MessageSource implements IErrorMessageSource
	{
		private final Set<String> triedKeys = new LinkedHashSet<>();

		@Override
		public String getMessage(String key, Map<String, Object> vars)
		{
			final FormComponent<T> formComponent = FormComponent.this;

			// Use the following slf4j-simple config for detailed logging
			// on the property resolution process
			// org.slf4j.simpleLogger.log.org.apache.wicket.resource.loader=DEBUG
			// org.slf4j.simpleLogger.log.org.apache.wicket.Localizer=DEBUG

			final Localizer localizer = formComponent.getLocalizer();

			// retrieve prefix that will be used to construct message keys
			String prefix = formComponent.getValidatorKeyPrefix();
			String message;

			// first try the full form of key [form-component-id].[prefix].[key]
			String resource = getId() + "." + prefix(prefix, key);
			message = getString(localizer, resource, formComponent);

			// if not found, try a more general form (without prefix)
			// [form-component-id].[key]
			if (Strings.isEmpty(message) && Strings.isEmpty(prefix))
			{
				resource = getId() + "." + key;
				message = getString(localizer, resource, formComponent);
			}

			// If not found try a more general form [prefix].[key]
			if (Strings.isEmpty(message))
			{
				resource = prefix(prefix, key);
				message = getString(localizer, resource, formComponent);
			}

			// If not found try the most general form [key]
			if (Strings.isEmpty(message))
			{
				// Try a variation of the resource key
				message = getString(localizer, key, formComponent);
			}

			// convert empty string to null in case our default value of "" was
			// returned from localizer
			if (Strings.isEmpty(message))
			{
				message = null;
			}
			else
			{
				message = substitute(message, addDefaultVars(vars));
			}
			return message;
		}

		private String prefix(String prefix, String key)
		{
			if (!Strings.isEmpty(prefix))
			{
				return prefix + "." + key;
			}
			else
			{
				return key;
			}
		}

		/**
		 * 
		 * @param localizer
		 * @param key
		 * @param component
		 * @return string
		 */
		private String getString(Localizer localizer, String key, Component component)
		{
			triedKeys.add(key);

			// Note: It is important that the default value of "" is
			// provided to getString() not to throw a MissingResourceException or to
			// return a default string like "[Warning: String ..."
			return localizer.getString(key, component, "");
		}

		private String substitute(String string, final Map<String, Object> vars)
			throws IllegalStateException
		{
			return new VariableInterpolator(string, Application.get()
				.getResourceSettings()
				.getThrowExceptionOnMissingResource())
			{
				private static final long serialVersionUID = 1L;

				@SuppressWarnings({ "rawtypes", "unchecked" })
				@Override
				protected String getValue(String variableName)
				{
					Object value = vars.get(variableName);
					
					if (value == null ||value instanceof String)
					{
						return String.valueOf(value);
					}
					
					IConverter converter = getConverter(value.getClass());
					
					if (converter == null)
					{
						return Strings.toString(value);
					}
					else
					{
						return converter.convertToString(value, getLocale());
					}
				}
			}.toString();
		}

		/**
		 * Creates a new params map that additionally contains the default input, name, label
		 * parameters
		 * 
		 * @param params
		 *            original params map
		 * @return new params map
		 */
		private Map<String, Object> addDefaultVars(Map<String, Object> params)
		{
			// create and fill the new params map
			final HashMap<String, Object> fullParams;
			if (params == null)
			{
				fullParams = new HashMap<>(6);
			}
			else
			{
				fullParams = new HashMap<>(params.size() + 6);
				fullParams.putAll(params);
			}

			// add the input param if not already present
			if (!fullParams.containsKey("input"))
			{
				fullParams.put("input", getInput());
			}

			// add the name param if not already present
			if (!fullParams.containsKey("name"))
			{
				fullParams.put("name", getId());
			}

			// add the label param if not already present
			if (!fullParams.containsKey("label"))
			{
				fullParams.put("label", getLabel());
			}
			return fullParams;
		}

		/**
		 * @return value of label param for this form component
		 */
		private String getLabel()
		{
			final FormComponent<T> fc = FormComponent.this;
			String label = null;

			// first try the label model ...
			if (fc.getLabel() != null)
			{
				label = fc.getLabel().getObject();
			}
			// ... then try a resource of format [form-component-id] with
			// default of '[form-component-id]'
			if (label == null)
			{

				label = fc.getDefaultLabel();
			}
			return label;
		}
	}

	@Override
	protected void onBeforeRender()
	{
		// WICKET-7101 update related auto-label
		getRequestCycle().find(IPartialPageRequestHandler.class).ifPresent(handler -> updateAutoLabels(handler, true));
		super.onBeforeRender();
	}

	/**
	 * Adapter that makes this component appear as {@link IValidatable}
	 * 
	 * @author ivaynberg
	 */
	private class ValidatableAdapter implements IValidatable<T>
	{
		/**
		 * @see org.apache.wicket.validation.IValidatable#error(org.apache.wicket.validation.IValidationError)
		 */
		@Override
		public void error(IValidationError error)
		{
			FormComponent.this.error(error);
		}

		/**
		 * @see org.apache.wicket.validation.IValidatable#getValue()
		 */
		@Override
		public T getValue()
		{
			return getConvertedInput();
		}

		/**
		 * @see org.apache.wicket.validation.IValidatable#isValid()
		 */
		@Override
		public boolean isValid()
		{
			return FormComponent.this.isValid();
		}

		@Override
		public IModel<T> getModel()
		{
			return FormComponent.this.getModel();
		}
	}

	/**
	 * The value separator
	 */
	public static final String VALUE_SEPARATOR = ";";

	private static final String[] EMPTY_STRING_ARRAY = new String[] { "" };

	/** Whether or not this component's value is required (non-empty) */
	private static final short FLAG_REQUIRED = FLAG_RESERVED3;

	private static final String NO_RAW_INPUT = "[-NO-RAW-INPUT-]";

	private static final long serialVersionUID = 1L;

	/**
	 * Make empty strings null values boolean. Used by AbstractTextComponent subclass.
	 */
	protected static final short FLAG_CONVERT_EMPTY_INPUT_STRING_TO_NULL = FLAG_RESERVED1;

	/**
	 * Visits any form components inside component if it is a container, or component itself if it
	 * is itself a form component
	 * 
	 * @param <R>
	 *            the type of the visitor's result
	 * 
	 * @param component
	 *            starting point of the traversal
	 * 
	 * @param visitor
	 *            The visitor to call
	 * @return the visitor's result
	 */
	public static <R> R visitFormComponentsPostOrder(Component component,
		final IVisitor<? extends FormComponent<?>, R> visitor)
	{
		return Visits.visitPostOrder(component, visitor, new IVisitFilter()
		{

			@Override
			public boolean visitChildren(Object object)
			{
				if (object instanceof IFormVisitorParticipant)
				{
					return ((IFormVisitorParticipant)object).processChildren();
				}
				return true;
			}

			@Override
			public boolean visitObject(Object object)
			{
				return (object instanceof FormComponent<?>);
			}

		});

	}

	/**
	 * Visits any form components inside component if it is a container, or component itself if it
	 * is itself a form component
	 * 
	 * @param <R>
	 *            the type of the visitor's result
	 * @param component
	 *            starting point of the traversal
	 * 
	 * @param visitor
	 *            The visitor to call
	 * @return the visitor's result
	 */
	public static <R> R visitComponentsPostOrder(Component component,
		final org.apache.wicket.util.visit.IVisitor<Component, R> visitor)
	{
		Args.notNull(visitor, "visitor");

		return Visits.visitPostOrder(component, visitor, new IVisitFilter()
		{
			@Override
			public boolean visitObject(Object object)
			{
				return true;
			}

			@Override
			public boolean visitChildren(Object object)
			{
				if (object instanceof IFormVisitorParticipant)
				{
					return ((IFormVisitorParticipant)object).processChildren();
				}
				return true;
			}

		});
	}

	private transient T convertedInput;

	/**
	 * Raw Input entered by the user or NO_RAW_INPUT if nothing is filled in.
	 */
	private String rawInput = NO_RAW_INPUT;

	/**
	 * Type that the raw input string will be converted to
	 */
	private String typeName;

	/**
	 * @see org.apache.wicket.Component#Component(String)
	 */
	public FormComponent(final String id)
	{
		this(id, null);
	}

	/**
	 * @param id
	 * @param model
	 * @see org.apache.wicket.Component#Component(String, IModel)
	 */
	public FormComponent(final String id, IModel<T> model)
	{
		super(id, model);
		// the form decides whether form components are versioned or not
		// see Form.setVersioned
		setVersioned(false);
	}

	/**
	 * Gets the string the component would use as a label when one was requested but no label model
	 * was set via {@link #getLabel()}. The value of this string is usually set in a property file;
	 * if the value is not set the default value equivalent to component id will be returned.
	 * 
	 * @return localized label
	 */
	public final String getDefaultLabel()
	{
		return getDefaultLabel(getId());
	}

	/**
	 * Gets the string the component would use as a label when one was requested but no label model
	 * was set via {@link #getLabel()}. The value of this string is usually set in a property file;
	 * if the value is not set the {@code defaultValue} will be returned.
	 * 
	 * @param defaultValue
	 * 
	 * @return localized label
	 */
	public final String getDefaultLabel(String defaultValue)
	{
		return getLocalizer().getString(getId(), getParent(), defaultValue);
	}


	/**
	 * Adds a validator to this form component
	 * 
	 * @param validator
	 *            validator to be added
	 * @return <code>this</code> for chaining
	 * @throws IllegalArgumentException
	 *             if validator is null
	 * @see IValidator
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public final FormComponent<T> add(final IValidator<? super T> validator)
	{
		Args.notNull(validator, "validator");

		if (validator instanceof Behavior)
		{
			add((Behavior)validator);
		}
		else
		{
			add((Behavior)new ValidatorAdapter(validator));
		}
		return this;
	}

	/**
	 * Removes a validator from the form component.
	 * 
	 * @param validator
	 *            validator
	 * @throws IllegalArgumentException
	 *             if validator is null or not found
	 * @see IValidator
	 * @see #add(IValidator)
	 * @return form component for chaining
	 */
	public final FormComponent<T> remove(final IValidator<? super T> validator)
	{
		Args.notNull(validator, "validator");
		Behavior match = null;
		for (Behavior behavior : getBehaviors())
		{
			if (behavior.equals(validator))
			{
				match = behavior;
				break;
			}
			else if (behavior instanceof ValidatorAdapter)
			{
				if (((ValidatorAdapter<?>)behavior).getValidator().equals(validator))
				{
					match = behavior;
					break;
				}
			}
		}

		if (match != null)
		{
			remove(match);
		}
		else
		{
			throw new IllegalStateException(
				"Tried to remove validator that was not previously added. "
					+ "Make sure your validator's equals() implementation is sufficient");
		}
		return this;
	}

	/**
	 * Adds a validator to this form component.
	 * 
	 * @param validators
	 *            The validator(s) to be added
	 * @return This
	 * @throws IllegalArgumentException
	 *             if validator is null
	 * @see IValidator
	 */
	public final FormComponent<T> add(final IValidator<? super T>... validators)
	{
		Args.notNull(validators, "validators");

		for (IValidator<? super T> validator : validators)
		{
			add(validator);
		}

		// return this for chaining
		return this;
	}

	/**
	 * Checks if the form component's 'required' requirement is met by first checking
	 * {@link #isRequired()} to see if it has to check for requirement. If that is true then by
	 * default it checks if the input is null or an empty String
	 * {@link Strings#isEmpty(CharSequence)}
	 * <p>
	 * Subclasses that overwrite this method should also call {@link #isRequired()} first.
	 * </p>
	 * 
	 * @return true if the 'required' requirement is met, false otherwise
	 * 
	 * @see Strings#isEmpty(CharSequence)
	 * @see #isInputNullable()
	 */
	public boolean checkRequired()
	{
		if (isRequired())
		{
			final String input = getInput();

			// when null, check whether this is natural for that component, or
			// whether - as is the case with text fields - this can only happen
			// when the component was disabled
			if (input == null && !isInputNullable() && !isEnabledInHierarchy())
			{
				// this value must have come from a disabled field
				// do not perform validation
				return true;
			}

			// perform validation by looking whether the value is null or empty
			return !Strings.isEmpty(input);
		}
		return true;
	}

	/**
	 * Clears the user input.
	 */
	public void clearInput()
	{
		rawInput = NO_RAW_INPUT;
	}

	/**
	 * Reports a validation error against this form component.
	 * 
	 * The actual error is reported by creating a {@link ValidationErrorFeedback} object that holds
	 * both the validation error and the generated error message - so a custom feedback panel can
	 * have access to both.
	 * 
	 * @param error
	 *            validation error
	 */
	public void error(IValidationError error)
	{
		Args.notNull(error, "error");

		MessageSource source = new MessageSource();
		Serializable message = error.getErrorMessage(source);

		if (message == null)
		{
			StringBuilder buffer = new StringBuilder();
			buffer.append("Could not locate error message for component: ");
			buffer.append(Classes.simpleName(getClass()));
			buffer.append("@");
			buffer.append(getPageRelativePath());
			buffer.append(" and error: ");
			buffer.append(error.toString());
			buffer.append(". Tried keys: ");
			Iterator<String> keys = source.triedKeys.iterator();
			while (keys.hasNext())
			{
				buffer.append(keys.next());
				if (keys.hasNext())
				{
					buffer.append(", ");
				}
			}
			buffer.append('.');
			message = buffer.toString();
			logger.warn(message.toString());
		}
		error(message);
	}

	/**
	 * Gets the converted input. The converted input is set earlier though the implementation of
	 * {@link #convertInput()}.
	 * 
	 * {@link FormComponentPanel} often access this method when constructing their converted input
	 * value which is often the combination of converted values of the embedded FormComponents
	 * 
	 * To access the model object resulted by the full form processing, use
	 * {@link #getModelObject()} instead, that is an generified version of
	 * {@link #getDefaultModelObject()}
	 * 
	 * @return value of input possibly converted into an appropriate type
	 */
	public final T getConvertedInput()
	{
		return convertedInput;
	}

	/**
	 * Sets the converted input. This method is typically not called by clients, unless they
	 * override {@link #convertInput()}, in which case they should call this method to update the
	 * input for this component instance.
	 * 
	 * @param convertedInput
	 *            the converted input
	 */
	public final void setConvertedInput(T convertedInput)
	{
		this.convertedInput = convertedInput;
	}

	/**
	 * @return The parent form for this form component
	 */
	public Form<?> getForm()
	{
		Form<?> form = Form.findForm(this);
		if (form == null)
		{
			throw new WicketRuntimeException("Could not find Form parent for " + this);
		}
		return form;
	}


	/**
	 * Gets the request parameter for this component as a string.
	 * 
	 * @return The value in the request for this component
	 */
	public String getInput()
	{
		String[] input = getInputAsArray();
		if (input == null || input.length == 0)
		{
			return null;
		}
		else
		{
			return trim(input[0]);
		}
	}

	/**
	 * Gets the request parameters for this component as strings.
	 * 
	 * @return The values in the request for this component
	 */
	public String[] getInputAsArray()
	{
		List<StringValue> list = getParameterValues(getInputName());

		String[] values = null;
		if (list != null)
		{
			values = new String[list.size()];
			for (int i = 0; i < list.size(); ++i)
			{
				values[i] = list.get(i).toString();
			}
		}

		if (!isInputNullable())
		{
			if (values != null && values.length == 1 && values[0] == null)
			{
				// the key got passed in (otherwise values would be null),
				// but the value was set to null.
				// As the servlet spec isn't clear on what to do with 'empty'
				// request values - most return an empty string, but some null -
				// we have to workaround here and deliberately set to an empty
				// string if the component is not nullable (text components)
				return EMPTY_STRING_ARRAY;
			}
		}
		return values;
	}

	/**
	 * Reads the value(s) of the request parameter with name <em>inputName</em>
	 * from either the query parameters for <em>GET</em> method or the request body
	 * for <em>POST</em> method.
	 *
	 * @param inputName
	 *      The name of the request parameter
	 * @return The parameter's value(s)
	 */
	protected List<StringValue> getParameterValues(String inputName)
	{
		final IRequestParameters parameters = Form.getRequestParameters(this);

		return parameters.getParameterValues(inputName);
	}

	/**
	 * Gets the string to be used for the <tt>name</tt> attribute of the form element. Generated
	 * using the path from the form to the component, excluding the form itself. Override it if you
	 * want even a smaller name. E.g. if you know for sure that the id is unique within a form.
	 * 
	 * @return The string to use as the form element's name attribute
	 */
	public String getInputName()
	{
		String inputName = Form.getRootFormRelativeId(this);
		Form<?> form = findParent(Form.class);

		if (form != null)
		{
			return form.getInputNamePrefix() + inputName;
		}
		else
		{
			return inputName;
		}
	}

	/**
	 * Use hasRawInput() to check if this component has raw input because null can mean 2 things: It
	 * doesn't have rawinput or the rawinput is really null.
	 * 
	 * @return The raw form input that is stored for this formcomponent
	 */
	public final String getRawInput()
	{
		return NO_RAW_INPUT.equals(rawInput) ? null : rawInput;
	}

	/**
	 * @return the type to use when updating the model for this form component
	 */
	@SuppressWarnings("unchecked")
	public final Class<T> getType()
	{
		return typeName == null ? null : (Class<T>)WicketObjects.resolveClass(typeName);
	}

	/**
	 * @see Form#getValidatorKeyPrefix()
	 * @return prefix used when constructing validator key messages
	 */
	public String getValidatorKeyPrefix()
	{
		Form<?> form = findParent(Form.class);
		if (form != null)
		{
			return getForm().getValidatorKeyPrefix();
		}
		return null;
	}

	/**
	 * Gets an unmodifiable list of validators for this FormComponent.
	 * 
	 * @return List of validators
	 */
	@SuppressWarnings("unchecked")
	public final List<IValidator<? super T>> getValidators()
	{
		final List<IValidator<? super T>> list = new ArrayList<>();

		for (Behavior behavior : getBehaviors())
		{
			if (behavior instanceof IValidator)
			{
				list.add((IValidator<? super T>)behavior);
			}
		}

		return Collections.unmodifiableList(list);
	}

	/**
	 * Gets current value for a form component, which can be either input data entered by the user,
	 * or the component's model object if no input was provided.
	 * 
	 * @return The value
	 */
	public final String getValue()
	{
		if (NO_RAW_INPUT.equals(rawInput))
		{
			return getModelValue();
		}
		else
		{
			if (getEscapeModelStrings() && rawInput != null)
			{
				return Strings.escapeMarkup(rawInput).toString();
			}
			return rawInput;
		}
	}

	/**
	 * Returns whether this component has raw input. Raw input is unconverted input straight from
	 * the client.
	 * 
	 * @return boolean whether this component has raw input.
	 */
	public final boolean hasRawInput()
	{
		return !NO_RAW_INPUT.equals(rawInput);
	}

	/**
	 * Used by Form to tell the FormComponent that a new user input is available
	 */
	public final void inputChanged()
	{
		if (isVisibleInHierarchy() && isEnabledInHierarchy())
		{
			// Get input as String array
			final String[] input = getInputAsArray();

			// If there is any input
			if (input != null && input.length > 0 && input[0] != null)
			{
				// join the values together with ";", for example, "id1;id2;id3"
				rawInput = StringList.valueOf(input).join(VALUE_SEPARATOR);
			}
			else if (isInputNullable())
			{
				// no input
				rawInput = null;
			}
			else
			{
				rawInput = NO_RAW_INPUT;
			}
		}
	}

	/**
	 * Indicate that validation of this form component failed.
	 */
	public final void invalid()
	{
		onInvalid();
	}

	/**
	 * Gets whether this component's input can be null. By default, components that do not get input
	 * will have null values passed in for input. However, component TextField is an example
	 * (possibly the only one) that never gets a null passed in, even if the field is left empty
	 * UNLESS it has attribute <code>disabled="disabled"</code> set.
	 * 
	 * @return True if this component's input can be null. Returns true by default.
	 */
	public boolean isInputNullable()
	{
		return true;
	}

	/**
	 * @return True if this component encodes data in a multipart form submit
	 */
	public boolean isMultiPart()
	{
		return false;
	}

	/**
	 * @return whether or not this component's value is required
	 */
	public boolean isRequired()
	{
		return getFlag(FLAG_REQUIRED);
	}

	/**
	 * Gets whether this component is 'valid'. Valid in this context means that no validation errors
	 * were reported the last time the form component was processed. This variable not only is
	 * convenient for 'business' use, but is also necessary as we don't want the form component
	 * models updated with invalid input.
	 * 
	 * @return valid whether this component is 'valid'
	 */
	public final boolean isValid()
	{
		class IsValidVisitor implements IVisitor<FormComponent<?>, Boolean>
		{
			@Override
			public void component(final FormComponent<?> formComponent, final IVisit<Boolean> visit)
			{
				if (formComponent.hasErrorMessage())
				{
					visit.stop(Boolean.FALSE);
				}
			}
		}
		IsValidVisitor tmp = new IsValidVisitor();
		final Object result = visitFormComponentsPostOrder(this, tmp);
		return (Boolean.FALSE != result);
	}

	/**
	 * @see IFormVisitorParticipant#processChildren()
	 */
	@Override
	public boolean processChildren()
	{
		return true;
	}

	/**
	 * This method will retrieve the request parameter, validate it, and if valid update the model.
	 * These are the same steps as would be performed by the form.
	 * 
	 * This is useful when a formcomponent is used outside a form.
	 * 
	 */
	public final void processInput()
	{
		inputChanged();
		validate();
		if (hasErrorMessage())
		{
			invalid();
		}
		else
		{
			valid();
			updateModel();
		}
	}

	/**
	 * The value will be made available to the validator property by means of ${label}. It does not
	 * have any specific meaning to FormComponent itself.
	 * 
	 * @param labelModel
	 * @return this for chaining
	 */
	@Override
	public FormComponent<T> setLabel(IModel<String> labelModel)
	{
		super.setLabel(labelModel);
		return this;
	}

	/**
	 * THIS METHOD IS NOT PART OF THE WICKET PUBLIC API. DO NOT USE IT!
	 *
	 * Sets the value for a form component.
	 * 
	 * @param value
	 *            The value
	 */
	public void setModelValue(final String[] value)
	{
		convertedInput = convertValue(value);
		updateModel();
	}

	/**
	 * Sets the required flag
	 * 
	 * @param required
	 * @return this for chaining
	 */
	public final FormComponent<T> setRequired(final boolean required)
	{
		if (!required && getType() != null && getType().isPrimitive())
		{
			throw new WicketRuntimeException(
				"FormComponent has to be required when the type is primitive class: " + this);
		}
		if (required != isRequired())
		{
			addStateChange();
		}
		setFlag(FLAG_REQUIRED, required);
		return this;
	}

	/**
	 * Sets the type that will be used when updating the model for this component. If no type is
	 * specified String type is assumed.
	 * 
	 * @param type
	 * @return this for chaining
	 */
	public FormComponent<T> setType(Class<?> type)
	{
		typeName = type == null ? null : type.getName();
		if (type != null && type.isPrimitive())
		{
			setRequired(true);
		}
		return this;
	}

	/**
	 * Updates this components model from the request, it expects that the object is already
	 * converted through the convertInput() call that is called by the validate() method when a form
	 * is being processed.
	 * 
	 * By default it just does this:
	 * 
	 * <pre>
	 * setModelObject(getConvertedInput());
	 * </pre>
	 * 
	 * <strong>DO NOT CALL THIS METHOD DIRECTLY UNLESS YOU ARE SURE WHAT YOU ARE DOING. USUALLY UPDATING
	 * YOUR MODEL IS HANDLED BY THE FORM, NOT DIRECTLY BY YOU.</strong>
	 */
	@Override
	public void updateModel()
	{
		setModelObject(getConvertedInput());
	}


	/**
	 * Called to indicate that the user input is valid.
	 */
	public final void valid()
	{
		clearInput();

		onValid();
	}

	/**
	 * Performs full validation of the form component, which consists of calling validateRequired(),
	 * convertInput(), and validateValidators(). This method should only be used if the form
	 * component needs to be fully validated outside the form process.
	 */
	public void validate()
	{
		// clear any previous feedback messages

		if (hasFeedbackMessage())
		{
			getFeedbackMessages().clear();
		}

		// validate

		validateRequired();
		if (isValid())
		{
			convertInput();
			if (isValid())
			{
				if (isRequired() && getConvertedInput() == null && isInputNullable())
				{
					reportRequiredError();
				}
				else
				{
					validateValidators();
				}
			}
		}
	}

	/**
	 * Converts and validates the conversion of the raw input string into the object specified by
	 * {@link FormComponent#getType()} and records any thrown {@link ConversionException}s.
	 * Converted value is available through {@link FormComponent#getConvertedInput()}.
	 * 
	 * <p>
	 * Usually the user should do custom conversions by specifying an {@link IConverter} by
	 * registering it with the application by overriding {@link Application#getConverterLocator()},
	 * or at the component level by overriding {@link #getConverter(Class)} .
	 * </p>
	 *
	 * <strong>DO NOT CALL THIS METHOD DIRECTLY UNLESS YOU ARE SURE WHAT YOU ARE DOING. USUALLY UPDATING
	 * YOUR MODEL IS HANDLED BY THE FORM, NOT DIRECTLY BY YOU.</strong>
	 *
	 * @see IConverterLocator
	 * @see Application#newConverterLocator()
	 * @see IConverter#convertToObject(String, Locale)
	 * @see #newValidationError(ConversionException)
	 */
	public void convertInput()
	{
		if (typeName == null)
		{
			try
			{
				convertedInput = convertValue(getInputAsArray());
			}
			catch (ConversionException e)
			{
				error(newValidationError(e));
			}
		}
		else
		{
			final IConverter<T> converter = getConverter(getType());

			try
			{
				convertedInput = converter.convertToObject(getInput(), getLocale());
			}
			catch (ConversionException e)
			{
				error(newValidationError(e));
			}
		}
	}

	/**
	 * This method is called, when the validation triggered by {@link FormComponent#convertInput()}
	 * failed with a {@link ConversionException}, to construct a {@link ValidationError} based on
	 * the exception.
	 * <p>
	 * Override this method to modify the ValidationError object, e.g. add a custom variable for
	 * message substitution:
	 * <p>
	 * 
	 * <pre>
	 * new FormComponent&lt;T&gt;(id)
	 * {
	 * 	protected ValidationError newValidationError(ConversionException cause)
	 * 	{
	 * 		return super.newValidationError(cause).setVariable(&quot;foo&quot;, foovalue);
	 * 	}
	 * };
	 * </pre>
	 * 
	 * @param cause
	 *            the original cause
	 * @return {@link ValidationError}
	 */
	protected ValidationError newValidationError(ConversionException cause)
	{
		ValidationError error = new ValidationError(cause.getMessage());

		if (cause.getResourceKey() != null)
		{
			error.addKey(cause.getResourceKey());
		}

		if (typeName == null)
		{
			if (cause.getTargetType() != null)
			{
				error.addKey("ConversionError." + Classes.simpleName(cause.getTargetType()));
			}
			error.addKey("ConversionError");
		}
		else
		{
			String simpleName = Classes.simpleName(getType());
			error.addKey("IConverter." + simpleName);
			error.addKey("IConverter");
			error.setVariable("type", simpleName);
		}

		final Locale locale = cause.getLocale();
		if (locale != null)
		{
			error.setVariable("locale", locale);
		}

		error.setVariable("exception", cause);

		Format format = cause.getFormat();
		if (format instanceof SimpleDateFormat)
		{
			error.setVariable("format", ((SimpleDateFormat)format).toLocalizedPattern());
		}

		Map<String, Object> variables = cause.getVariables();
		if (variables != null)
		{
			error.getVariables().putAll(variables);
		}

		return error;
	}

	/**
	 * Subclasses should overwrite this if the conversion is not done through the type field and the
	 * {@link IConverter}. <strong>WARNING: this method may be removed in future versions.</strong>
	 * 
	 * If conversion fails then a ConversionException should be thrown
	 * 
	 * @param value
	 *            The value can be the getInput() or through a cookie
	 * 
	 * @return The converted value. default returns just the given value
	 * @throws ConversionException
	 *             If input can't be converted
	 */
	@SuppressWarnings("unchecked")
	protected T convertValue(String[] value) throws ConversionException
	{
		return (T)(value != null && value.length > 0 && value[0] != null ? trim(value[0]) : null);
	}

	/**
	 * @return Value to return when model value is needed
	 */
	protected String getModelValue()
	{
		return getDefaultModelObjectAsString();
	}

	/**
	 * Gets the request parameter for this component as an int.
	 * 
	 * @return The value in the request for this component
	 */
	protected final int inputAsInt()
	{
		final String string = getInput();
		try
		{
			return Integer.parseInt(string);
		}
		catch (NumberFormatException e)
		{
			throw new IllegalArgumentException(
				exceptionMessage("Internal error.  Request string '" + string +
					"' not a valid integer"));
		}
	}

	/**
	 * Gets the request parameter for this component as an int, using the given default in case no
	 * corresponding request parameter was found.
	 * 
	 * @param defaultValue
	 *            Default value to return if request does not have an integer for this component
	 * @return The value in the request for this component
	 */
	protected final int inputAsInt(final int defaultValue)
	{
		final String string = getInput();
		if (string != null)
		{
			try
			{
				return Integer.parseInt(string);
			}
			catch (NumberFormatException e)
			{
				throw new IllegalArgumentException(exceptionMessage("Request string '" + string +
					"' is not a valid integer"));
			}
		}
		else
		{
			return defaultValue;
		}
	}

	/**
	 * Gets the request parameters for this component as ints.
	 * 
	 * @return The values in the request for this component
	 */
	protected final int[] inputAsIntArray()
	{
		final String[] strings = getInputAsArray();
		if (strings != null)
		{
			final int[] ints = new int[strings.length];
			for (int i = 0; i < strings.length; i++)
			{
				ints[i] = Integer.parseInt(strings[i]);
			}
			return ints;
		}
		return null;
	}

	/**
	 * @see org.apache.wicket.Component#internalOnModelChanged()
	 */
	@Override
	protected void internalOnModelChanged()
	{
		// If the model for this form component changed, we should make it
		// valid again because there can't be any invalid input for it anymore.
		valid();
	}

	/**
	 * Processes the component tag.
	 * 
	 * @param tag
	 *            Tag to modify
	 * @see org.apache.wicket.Component#onComponentTag(ComponentTag)
	 */
	@Override
	protected void onComponentTag(final ComponentTag tag)
	{
		tag.put("name", getInputName());

		if (!isEnabledInHierarchy())
		{
			onDisabled(tag);
		}

		super.onComponentTag(tag);
	}

	/**
	 * Sets the temporary converted input value to null.
	 * 
	 * @see org.apache.wicket.Component#onDetach()
	 */
	@Override
	protected void onDetach()
	{
		super.onDetach();
		convertedInput = null;
	}

	/**
	 * Called by {@link #onComponentTag(ComponentTag)} when the component is disabled. By default,
	 * this method will add a disabled="disabled" attribute to the tag. Components may override this
	 * method to tweak the tag as they think is fit.
	 * 
	 * @param tag
	 *            the tag that is being rendered
	 */
	protected void onDisabled(final ComponentTag tag)
	{
		tag.put("disabled", "disabled");
	}

	/**
	 * Handle invalidation
	 */
	protected void onInvalid()
	{
	}

	/**
	 * Handle validation
	 */
	protected void onValid()
	{
	}

	/**
	 * Determines whether or not this component should trim its input prior to processing it. The
	 * default value is <code>true</code>
	 * 
	 * @return True if the input should be trimmed.
	 */
	protected boolean shouldTrimInput()
	{
		return true;
	}

	/**
	 * Trims the input according to {@link #shouldTrimInput()}
	 * 
	 * @param string
	 * @return trimmed input if {@link #shouldTrimInput()} returns true, unchanged input otherwise
	 */
	protected String trim(String string)
	{
		String trimmed = string;
		if (trimmed != null && shouldTrimInput())
		{
			trimmed = trimmed.trim();
		}
		return trimmed;
	}

	/**
	 * Checks if the raw input value is not null if this component is required.
	 */
	protected final void validateRequired()
	{
		if (!checkRequired())
		{
			reportRequiredError();
		}
	}

	/**
	 * Reports required error against this component
	 */
	protected void reportRequiredError()
	{
		error(new ValidationError().addKey("Required"));
	}

	/**
	 * Validates this component using the component's validators.
	 */
	@SuppressWarnings("unchecked")
	protected final void validateValidators()
	{
		final IValidatable<T> validatable = newValidatable();

		boolean isNull = getConvertedInput() == null;

		IValidator<T> validator;

		for (Behavior behavior : getBehaviors())
		{
			if (isBehaviorAccepted(behavior) == false)
			{
				continue;
			}

			validator = null;
			if (behavior instanceof ValidatorAdapter)
			{
				validator = ((ValidatorAdapter<T>)behavior).getValidator();
			}
			else if (behavior instanceof IValidator)
			{
				validator = (IValidator<T>)behavior;
			}
			if (validator != null)
			{
				if (isNull == false || validator instanceof INullAcceptingValidator<?>)
				{
					try
					{
						validator.validate(validatable);
					}
					catch (Exception e)
					{
						throw new WicketRuntimeException("Exception '" + e.getMessage() +
								"' occurred during validation " + validator.getClass().getName() +
								" on component " + getPath(), e);
					}
				}
				if (!isValid())
				{
					break;
				}
			}
		}
	}

	/**
	 * Creates an IValidatable that can be used to validate this form component. This validatable
	 * incorporates error key lookups that correspond to this form component.
	 * 
	 * This method is useful when validation needs to happen outside the regular validation workflow
	 * but error messages should still be properly reported against the form component.
	 * 
	 * @return IValidatable<T> for this form component
	 */
	public final IValidatable<T> newValidatable()
	{
		return new ValidatableAdapter();
	}

	/**
	 * Updates auto label css classes such as error/required during ajax updates when the labels may
	 * not be directly repainted in the response.
	 * 
	 * @param target The {@link IPartialPageRequestHandler}
	 * @param checkAuto if true then we check is the related auto-label is marked as auto before updating it.
	 */
	public final void updateAutoLabels(IPartialPageRequestHandler target, boolean checkAuto)
	{
		AutoLabelMarker marker = getMetaData(AutoLabelResolver.MARKER_KEY);
	
		if (marker == null)
		{
			// this component does not have an auto label
			return;
		}

		if (checkAuto)
		{
			if (!marker.isAuto())
			{
				return;
			}
			marker.updateFrom(this, target);
			return;
		}

		marker.updateFrom(this, target);
	}


	/**
	 * @deprecated method in favor of the one receiving {@link IPartialPageRequestHandler}
	 */
	@Deprecated(since = "9.17.0, 10.0.0", forRemoval = true)
	public final void updateAutoLabels(AjaxRequestTarget target)
	{
		updateAutoLabels(target, false);
	}

	/**
	 * @return if this form component is visited during the form processing
	 */
	public boolean isFormParticipant()
	{
		Component parent = getParent();
		boolean outsideParentForm = false;
		while (parent != null)
		{
			if (parent instanceof IFormVisitorParticipant && !((IFormVisitorParticipant)parent).processChildren() && !outsideParentForm)
			{
				return false;
			}
			if (parent instanceof Form form)
			{
				if (!form.wantSubmitOnParentFormSubmit())
				{
					return false;
				}
				if (form == this.getForm())
				{
					outsideParentForm = true;
				}
			}
			parent = parent.getParent();
		}
		return true;
	}

	/**
	 * Update the model of a {@link FormComponent} containing a {@link Collection}.
	 * 
	 * If the model object does not yet exists, a new suitable collection is filled with the converted
	 * input and used as the new model object. Otherwise the existing collection is modified
	 * in-place, then {@link IModel#setObject(Object)} is called with the same instance: it allows
	 * the Model to be notified of changes even when {@link Model#getObject()} returns a different
	 * {@link Collection} at every invocation.
	 * 
	 * @param <S>
	 *            collection type
	 * @param formComponent
	 *            the form component to update
	 * @see FormComponent#updateModel()
	 * @throws WicketRuntimeException
	 *             if the existing model object collection is unmodifiable and no setter exists
	 */
	public static <S> void updateCollectionModel(FormComponent<Collection<S>> formComponent)
	{
		Collection<S> convertedInput = formComponent.getConvertedInput();
		if (convertedInput == null) {
			convertedInput = Collections.emptyList();
		}

		Collection<S> collection = formComponent.getModelObject();
		if (collection == null)
		{
			Class<?> hint = null;
			if (formComponent.getModel() instanceof IObjectClassAwareModel) {
				hint = ((IObjectClassAwareModel)formComponent.getModel()).getObjectClass();
			}
			if (hint == null) {
				hint = List.class;
			}
			collection = newCollection(hint, convertedInput);
			formComponent.setModelObject(collection);
		}
		else
		{
			boolean modified = false;

			formComponent.modelChanging();

			try
			{
				collection.clear();
				collection.addAll(convertedInput);
				modified = true;
			}
			catch (UnsupportedOperationException unmodifiable)
			{
				if (logger.isDebugEnabled())
				{
					logger.debug("An error occurred while trying to modify the collection attached to "
							+ formComponent, unmodifiable);
				}
				collection = newCollection(collection.getClass(), convertedInput);
			}
			
			try
			{
				formComponent.getModel().setObject(collection);
			}
			catch (Exception noSetter)
			{
				if (!modified)
				{
					throw new WicketRuntimeException("An error occurred while trying to set the collection attached to "
							+ formComponent, noSetter);
				}
				else if (logger.isDebugEnabled())
				{
					logger.debug("An error occurred while trying to set the collection attached to "
							+ formComponent, noSetter);
				}
			}
			
			formComponent.modelChanged();
		}
	}
	
	/**
	 * Creates a new collection. 
	 * 
	 * @param hint type deciding the type of the returned collection
	 * @param  elements elements for the new collection
	 * @return collection
	 * @throws IllegalArgumentException if type is not supported
	 */
	private static <S> Collection<S> newCollection(Class<?> hint, Collection<S> elements)
	{
		if (Set.class.isAssignableFrom(hint)) {
			return new LinkedHashSet<>(elements);
		} else {
			return new ArrayList<>(elements);
		}
	}
}
