blob: fc99c6c8a107a88b40a72c0c3dda0edbe8cdcc2d [file] [log] [blame]
/*
* 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.util.lang.Args;
/**
* Manages behaviors for {@link Component} instances
*
* @author igor
*/
final class Behaviors
{
private Behaviors()
{
// utility class
}
public static void add(Component component, Behavior... behaviors)
{
Args.notNull(behaviors, "behaviors");
for (Behavior behavior : behaviors)
{
Args.notNull(behavior, "behavior");
internalAdd(component, behavior);
if (!behavior.isTemporary(component))
{
component.addStateChange();
}
// Give handler the opportunity to bind this component
behavior.bind(component);
}
}
private static void internalAdd(Component component, Behavior behavior)
{
component.data_add(behavior);
if (behavior.getStatelessHint(component) == false)
{
getBehaviorId(component, behavior);
}
}
@SuppressWarnings("unchecked")
public static <M extends Behavior> List<M> getBehaviors(Component component, Class<M> type)
{
int len = component.data_length();
if (len == 0)
{
return Collections.emptyList();
}
int start = component.data_start();
if (len < start)
{
return Collections.emptyList();
}
List<M> subset = null;
for (int i = start; i < len; i++)
{
Object obj = component.data_get(i);
if (obj instanceof Behavior)
{
if (type == null || type.isAssignableFrom(obj.getClass()))
{
if (subset == null)
{
subset = new ArrayList<>(len);
}
subset.add((M)obj);
}
}
}
if (subset == null || subset.isEmpty())
{
return Collections.emptyList();
}
else
{
return Collections.unmodifiableList(subset);
}
}
public static void remove(Component component, Behavior behavior)
{
Args.notNull(behavior, "behavior");
if (internalRemove(component, 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).
*/
public static void detach(Component component)
{
int len = component.data_length();
if (len == 0)
{
return;
}
int start = component.data_start();
if (len < start)
{
return;
}
for (int i = start; i < len; i++)
{
Object obj = component.data_get(i);
if (obj instanceof Behavior)
{
final Behavior behavior = (Behavior)obj;
behavior.detach(component);
if (behavior.isTemporary(component))
{
internalRemove(component, behavior);
i--;
len--;
}
}
}
}
private static boolean internalRemove(Component component, 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(component, 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(component);
}
}
return true;
}
}
return false;
}
private static void removeBehaviorsIdList(Component component)
{
for (int i = component.data_start(); i < component.data_length(); i++)
{
Object obj = component.data_get(i);
if (obj instanceof BehaviorIdList)
{
component.data_remove(i);
return;
}
}
}
private static BehaviorIdList getBehaviorsIdList(Component component, boolean createIfNotFound)
{
int len = component.data_length();
for (int i = component.data_start(); i < len; i++)
{
Object obj = component.data_get(i);
if (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 static void onRemove(Component component)
{
int len = component.data_length();
if (len == 0)
{
return;
}
int start = component.data_start();
if (len < start)
{
return;
}
for (int i = start; i < len; i++)
{
Object obj = component.data_get(i);
if (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 static int getBehaviorId(Component component, 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: " + component);
}
ArrayList<Behavior> ids = getBehaviorsIdList(component, 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 static Behavior getBehaviorById(Component component, int id)
{
Behavior behavior = null;
ArrayList<Behavior> ids = getBehaviorsIdList(component, false);
if (ids != null)
{
if (id >= 0 && id < ids.size())
{
behavior = ids.get(id);
}
}
if (behavior != null)
{
return behavior;
}
throw new InvalidBehaviorIdException(component, id);
}
}