/* Copyright (c) 2001 - 2009 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.web.translator.view;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.html.tree.BaseTree;
import org.apache.wicket.markup.html.tree.ITreeState;
import org.apache.wicket.markup.html.tree.ITreeStateListener;
import org.apache.wicket.markup.html.tree.LinkTree;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.geoserver.web.translator.model.TranslateBean;
/**
* A panel presenting the list of user interface string resource keys.
* <p>
* The contents of the tree view for this panel may be customized in order to show the resource keys
* as a tree or as a flat view, collapse or expand all nodes, or show only the resource keys that
* are not yet translated for the ongoing locale translation.
* </p>
*
* @author Gabriel Roldan
* @version $Id: ResourceKeyTreePanel.java 13241 2009-08-30 05:44:14Z groldan $
* @since 2.0
*/
public class ResourceKeyTreePanel extends Panel {
private static final long serialVersionUID = 4308370741356525991L;
private final BaseTree keyTree;
private Set<String> keys;
public ResourceKeyTreePanel(final String id, final IModel translateBeanModel) {
super(id);
setModel(translateBeanModel);
setOutputMarkupId(true);
keyTree = new LinkTree("keyTreePanel", new Model()) {
private static final long serialVersionUID = 1L;
@Override
protected Component newNodeComponent(String id, IModel nodeModel) {
Component nodeComponent = super.newNodeComponent(id, nodeModel);
return nodeComponent;
}
};
keyTree.setOutputMarkupId(true);
keyTree.setRootLess(true);
keyTree.getTreeState().addTreeStateListener(new SelectionKeyListener());
refresh();
add(keyTree);
}
/**
* Parses the keywords and sets them as the or'ed filtered criteria, forces update of the
* components that need to as a result of the different filtering
*/
private Set<String> updateFilter(final String flatKeywords, final Collection<String> keys,
final TranslateBean translationState) {
final List<Pattern> patterns;
{
final String[] keywords = flatKeywords.split("\\s+");
patterns = new ArrayList<Pattern>(keywords.length);
String regex;
for (String keyword : keywords) {
regex = ".*" + escape(keyword) + ".*";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE
| Pattern.UNICODE_CASE);
patterns.add(pattern);
}
}
Set<String> matches = new HashSet<String>();
final Locale baseLanguage = translationState.getBaseLocale();
final Locale targetLanguage = translationState.getTargetLanguage();
for (String resourceKey : keys) {
for (Pattern pattern : patterns) {
if (pattern.matcher(resourceKey).matches()) {
matches.add(resourceKey);
break;
}
if (matchesContent(resourceKey, pattern, baseLanguage, translationState)) {
matches.add(resourceKey);
break;
}
if (matchesContent(resourceKey, pattern, targetLanguage, translationState)) {
matches.add(resourceKey);
break;
}
}
}
return matches;
}
private boolean matchesContent(final String resourceKey, final Pattern pattern,
final Locale locale, final TranslateBean translationState) {
String resource = translationState.getResource(locale, resourceKey);
if (resource == null) {
return false;
}
return pattern.matcher(resource).matches();
}
/**
* Escape any character that's special for the regex api
*
* @param keyword
* @return
*/
private String escape(String keyword) {
final String escapeSeq = "\\";
final int len = keyword.length();
StringBuilder sb = new StringBuilder();
char c;
for (int i = 0; i < len; i++) {
c = keyword.charAt(i);
if (isSpecial(c)) {
sb.append(escapeSeq);
}
sb.append(keyword.charAt(i));
}
return sb.toString();
}
/**
* Convenience method to determine if a character is special to the regex system.
*
* @param chr
* the character to test
*
* @return is the character a special character.
*/
private boolean isSpecial(final char chr) {
return ((chr == '.') || (chr == '?') || (chr == '*') || (chr == '^') || (chr == '$')
|| (chr == '+') || (chr == '[') || (chr == ']') || (chr == '(') || (chr == ')')
|| (chr == '|') || (chr == '\\') || (chr == '&'));
}
/**
* Updates the state of the tree.
* <p>
* The following variables are considered to rebuild the tree:
* <ul>
* <li> {@link #flatView}
* <li> {@link TranslateBean#isShowMissingOnly()}
* </ul>
* </p>
*/
@SuppressWarnings("unchecked")
public void refresh() {
final IModel translateBeanModel = getModel();
final Boolean missingOnly = (Boolean) new PropertyModel(translateBeanModel,
"showMissingOnly").getObject();
PropertyModel keysModel;
if (missingOnly) {
keysModel = new PropertyModel(translateBeanModel, "missingKeys");
} else {
keysModel = new PropertyModel(translateBeanModel, "resourceKeys");
}
Collection<String> keys = (Collection<String>) keysModel.getObject();
final String flatKeywords = (String) new PropertyModel(translateBeanModel, "filter")
.getObject();
if (flatKeywords != null && flatKeywords.trim().length() > 0) {
final TranslateBean translationState = (TranslateBean) translateBeanModel.getObject();
this.keys = updateFilter(flatKeywords, keys, translationState);
} else {
this.keys = new TreeSet<String>(keys);
}
IModel flatViewModel = new PropertyModel(translateBeanModel, "flatView");
boolean flatView = ((Boolean) flatViewModel.getObject()).booleanValue();
if (flatView) {
setFlatView();
} else {
setTreeView();
}
}
public BaseTree getTree() {
return keyTree;
}
public void expandAll(final AjaxRequestTarget target) {
getTree().getTreeState().expandAll();
if (target == null) {
getTree().updateTree();
} else {
getTree().updateTree(target);
}
}
public void collapseAll(final AjaxRequestTarget target) {
getTree().getTreeState().collapseAll();
if (target == null) {
getTree().updateTree();
} else {
getTree().updateTree(target);
}
}
private void setTreeView() {
final TreeModel treeModel = createTreeModel();
keyTree.setModelObject(treeModel);
}
private void setFlatView() {
final TreeModel flatModel = creatFlatModel();
keyTree.setModelObject(flatModel);
}
private TreeModel creatFlatModel() {
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(new NodeBean("__ROOT", null));
List<String> keySet = new ArrayList<String>(keys);
Collections.sort(keySet, String.CASE_INSENSITIVE_ORDER);
for (String key : keySet) {
NodeBean node = new NodeBean(key, key);
DefaultMutableTreeNode child = new DefaultMutableTreeNode(node);
rootNode.add(child);
}
TreeModel treeModel = new DefaultTreeModel(rootNode);
return treeModel;
}
private TreeModel createTreeModel() {
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("__ROOT");
List<String> keySet = new ArrayList<String>(keys);
Collections.sort(keySet, String.CASE_INSENSITIVE_ORDER);
HashMap<String, DefaultMutableTreeNode> hashMap = new HashMap<String, DefaultMutableTreeNode>();
for (String key : keySet) {
addNodes(rootNode, key, hashMap);
}
TreeModel treeModel = new DefaultTreeModel(rootNode);
return treeModel;
}
private void addNodes(final DefaultMutableTreeNode parent, final String key,
HashMap<String, DefaultMutableTreeNode> hashMap) {
final int idx = key.indexOf('.');
String name;
if (idx == -1) {
name = key;
} else {
name = key.substring(0, idx);
}
String path = getPath(parent, name);
NodeBean node = new NodeBean(name, path);
DefaultMutableTreeNode child = hashMap.get(path);
if (child == null) {
child = new DefaultMutableTreeNode(node);
parent.add(child);
hashMap.put(path, child);
}
if (idx > -1) {
addNodes(child, key.substring(idx + 1), hashMap);
}
}
private String getPath(DefaultMutableTreeNode node, String child) {
String parentPath = getPath(node);
StringBuilder sb = new StringBuilder(parentPath);
if (sb.length() > 0) {
sb.append('.');
}
sb.append(child);
return sb.toString();
}
private String getPath(final DefaultMutableTreeNode node) {
Object[] userObjectPath = node.getUserObjectPath();
StringBuilder sb = new StringBuilder();
// Ignore root
for (int i = 1; i < userObjectPath.length; i++) {
Object mb = userObjectPath[i];
sb.append(mb.toString());
if (i < userObjectPath.length - 1) {
sb.append('.');
}
}
return sb.toString();
}
private final class SelectionKeyListener implements ITreeStateListener, Serializable {
private static final long serialVersionUID = -5354495649059977267L;
private TreeNode previouslySelected;
public void nodeUnselected(final TreeNode node) {
// nothing to do
}
public void nodeSelected(final TreeNode node) {
final ITreeState treeState = getTree().getTreeState();
if (!node.isLeaf()) {
if (node.equals(previouslySelected) && treeState.isNodeExpanded(node)) {
treeState.collapseNode(node);
} else {
treeState.expandNode(node);
}
}
previouslySelected = node;
}
public void nodeExpanded(final TreeNode node) {
// nothing to do
}
public void nodeCollapsed(final TreeNode node) {
// nothing to do
}
public void allNodesExpanded() {
// nothing to do
}
public void allNodesCollapsed() {
// nothing to do
}
}
public static class NodeBean {
private String name;
private String resourceKey;
/**
*
* @param name
* the node name as it is to be shown in the tree
* @param resourceKey
* the resource key, or {@code null} if the node is not for an actual resource
* key but an intermediate node in the tree
*/
public NodeBean(final String name, final String resourceKey) {
this.name = name;
this.resourceKey = resourceKey;
}
public String getName() {
return name;
}
public String getResourceKey() {
return resourceKey;
}
/**
* @return the name as to be shown in the UI
*/
@Override
public String toString() {
return getName();
}
}
@SuppressWarnings("unchecked")
public String getSelectedNodeKey() {
ITreeState treeState = getTree().getTreeState();
Collection<DefaultMutableTreeNode> selectedNodes = treeState.getSelectedNodes();
String selectedKey = null;
if (selectedNodes.size() > 0) {
DefaultMutableTreeNode selectedNode = selectedNodes.iterator().next();
ResourceKeyTreePanel.NodeBean userObject = (NodeBean) selectedNode.getUserObject();
selectedKey = userObject.getResourceKey();
}
return selectedKey;
}
}