org.apache.wicket.devutils.inspector.EnhancedPageView Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wicket-devutils Show documentation
Show all versions of wicket-devutils Show documentation
Wicket development utilities provide helpful features that
are typically used during development only, but may be
turned on for additional production debugging.
/*
* 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.devutils.inspector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.ajax.markup.html.form.AjaxFallbackButton;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.core.util.lang.WicketObjects;
import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
import org.apache.wicket.extensions.markup.html.repeater.tree.AbstractTree;
import org.apache.wicket.extensions.markup.html.repeater.tree.DefaultTableTree;
import org.apache.wicket.extensions.markup.html.repeater.tree.table.TreeColumn;
import org.apache.wicket.extensions.markup.html.repeater.util.SortableTreeProvider;
import org.apache.wicket.markup.head.CssHeaderItem;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.debug.PageView;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.CheckBoxMultipleChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.GenericPanel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.OddEvenItem;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.resource.CssResourceReference;
import org.apache.wicket.util.io.IClusterable;
import org.apache.wicket.util.lang.Bytes;
import org.apache.wicket.util.string.Strings;
/**
* Enhanced {@link PageView} which displays all Component
s and Behavior
s
* of a Page
in a TableTree
representation. Component
s and
* Behavior
s can be shown based on their statefulness status. There are also filtering
* options to choose the information displayed. Useful for debugging.
*
* @author Bertrand Guay-Paquet
*/
public final class EnhancedPageView extends GenericPanel
{
private static final long serialVersionUID = 1L;
private ExpandState expandState;
private boolean showStatefulAndParentsOnly;
private boolean showBehaviors;
private List> allColumns;
private List> visibleColumns;
private AbstractTree componentTree;
/**
* Constructor.
*
* @param id
* See Component
* @param model
* The page to be analyzed
*/
public EnhancedPageView(String id, IModel model)
{
super(id, model);
expandState = new ExpandState();
expandState.expandAll();
showStatefulAndParentsOnly = false;
showBehaviors = true;
allColumns = allColumns();
visibleColumns = new ArrayList<>(allColumns);
// Name of page
add(new Label("info", new Model()
{
private static final long serialVersionUID = 1L;
@Override
public String getObject()
{
Page page = getModelObject();
return page == null ? "[Stateless Page]" : page.toString();
}
}));
Model pageRenderDuration = new Model()
{
private static final long serialVersionUID = 1L;
@Override
public String getObject()
{
Page page = getModelObject();
if (page != null)
{
Long renderTime = page.getMetaData(PageView.RENDER_KEY);
if (renderTime != null)
{
return renderTime.toString();
}
}
return "n/a";
}
};
add(new Label("pageRenderDuration", pageRenderDuration));
addTreeControls();
componentTree = newTree();
add(componentTree);
}
private List> allColumns()
{
List> columns = new ArrayList<>();
columns.add(new PropertyColumn(Model.of("Path"), "path")
{
private static final long serialVersionUID = 1L;
@Override
public String getCssClass()
{
return "col_path";
}
@Override
public String toString()
{
return getDisplayModel().getObject();
}
});
columns.add(new TreeColumn(Model.of("Tree"))
{
private static final long serialVersionUID = 1L;
@Override
public String toString()
{
return getDisplayModel().getObject();
}
});
columns.add(new PropertyColumn(Model.of("Stateless"), "stateless")
{
private static final long serialVersionUID = 1L;
@Override
public String getCssClass()
{
return "col_stateless";
}
@Override
public String toString()
{
return getDisplayModel().getObject();
}
});
columns.add(new PropertyColumn(Model.of("Render time (ms)"), "renderTime")
{
private static final long serialVersionUID = 1L;
@Override
public String getCssClass()
{
return "col_renderTime";
}
@Override
public String toString()
{
return getDisplayModel().getObject();
}
});
columns.add(new AbstractColumn(Model.of("Size"))
{
private static final long serialVersionUID = 1L;
@Override
public void populateItem(Item> item, String componentId,
IModel rowModel)
{
item.add(new Label(componentId, Bytes.bytes(rowModel.getObject().getSize())
.toString()));
}
@Override
public String getCssClass()
{
return "col_size";
}
@Override
public String toString()
{
return getDisplayModel().getObject();
}
});
columns.add(new PropertyColumn(Model.of("Type"), "type")
{
private static final long serialVersionUID = 1L;
@Override
public String toString()
{
return getDisplayModel().getObject();
}
});
columns.add(new PropertyColumn(Model.of("Model Object"), "model")
{
private static final long serialVersionUID = 1L;
@Override
public String toString()
{
return getDisplayModel().getObject();
}
});
return columns;
}
private void addTreeControls()
{
Form form = new Form<>("form");
add(form);
form.add(new CheckBox("showStateless", new PropertyModel(this,
"showStatefulAndParentsOnly")));
form.add(new CheckBox("showBehaviors", new PropertyModel(this, "showBehaviors")));
form.add(new CheckBoxMultipleChoice<>("visibleColumns",
new PropertyModel>>(this, "visibleColumns"), allColumns).setSuffix(" "));
form.add(new AjaxFallbackButton("submit", form)
{
private static final long serialVersionUID = 1L;
@Override
protected void onAfterSubmit(Optional target)
{
target.ifPresent(t -> t.add(componentTree));
}
});
add(new AjaxFallbackLink("expandAll")
{
private static final long serialVersionUID = 1L;
@Override
public void onClick(Optional targetOptional)
{
expandState.expandAll();
targetOptional.ifPresent(target -> target.add(componentTree));
}
});
add(new AjaxFallbackLink("collapseAll")
{
private static final long serialVersionUID = 1L;
@Override
public void onClick(Optional targetOptional)
{
expandState.collapseAll();
targetOptional.ifPresent(target -> target.add(componentTree));
}
});
}
private AbstractTree newTree()
{
TreeProvider provider = new TreeProvider();
IModel> expandStateModel = new LoadableDetachableModel>()
{
private static final long serialVersionUID = 1L;
@Override
protected Set load()
{
return expandState;
}
};
AbstractTree tree = new DefaultTableTree("tree", visibleColumns,
provider, Integer.MAX_VALUE, expandStateModel)
{
private static final long serialVersionUID = 1L;
@Override
protected Item newRowItem(String id, int index, IModel model)
{
return new OddEvenItem<>(id, index, model);
}
};
tree.setOutputMarkupId(true);
return tree;
}
/**
* Tree node representing either a Page
, a Component
or a
* Behavior
*/
private static class TreeNode
{
public IClusterable node;
public TreeNode parent;
public List children;
public TreeNode(IClusterable node, TreeNode parent)
{
this.node = node;
this.parent = parent;
children = new ArrayList<>();
if (!(node instanceof Component) && !(node instanceof Behavior))
throw new IllegalArgumentException("Only accepts Components and Behaviors");
}
public boolean hasChildren()
{
return !children.isEmpty();
}
/**
* @return list of indexes to navigate from the root of the tree to this node (e.g. the path
* to the node).
*/
public List getPathIndexes()
{
List path = new ArrayList<>();
TreeNode nextChild = this;
TreeNode parent;
while ((parent = nextChild.parent) != null)
{
int indexOf = parent.children.indexOf(nextChild);
if (indexOf < 0)
throw new AssertionError("Child not found in parent");
path.add(indexOf);
nextChild = parent;
}
Collections.reverse(path);
return path;
}
public String getPath()
{
if (node instanceof Component)
{
return ((Component)node).getPath();
}
else
{
Behavior behavior = (Behavior)node;
Component parent = (Component)this.parent.node;
String parentPath = parent.getPath();
int indexOf = parent.getBehaviors().indexOf(behavior);
return parentPath + Component.PATH_SEPARATOR + "Behavior_" + indexOf;
}
}
public String getRenderTime()
{
if (node instanceof Component)
{
Long renderDuration = ((Component)node).getMetaData(PageView.RENDER_KEY);
if (renderDuration != null)
{
return renderDuration.toString();
}
}
return "n/a";
}
public long getSize()
{
if (node instanceof Component)
{
long size = ((Component)node).getSizeInBytes();
return size;
}
else
{
long size = WicketObjects.sizeof(node);
return size;
}
}
public String getType()
{
// anonymous class? Get the parent's class name
String type = node.getClass().getName();
if (type.indexOf("$") > 0)
{
type = node.getClass().getSuperclass().getName();
}
return type;
}
public String getModel()
{
if (node instanceof Component)
{
String model;
try
{
model = ((Component)node).getDefaultModelObjectAsString();
}
catch (Exception e)
{
model = e.getMessage();
}
return model;
}
return null;
}
public boolean isStateless()
{
if (node instanceof Page)
{
return ((Page)node).isPageStateless();
}
else if (node instanceof Component)
{
return ((Component)node).isStateless();
}
else
{
Behavior behavior = (Behavior)node;
Component parent = (Component)this.parent.node;
return behavior.getStatelessHint(parent);
}
}
@Override
public String toString()
{
if (node instanceof Page)
{
// Last component of getType() i.e. almost the same as getClass().getSimpleName();
String type = getType();
type = Strings.lastPathComponent(type, '.');
return type;
}
else if (node instanceof Component)
{
return ((Component)node).getId();
}
else
{
// Last component of getType() i.e. almost the same as getClass().getSimpleName();
String type = getType();
type = Strings.lastPathComponent(type, '.');
return type;
}
}
}
/**
* TreeNode provider for the page. Provides nodes for the components and behaviors of the
* analyzed page.
*/
private class TreeProvider extends SortableTreeProvider
{
private static final long serialVersionUID = 1L;
private TreeModel componentTreeModel = new TreeModel();
@Override
public void detach()
{
componentTreeModel.detach();
}
@Override
public Iterator extends TreeNode> getRoots()
{
TreeNode tree = componentTreeModel.getObject();
List roots;
if (tree == null)
roots = Collections.emptyList();
else
roots = Arrays.asList(tree);
return roots.iterator();
}
@Override
public boolean hasChildren(TreeNode node)
{
return node.hasChildren();
}
@Override
public Iterator extends TreeNode> getChildren(TreeNode node)
{
return node.children.iterator();
}
@Override
public IModel model(TreeNode object)
{
return new TreeNodeModel(object);
}
/**
* Model of the page component and behavior tree
*/
private class TreeModel extends LoadableDetachableModel
{
private static final long serialVersionUID = 1L;
@Override
protected TreeNode load()
{
Page page = getModelObject();
if (page == null)
return null;
return buildTree(page, null);
}
private TreeNode buildTree(Component node, TreeNode parent)
{
TreeNode treeNode = new TreeNode(node, parent);
List children = treeNode.children;
// Add its behaviors
if (showBehaviors)
{
for (Behavior behavior : node.getBehaviors())
{
if (!showStatefulAndParentsOnly || !behavior.getStatelessHint(node))
children.add(new TreeNode(behavior, treeNode));
}
}
// Add its children
if (node instanceof MarkupContainer)
{
MarkupContainer container = (MarkupContainer)node;
for (Component child : container)
{
buildTree(child, treeNode);
}
}
// Sort the children list, putting behaviors first
Collections.sort(children, new Comparator()
{
@Override
public int compare(TreeNode o1, TreeNode o2)
{
if (o1.node instanceof Component)
{
if (o2.node instanceof Component)
{
return o1.getPath().compareTo((o2).getPath());
}
else
{
return 1;
}
}
else
{
if (o2.node instanceof Component)
{
return -1;
}
else
{
return o1.getPath().compareTo((o2).getPath());
}
}
}
});
// Add this node to its parent if
// -it has children or
// -it is stateful or
// -stateless components are visible
if (parent != null &&
(!showStatefulAndParentsOnly || treeNode.hasChildren() || !node.isStateless()))
{
parent.children.add(treeNode);
}
return treeNode;
}
}
/**
* Rertrieves a TreeNode based on its path
*/
private class TreeNodeModel extends LoadableDetachableModel
{
private static final long serialVersionUID = 1L;
private List path;
public TreeNodeModel(TreeNode treeNode)
{
super(treeNode);
path = treeNode.getPathIndexes();
}
@Override
protected TreeNode load()
{
TreeNode tree = componentTreeModel.getObject();
TreeNode currentItem = tree;
for (Integer index : path)
{
currentItem = currentItem.children.get(index);
}
return currentItem;
}
/**
* Important! Models must be identifyable by their contained object.
*/
@Override
public int hashCode()
{
return path.hashCode();
}
/**
* Important! Models must be identifyable by their contained object.
*/
@Override
public boolean equals(Object obj)
{
if (obj instanceof TreeNodeModel)
{
return ((TreeNodeModel)obj).path.equals(path);
}
return false;
}
}
}
/**
* Expansion state of the tree's nodes
*/
private static class ExpandState implements Set, IClusterable
{
private static final long serialVersionUID = 1L;
private HashSet> set = new HashSet<>();
private boolean reversed = false;
public void expandAll()
{
set.clear();
reversed = true;
}
public void collapseAll()
{
set.clear();
reversed = false;
}
@Override
public boolean add(TreeNode a_e)
{
List pathIndexes = a_e.getPathIndexes();
if (reversed)
{
return set.remove(pathIndexes);
}
else
{
return set.add(pathIndexes);
}
}
@Override
public boolean remove(Object a_o)
{
TreeNode item = (TreeNode)a_o;
List pathIndexes = item.getPathIndexes();
if (reversed)
{
return set.add(pathIndexes);
}
else
{
return set.remove(pathIndexes);
}
}
@Override
public boolean contains(Object a_o)
{
TreeNode item = (TreeNode)a_o;
List pathIndexes = item.getPathIndexes();
if (reversed)
{
return !set.contains(pathIndexes);
}
else
{
return set.contains(pathIndexes);
}
}
@Override
public int size()
{
throw new UnsupportedOperationException();
}
@Override
public boolean isEmpty()
{
throw new UnsupportedOperationException();
}
@Override
public Iterator iterator()
{
throw new UnsupportedOperationException();
}
@Override
public Object[] toArray()
{
throw new UnsupportedOperationException();
}
@Override
public T[] toArray(T[] a_a)
{
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(Collection> a_c)
{
throw new UnsupportedOperationException();
}
@Override
public boolean addAll(Collection extends TreeNode> a_c)
{
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(Collection> a_c)
{
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(Collection> a_c)
{
throw new UnsupportedOperationException();
}
@Override
public void clear()
{
throw new UnsupportedOperationException();
}
}
@Override
public void renderHead(IHeaderResponse response)
{
super.renderHead(response);
response.render(CssHeaderItem.forReference(
new CssResourceReference(EnhancedPageView.class, "enhancedpageview.css")));
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy