/*
* 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);
}
}
}
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()
{
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.detach(component);
if (behavior.isTemporary(component))
{
internalRemove(behavior);
}
}
}
}
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);
}
}