Merge remote-tracking branch 'origin/master' into wicket-6774
diff --git a/wicket-core/src/main/java/org/apache/wicket/Behaviors.java b/wicket-core/src/main/java/org/apache/wicket/Behaviors.java
deleted file mode 100644
index aceb318..0000000
--- a/wicket-core/src/main/java/org/apache/wicket/Behaviors.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*

- * 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;

-

-import java.util.ArrayList;

-import java.util.Collections;

-import java.util.List;

-

-import org.apache.wicket.behavior.Behavior;

-import org.apache.wicket.behavior.InvalidBehaviorIdException;

-import org.apache.wicket.model.IDetachable;

-import org.apache.wicket.util.lang.Args;

-

-/**

- * Manages behaviors in a {@link Component} instance

- * 

- * @author igor

- */

-final class Behaviors implements IDetachable

-{

-	private static final long serialVersionUID = 1L;

-	private final Component component;

-

-	public Behaviors(Component component)

-	{

-		this.component = component;

-	}

-

-	public void add(Behavior... behaviors)

-	{

-		Args.notNull(behaviors, "behaviors");

-

-		for (Behavior behavior : behaviors)

-		{

-			Args.notNull(behavior, "behavior");

-

-			internalAdd(behavior);

-

-			if (!behavior.isTemporary(component))

-			{

-				component.addStateChange();

-			}

-

-			// Give handler the opportunity to bind this component

-			behavior.bind(component);

-		}

-	}

-

-	private void internalAdd(final Behavior behavior)

-	{

-		component.data_add(behavior);

-		if (behavior.getStatelessHint(component) == false)

-		{

-			getBehaviorId(behavior);

-		}

-	}

-

-	@SuppressWarnings("unchecked")

-	public <M extends Behavior> List<M> getBehaviors(Class<M> type)

-	{

-		final int len = component.data_length();

-		final int start = component.data_start();

-		if (len < start)

-		{

-			return Collections.emptyList();

-		}

-

-		List<M> subset = new ArrayList<>(len);

-		for (int i = component.data_start(); i < len; i++)

-		{

-			Object obj = component.data_get(i);

-			if (obj != null && obj instanceof Behavior)

-			{

-				if (type == null || type.isAssignableFrom(obj.getClass()))

-				{

-					subset.add((M)obj);

-				}

-			}

-		}

-		if (subset.isEmpty()) {
-			return Collections.emptyList();
-		}
-		return Collections.unmodifiableList(subset);

-	}

-

-

-	public void remove(Behavior behavior)

-	{

-		Args.notNull(behavior, "behavior");

-

-		if (internalRemove(behavior))

-		{

-			if (!behavior.isTemporary(component))

-			{

-				component.addStateChange();

-			}

-			behavior.detach(component);

-		}

-		else

-		{

-			throw new IllegalStateException(

-				"Tried to remove a behavior that was not added to the component. Behavior: " +

-					behavior.toString());

-		}

-	}

-

-	/**

-	 * THIS IS WICKET INTERNAL ONLY. DO NOT USE IT.

-	 * 

-	 * Traverses all behaviors and calls detachModel() on them. This is needed to cleanup behavior

-	 * after render. This method is necessary for {@link org.apache.wicket.ajax.AjaxRequestTarget} to be able to cleanup

-	 * component's behaviors after header contribution has been done (which is separated from

-	 * component render).

-	 */

-	@Override

-	public final void detach()

-	{

-		int len = component.data_length();

-		for (int i = component.data_start(); i < len; i++)

-		{

-			Object obj = component.data_get(i);

-			if (obj != null && obj instanceof Behavior)

-			{

-				final Behavior behavior = (Behavior)obj;

-

-				behavior.detach(component);

-

-				if (behavior.isTemporary(component))

-				{

-					internalRemove(behavior);

-					i--;

-					len--;

-				}

-			}

-		}

-	}

-

-	private boolean internalRemove(final Behavior behavior)

-	{

-		final int len = component.data_length();

-		for (int i = component.data_start(); i < len; i++)

-		{

-			Object o = component.data_get(i);

-			if (o != null && o.equals(behavior))

-			{

-				component.data_remove(i);

-				behavior.unbind(component);

-

-				// remove behavior from behavior-ids

-				ArrayList<Behavior> ids = getBehaviorsIdList(false);

-				if (ids != null)

-				{

-					int idx = ids.indexOf(behavior);

-					if (idx == ids.size() - 1)

-					{

-						ids.remove(idx);

-					}

-					else if (idx >= 0)

-					{

-						ids.set(idx, null);

-					}

-					ids.trimToSize();

-

-					if (ids.isEmpty())

-					{

-						removeBehaviorsIdList();

-					}

-

-				}

-				return true;

-			}

-		}

-		return false;

-	}

-

-	private void removeBehaviorsIdList()

-	{

-		for (int i = component.data_start(); i < component.data_length(); i++)

-		{

-			Object obj = component.data_get(i);

-			if (obj != null && obj instanceof BehaviorIdList)

-			{

-				component.data_remove(i);

-				return;

-			}

-		}

-	}

-

-	private BehaviorIdList getBehaviorsIdList(boolean createIfNotFound)

-	{

-		int len = component.data_length();

-		for (int i = component.data_start(); i < len; i++)

-		{

-			Object obj = component.data_get(i);

-			if (obj != null && obj instanceof BehaviorIdList)

-			{

-				return (BehaviorIdList)obj;

-			}

-		}

-		if (createIfNotFound)

-		{

-			BehaviorIdList list = new BehaviorIdList();

-			component.data_add(list);

-			return list;

-		}

-		return null;

-	}

-

-	/**

-	 * Called when the component is going to be removed. Notifies all

-	 * behaviors assigned to this component.

-	 *

-	 * @param component

-	 *      the component that will be removed from its parent

-	 */

-	public void onRemove(Component component)

-	{

-		final int len = component.data_length();

-		for (int i = component.data_start(); i < len; i++)

-		{

-			Object obj = component.data_get(i);

-			if (obj != null && obj instanceof Behavior)

-			{

-				final Behavior behavior = (Behavior)obj;

-

-				behavior.onRemove(component);

-			}

-		}

-	}

-

-	private static class BehaviorIdList extends ArrayList<Behavior>

-	{

-		private static final long serialVersionUID = 1L;

-

-		public BehaviorIdList()

-		{

-			super(1);

-		}

-	}

-

-	public final int getBehaviorId(Behavior behavior)

-	{

-		Args.notNull(behavior, "behavior");

-

-		boolean found = false;

-		for (int i = component.data_start(); i < component.data_length(); i++)

-		{

-			if (behavior == component.data_get(i))

-			{

-				found = true;

-				break;

-			}

-		}

-		if (!found)

-		{

-			throw new IllegalStateException(

-				"Behavior must be added to component before its id can be generated. Behavior: " +

-					behavior + ", Component: " + this);

-		}

-

-		ArrayList<Behavior> ids = getBehaviorsIdList(true);

-

-		int id = ids.indexOf(behavior);

-

-		if (id < 0)

-		{

-			// try to find an unused slot

-			for (int i = 0; i < ids.size(); i++)

-			{

-				if (ids.get(i) == null)

-				{

-					ids.set(i, behavior);

-					id = i;

-					break;

-				}

-			}

-		}

-

-		if (id < 0)

-		{

-			// no unused slots, add to the end

-			id = ids.size();

-			ids.add(behavior);

-			ids.trimToSize();

-		}

-

-		return id;

-	}

-

-	public final Behavior getBehaviorById(int id)

-	{

-		Behavior behavior = null;

-

-		ArrayList<Behavior> ids = getBehaviorsIdList(false);

-		if (ids != null)

-		{

-			if (id >= 0 && id < ids.size())

-			{

-				behavior = ids.get(id);

-			}

-		}

-

-		if (behavior != null)

-		{

-			return behavior;

-		}

-		throw new InvalidBehaviorIdException(component, id);

-	}

-

-

-}

diff --git a/wicket-core/src/main/java/org/apache/wicket/Component.java b/wicket-core/src/main/java/org/apache/wicket/Component.java
index ef9dc41..6a81617 100644
--- a/wicket-core/src/main/java/org/apache/wicket/Component.java
+++ b/wicket-core/src/main/java/org/apache/wicket/Component.java
@@ -381,12 +381,18 @@
 	/**
 	 * Flag that determines whether the model is set. This is necessary because of the way we
 	 * represent component state ({@link #data}). We can't distinguish between model and behavior
-	 * using instanceof, because one object can implement both interfaces. Thus we need this flag -
-	 * when the flag is set, first object in {@link #data} is always model.
+	 * using instanceof, because one object can implement both interfaces.
 	 */
 	private static final int FLAG_MODEL_SET = 0x100000;
 
 	/**
+	 * Flag that is set when {@link #getBehaviorId(Behavior)} is called on this component. Once this
+	 * flag is set, the indexes of all behaviors must remain fixed to keep the contract of
+	 * {@link #getBehaviorId(Behavior)}.
+	 */
+	private static final int FLAG_BEHAVIOR_IDS_FIXED = 0x200000;
+	
+	/**
 	 * Flag that restricts visibility of a component when set to true. This is usually used when a
 	 * component wants to restrict visibility of another component. Calling
 	 * {@link #setVisible(boolean)} on a component does not always have the desired effect because
@@ -456,8 +462,7 @@
 
 	/**
 	 * Instead of remembering the whole markupId, we just remember the number for this component so
-	 * we can "reconstruct" the markupId on demand. While this could be part of {@link #data},
-	 * profiling showed that having it as separate property consumes less memory.
+	 * we can "reconstruct" the markupId on demand.
 	 */
 	int generatedMarkupId = -1;
 
@@ -480,170 +485,11 @@
 	 * <li>MetaDataEntry (optionally {@link MetaDataEntry}[] if more metadata entries are present) *
 	 * <li>{@link Behavior}(s) added to component. The behaviors are not stored in separate array,
 	 * they are part of the {@link #data} array (this is in order to save the space of the pointer
-	 * to an empty array as most components have no behaviours). - FIXME - explain why - is this
-	 * correct?
+	 * to an empty array as most components have no behaviours).
+	 * <li>A {@link ComponentState} if a combination of the attributes is set.
 	 * </ul>
-	 * If there is only one attribute set (i.e. model or MetaDataEntry([]) or one behavior), the
-	 * #data object points directly to value of that attribute. Otherwise the data is of type
-	 * Object[] where the attributes are ordered as specified above.
-	 * <p>
 	 */
-	Object data = null;
-
-	final int data_start()
-	{
-		return getFlag(FLAG_MODEL_SET) ? 1 : 0;
-	}
-
-	final int data_length()
-	{
-		if (data == null)
-		{
-			return 0;
-		}
-		else if (data instanceof Object[] && !(data instanceof MetaDataEntry<?>[]))
-		{
-			return ((Object[])data).length;
-		}
-		else
-		{
-			return 1;
-		}
-	}
-
-	final Object data_get(int index)
-	{
-		if (data == null)
-		{
-			return null;
-		}
-		else if (data instanceof Object[] && !(data instanceof MetaDataEntry<?>[]))
-		{
-			Object[] array = (Object[])data;
-			return index < array.length ? array[index] : null;
-		}
-		else if (index == 0)
-		{
-			return data;
-		}
-		else
-		{
-			return null;
-		}
-	}
-
-	final void data_set(int index, Object object)
-	{
-		if (index > data_length() - 1)
-		{
-			throw new IndexOutOfBoundsException("can not set data at " + index +
-				" when data_length() is " + data_length());
-		}
-		else if (index == 0 && !(data instanceof Object[] && !(data instanceof MetaDataEntry<?>[])))
-		{
-			data = object;
-		}
-		else
-		{
-			Object[] array = (Object[])data;
-			array[index] = object;
-		}
-	}
-
-	final void data_add(Object object)
-	{
-		data_insert(-1, object);
-	}
-
-	final void data_insert(int position, Object object)
-	{
-		int currentLength = data_length();
-		if (position == -1)
-		{
-			position = currentLength;
-		}
-		if (position > currentLength)
-		{
-			throw new IndexOutOfBoundsException("can not insert data at " + position +
-				" when data_length() is " + currentLength);
-		}
-		if (currentLength == 0)
-		{
-			data = object;
-		}
-		else if (currentLength == 1)
-		{
-			Object[] array = new Object[2];
-			if (position == 0)
-			{
-				array[0] = object;
-				array[1] = data;
-			}
-			else
-			{
-				array[0] = data;
-				array[1] = object;
-			}
-			data = array;
-		}
-		else
-		{
-			Object[] array = new Object[currentLength + 1];
-			Object[] current = (Object[])data;
-			int after = currentLength - position;
-			if (position > 0)
-			{
-				System.arraycopy(current, 0, array, 0, position);
-			}
-			array[position] = object;
-			if (after > 0)
-			{
-				System.arraycopy(current, position, array, position + 1, after);
-			}
-			data = array;
-		}
-	}
-
-	final void data_remove(int position)
-	{
-		int currentLength = data_length();
-
-		if (position > currentLength - 1)
-		{
-			throw new IndexOutOfBoundsException();
-		}
-		else if (currentLength == 1)
-		{
-			data = null;
-		}
-		else if (currentLength == 2)
-		{
-			Object[] current = (Object[])data;
-			if (position == 0)
-			{
-				data = current[1];
-			}
-			else
-			{
-				data = current[0];
-			}
-		}
-		else
-		{
-			Object[] current = (Object[])data;
-			data = new Object[currentLength - 1];
-
-			if (position > 0)
-			{
-				System.arraycopy(current, 0, data, 0, position);
-			}
-			if (position != currentLength - 1)
-			{
-				final int left = currentLength - position - 1;
-				System.arraycopy(current, position + 1, data, position, left);
-			}
-		}
-	}
+	private Object data = null;
 
 	/**
 	 * Constructor. All components have names. A component's id cannot be null. This is the minimal
@@ -1090,7 +936,7 @@
 				getClass().getName() +
 				" has not called super.onRemove() in the override of onRemove() method");
 		}
-		new Behaviors(this).onRemove(this);
+		ComponentState.onRemoveBehaviors(this, data, getFlag(FLAG_MODEL_SET));
 		removeChildren();
 	}
 
@@ -1119,7 +965,8 @@
 			detachModels();
 
 			// detach any behaviors
-			new Behaviors(this).detach();
+			data = ComponentState.detachBehaviors(this, data, getFlag(FLAG_MODEL_SET),
+				getFlag(FLAG_BEHAVIOR_IDS_FIXED));
 		}
 		catch (Exception x)
 		{
@@ -1512,39 +1359,20 @@
 	 * @see MetaDataKey
 	 */
 	@Override
+	@SuppressWarnings("unchecked")
 	public final <M extends Serializable> M getMetaData(final MetaDataKey<M> key)
 	{
-		return key.get(getMetaData());
-	}
-
-	/**
-	 * Gets the meta data entries for this component as an array of {@link MetaDataEntry} objects.
-         *
-	 * @return the meta data entries for this component
-	 */
-	private MetaDataEntry<?>[] getMetaData()
-	{
-		MetaDataEntry<?>[] metaData = null;
-
-		// index where we should expect the entry
-		int index = getFlag(FLAG_MODEL_SET) ? 1 : 0;
-
-		int length = data_length();
-
-		if (index < length)
+		Object metaData = ComponentState.getMetaData(data, getFlag(FLAG_MODEL_SET));
+		if (metaData == null)
 		{
-			Object object = data_get(index);
-			if (object instanceof MetaDataEntry<?>[])
-			{
-				metaData = (MetaDataEntry<?>[])object;
-			}
-			else if (object instanceof MetaDataEntry)
-			{
-				metaData = new MetaDataEntry[] { (MetaDataEntry<?>)object };
-			}
+			return null;
 		}
-
-		return metaData;
+		else if (metaData instanceof MetaDataEntry)
+		{
+			MetaDataEntry< ? > entry = (MetaDataEntry< ? >) metaData;
+			return entry.key.equals(key) ? (M) entry.object : null;
+		}
+		return key.get((MetaDataEntry< ? >[]) metaData);
 	}
 
 	/**
@@ -2872,29 +2700,7 @@
 	@Override
 	public final <M extends Serializable> Component setMetaData(final MetaDataKey<M> key, final M object)
 	{
-		MetaDataEntry<?>[] old = getMetaData();
-
-		Object metaData = null;
-		MetaDataEntry<?>[] metaDataArray = key.set(getMetaData(), object);
-		if (metaDataArray != null && metaDataArray.length > 0)
-		{
-			metaData = (metaDataArray.length > 1) ? metaDataArray : metaDataArray[0];
-		}
-
-		int index = getFlag(FLAG_MODEL_SET) ? 1 : 0;
-
-		if (old == null && metaData != null)
-		{
-			data_insert(index, metaData);
-		}
-		else if (old != null && metaData != null)
-		{
-			data_set(index, metaData);
-		}
-		else if (old != null && metaData == null)
-		{
-			data_remove(index);
-		}
+		data = ComponentState.setMetaData(data, getFlag(FLAG_MODEL_SET), key, object);
 		return this;
 	}
 
@@ -2944,11 +2750,7 @@
 	 */
 	IModel<?> getModelImpl()
 	{
-		if (getFlag(FLAG_MODEL_SET))
-		{
-			return (IModel<?>)data_get(0);
-		}
-		return null;
+		return ComponentState.getModel(data, getFlag(FLAG_MODEL_SET));
 	}
 
 	/**
@@ -2957,26 +2759,8 @@
 	 */
 	void setModelImpl(IModel<?> model)
 	{
-		if (getFlag(FLAG_MODEL_SET))
-		{
-			if (model != null)
-			{
-				data_set(0, model);
-			}
-			else
-			{
-				data_remove(0);
-				setFlag(FLAG_MODEL_SET, false);
-			}
-		}
-		else
-		{
-			if (model != null)
-			{
-				data_insert(0, model);
-				setFlag(FLAG_MODEL_SET, true);
-			}
-		}
+		data = ComponentState.setModel(model, data, getFlag(FLAG_MODEL_SET));
+		setFlag(FLAG_MODEL_SET, model != null);
 	}
 
 	/**
@@ -3620,7 +3404,7 @@
 	 */
 	public <M extends Behavior> List<M> getBehaviors(Class<M> type)
 	{
-		return new Behaviors(this).getBehaviors(type);
+		return ComponentState.getBehaviors(type, data, getFlag(FLAG_MODEL_SET));
 	}
 
 	/**
@@ -4429,11 +4213,7 @@
 	 */
 	public Component remove(final Behavior... behaviors)
 	{
-		Behaviors helper = new Behaviors(this);
-		for (Behavior behavior : behaviors)
-		{
-			helper.remove(behavior);
-		}
+		data = ComponentState.removeBehaviors(this, data, getFlag(FLAG_MODEL_SET), behaviors);
 		return this;
 	}
 
@@ -4441,7 +4221,10 @@
 	@Override
 	public final Behavior getBehaviorById(int id)
 	{
-		return new Behaviors(this).getBehaviorById(id);
+		data = ComponentState.compactBehaviors(this, data, getFlag(FLAG_MODEL_SET),
+			getFlag(FLAG_BEHAVIOR_IDS_FIXED));
+		setFlag(FLAG_BEHAVIOR_IDS_FIXED, true);
+		return ComponentState.getBehaviorById(this, id, data, getFlag(FLAG_MODEL_SET));
 	}
 
 	/** {@inheritDoc} */
@@ -4453,7 +4236,10 @@
 			throw new IllegalArgumentException(
 				"Cannot get a stable id for temporary behavior " + behavior);
 		}
-		return new Behaviors(this).getBehaviorId(behavior);
+		data = ComponentState.compactBehaviors(this, data, getFlag(FLAG_MODEL_SET),
+			getFlag(FLAG_BEHAVIOR_IDS_FIXED));
+		setFlag(FLAG_BEHAVIOR_IDS_FIXED, true);
+		return ComponentState.getBehaviorId(this, behavior, data, getFlag(FLAG_MODEL_SET));
 	}
 
 	/**
@@ -4465,7 +4251,11 @@
 	 */
 	public Component add(final Behavior... behaviors)
 	{
-		new Behaviors(this).add(behaviors);
+		data = ComponentState.addBehaviors(this, data, getFlag(FLAG_MODEL_SET), behaviors);
+		for (Behavior curBehavior : behaviors)
+		{
+			ComponentState.bindBehavior(this, curBehavior);
+		}
 		return this;
 	}
 
diff --git a/wicket-core/src/main/java/org/apache/wicket/ComponentState.java b/wicket-core/src/main/java/org/apache/wicket/ComponentState.java
new file mode 100644
index 0000000..363aa7c
--- /dev/null
+++ b/wicket-core/src/main/java/org/apache/wicket/ComponentState.java
@@ -0,0 +1,1081 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.behavior.InvalidBehaviorIdException;
+import org.apache.wicket.model.IModel;
+
+/**
+ * This class keeps track of the flexible state of a component: model, behaviors and meta data.
+ * These types of state vary per component. Not every component contains these elements or their
+ * numbers differ. The state is stored in the {@code data} field in {@link Component}. To keep the
+ * size of this state as small as possible, the following cases are identified:
+ * <ul>
+ * <li>No state at all: {@code data} is {@code null}
+ * <li>Only a model: {@code data} contains the model
+ * <li>Only one or more behaviors: {@code data} contains the behavior, or an array of behaviors
+ * <li>Only one or more meta data entries: {@code data} contains the entry, or an array of entries
+ * <li>A model and one or more behaviors: {@code data} contains an instance of
+ * {@link ModelBehaviorsComponentState}
+ * <li>A model and one or more meta data entries: {@code data} contains an instance of
+ * {@link ModelMetaDataComponentState}
+ * <li>One or more behaviors and one or more meta data entries: {@code data} contains an instance of
+ * {@link BehaviorsMetaDataComponentState}
+ * <li>A model, one or more behaviors and one or more meta data entries: {@code data} contains an
+ * instance of {@link ModelBehaviorsMetaDataComponentState}
+ * </ul>
+ * 
+ * @author papegaaij
+ */
+abstract class ComponentState implements Serializable
+{
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * @return The model stored by this state, or null.
+	 */
+	abstract IModel< ? > getModel();
+
+	/**
+	 * @param model
+	 *            the new model or null
+	 * @return the new state for the component using the rules defined above
+	 */
+	abstract Object setModel(IModel< ? > model);
+
+	/**
+	 * @return The behaviors stored by this state: null, a single behavior or an array of behaviors.
+	 */
+	abstract Object getBehaviors();
+
+	/**
+	 * @param behaviors
+	 *            the new behaviors (null, one behavior or an array of behaviors)
+	 * @return the new state for the component using the rules defined above
+	 */
+	abstract Object setBehaviors(Object behaviors);
+
+	/**
+	 * @return The meta data entries stored by this state: null, a single entry or an array of
+	 *         entries.
+	 */
+	abstract Object getMetaData();
+
+	/**
+	 * @param metaData
+	 *            the new meta data entries (null, one entry or an array of entries)
+	 * @return the new state for the component using the rules defined above
+	 */
+	abstract Object setMetaData(Object metaData);
+
+	/**
+	 * Combines a model and one or more behaviors.
+	 */
+	static class ModelBehaviorsComponentState extends ComponentState
+	{
+		private static final long serialVersionUID = 1L;
+
+		private IModel< ? > model;
+
+		private Object behaviors;
+
+		private ModelBehaviorsComponentState(IModel< ? > model, Object behaviors)
+		{
+			this.model = model;
+			this.behaviors = behaviors;
+		}
+
+		@Override
+		IModel< ? > getModel()
+		{
+			return model;
+		}
+
+		@Override
+		Object setModel(IModel< ? > model)
+		{
+			if (model == null)
+			{
+				return behaviors;
+			}
+			this.model = model;
+			return this;
+		}
+
+		@Override
+		Object getBehaviors()
+		{
+			return behaviors;
+		}
+
+		@Override
+		Object setBehaviors(Object behaviors)
+		{
+			if (behaviors == null)
+			{
+				return model;
+			}
+			this.behaviors = behaviors;
+			return this;
+		}
+
+		@Override
+		Object getMetaData()
+		{
+			return null;
+		}
+
+		@Override
+		Object setMetaData(Object metaData)
+		{
+			if (metaData == null)
+			{
+				return this;
+			}
+			return new ModelBehaviorsMetaDataComponentState(model, behaviors, metaData);
+		}
+	}
+
+	/**
+	 * Combines a model and one or more meta data entries.
+	 */
+	static class ModelMetaDataComponentState extends ComponentState
+	{
+		private static final long serialVersionUID = 1L;
+
+		private IModel< ? > model;
+
+		private Object metaData;
+
+		private ModelMetaDataComponentState(IModel< ? > model, Object metaData)
+		{
+			this.model = model;
+			this.metaData = metaData;
+		}
+
+		@Override
+		IModel< ? > getModel()
+		{
+			return model;
+		}
+
+		@Override
+		Object setModel(IModel< ? > model)
+		{
+			if (model == null)
+			{
+				return metaData;
+			}
+			this.model = model;
+			return this;
+		}
+
+		@Override
+		Object getBehaviors()
+		{
+			return null;
+		}
+
+		@Override
+		Object setBehaviors(Object behaviors)
+		{
+			if (behaviors == null)
+			{
+				return this;
+			}
+			return new ModelBehaviorsMetaDataComponentState(model, behaviors, metaData);
+		}
+
+		@Override
+		Object getMetaData()
+		{
+			return metaData;
+		}
+
+		@Override
+		Object setMetaData(Object metaData)
+		{
+			if (metaData == null)
+			{
+				return model;
+			}
+			this.metaData = metaData;
+			return this;
+		}
+	}
+
+	/**
+	 * Combines one or more behaviors and one ore more meta data entries.
+	 */
+	static class BehaviorsMetaDataComponentState extends ComponentState
+	{
+		private static final long serialVersionUID = 1L;
+
+		private Object behaviors;
+
+		private Object metaData;
+
+		private BehaviorsMetaDataComponentState(Object behaviors, Object metaData)
+		{
+			this.behaviors = behaviors;
+			this.metaData = metaData;
+		}
+
+		@Override
+		IModel< ? > getModel()
+		{
+			return null;
+		}
+
+		@Override
+		Object setModel(IModel< ? > model)
+		{
+			if (model == null)
+			{
+				return this;
+			}
+			return new ModelBehaviorsMetaDataComponentState(model, behaviors, metaData);
+		}
+
+		@Override
+		Object getBehaviors()
+		{
+			return behaviors;
+		}
+
+		@Override
+		Object setBehaviors(Object behaviors)
+		{
+			if (behaviors == null)
+			{
+				return metaData;
+			}
+			this.behaviors = behaviors;
+			return this;
+		}
+
+		@Override
+		Object getMetaData()
+		{
+			return metaData;
+		}
+
+		@Override
+		Object setMetaData(Object metaData)
+		{
+			if (metaData == null)
+			{
+				return behaviors;
+			}
+			this.metaData = metaData;
+			return this;
+		}
+	}
+
+	/**
+	 * Combines a model, one or more behaviors and one or more meta data entries.
+	 */
+	static class ModelBehaviorsMetaDataComponentState extends ComponentState
+	{
+		private static final long serialVersionUID = 1L;
+
+		private IModel< ? > model;
+
+		private Object behaviors;
+
+		private Object metaData;
+
+		private ModelBehaviorsMetaDataComponentState(IModel< ? > model, Object behaviors,
+				Object metaData)
+		{
+			this.model = model;
+			this.behaviors = behaviors;
+			this.metaData = metaData;
+		}
+
+		@Override
+		IModel< ? > getModel()
+		{
+			return model;
+		}
+
+		@Override
+		Object setModel(IModel< ? > model)
+		{
+			if (model == null)
+			{
+				return new BehaviorsMetaDataComponentState(behaviors, metaData);
+			}
+			this.model = model;
+			return this;
+		}
+
+		@Override
+		Object getBehaviors()
+		{
+			return behaviors;
+		}
+
+		@Override
+		Object setBehaviors(Object behaviors)
+		{
+			if (behaviors == null)
+			{
+				return new ModelMetaDataComponentState(model, metaData);
+			}
+			this.behaviors = behaviors;
+			return this;
+		}
+
+		@Override
+		Object getMetaData()
+		{
+			return metaData;
+		}
+
+		@Override
+		Object setMetaData(Object metaData)
+		{
+			if (metaData == null)
+			{
+				return new ModelBehaviorsComponentState(model, behaviors);
+			}
+			this.metaData = metaData;
+			return this;
+		}
+	}
+
+	/**
+	 * @param state
+	 *            the component state
+	 * @param modelSet
+	 *            a boolean indicating if the model is set
+	 * @return the model from the given state or null
+	 */
+	static IModel< ? > getModel(Object state, boolean modelSet)
+	{
+		if (state instanceof ComponentState)
+		{
+			return ((ComponentState) state).getModel();
+		}
+		return modelSet ? (IModel< ? >) state : null;
+	}
+
+	/**
+	 * @param state
+	 *            the component state
+	 * @param modelSet
+	 *            a boolean indicating if the model is set
+	 * @return the behaviors from the given state: null, one behavior or an array of behaviors
+	 */
+	static Object getBehaviors(Object state, boolean modelSet)
+	{
+		if (state instanceof ComponentState)
+		{
+			return ((ComponentState) state).getBehaviors();
+		}
+		return modelSet || !(state instanceof Behavior || state instanceof Behavior[]) ? null
+			: state;
+	}
+
+	/**
+	 * @param state
+	 *            the component state
+	 * @param modelSet
+	 *            a boolean indicating if the model is set
+	 * @return the meta data entries from the given state: null, one entry or an array of entries
+	 */
+	static Object getMetaData(Object state, boolean modelSet)
+	{
+		if (state instanceof ComponentState)
+		{
+			return ((ComponentState) state).getMetaData();
+		}
+		return modelSet || !(state instanceof MetaDataEntry || state instanceof MetaDataEntry[])
+			? null : state;
+	}
+
+	/**
+	 * Construct a new component state with the given model value
+	 * 
+	 * @param model
+	 *            the new model to set or null to clear
+	 * @param state
+	 *            the current component state
+	 * @param modelSet
+	 *            a boolean indicating if the model is set
+	 * @return the new component state
+	 */
+	static Object setModel(IModel< ? > model, Object state, boolean modelSet)
+	{
+		if (state instanceof ComponentState)
+		{
+			ComponentState compState = (ComponentState) state;
+			return compState.setModel(model);
+		}
+		else if (modelSet || state == null)
+		{
+			return model;
+		}
+		// state does not have a model, clear is a no-op
+		else if (model == null)
+		{
+			return state;
+		}
+		else if (state instanceof MetaDataEntry || state instanceof MetaDataEntry[])
+		{
+			return new ModelMetaDataComponentState(model, state);
+		}
+		else
+		{
+			return new ModelBehaviorsComponentState(model, state);
+		}
+	}
+
+	/**
+	 * Construct a new component state with the given behaviors added
+	 * 
+	 * @param component
+	 *            the component to add the behaviors to
+	 * @param state
+	 *            the current component state
+	 * @param modelSet
+	 *            a boolean indicating if the model is set
+	 * @param behaviorsToAdd
+	 *            the behaviors to add
+	 * @return the new component state
+	 */
+	static Object addBehaviors(Component component, Object state, boolean modelSet,
+			Behavior... behaviorsToAdd)
+	{
+		if (behaviorsToAdd.length == 0)
+		{
+			return state;
+		}
+		else if (state instanceof ComponentState)
+		{
+			ComponentState compState = (ComponentState) state;
+			return compState
+				.setBehaviors(addBehaviors(component, compState.getBehaviors(), behaviorsToAdd));
+		}
+		else if (modelSet)
+		{
+			return new ModelBehaviorsComponentState((IModel< ? >) state,
+				addBehaviors(component, null, behaviorsToAdd));
+		}
+		else if (state instanceof MetaDataEntry || state instanceof MetaDataEntry[])
+		{
+			return new BehaviorsMetaDataComponentState(
+				addBehaviors(component, null, behaviorsToAdd), state);
+		}
+		else
+		{
+			return addBehaviors(component, state, behaviorsToAdd);
+		}
+	}
+
+	/**
+	 * Construct a new component state with the given behaviors removed
+	 * 
+	 * @param component
+	 *            the component to remove the behaviors from
+	 * @param state
+	 *            the current component state
+	 * @param modelSet
+	 *            a boolean indicating if the model is set
+	 * @param behaviorsToRemove
+	 *            the behaviors to removed
+	 * @return the new component state
+	 */
+	static Object removeBehaviors(Component component, Object state, boolean modelSet,
+			Behavior... behaviorsToRemove)
+	{
+		if (behaviorsToRemove.length == 0)
+		{
+			return state;
+		}
+		else if (state instanceof ComponentState)
+		{
+			ComponentState compState = (ComponentState) state;
+			return compState.setBehaviors(
+				removeBehaviors(component, compState.getBehaviors(), behaviorsToRemove));
+		}
+		else if (modelSet)
+		{
+			throw cannotRemove(behaviorsToRemove[0]);
+		}
+		else if (state instanceof MetaDataEntry || state instanceof MetaDataEntry[])
+		{
+			throw cannotRemove(behaviorsToRemove[0]);
+		}
+		else
+		{
+			return removeBehaviors(component, state, behaviorsToRemove);
+		}
+	}
+
+	/**
+	 * Construct a new component state with the behaviors replaced
+	 * 
+	 * @param state
+	 *            the current component state
+	 * @param modelSet
+	 *            a boolean indicating if the model is set
+	 * @param behaviors
+	 *            the new value for the behaviors: null, one behavior or an array of behaviors
+	 * @return the new component state
+	 */
+	static Object setBehaviors(Object state, boolean modelSet, Object behaviors)
+	{
+		if (state instanceof ComponentState)
+		{
+			ComponentState compState = (ComponentState) state;
+			return compState.setBehaviors(behaviors);
+		}
+		else if (state instanceof Behavior || state instanceof Behavior[] || state == null)
+		{
+			return behaviors;
+		}
+		else if (behaviors == null)
+		{
+			return state;
+		}
+		else if (modelSet)
+		{
+			return new ModelBehaviorsComponentState((IModel< ? >) state, behaviors);
+		}
+		else
+		{
+			return new BehaviorsMetaDataComponentState(behaviors, state);
+		}
+	}
+
+	/**
+	 * Construct a new component state with the given meta data entry set or reset
+	 * 
+	 * @param state
+	 *            the current component state
+	 * @param modelSet
+	 *            a boolean indicating if the model is set
+	 * @param key
+	 *            the key to replace the value for
+	 * @param data
+	 *            the new value for the meta data entry, null to clear
+	 * @return the new component state
+	 */
+	static <T> Object setMetaData(Object state, boolean modelSet, MetaDataKey<T> key, T data)
+	{
+		if (state instanceof ComponentState)
+		{
+			ComponentState compState = (ComponentState) state;
+			return compState.setMetaData(setMetaData(compState.getMetaData(), key, data));
+		}
+		else if (state instanceof MetaDataEntry || state instanceof MetaDataEntry[]
+			|| state == null)
+		{
+			return setMetaData(state, key, data);
+		}
+		else if (data == null)
+		{
+			return state;
+		}
+		else if (modelSet)
+		{
+			return new ModelMetaDataComponentState((IModel< ? >) state,
+				new MetaDataEntry<>(key, data));
+		}
+		else
+		{
+			return new BehaviorsMetaDataComponentState(state, new MetaDataEntry<>(key, data));
+		}
+	}
+
+	/**
+	 * Bind a behavior to a component, adding a state change if needed.
+	 * 
+	 * @param component
+	 * @param behavior
+	 */
+	static void bindBehavior(Component component, Behavior behavior)
+	{
+		if (!behavior.isTemporary(component))
+		{
+			component.addStateChange();
+		}
+		behavior.bind(component);
+	}
+
+	private static Object addBehaviors(Component component, Object behaviors,
+			Behavior... behaviorsToAdd)
+	{
+		// nothing to add
+		if (behaviorsToAdd.length == 0)
+		{
+			return behaviors;
+		}
+
+		// the existing array is compact, adding cannot shrink it
+		int curLength = getBehaviorsLength(behaviors);
+		int newSize = Math.max(curLength, behaviorsToAdd.length + getBehaviorsLength(behaviors)
+			- getEmptyBehaviorsSlots(behaviors));
+
+		// new size is 1, it must be we are adding 1 to 0
+		if (newSize == 1)
+		{
+			return behaviorsToAdd[0];
+		}
+
+		// construct the return array and copy existing behaviors
+		Behavior[] ret = new Behavior[newSize];
+		if (behaviors instanceof Behavior[])
+		{
+			System.arraycopy(behaviors, 0, ret, 0, curLength);
+		}
+		else
+		{
+			ret[0] = (Behavior) behaviors;
+		}
+
+		// fill empty slots with behaviors to add
+		int checkSlot = 0;
+		for (Behavior behaviorToAdd : behaviorsToAdd)
+		{
+			while (ret[checkSlot] != null)
+			{
+				checkSlot++;
+			}
+			ret[checkSlot] = behaviorToAdd;
+		}
+		return ret;
+	}
+
+	private static Object removeBehaviors(Component component, Object behaviors,
+			Behavior... behaviorsToRemove)
+	{
+		// nothing to remove
+		if (behaviorsToRemove.length == 0)
+		{
+			return behaviors;
+		}
+		if (behaviors == null)
+		{
+			throw cannotRemove(behaviorsToRemove[0]);
+		}
+
+		if (behaviors instanceof Behavior)
+		{
+			if (!behaviorsToRemove[0].equals(behaviors))
+			{
+				throw cannotRemove(behaviorsToRemove[0]);
+			}
+			if (behaviorsToRemove.length > 1)
+			{
+				throw cannotRemove(behaviorsToRemove[1]);
+			}
+			unbindBehavior(component, (Behavior) behaviors);
+			return null;
+		}
+
+		Behavior[] behaviorArr = (Behavior[]) behaviors;
+		for (Behavior behaviorToRemove : behaviorsToRemove)
+		{
+			boolean found = false;
+			for (int i = 0; i < behaviorArr.length; i++)
+			{
+				Behavior curBehavior = behaviorArr[i];
+				if (curBehavior != null && behaviorToRemove.equals(curBehavior))
+				{
+					found = true;
+					unbindBehavior(component, curBehavior);
+					behaviorArr[i] = null;
+					break;
+				}
+			}
+			if (!found)
+			{
+				throw cannotRemove(behaviorToRemove);
+			}
+		}
+		return behaviorArr;
+	}
+
+	private static IllegalStateException cannotRemove(Behavior behavior)
+	{
+		return new IllegalStateException(
+			"Tried to remove a behavior that was not added to the component. Behavior: "
+				+ behavior.toString());
+	}
+
+	private static void unbindBehavior(Component component, Behavior behavior)
+	{
+		behavior.unbind(component);
+		if (!behavior.isTemporary(component))
+		{
+			component.addStateChange();
+		}
+		behavior.detach(component);
+	}
+
+	private static int getBehaviorsLength(Object behaviors)
+	{
+		if (behaviors == null)
+		{
+			return 0;
+		}
+		return behaviors instanceof Behavior[] ? ((Behavior[]) behaviors).length : 1;
+	}
+
+	private static int getEmptyBehaviorsSlots(Object behaviors)
+	{
+		if (!(behaviors instanceof Behavior[]))
+		{
+			return 0;
+		}
+		Behavior[] arr = (Behavior[]) behaviors;
+		int emptyCount = 0;
+		for (Behavior curBehavior : arr)
+		{
+			if (curBehavior == null)
+			{
+				emptyCount++;
+			}
+		}
+		return emptyCount;
+	}
+
+	private static <T> Object setMetaData(Object metadata, MetaDataKey<T> key, T data)
+	{
+		if (metadata == null)
+		{
+			if (data == null)
+			{
+				return null;
+			}
+			else
+			{
+				return new MetaDataEntry<>(key, data);
+			}
+		}
+		else if (metadata instanceof MetaDataEntry)
+		{
+			MetaDataEntry< ? > curEntry = (MetaDataEntry< ? >) metadata;
+			if (curEntry.key.equals(key))
+			{
+				if (data == null)
+				{
+					return null;
+				}
+				else
+				{
+					curEntry.object = data;
+					return curEntry;
+				}
+			}
+			else
+			{
+				if (data == null)
+				{
+					return metadata;
+				}
+				else
+				{
+					MetaDataEntry< ? >[] ret = new MetaDataEntry< ? >[2];
+					ret[0] = (MetaDataEntry< ? >) metadata;
+					ret[1] = new MetaDataEntry<>(key, data);
+					return ret;
+				}
+			}
+		}
+		else
+		{
+			MetaDataEntry< ? >[] metadataArr = (MetaDataEntry< ? >[]) metadata;
+			for (int i = 0; i < metadataArr.length; i++)
+			{
+				MetaDataEntry< ? > curEntry = metadataArr[i];
+				if (curEntry.key.equals(key))
+				{
+					if (data == null)
+					{
+						if (metadataArr.length == 2)
+						{
+							return metadataArr[i == 0 ? 1 : 0];
+						}
+						else
+						{
+							MetaDataEntry< ? >[] ret =
+								new MetaDataEntry< ? >[metadataArr.length - 1];
+							System.arraycopy(metadataArr, 0, ret, 0, i);
+							System.arraycopy(metadataArr, i + 1, ret, i, ret.length - i);
+							return ret;
+						}
+					}
+					else
+					{
+						curEntry.object = data;
+						return metadataArr;
+					}
+				}
+			}
+			if (data == null)
+			{
+				return metadataArr;
+			}
+			MetaDataEntry< ? >[] ret = new MetaDataEntry< ? >[metadataArr.length + 1];
+			System.arraycopy(metadataArr, 0, ret, 0, metadataArr.length);
+			ret[metadataArr.length] = new MetaDataEntry<>(key, data);
+			return ret;
+		}
+	}
+
+	static Behavior getBehaviorById(Component component, int id, Object state, boolean modelSet)
+	{
+		Object behaviors = getBehaviors(state, modelSet);
+		if (behaviors instanceof Behavior)
+		{
+			if (id == 0)
+			{
+				return (Behavior) behaviors;
+			}
+		}
+		else if (behaviors instanceof Behavior[])
+		{
+			Behavior[] behaviorsArr = (Behavior[]) behaviors;
+			if (behaviorsArr.length > id && behaviorsArr[id] != null)
+			{
+				return behaviorsArr[id];
+			}
+		}
+		throw new InvalidBehaviorIdException(component, id);
+	}
+
+	static int getBehaviorId(Component component, Behavior behavior, Object state, boolean modelSet)
+	{
+		Object behaviors = getBehaviors(state, modelSet);
+		if (behavior.equals(behaviors))
+		{
+			return 0;
+		}
+		else if (behaviors instanceof Behavior[])
+		{
+			Behavior[] behaviorsArr = (Behavior[]) behaviors;
+			for (int i = 0; i < behaviorsArr.length; i++)
+			{
+				if (behavior.equals(behaviorsArr[i]))
+				{
+					return i;
+				}
+			}
+		}
+		throw new IllegalStateException(
+			"Behavior must be added to component before its id can be generated. Behavior: "
+				+ behavior + ", Component: " + component);
+	}
+
+	@SuppressWarnings("unchecked")
+	static <M extends Behavior> List<M> getBehaviors(Class<M> type, Object state, boolean modelSet)
+	{
+		Object behaviors = getBehaviors(state, modelSet);
+		if (behaviors == null)
+		{
+			return List.of();
+		}
+
+		if (behaviors instanceof Behavior)
+		{
+			if (type == null || type.isInstance(behaviors))
+			{
+				return List.of((M) behaviors);
+			}
+			return List.of();
+		}
+
+		Behavior[] behaviorsArr = (Behavior[]) behaviors;
+		List<M> subset = new ArrayList<>(behaviorsArr.length);
+		for (Behavior curBehavior : behaviorsArr)
+		{
+			if (curBehavior != null && (type == null || type.isInstance(curBehavior)))
+			{
+				subset.add((M) curBehavior);
+			}
+		}
+		if (subset.isEmpty())
+		{
+			return List.of();
+		}
+		return Collections.unmodifiableList(subset);
+	}
+
+	static void onRemoveBehaviors(Component component, Object state, boolean modelSet)
+	{
+		Object behaviors = getBehaviors(state, modelSet);
+		if (behaviors instanceof Behavior)
+		{
+			((Behavior) behaviors).onRemove(component);
+		}
+		else if (behaviors instanceof Behavior[])
+		{
+			Behavior[] behaviorsArr = (Behavior[]) behaviors;
+			for (Behavior curBehavior : behaviorsArr)
+			{
+				if (curBehavior != null)
+				{
+					curBehavior.onRemove(component);
+				}
+			}
+		}
+	}
+
+	static Object compactBehaviors(Component component, Object state, boolean modelSet,
+			boolean fixedIds)
+	{
+		if (fixedIds)
+		{
+			return state;
+		}
+		Object behaviors = getBehaviors(state, modelSet);
+		if (!(behaviors instanceof Behavior[]))
+		{
+			return state;
+		}
+
+		Behavior[] behaviorsArr = (Behavior[]) behaviors;
+		int setIndex = 0;
+		int endIndex = behaviorsArr.length - 1;
+		int checkIndex = 0;
+		while (checkIndex <= endIndex)
+		{
+			Behavior curBehavior = behaviorsArr[checkIndex];
+			if (curBehavior == null)
+			{
+				checkIndex++;
+				continue;
+			}
+
+			// move tmp behaviors to the end of the array, swap with what's there
+			if (curBehavior.isTemporary(component))
+			{
+				Behavior tmp = behaviorsArr[endIndex];
+				behaviorsArr[endIndex] = curBehavior;
+				behaviorsArr[checkIndex] = tmp;
+				endIndex--;
+				continue;
+			}
+			behaviorsArr[setIndex] = curBehavior;
+			checkIndex++;
+			setIndex++;
+		}
+
+		// wipe the remainder of the array
+		for (; setIndex <= endIndex; setIndex++)
+		{
+			behaviorsArr[setIndex] = null;
+		}
+		return state;
+	}
+
+	static Object detachBehaviors(Component component, Object state, boolean modelSet,
+			boolean fixedIds)
+	{
+		Object behaviors = getBehaviors(state, modelSet);
+		if (behaviors instanceof Behavior)
+		{
+			Behavior behavior = (Behavior) behaviors;
+			behavior.detach(component);
+			if (behavior.isTemporary(component))
+			{
+				behavior.unbind(component);
+				return setBehaviors(state, modelSet, null);
+			}
+		}
+		else if (behaviors instanceof Behavior[])
+		{
+			// remove temporary behaviors and compact the array
+			int highestId = -1;
+			int filledSlots = 0;
+			Behavior[] behaviorsArr = (Behavior[]) behaviors;
+
+			// iterate over all behaviors, detaching them and removing temporary behaviors
+			// remaining behaviors are counted and for stateful behaviors slots assigned
+			for (int i = 0; i < behaviorsArr.length; i++)
+			{
+				Behavior curBehavior = behaviorsArr[i];
+				if (curBehavior != null)
+				{
+					curBehavior.detach(component);
+					if (curBehavior.isTemporary(component))
+					{
+						curBehavior.unbind(component);
+						behaviorsArr[i] = null;
+					}
+					else
+					{
+						filledSlots++;
+						highestId = i;
+					}
+				}
+			}
+
+			// if at most 1 behavior remains, no array is needed
+			int newSize = fixedIds ? Math.max(highestId + 1, filledSlots) : filledSlots;
+			if (newSize == 0)
+			{
+				return setBehaviors(state, modelSet, null);
+			}
+			if (newSize == 1)
+			{
+				return setBehaviors(state, modelSet, behaviorsArr[highestId]);
+			}
+
+			// the calculated size is equal to the current size, cannot compact
+			if (newSize == behaviorsArr.length)
+			{
+				return state;
+			}
+
+			// multiple behaviors (or one with an id > 0)
+			// construct a new array and compact the behaviors
+			Behavior[] ret = new Behavior[newSize];
+
+			if (fixedIds)
+			{
+				System.arraycopy(behaviorsArr, 0, ret, 0, ret.length);
+			}
+			else
+			{
+				int targetIndex = 0;
+				for (int i = 0; i < behaviorsArr.length; i++)
+				{
+					Behavior curBehavior = behaviorsArr[i];
+					if (curBehavior == null)
+					{
+						continue;
+					}
+					ret[targetIndex] = curBehavior;
+					targetIndex++;
+				}
+			}
+			return setBehaviors(state, modelSet, ret);
+		}
+		return state;
+	}
+}
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractDefaultAjaxBehavior.java b/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractDefaultAjaxBehavior.java
index 291303f..76c4fb4 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractDefaultAjaxBehavior.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/AbstractDefaultAjaxBehavior.java
@@ -83,12 +83,6 @@
 		final Component component = getComponent();
 		
 		component.setOutputMarkupId(true);
-		
-		if (getStatelessHint(component))
-		{
-			//generate behavior id
-			component.getBehaviorId(this);
-		}
 	}
 
 	/**
diff --git a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/WicketObjects.java b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/WicketObjects.java
index 4649150..f3b0b95 100644
--- a/wicket-core/src/main/java/org/apache/wicket/core/util/lang/WicketObjects.java
+++ b/wicket-core/src/main/java/org/apache/wicket/core/util/lang/WicketObjects.java
@@ -17,6 +17,7 @@
 package org.apache.wicket.core.util.lang;

 

 import java.io.Serializable;

+import java.nio.file.Files;

 

 import org.apache.wicket.Application;

 import org.apache.wicket.Component;

diff --git a/wicket-core/src/test/java/org/apache/wicket/behavior/ImmutableBehaviorIdsTest.java b/wicket-core/src/test/java/org/apache/wicket/behavior/ImmutableBehaviorIdsTest.java
index 4f161ab..98e9a44 100644
--- a/wicket-core/src/test/java/org/apache/wicket/behavior/ImmutableBehaviorIdsTest.java
+++ b/wicket-core/src/test/java/org/apache/wicket/behavior/ImmutableBehaviorIdsTest.java
@@ -75,10 +75,10 @@
 		assertTrue(output.contains("autocomplete=\"off\""));

 		assertTrue(output.contains("class2=\"border\""));

 		assertTrue(output.contains("autocomplete2=\"off\""));

-		assertTrue(output.contains(".0"));

-		assertTrue(output.contains(".1"));

-		assertEquals(link, page.getContainer().getBehaviorById(0));

-		assertEquals(link2, page.getContainer().getBehaviorById(1));

+		assertTrue(output.contains(".2"));

+		assertTrue(output.contains(".4"));

+		assertEquals(link, page.getContainer().getBehaviorById(2));

+		assertEquals(link2, page.getContainer().getBehaviorById(4));

 

 		// if we remove a behavior that is before the ibehaviorlistener its url index should not

 		// change

@@ -89,11 +89,11 @@
 		page.getContainer().remove(auto2);

 		tester.startPage(page);

 		output = tester.getLastResponseAsString();

-		// System.out.println(output);

-		assertTrue(output.contains(".0"));

-		assertTrue(output.contains(".1"));

-		assertEquals(link, page.getContainer().getBehaviorById(0));

-		assertEquals(link2, page.getContainer().getBehaviorById(1));

+//		System.out.println(output);

+		assertTrue(output.contains(".2"));

+		assertTrue(output.contains(".4"));

+		assertEquals(link, page.getContainer().getBehaviorById(2));

+		assertEquals(link2, page.getContainer().getBehaviorById(4));

 	}

 

 	/**

diff --git a/wicket-core/src/test/java/org/apache/wicket/markup/html/basic/SimplePageExpectedResult_13.html b/wicket-core/src/test/java/org/apache/wicket/markup/html/basic/SimplePageExpectedResult_13.html
index c0ae3c9..e019755 100644
--- a/wicket-core/src/test/java/org/apache/wicket/markup/html/basic/SimplePageExpectedResult_13.html
+++ b/wicket-core/src/test/java/org/apache/wicket/markup/html/basic/SimplePageExpectedResult_13.html
@@ -18,7 +18,7 @@
 <script type="text/javascript">
 /*<![CDATA[*/
 Wicket.Event.add(window, "domready", function(event) { 
-Wicket.Ajax.ajax({"u":"./org.apache.wicket.markup.html.basic.SimplePage_13?0-1.0-html","c":"html1","e":"click","pd":true});;
+Wicket.Ajax.ajax({"u":"./org.apache.wicket.markup.html.basic.SimplePage_13?0-1.1-html","c":"html1","e":"click","pd":true});;
 Wicket.Event.publish(Wicket.Event.Topic.AJAX_HANDLERS_BOUND);
 ;});
 /*]]>*/