![JAR search and dependency download from the Maven repository](/logo.png)
net.sf.cuf.ui.SwingMapping Maven / Gradle / Ivy
/**************************************************************************
* P A C K A G E *
**************************************************************************/
package net.sf.cuf.ui;
/**************************************************************************/
/**************************************************************************
* I M P O R T S *
**************************************************************************/
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.awt.Component;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.JViewport;
/**************************************************************************/
/**************************************************************************
* C L A S S E S *
**************************************************************************/
/**************************************************************************/
/**
* SwingMapping generates a string mapping for a tree of JComponent's.
*
* This mapping is build upon the name property: starting from the root,
* all elements in the component tree that have a a non-null name property
* (all their parents must also have a non-null name property!) are
* mapped to name by concatenating the names of all components on the
* path separated with a "/".
* As a special case, a Viewport inside a ScrollPane is silently skipped
* After a mapping was constructed, components can be accessed by name, e.g.
* "myPanel/subPanel/okButton".
*
* As a very useful feature ".." can be used inside a name to navigate to
* another component, e.g.
* "myPanel/subPanel/okButton/../textField" can be used to access a
* the component "textField" that is also a child of "subPanel".
* With this feature, it is very easy to access the "needed" components
* inside an ActionEvent callback.
* In addition to the hierarcical namespace a flat (but not uniq) namespace
* is provided.
*
* @author Jürgen Zeller, [email protected]
*/
public class SwingMapping
{
/** Separator of component names. */
private static final String SEPARATOR= "/";
/** Non-null root of our mapped tree. */
private JComponent mRoot;
/**
* mNameToComponent maps the full qualified name to a JComponent.
* key : SEPARATOR separated String of JComponent name's
* value: the matching JComponent
*/
private Map mNameToComponent;
/**
* mShortNameToComponent maps the non-hierarchical (short) name
* to a component.
* key: name
* value: the matching JComponent
*/
private Map mShortNameToComponent;
/**
* mComponentToName does the reverse mapping of mNameToComponent.
* key : a JComponent inside mRoot
* value: the matching SEPARATOR separated String
*/
private Map mComponentToName;
/**
* This factory method creates a new SwingMapping, starting from the handed
* root component.
*
* @param pRoot the root component
* @return a new SwingMapping
* @throws IllegalArgumentException if the names inside the component are not
* uniq or pRoot is null
*/
public static SwingMapping createMapping(final JComponent pRoot)
{
if (pRoot==null)
{
throw new IllegalArgumentException(
"SwingMapping.createMapping(): pRoot must not be null");
}
SwingMapping mapping= new SwingMapping(pRoot);
mapping.addMapping("", pRoot);
return mapping;
}
/**
* This method returns null or the component matching the handed name
* after resolving ".." shortcuts.
*
* @param pName component name (may be null), inside the
* name all ".."'s are resolved first, so it is o.k. to
* use "somePanel/someButton/../someTextField" to get
* from any component to any other.
* @return null or the component that matches the (resolved) name
* @throws IllegalArgumentException the name is not resolvable due too many ".."'s
*/
public JComponent getComponentByName(final String pName)
{
// null maps always to null
if (pName==null)
{
return null;
}
String name;
// check if we need the ".." resolving at all
if (!pName.contains(".."))
{
name= pName;
}
else
{
// the following code does the ".." resolving
StringTokenizer tokenizer = new StringTokenizer(pName, SEPARATOR);
List list = new ArrayList(tokenizer.countTokens());
int stackCount= 0;
while (tokenizer.hasMoreTokens())
{
String token= tokenizer.nextToken();
if ("..".equals(token))
{
// pop
stackCount--;
if (stackCount<0)
{
throw new IllegalArgumentException(
pName + ": contains more ..'s than elements");
}
list.remove(stackCount);
}
else
{
// push
list.add(token);
stackCount++;
}
}
StringBuilder nameBuffer = new StringBuilder();
for (int i= 0, n= list.size(); i < n; i++)
{
nameBuffer.append(list.get(i));
if (i < n-1)
{
nameBuffer.append(SEPARATOR);
}
}
name= nameBuffer.toString();
}
// name now contains the resolved name, the Map will either return
// null or the matching compontent
return mNameToComponent.get(name);
}
/**
* This method returns null or the component matching the handed
* short name. If there is more than one component with that short
* name, it is undefined which is return.
* @param pName component short name (may be null)
* @return null or the component that matches the (resolved) name
*/
public JComponent getByShortName(final String pName)
{
return mShortNameToComponent.get(pName);
}
/**
* This method returns the full qualified name of the handed component.
*
* @param pComponent the component we want the name for
* @return null or the full qualified name of the component.
* @throws IllegalArgumentException if pComponent is not inside the hierarchy
* the SwingMapping object was created with
*/
public String getNameByComponent(final JComponent pComponent)
{
if (!SwingUtilities.isDescendingFrom(pComponent, mRoot))
{
throw new IllegalArgumentException(pComponent +
" doesn't descend from " +
mRoot);
}
return mComponentToName.get(pComponent);
}
/**
* toString() generates a line-by-line description of all mappings,
* separated with " = " and containing first the full mapping name
* and second the class name of the mapped to object.
*
* @return complete map that is described with this mapping object
*/
public String toString()
{
StringBuilder sb = new StringBuilder();
for (final Map.Entry entry : mNameToComponent.entrySet())
{
sb.append(entry.getKey());
sb.append(" = ");
sb.append(entry.getValue().getClass().getName());
sb.append(System.getProperty("line.separator"));
}
return sb.toString();
}
/**
* SwingMapping has a private constructor, because all instances are
* provided by the createMapping() factory methods, that also does the
* error checking.
*
* @param pRoot the (non-null!) component tree root
*/
private SwingMapping(final JComponent pRoot)
{
super();
mRoot = pRoot;
mNameToComponent = new HashMap<>();
mShortNameToComponent= new HashMap<>();
mComponentToName = new HashMap<>();
}
/**
* This method recursivly adds all named components.
*
* @param pPrefix the concatenate name of our parents
* @param pNode the current examined node
* @throws IllegalArgumentException if the names inside the component are not
* uniq after concatenating them together
*/
private void addMapping(final String pPrefix, final JComponent pNode)
{
String myName = pNode.getName();
Component[] children= pNode.getComponents();
boolean hasName = (myName!=null);
boolean isScrollPane= (pNode instanceof JViewport);
if ((hasName || isScrollPane) && children!=null)
{
String newPrefix;
if (isScrollPane)
{
newPrefix= pPrefix;
}
else
{
String fullName= pPrefix+myName;
if (mNameToComponent.containsKey(fullName))
{
throw new IllegalArgumentException(
"SwingMapping: name inconsistancy detected for " +
fullName);
}
mNameToComponent. put(fullName, pNode);
mShortNameToComponent.put(myName, pNode);
mComponentToName. put(pNode, fullName);
newPrefix= fullName+SEPARATOR;
}
for (final Component aChildren : children)
{
if (aChildren instanceof JComponent)
{
addMapping(newPrefix, (JComponent) aChildren);
}
}
}
}
}
/**************************************************************************/