/*******************************************************************************
* Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
******************************************************************************/
package org.eclipse.persistence.tools.workbench.mappingsplugin.ui.xml;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Iterator;
import java.util.List;
import javax.swing.AbstractSpinnerModel;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.JTree;
import javax.swing.SpinnerModel;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeCellEditor;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.eclipse.persistence.tools.workbench.framework.context.WorkbenchContext;
import org.eclipse.persistence.tools.workbench.framework.resources.ResourceRepository;
import org.eclipse.persistence.tools.workbench.framework.ui.dialog.AbstractDialog;
import org.eclipse.persistence.tools.workbench.framework.uitools.SwingComponentFactory;
import org.eclipse.persistence.tools.workbench.mappingsmodel.schema.MWAttributeDeclaration;
import org.eclipse.persistence.tools.workbench.mappingsmodel.schema.MWElementDeclaration;
import org.eclipse.persistence.tools.workbench.mappingsmodel.schema.MWSchemaContextComponent;
import org.eclipse.persistence.tools.workbench.mappingsmodel.schema.MWXpathableSchemaComponent;
import org.eclipse.persistence.tools.workbench.mappingsmodel.xml.MWXmlField;
import org.eclipse.persistence.tools.workbench.mappingsmodel.xml.MWXpathSpec;
import org.eclipse.persistence.tools.workbench.uitools.app.BufferedPropertyValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.PropertyAspectAdapter;
import org.eclipse.persistence.tools.workbench.uitools.app.PropertyValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.SimplePropertyValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.TransformationPropertyValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.ValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.swing.AbstractTreeModel;
import org.eclipse.persistence.tools.workbench.uitools.app.swing.DocumentAdapter;
import org.eclipse.persistence.tools.workbench.uitools.cell.EditingNode;
import org.eclipse.persistence.tools.workbench.uitools.cell.NodeTreeCellEditor;
import org.eclipse.persistence.tools.workbench.uitools.cell.NodeTreeCellRenderer;
import org.eclipse.persistence.tools.workbench.uitools.cell.NullTreeCellEditor;
import org.eclipse.persistence.tools.workbench.uitools.cell.RenderingNode;
import org.eclipse.persistence.tools.workbench.uitools.cell.SimpleTreeCellRenderer;
import org.eclipse.persistence.tools.workbench.uitools.cell.SpinnerTreeCellRenderer;
import org.eclipse.persistence.tools.workbench.uitools.cell.TreeCellEditorAdapter;
import org.eclipse.persistence.tools.workbench.utility.AbstractModel;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.events.StateChangeEvent;
import org.eclipse.persistence.tools.workbench.utility.events.StateChangeListener;
import org.eclipse.persistence.tools.workbench.utility.iterators.CompositeIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.FilteringIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.NullIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.SingleElementIterator;
import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator;
/**
* This dialog presents a tree of the available "XPath-able"
* elements or attibutes within a given schema context.
* When the user selects a tree node, the text preview
* of the selected XPath appears in the text field.
* <p>
* Once the user presses the OK button, the selected XPath
* string is set on the provided MWXpath (through the
* provided xpathHolder).
* <p>
* Here is the layout of the dialog:
* <pre>
* ________________________________________
* | |
* | Title |
* |______________________________________|
* | ____________________________________ |
* | | schema context |^| |
* | | |-attribute1 | | |
* | | |-attribute2 | | |
* | | |-element1 | | |
* | | | |-subattribute1 ||| |
* | | | |-subelement1 | | |
* | | |-element2 | | |
* | | | | |
* | | |v| |
* | ------------------------------------ |
* | ____________________________________ |
* | |(XPath preview) | |
* | ------------------------------------ |
* |______________________________________|
* | ________ ________ |
* | | OK | |Cancel| |
* | -------- -------- |
* ----------------------------------------</pre>
*
*/
public final class XpathChooserDialog
extends AbstractDialog
{
// **************** Variables *********************************************
/** Holds the xml field from which context information is retrieved */
private ValueModel xmlFieldHolder;
/** Holds the xpath initially passed to or set by the dialog */
private PropertyValueModel xpathHolder;
/** Holds the xpath until the dialog is committed */
private BufferedPropertyValueModel selectedXpathHolder;
/** The trigger to accept the selected xpath */
private BufferedPropertyValueModel.Trigger xpathTrigger;
/** The xpath tree model */
private XpathTreeModel xpathTreeModel;
/** The xpath tree selection model */
private TreeSelectionModel xpathTreeSelectionModel;
private Object[] selectedPath;
private StateChangeListener positionListener;
// **************** Static methods ****************************************
/**
* Use this prompter if there is no convenient way to create a value holder for the
* xml field
*/
public static void promptToSelectXpath(
MWXmlField xmlField, WorkbenchContext context
) {
promptToSelectXpath(new SimplePropertyValueModel(xmlField), context);
}
/**
* Use this prompter if you wish to automatically set the xpath on the xml field
* on dialog confirmation
*/
public static void promptToSelectXpath(
ValueModel xmlFieldHolder, WorkbenchContext context
) {
promptToSelectXpath(xmlFieldHolder, buildDefaultXpathHolder(xmlFieldHolder), context);
}
/**
* Use this prompter if you wish to allow further processing of information before
* the xpath is set on the xml field, by passing in some other sort of PVM (such as
* a BufferedPropertyValueModel) for the xpath holder.
*/
public static void promptToSelectXpath(
ValueModel xmlFieldHolder, PropertyValueModel xpathHolder, WorkbenchContext context
) {
Window currentWindow = context.getCurrentWindow();
XpathChooserDialog dialog;
if (currentWindow instanceof Dialog) {
dialog = new XpathChooserDialog(context, (Dialog) currentWindow, xmlFieldHolder, xpathHolder);
}
else {
dialog = new XpathChooserDialog(context, xmlFieldHolder, xpathHolder);
}
dialog.show();
}
private static PropertyValueModel buildDefaultXpathHolder(ValueModel xmlFieldHolder) {
return new PropertyAspectAdapter(xmlFieldHolder, MWXmlField.XPATH_PROPERTY) {
protected Object getValueFromSubject() {
return ((MWXmlField) this.subject).getXpath();
}
protected void setValueOnSubject(Object value) {
((MWXmlField) this.subject).setXpath((String) value);
}
};
}
//***************** Constructors ******************************************
private XpathChooserDialog(
WorkbenchContext context, ValueModel xmlFieldHolder, PropertyValueModel xpathHolder
) {
super(context);
this.initialize(xmlFieldHolder, xpathHolder);
}
private XpathChooserDialog(
WorkbenchContext context, Dialog dialog, ValueModel xmlFieldHolder, PropertyValueModel xpathHolder
) {
super(context, dialog);
this.initialize(xmlFieldHolder, xpathHolder);
}
//***************** Initialization ****************************************
protected void initialize() {
super.initialize();
// initialize dialog settings
this.setTitle(this.resourceRepository().getString("XPATH_CHOOSER_DIALOG.TITLE"));
this.getOKAction().setEnabled(false);
}
private void initialize(ValueModel xmlFieldHolder, PropertyValueModel xpathHolder) {
this.xmlFieldHolder = xmlFieldHolder;
this.xpathHolder = xpathHolder;
this.xpathTrigger = new BufferedPropertyValueModel.Trigger();
this.selectedXpathHolder = this.buildSelectedXpathHolder(this.xpathHolder, this.xpathTrigger);
this.xpathTreeModel = this.buildXpathTreeModel();
this.xpathTreeSelectionModel = this.buildXpathTreeSelectionModel();
this.selectedPath = new Object[0];
this.positionListener = this.buildPositionListener();
}
private BufferedPropertyValueModel buildSelectedXpathHolder(
PropertyValueModel xpathHolder, BufferedPropertyValueModel.Trigger trigger
) {
BufferedPropertyValueModel holder = new BufferedPropertyValueModel(xpathHolder, trigger);
holder.addPropertyChangeListener(PropertyValueModel.VALUE, this.buildSelectedXpathListener());
return holder;
}
private PropertyChangeListener buildSelectedXpathListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent pce) {
XpathChooserDialog.this.getOKAction().setEnabled(! "".equals(pce.getNewValue()));
}
};
}
private XpathTreeModel buildXpathTreeModel() {
return new XpathTreeModel(this.xmlField().schemaContext(), this.xmlField().xpathSpec());
}
private TreeSelectionModel buildXpathTreeSelectionModel() {
TreeSelectionModel treeSelectionModel = new DefaultTreeSelectionModel();
treeSelectionModel.setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
return treeSelectionModel;
}
private StateChangeListener buildPositionListener() {
return new StateChangeListener() {
public void stateChanged(StateChangeEvent e) {
XpathChooserDialog.this.rebuildXpath();
}
};
}
protected Component buildMainPanel() {
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
panel.setPreferredSize(new Dimension(350, 400));
GridBagConstraints constraints = new GridBagConstraints();
// build the tree
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 2;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 1;
constraints.anchor = GridBagConstraints.PAGE_START;
constraints.fill = GridBagConstraints.BOTH;
constraints.insets = new Insets(0, 0, 0, 0);
panel.add(this.buildXpathTreePane(), constraints);
// build the preview text field label
JLabel xpathPreviewLabel = this.buildXpathPreviewLabel();
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 0;
constraints.weighty = 0;
constraints.anchor = GridBagConstraints.WEST;
constraints.fill = GridBagConstraints.NONE;
constraints.insets = new Insets(5, 0, 0, 5);
panel.add(xpathPreviewLabel, constraints);
// build the preview text field
JTextField xpathPreviewTextField = this.buildXpathPreviewTextField();
xpathPreviewLabel.setLabelFor(xpathPreviewTextField);
constraints.gridx = 1;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 0;
constraints.anchor = GridBagConstraints.CENTER;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.insets = new Insets(5, 0, 0, 0);
panel.add(xpathPreviewTextField, constraints);
return panel;
}
private JComponent buildXpathTreePane() {
return new JScrollPane(this.buildXpathTree());
}
private JTree buildXpathTree() {
JTree tree = SwingComponentFactory.buildTree(this.xpathTreeModel);
tree.setSelectionModel(this.xpathTreeSelectionModel);
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
tree.setEditable(true);
tree.setInvokesStopCellEditing(true);
tree.setCellRenderer(this.buildTreeCellRenderer());
tree.setCellEditor(this.buildTreeCellEditor());
tree.setRowHeight(20);
tree.addTreeSelectionListener(this.buildTreeSelectionListener());
return tree;
}
private TreeCellRenderer buildTreeCellRenderer() {
return new NodeTreeCellRenderer();
}
private TreeCellEditor buildTreeCellEditor() {
return new NodeTreeCellEditor();
}
private TreeSelectionListener buildTreeSelectionListener() {
return new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent tse) {
XpathChooserDialog.this.xpathTreeSelectionChanged();
}
};
}
private JLabel buildXpathPreviewLabel() {
// "XPath" does not need to be translated
JLabel xpathPreviewLabel = new JLabel("XPath:");
xpathPreviewLabel.setDisplayedMnemonic('X');
return xpathPreviewLabel;
}
private JTextField buildXpathPreviewTextField() {
JTextField textField = new JTextField();
textField.setEditable(false);
textField.setEnabled(true);
textField.setDocument(this.buildXpathPreviewDocument());
return textField;
}
private DocumentAdapter buildXpathPreviewDocument() {
return new DocumentAdapter(this.buildSelectedXpathDisplayStringHolder());
}
private PropertyValueModel buildSelectedXpathDisplayStringHolder() {
return new TransformationPropertyValueModel(this.selectedXpathHolder) {
protected Object transform(Object value) {
if ("".equals(value)) {
return XpathChooserDialog.this.resourceRepository().getString("NONE_SELECTED");
}
else {
return (String) value;
}
}
};
}
// **************** AbstractDialog contract *******************************
protected void okConfirmed() {
this.xpathTrigger.accept();
super.okConfirmed();
}
protected String helpTopicId() {
return "dialog.xpathChooser";
}
// **************** Convenience *******************************************
private MWXmlField xmlField() {
return (MWXmlField) this.xmlFieldHolder.getValue();
}
// **************** Behavior **********************************************
private void xpathTreeSelectionChanged() {
for (int i = this.selectedPath.length; i-- > 0; ) {
((XpathTreeNode) this.selectedPath[i]).removeStateChangeListener(this.positionListener);
}
TreePath tempPath = this.xpathTreeSelectionModel.getSelectionPath();
if (tempPath == null) {
this.selectedPath = new Object[0];
} else {
this.selectedPath = tempPath.getPath();
}
for (int i = this.selectedPath.length; i-- > 0; ) {
((XpathTreeNode) this.selectedPath[i]).addStateChangeListener(this.positionListener);
}
rebuildXpath();
}
private void rebuildXpath() {
String newXpath = "";
TreePath selectedTreePath = this.xpathTreeSelectionModel.getSelectionPath();
if (selectedTreePath != null) {
newXpath = ((XpathTreeNode) selectedTreePath.getLastPathComponent()).xpath();
}
this.selectedXpathHolder.setValue(newXpath);
}
public void show() {
if (this.xmlField().schemaContext() == null) {
this.showErrorDialog();
}
else {
super.show();
}
}
private void showErrorDialog() {
Component currentWindow = this.currentWindow();
ResourceRepository repo = this.resourceRepository();
String errorTitle = repo.getString("XPATH_CHOOSER_DIALOG.NO_CONTEXT_SPECIFIED_TITLE");
String errorMessage = repo.getString("XPATH_CHOOSER_DIALOG.NO_CONTEXT_SPECIFIED_MESSAGE");
JOptionPane.showMessageDialog(currentWindow, errorMessage, errorTitle, JOptionPane.WARNING_MESSAGE);
}
// **************** Member classes ****************************************
private final static class XpathTreeModel
extends AbstractTreeModel
{
// **************** Instance variables ********************************
private SchemaContextNode contextNode;
// **************** Constructors **************************************
private XpathTreeModel() {
super();
}
XpathTreeModel(MWSchemaContextComponent contextComponent, MWXpathSpec xpathSpec) {
this();
this.initialize(contextComponent, xpathSpec);
}
// ************ Initialization ****************************************
private void initialize(MWSchemaContextComponent contextComponent, MWXpathSpec xpathSpec) {
this.contextNode = new SchemaContextNode(contextComponent, xpathSpec);
}
// **************** TreeModel contract ********************************
public Object getRoot() {
return this.contextNode;
}
public Object getChild(Object parent, int index) {
return ((XpathTreeNode) parent).child(index);
}
public int getChildCount(Object parent) {
return ((XpathTreeNode) parent).childrenSize();
}
public boolean isLeaf(Object node) {
return ((XpathTreeNode) node).isLeaf();
}
public int getIndexOfChild(Object parent, Object child) {
return ((XpathTreeNode) parent).childIndex((XpathTreeNode) child);
}
public void valueForPathChanged(TreePath path, Object newValue) {
ElementNode elementNode = (ElementNode) path.getLastPathComponent();
elementNode.setPosition((String) newValue);
this.fireTreeNodeChanged(path.getParentPath().getPath(),
this.getIndexOfChild(elementNode.getParent(), elementNode),
elementNode);
}
}
private abstract static class XpathTreeNode
extends AbstractModel
implements RenderingNode, EditingNode
{
private XpathTreeNode parent;
private List children;
private TreeCellRenderer renderer;
private TreeCellEditor editor;
protected XpathTreeNode(XpathTreeNode parent) {
super();
this.parent = parent;
}
// **************** Internal ******************************************
private List getChildren() {
if (this.children == null) {
this.children = this.buildChildren();
}
return this.children;
}
private List buildChildren() {
return CollectionTools.list(this.newChildren());
}
protected abstract Iterator newChildren();
// **************** RenderingNode contract ****************************
public TreeCellRenderer getRenderer() {
if (this.renderer == null) {
this.renderer = this.buildRenderer();
}
return this.renderer;
}
protected abstract TreeCellRenderer buildRenderer();
// **************** EditingNode contract ******************************
public TreeCellEditor getEditor() {
if (this.editor == null) {
this.editor = this.buildEditor();
}
return this.editor;
}
protected abstract TreeCellEditor buildEditor();
// **************** Exposed *******************************************
XpathTreeNode getParent() {
return this.parent;
}
Iterator children() {
return this.getChildren().iterator();
}
int childrenSize() {
return this.getChildren().size();
}
XpathTreeNode child(int index) {
return (XpathTreeNode) this.getChildren().get(index);
}
int childIndex(XpathTreeNode child) {
return this.getChildren().indexOf(child);
}
boolean isLeaf() {
return this.childrenSize() == 0;
}
String xpath() {
if (this.parent == null) {
return this.xpathStepString();
}
else {
String parentXpath = this.parent.xpath();
if ("".equals(parentXpath)) {
return this.xpathStepString();
}
else {
return parentXpath + "/" + this.xpathStepString();
}
}
}
abstract String xpathStepString();
}
private final static class SchemaContextNode
extends XpathTreeNode
implements RenderingNode
{
private MWSchemaContextComponent contextComponent;
private MWXpathSpec xpathSpec;
SchemaContextNode(MWSchemaContextComponent contextComponent, MWXpathSpec xpathSpec) {
super(null);
this.initialize(contextComponent, xpathSpec);
}
// **************** Internal ******************************************
private void initialize(MWSchemaContextComponent contextComponent, MWXpathSpec xpathSpec) {
this.contextComponent = contextComponent;
this.xpathSpec = xpathSpec;
}
protected Iterator newChildren() {
return new CompositeIterator(this.newTextChildren(), this.newComponentChildren());
}
protected Iterator newTextChildren() {
if (this.xpathSpec.mayUseSimpleData() && this.contextComponent.containsText()) {
return new SingleElementIterator(this.buildChildTextNode());
}
else {
return NullIterator.instance();
}
}
private Object buildChildTextNode() {
return new TextNode(this);
}
protected Iterator newComponentChildren() {
return new TransformationIterator(SchemaContextNode.this.specXpathComponents()) {
protected Object transform(Object next) {
return SchemaContextNode.this.buildChildComponentNode((MWXpathableSchemaComponent) next);
}
};
}
private Iterator specXpathComponents() {
return new FilteringIterator(this.xpathComponents()) {
protected boolean accept(Object o) {
if (o instanceof MWAttributeDeclaration) {
return SchemaContextNode.this.xpathSpec.mayUseSimpleData();
}
else {
return true;
}
}
};
}
private Iterator xpathComponents() {
return (this.contextComponent == null) ? NullIterator.instance() : this.contextComponent.xpathComponents();
}
private Object buildChildComponentNode(MWXpathableSchemaComponent xpathComponent) {
return XpathableComponentNode.buildNode(this, xpathComponent, this.xpathSpec);
}
// **************** RenderingNode contract ****************************
public Object getCellValue() {
return this.contextComponent;
}
public TreeCellRenderer buildRenderer() {
return new SimpleTreeCellRenderer() {
protected Icon buildIcon(Object value) {
return null;
}
protected String buildText(Object value) {
return "";
}
};
}
// **************** EditingNode contract ******************************
protected TreeCellEditor buildEditor() {
return NullTreeCellEditor.instance();
}
// **************** Exposed *******************************************
String xpathStepString() {
return "";
}
}
private static class TextNode
extends XpathTreeNode
{
// *************** Constructors ***************************************
TextNode(XpathTreeNode parent) {
super(parent);
}
// **************** Internal ******************************************
protected Iterator newChildren() {
return NullIterator.instance();
}
// **************** RenderingNode contract ****************************
public Object getCellValue() {
return null;
}
protected TreeCellRenderer buildRenderer() {
return new SimpleTreeCellRenderer() {
protected Icon buildIcon(Object value) {
return null;
}
protected String buildText(Object value) {
return MWXmlField.TEXT;
}
};
}
// **************** EditingNode contract ******************************
protected TreeCellEditor buildEditor() {
return NullTreeCellEditor.instance();
}
// **************** Exposed *******************************************
String xpathStepString() {
return MWXmlField.TEXT;
}
}
private abstract static class XpathableComponentNode
extends XpathTreeNode
{
// ************ Instance variables ************************************
protected MWXpathableSchemaComponent xpathComponent;
// **************** Static methods ************************************
static XpathableComponentNode buildNode(XpathTreeNode parent,
MWXpathableSchemaComponent xpathComponent,
MWXpathSpec xpathSpec)
{
if (xpathComponent instanceof MWAttributeDeclaration) {
return new AttributeNode(parent, (MWAttributeDeclaration) xpathComponent);
}
else if (xpathComponent instanceof MWElementDeclaration) {
return new ElementNode(parent, (MWElementDeclaration) xpathComponent, xpathSpec);
}
else {
throw new IllegalArgumentException("Illegal xpath component");
}
}
// **************** Constructors **************************************
protected XpathableComponentNode(XpathTreeNode parent, MWXpathableSchemaComponent component) {
super(parent);
this.xpathComponent = component;
}
// **************** RenderingNode contract ********************************
public Object getCellValue() {
// Since the component is displayed by the label, what we're returning here is the "additional" value.
// On this level, there is no additional value.
return null;
}
/** For use in subclasses */
protected SimpleTreeCellRenderer buildSimpleRenderer() {
return new SimpleTreeCellRenderer() {
protected Icon buildIcon(Object value) {
return null;
}
protected String buildText(Object value) {
return XpathableComponentNode.this.displayString();
}
};
}
// **************** Internal **********************************************
protected abstract String displayString();
}
private final static class AttributeNode
extends XpathableComponentNode
{
// **************** Constructors **************************************
AttributeNode(XpathTreeNode parent, MWAttributeDeclaration attribute) {
super(parent, attribute);
}
// **************** Internal ******************************************
protected Iterator newChildren() {
return NullIterator.instance();
}
private String internalXpathStepString() {
return "@" + this.xpathComponent.qName();
}
// **************** RenderingNode contract ****************************
public TreeCellRenderer buildRenderer() {
return this.buildSimpleRenderer();
}
public TreeCellEditor buildEditor() {
return NullTreeCellEditor.instance();
}
protected String displayString() {
return this.internalXpathStepString();
}
// **************** Exposed *******************************************
String xpathStepString() {
return this.internalXpathStepString();
}
}
private final static class ElementNode
extends XpathableComponentNode
{
// ************ Instance variables ****************************************
private MWXpathSpec xpathSpec;
private int position;
private boolean textBuiltIn;
// **************** Constructors ******************************************
protected ElementNode(XpathTreeNode parent, MWElementDeclaration element, MWXpathSpec xpathSpec) {
super(parent, element);
this.initialize(xpathSpec);
}
// **************** Internal ******************************************
private void initialize(MWXpathSpec xpathSpec) {
this.xpathSpec = xpathSpec;
this.position = this.initialPosition();
this.textBuiltIn = this.calculateTextBuiltIn();
}
private boolean calculateTextBuiltIn() {
return
(! this.xpathSpec.mayUseComplexData()
&& this.xpathSpec.mayUseSimpleData()
&& this.xpathComponent.containsText()
&& CollectionTools.size(this.xpathComponent.xpathComponents()) == 0);
}
protected Iterator newChildren() {
return new CompositeIterator(this.newTextChildren(), this.newComponentChildren());
}
protected Iterator newTextChildren() {
if (! this.textBuiltIn
&& this.xpathSpec.mayUseSimpleData()
&& this.xpathComponent.containsText()
) {
return new SingleElementIterator(this.buildChildTextNode());
}
else {
return NullIterator.instance();
}
}
private Object buildChildTextNode() {
return new TextNode(this);
}
protected Iterator newComponentChildren() {
return new TransformationIterator(ElementNode.this.specXpathComponents()) {
protected Object transform(Object next) {
return ElementNode.this.buildChildNode((MWXpathableSchemaComponent) next);
}
};
}
private Iterator specXpathComponents() {
return new FilteringIterator(this.xpathComponents()) {
protected boolean accept(Object o) {
if (o instanceof MWAttributeDeclaration) {
return ElementNode.this.xpathSpec.mayUseSimpleData();
}
else {
return true;
}
}
};
}
private Iterator xpathComponents() {
return this.xpathComponent.xpathComponents();
}
private Object buildChildNode(MWXpathableSchemaComponent xpathComponent) {
return XpathableComponentNode.buildNode(this, xpathComponent, this.xpathSpec);
}
// **************** RenderingNode contract ********************************
public Object getCellValue() {
return (this.position == 0) ? "all" : String.valueOf(this.position);
}
protected TreeCellRenderer buildRenderer() {
if (this.canSetPosition()) {
return this.buildSpinnerRenderer();
}
else {
return this.buildSimpleRenderer();
}
}
private SpinnerTreeCellRenderer buildSpinnerRenderer() {
return new SpinnerTreeCellRenderer(this.displayString(), this.buildSpinnerModel()) {
protected JComponent buildComponent() {
JSpinner spinner = (JSpinner) super.buildComponent();
spinner.setPreferredSize(new Dimension(40, spinner.getPreferredSize().height));
JFormattedTextField textField = ((JSpinner.DefaultEditor) spinner.getEditor()).getTextField();
textField.setEditable(false);
textField.setColumns(4);
return spinner;
}
};
}
private SpinnerModel buildSpinnerModel() {
return new ElementPositionSpinnerModel(this.position, this.minPosition(), this.maxPosition());
}
protected TreeCellEditor buildEditor() {
if (this.canSetPosition()) {
return this.buildSpinnerEditor();
}
else {
return NullTreeCellEditor.instance();
}
}
private TreeCellEditor buildSpinnerEditor() {
return new TreeCellEditorAdapter(this.buildSpinnerRenderer());
}
protected String displayString() {
String displayString = this.xpathComponent.qName();
if (this.textBuiltIn) {
displayString += ("/" + MWXmlField.TEXT);
}
return displayString;
}
// **************** Internal **********************************************
private MWElementDeclaration element() {
return (MWElementDeclaration) this.xpathComponent;
}
private boolean canSetPosition() {
return this.maxPosition() > 1;
}
private int initialPosition() {
return (this.canSetPosition()) ? this.minPosition() : 0;
}
private int minPosition() {
return (this.xpathSpec.mayUseCollectionData()) ? 0 : 1;
}
private int maxPosition() {
return this.element().getMaxOccurs();
}
// **************** Exposed ***********************************************
void setPosition(String positionString) {
int oldValue = this.position;
if (positionString.equals("all")) {
this.position = 0;
} else {
this.position = Integer.parseInt(positionString);
}
if (oldValue != this.position) {
this.fireStateChanged();
}
}
/** Add position to internal xpath step string */
String xpathStepString() {
String xpathStepString = this.xpathComponent.qName();
if (this.position != 0) {
xpathStepString += ("[" + this.position + "]");
}
if (this.textBuiltIn) {
xpathStepString += ("/" + MWXmlField.TEXT);
}
return xpathStepString;
}
}
private final static class ElementPositionSpinnerModel
extends AbstractSpinnerModel
{
private int value;
private int min;
private int max;
ElementPositionSpinnerModel(int value, int min, int max) {
super();
this.value = value;
this.min = min;
this.max = max;
}
public Object getValue() {
return (this.value == 0) ? "all" : String.valueOf(this.value);
}
public void setValue(Object value) {
if ((value == null) || ! (value instanceof String)) {
throw new IllegalArgumentException("illegal value: " + value);
}
int old = this.value;
if (value.equals("all")) {
this.value = 0;
} else {
try {
this.value = Integer.parseInt((String) value);
} catch (NumberFormatException ex) {
// just ignore bogus values...
}
}
if (old != this.value) {
this.fireStateChanged();
}
}
public Object getPreviousValue() {
if (this.value == 0) {
return null;
} else if (this.value == this.min) {
return "all";
} else {
return String.valueOf(this.value - 1);
}
}
public Object getNextValue() {
if (this.value == this.max) {
return null;
} else {
return String.valueOf(this.value + 1);
}
}
}
}