/*******************************************************************************
* 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.framework.ui.dialog;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
import javax.xml.bind.Validator;
import org.eclipse.persistence.tools.workbench.framework.context.WorkbenchContext;
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.ValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.swing.DocumentAdapter;
import org.eclipse.persistence.tools.workbench.utility.events.AWTChangeNotifier;
import org.eclipse.persistence.tools.workbench.utility.events.ChangeNotifier;
import org.eclipse.persistence.tools.workbench.utility.node.AbstractNodeModel;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
import org.eclipse.persistence.tools.workbench.utility.node.Problem;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
/**
* This dialog can be used to prompt the user for the name of a new
* object or for the new name of an existing object.
*
* To instantiate this dialog: instantiate a Builder, configure it,
* then build the dialog by calling Builder.buildDialog(WorkbenchContext).
*
* <pre>
* ________________________________________
* | Title |
* |--------------------------------------|
* | Description: |
* | ________________________ |
* | |New name text field | |
* | ------------------------ |
* | Error Message |
* | ____________________________________ |
* | ______ ________ ____ ________ |
* | |Help| |Custom| |OK| |Cancel| |
* | ------ -------- ---- -------- |
* ----------------------------------------
* </pre>
*
* NB: If you subclass this dialog, you will need to subclass
* the Builder class also.
*/
public class NewNameDialog extends AbstractValidatingDialog {
/** Holds all the settings used by the dialog when editing the name. */
private Builder builder;
/** We need to set the text on this after it is built. */
private JLabel textFieldLabel;
/** After this is built, we need to set the text and select it. */
protected JTextField textField;
/**
* The holder of the state object used by this dialog.
*/
private PropertyValueModel subjectHolder;
// ********** constructors/initialization **********
/**
* Do not call this constructor directly; use a Builder to
* instantiate this dialog.
*/
protected NewNameDialog(WorkbenchContext context, Builder builder) {
super(context, builder.getTitle());
this.builder = builder;
}
@Override
protected void initialize() {
super.initialize();
this.subjectHolder = new SimplePropertyValueModel();
}
protected Component buildMainPanel() {
JPanel mainPanel = new JPanel(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
// label
this.textFieldLabel = new JLabel();
constraints.gridx = 0;
constraints.gridy = 0;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 0;
constraints.weighty = 0;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.anchor = GridBagConstraints.PAGE_START;
constraints.insets = new Insets(5, 0, 5, 0);
mainPanel.add(this.textFieldLabel, constraints);
// text entry field
this.textField = new JTextField(25);
constraints.gridx = 0;
constraints.gridy = 1;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.weightx = 1;
constraints.weighty = 0;
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.anchor = GridBagConstraints.FIRST_LINE_START;
constraints.insets = new Insets(5, 0, 5, 0);
mainPanel.add(this.textField, constraints);
this.textFieldLabel.setText(this.builder.getTextFieldDescription());
Document document = this.buildDocument(this.builder.getOriginalName());
document.addDocumentListener(this.buildDocumentListener());
this.textField.setDocument(document);
this.textField.selectAll();
if (this.textField.getText().length() == 0) {
this.getOKAction().setEnabled(false);
}
this.helpManager().addTopicID(mainPanel, this.helpTopicId());
return mainPanel;
}
protected Builder getBuilder() {
return this.builder;
}
protected Document buildDocument(String originalName) {
Document document = this.buildDocument();
try {
document.insertString(0, originalName, null);
} catch (BadLocationException ex) {
throw new RuntimeException(ex); // this should not happen
}
return document;
}
/**
* Override if you want to restrict the allowable characters
* entered in the text field (e.g. with a regular expression
* document).
*/
protected Document buildDocument() {
return this.builder.getDocumentFactory().buildDocument();
}
protected Document buildDocumentWithStateObject() {
return new DocumentAdapter(
this.buildNameHolder(),
this.builder.getDocumentFactory().buildDocument()
);
}
protected final PropertyValueModel buildNameHolder() {
return new PropertyAspectAdapter(getSubjectHolder(), StateObject.NAME_PROPERTY) {
@Override
protected Object getValueFromSubject() {
return ((StateObject)subject).getName();
}
@Override
protected void setValueOnSubject(Object value) {
((StateObject)subject).setName((String)value);
}
};
}
protected DocumentListener buildDocumentListener() {
return new DocumentListener() {
public void removeUpdate(DocumentEvent e) {
NewNameDialog.this.documentChanged();
}
public void insertUpdate(DocumentEvent e) {
NewNameDialog.this.documentChanged();
}
public void changedUpdate(DocumentEvent e) {
// this probably will never happen...
}
};
}
// ********** opening **********
protected String helpTopicId() {
return this.builder.getHelpTopicId();
}
protected Component initialFocusComponent() {
return this.textField;
}
// ********** editing input **********
protected Builder builder() {
return this.builder;
}
/**
* The text field changed, edit it and update the error message.
*/
protected void documentChanged() {
if (this.isVisible()) {
this.editName();
}
}
/**
* Edit the name, using the settings in the builder, and update
* the error message.
*/
protected void editName() {
String text = this.textField.getText();
// empty string might not be allowed
if (this.builder.emptyNameIsIllegal() && (text.length() == 0)) {
this.setErrorMessageKey("NEW_NAME_DIALOG.EMPTY_VALUE");
return;
}
boolean nameIsSameAsOriginal = this.namesMatch(text, this.builder.getOriginalName());
// original name might be "illegal"
if (this.builder.originalNameIsIllegal() && nameIsSameAsOriginal) {
this.setErrorMessageKey("NEW_NAME_DIALOG.ORIGINAL_VALUE");
return;
}
// check for "existing" name
if (this.nameIsAlreadyTaken(text, nameIsSameAsOriginal)) {
this.setErrorMessageKey("NEW_NAME_DIALOG.DUPLICATE_VALUE");
return;
}
// check for "illegal" name
if (this.nameIsIllegal(text)) {
this.setErrorMessageKey("NEW_NAME_DIALOG.ILLEGAL_VALUE");
return;
}
// no problems...
this.clearErrorMessage();
}
protected void setErrorMessageKey(String key) {
super.setErrorMessageKey(key);
this.getOKAction().setEnabled(false);
}
protected void clearErrorMessage() {
super.clearErrorMessage();
this.getOKAction().setEnabled(true);
}
protected boolean namesMatch(String name1, String name2) {
return this.builder.comparisonIsCaseSensitive() ?
name1.equals(name2)
:
name1.equalsIgnoreCase(name2);
}
protected boolean nameIsAlreadyTaken(String name, boolean nameIsSameAsOriginal) {
for (Iterator stream = this.builder.existingNames(); stream.hasNext(); ) {
if (this.namesMatch(name, (String) stream.next())) {
if ( ! nameIsSameAsOriginal) {
// if the name can be the same as the original and the original
// is among the "existing" names, ignore it
return true;
}
}
}
return false;
}
protected boolean nameIsIllegal(String name) {
for (Iterator stream = this.builder.illegalNames(); stream.hasNext(); ) {
if (this.namesMatch(name, (String) stream.next())) {
// we may want to put a check for the "original" name here, also...
// see above
return true;
}
}
return false;
}
// ********** client API **********
/**
* Return the new name entered by the user.
* This will throw an exception the dialog was not "confirmed".
*/
public String getNewName() {
if (this.wasConfirmed()) {
return this.textField.getText();
}
throw new IllegalStateException("dialog was not confirmed");
}
protected String getNameInternal() {
return this.textField.getText();
}
// ********** member classes **********
protected final ValueModel getSubjectHolder()
{
return subjectHolder;
}
protected StateObject buildStateObject()
{
return null;
}
@Override
public void show() {
installSubject();
super.show();
}
private void installSubject()
{
StateObject subject = this.buildStateObject();
if (subject != null)
{
subject.setValidator(buildValidator());
subject.setChangeNotifier(AWTChangeNotifier.instance());
}
this.subjectHolder.setValue(subject);
}
/**
* Returns the subject of this dialog.
*
* @return The subject of this dialog or <code>null</code> if no subject was
* used
*/
public StateObject subject()
{
return (StateObject)this.subjectHolder.getValue();
}
Node.Validator buildValidator()
{
return Node.NULL_VALIDATOR;
}
/**
* Configure an instance of this class to build a NewNameDialog.
*
* Subclasses should probably override #buildDialog(WorkbenchContext, Builder)
* to return the appropriate subclass of NewNameDialog.
*/
public static class Builder implements Cloneable {
private String title;
private String textFieldDescription;
private String originalName;
private boolean emptyNameIsLegal;
private boolean originalNameIsLegal;
private ArrayList existingNames;
private ArrayList illegalNames;
private boolean comparisonIsCaseSensitive;
private DocumentFactory documentFactory;
private String helpTopicId;
// ********** constructors/initialization **********
public Builder() {
super();
this.initialize();
}
protected void initialize() {
this.title = null;
this.textFieldDescription = null;
this.originalName = null;
this.emptyNameIsLegal = false;
this.originalNameIsLegal = true;
this.existingNames = new ArrayList();
this.illegalNames = new ArrayList();
this.comparisonIsCaseSensitive = false;
this.documentFactory = this.buildDefaultDocumentFactory();
this.helpTopicId = "dialog.newName";
}
// ********** dialog instantiation **********
public NewNameDialog buildDialog(WorkbenchContext context) {
return this.buildDialog(context, (Builder) this.clone());
}
protected Object clone() {
Builder clone;
try {
clone = (Builder) super.clone();
} catch (CloneNotSupportedException ex) {
throw new RuntimeException(ex);
}
// make copies of collections
clone.existingNames = (ArrayList) this.existingNames.clone();
clone.illegalNames = (ArrayList) this.illegalNames.clone();
return clone;
}
protected NewNameDialog buildDialog(WorkbenchContext context, Builder clone) {
return new NewNameDialog(context, clone);
}
// ********** settings **********
/**
* The title of the dialog. The default is null.
*/
public void setTitle(String title) {
this.title = title;
}
public String getTitle() {
return this.title;
}
/**
* The description displayed above the text entry field.
* The default is null.
*/
public void setTextFieldDescription(String textFieldDescription) {
this.textFieldDescription = textFieldDescription;
}
public String getTextFieldDescription() {
return this.textFieldDescription;
}
/**
* The "original" name displayed in the text entry field.
* The default is null.
*/
public void setOriginalName(String originalName) {
this.originalName = originalName;
}
public String getOriginalName() {
return this.originalName;
}
/**
* Whether the name can be an empty string.
* The default is false.
*/
public void setEmptyNameIsLegal(boolean emptyNameIsLegal) {
this.emptyNameIsLegal = emptyNameIsLegal;
}
public boolean emptyNameIsLegal() {
return this.emptyNameIsLegal;
}
public boolean emptyNameIsIllegal() {
return ! this.emptyNameIsLegal;
}
/**
* Whether the "original" name can be re-used as the "new" name.
* The default is true.
*/
public void setOriginalNameIsLegal(boolean originalNameIsLegal) {
this.originalNameIsLegal = originalNameIsLegal;
}
public boolean originalNameIsLegal() {
return this.originalNameIsLegal;
}
public boolean originalNameIsIllegal() {
return ! this.originalNameIsLegal;
}
/**
* The set of "existing" names that are not allowed. In most
* situations duplicate names are not allowed.
*/
public void setExistingNames(Collection existingNames) {
this.setExistingNames(existingNames.iterator());
}
public void setExistingNames(Iterator existingNames) {
this.existingNames.clear();
this.addExistingNames(existingNames);
}
public void addExistingNames(Collection names) {
this.addExistingNames(names.iterator());
}
public void addExistingNames(Iterator names) {
while (names.hasNext()) {
this.existingNames.add(names.next());
}
}
public Iterator existingNames() {
return this.existingNames.iterator();
}
/**
* The set of "illegal" names that are not allowed. Typically this
* includes any reserved names.
*/
public void setIllegalNames(Collection illegalNames) {
this.setIllegalNames(illegalNames.iterator());
}
public void setIllegalNames(Iterator illegalNames) {
this.illegalNames.clear();
this.addIllegalNames(illegalNames);
}
public void addIllegalNames(Collection names) {
this.addIllegalNames(names.iterator());
}
public void addIllegalNames(Iterator names) {
while (names.hasNext()) {
this.illegalNames.add(names.next());
}
}
public Iterator illegalNames() {
return this.illegalNames.iterator();
}
/**
* Whether the comparison between the "new" name and the "illegal" names
* (or "original" name) is case-sensitive.
* The default is false.
*/
public void setComparisonIsCaseSensitive(boolean comparisonIsCaseSensitive) {
this.comparisonIsCaseSensitive = comparisonIsCaseSensitive;
}
public boolean comparisonIsCaseSensitive() {
return this.comparisonIsCaseSensitive;
}
/**
* The factory used by the NewNameDialog to build the text field's
* model Document. This can be used to build something besides a
* PlainDocument; e.g. when you want to prevent certain characters
* from being typed into the text field.
*/
public void setDocumentFactory(DocumentFactory documentFactory) {
this.documentFactory = documentFactory;
}
public DocumentFactory getDocumentFactory() {
return this.documentFactory;
}
protected DocumentFactory buildDefaultDocumentFactory() {
return new DocumentFactory() {
public Document buildDocument() {
return new PlainDocument();
}
};
}
public String getHelpTopicId() {
return this.helpTopicId;
}
public void setHelpTopicId(String helpTopidId) {
this.helpTopicId = helpTopidId;
}
}
/**
* Simple interface for building a Document model for use by
* the NewNameDialog's text field.
*/
public interface DocumentFactory {
/**
* Build and return a new Document to be used as
* the model for the NewNameDialog's text field.
*/
Document buildDocument();
}
/**
* The model object used by this dialog to automatically validate the input
* name.
*/
public static class StateObject extends AbstractNodeModel
{
private Builder builder;
private ChangeNotifier changeNotifier;
private String name;
private Validator validator;
public static final String NAME_PROPERTY = "name";
protected StateObject(Builder builder)
{
super();
this.builder = builder;
this.name = builder.getOriginalName();
if (name == null) {
name = "";
}
}
/*
* (non-Javadoc)
*/
@Override
protected void addProblemsTo(List currentProblems)
{
super.addProblemsTo(currentProblems);
editName(currentProblems);
}
/*
* (non-Javadoc)
*/
public String displayString()
{
return name;
}
/**
* Edit the name, using the settings in the builder, and update
* the error message.
*/
protected void editName(List<Problem> currentProblems) {
String text = this.name;
// empty string is not allowed
if (StringTools.stringIsEmpty(text)) {
currentProblems.add(buildProblem("NEW_NAME_DIALOG.EMPTY_VALUE"));
return;
}
boolean nameIsSameAsOriginal = this.namesMatch(text, this.builder.getOriginalName());
// original name might be "illegal"
if (this.builder.originalNameIsIllegal() && nameIsSameAsOriginal) {
currentProblems.add(buildProblem("NEW_NAME_DIALOG.ORIGINAL_VALUE"));
return;
}
// check for "existing" name
if (this.nameIsAlreadyTaken(text, nameIsSameAsOriginal)) {
currentProblems.add(buildProblem("NEW_NAME_DIALOG.DUPLICATE_VALUE"));
return;
}
// check for "illegal" name
if (this.nameIsIllegal(text)) {
currentProblems.add(buildProblem("NEW_NAME_DIALOG.ILLEGAL_VALUE"));
return;
}
// no problems...
//this.parentDialog.clearErrorMessage();
}
/*
* (non-Javadoc)
*/
@Override
public ChangeNotifier getChangeNotifier()
{
return changeNotifier;
}
public String getName()
{
return name;
}
/*
* (non-Javadoc)
*/
@Override
public Validator getValidator()
{
return validator;
}
protected boolean nameIsAlreadyTaken(String name, boolean nameIsSameAsOriginal) {
for (Iterator<String> stream = this.builder.existingNames(); stream.hasNext(); ) {
if (this.namesMatch(name, stream.next())) {
if ( ! nameIsSameAsOriginal) {
// if the name can be the same as the original and the original
// is among the "existing" names, ignore it
return true;
}
}
}
return false;
}
protected boolean nameIsIllegal(String name) {
for (Iterator<String> stream = this.builder.illegalNames(); stream.hasNext(); ) {
if (this.namesMatch(name, stream.next())) {
// we may want to put a check for the "original" name here, also...
// see above
return true;
}
}
return false;
}
protected boolean namesMatch(String name1, String name2) {
return this.builder.comparisonIsCaseSensitive() ?
name1.equals(name2)
:
name1.equalsIgnoreCase(name2);
}
/*
* (non-Javadoc)
*/
@Override
public void setChangeNotifier(ChangeNotifier changeNotifier)
{
this.changeNotifier = changeNotifier;
}
public void setName(String name)
{
String oldName = this.name;
this.name = name;
firePropertyChanged(NAME_PROPERTY, oldName, name);
}
/*
* (non-Javadoc)
*/
@Override
public void setValidator(Validator validator)
{
this.validator = validator;
}
}
}