/*******************************************************************************
* 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.app;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.prefs.Preferences;
import javax.swing.Icon;
import javax.swing.SwingConstants;
import org.eclipse.persistence.tools.workbench.framework.NodeManager;
import org.eclipse.persistence.tools.workbench.framework.Plugin;
import org.eclipse.persistence.tools.workbench.framework.context.ApplicationContext;
import org.eclipse.persistence.tools.workbench.framework.context.ApplicationContextWorkbenchContext;
import org.eclipse.persistence.tools.workbench.framework.context.ShellWorkbenchContext;
import org.eclipse.persistence.tools.workbench.framework.context.WorkbenchContext;
import org.eclipse.persistence.tools.workbench.framework.internal.FrameworkIconResourceFileNameMap;
import org.eclipse.persistence.tools.workbench.framework.resources.DefaultIconRepository;
import org.eclipse.persistence.tools.workbench.framework.resources.ResourceRepository;
import org.eclipse.persistence.tools.workbench.uitools.app.AbstractTreeNodeValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.ListValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.NullListValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.TreeNodeValueModel;
import org.eclipse.persistence.tools.workbench.uitools.swing.EmptyIcon;
import org.eclipse.persistence.tools.workbench.utility.Model;
import org.eclipse.persistence.tools.workbench.utility.events.ListChangeEvent;
import org.eclipse.persistence.tools.workbench.utility.events.ListChangeListener;
import org.eclipse.persistence.tools.workbench.utility.io.IndentingPrintWriter;
import org.eclipse.persistence.tools.workbench.utility.node.Node;
import org.eclipse.persistence.tools.workbench.utility.node.NodeModel;
import org.eclipse.persistence.tools.workbench.utility.node.Problem;
import org.eclipse.persistence.tools.workbench.utility.string.StringTools;
/**
* Subclasses should consider implementing the following methods:
*
* #getChildrenModel() [TreeNodeValueModel]
* "branch" nodes must return a list value model of the children nodes;
* "leaf" nodes can simply inherit the default implementation (an empty list)
*
* #helpTopicID()
* return Help Topic ID that can be used for Context-Sensitive Help;
* the default is, naturally, the "default" Topic ID
*
* #expandContext(WorkbenchContext)
* add any bundles or icon repository to the original context
*
* #buildDisplayString() [Displayable]
* return a display string appropriate to the value's current state;
* the default is to delegate to the value's #displayString() method
*
* #displayStringPropertyNames() [Displayable]
* return the names of the value's properties that affect the node's label;
* the default is an empty array
*
* #buildIconBuilder() or #buildIconKey() [Displayable]
* return an icon builder appropriate to the value's current state;
* the default is no icon and decorated with a warning sign if the node
* has any problems in its branch
*
* #iconPropertyNames() [Displayable]
* return the names of the value's properties that affect the node's icon;
* the default includes whether the value has any branch problems
*
* #buildDirtyFlag()
* return a dirty flag appropriate to the value's current state
* the default is to delegate to the value's #isDirtyBranch() method
*
* #dirtyPropertyNames()
* return the names of the value's properties that affect whether
* the node is "dirty"; the default includes whether the node is a
* "dirty branch"
*
* #buildMenuDescription()
* return a description that will be used to build the selection menu items
*
* #buildToolBarDescription()
* return a description that will be used to build the selection toolbar items
*
* #propertiesPage() [EditorNode]
* return a properties page to be shown in the editor view
*
* #buildPropertiesPageTitleText() [EditorNode]
* return a title string appropriate to the value's current state;
* the default is the node's display string
*
* #propertiesPageTitleTextPropertyNames() [EditorNode]
* return the names of the value's properties that affect the node's properties page title text;
* the default is the node's display string property names
*
* #buildPropertiesPageTitleIconBuilder() [EditorNode]
* return a title icon builder appropriate to the value's current state;
* the default is the node's icon builder
*
* #propertiesPageTitleIconPropertyNames() [EditorNode]
* return the names of the value's properties that affect the node's properties page title icon;
* the default is the node's icon property names
*
* #releasePropertiesPage(Component) [EditorNode]
* release the specified properties page, it is no longer needed
* by the properties page
*
* #rebuildApplicationProblems() [ApplicationProblemContainer]
* rebuild the list of "exclusive" application problems based on the value's
* branch problems
*
*
* If the subclass is for a "project root" node override the following methods:
*
* #save(File, WorkbenchContext)
* save the project
*
* #saveAs(File, WorkbenchContext)
* prompt the user to save the project in another location
*
* #saveLocation()
* return the project's save location
*
*/
public abstract class AbstractApplicationNode
extends AbstractTreeNodeValueModel
implements ApplicationNode
{
/** ValueModel.getValue() */
private NodeModel value;
/** TreeNodeValueModel.getParent() */
private TreeNodeValueModel parent;
/** Node.getPlugin() */
private Plugin plugin;
/** needed by subclasses etc. */
private ApplicationContext context;
/** cache the node's display string */
private String displayString;
/** this listens to the value's properties that affect the node's display string */
private PropertyChangeListener valueDisplayStringListener;
/** the model properties that affect the node's display string property */
protected static final String[] DEFAULT_DISPLAY_STRING_PROPERTY_NAMES = {};
/** cache the node's icon builder */
private IconBuilder iconBuilder;
/** cache the node's icon also */
private Icon icon;
/** this listens to the value's properties that affect the node's icon */
private PropertyChangeListener valueIconListener;
/** the model properties that affect the node's icon property */
protected static final String[] DEFAULT_ICON_PROPERTY_NAMES = {Node.HAS_BRANCH_PROBLEMS_PROPERTY};
/** cache the node's dirty flag */
private boolean dirty;
/** this listens to the value's properties that affect the node's dirty flag */
private PropertyChangeListener valueDirtyListener;
/** the model properties that affect the node's dirty property */
protected static final String[] DEFAULT_DIRTY_PROPERTY_NAMES = {Node.DIRTY_BRANCH_PROPERTY};
/** this holds the "application" problems that are "exclusive" to the node */
private List applicationProblems;
/** this holds the node's branch "application" problems */
private List branchApplicationProblems;
/** this listens to the value's branch problems so we can synch up the "application" problems, above */
private ListChangeListener valueBranchProblemsListener;
/** the model properties that affect the node's branch problems */
protected static final String[] DEFAULT_BRANCH_PROBLEMS_LIST_NAMES = {Node.BRANCH_PROBLEMS_LIST};
/** cache the node's properties page title text */
private String propertiesPageTitleText;
/** this listens to the value's properties that affect the node's properties page title text */
private PropertyChangeListener valuePropertiesPageTitleTextListener;
/** cache the node's properties page title icon builder */
private IconBuilder propertiesPageTitleIconBuilder;
/** cache the node's properties page title icon also */
private Icon propertiesPageTitleIcon;
/** this listens to the value's properties that affect the node's properties page title icon */
private PropertyChangeListener valuePropertiesPageTitleIconListener;
private static final Icon PROBLEM_ICON = new DefaultIconRepository(new FrameworkIconResourceFileNameMap()).getIcon("problem.small");
protected static final Icon EMPTY_ICON = new EmptyIcon(16);
// *************** constructors ***************
/**
* Only "pseudo-nodes" should use this constructor; and those
* are "framework internal".
*/
protected AbstractApplicationNode(ApplicationContext context) {
this(null, null, null, context);
}
/**
* "Normal" subclasses should use this constructor.
*/
protected AbstractApplicationNode(NodeModel value, TreeNodeValueModel parent, Plugin plugin, ApplicationContext context) {
super();
this.value = value;
this.context = this.expandContext(context);
this.plugin = plugin;
this.parent = parent;
// build the various listeners, but don't start listening yet
this.valueDisplayStringListener = this.buildValueDisplayStringListener();
this.valueIconListener = this.buildValueIconListener();
this.valueDirtyListener = this.buildValueDirtyListener();
this.valueBranchProblemsListener = this.buildValueBranchProblemsListener();
this.applicationProblems = new ArrayList();
this.branchApplicationProblems = new ArrayList();
this.valuePropertiesPageTitleTextListener = this.buildValuePropertiesPageTitleTextListener();
this.valuePropertiesPageTitleIconListener = this.buildValuePropertiesPageTitleIconListener();
}
// *************** initialization ***************
/**
* Expand the context passed into the node upon construction.
* By default, return the context unchanged.
*
* NB: Call super.expandContext(WorkbenchContext)
* when overriding this method.
* NB2: Assume that this method will return a different context
* than the context passed in, even if they *may* end up being
* the same.
*/
protected ApplicationContext expandContext(ApplicationContext ctx) {
return ctx;
}
/**
* Use this in places where a workbenchContext has been passed in to a subclass method.
* An example would be the buidMenuDescription(WorkbenchContext). We want the passed in
* WorkbenchContext but the node's ApplicationContext
*/
protected WorkbenchContext buildLocalWorkbenchContext(WorkbenchContext workbenchContext) {
return new ApplicationContextWorkbenchContext(workbenchContext, this.getApplicationContext());
}
protected WorkbenchContext buildShellWorkbenchContext() {
return new ShellWorkbenchContext(this.getApplicationContext());
}
private PropertyChangeListener buildValueDisplayStringListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
AbstractApplicationNode.this.displayStringChanged();
}
public String toString() {
return StringTools.buildToStringFor(this, "display string listener");
}
};
}
private PropertyChangeListener buildValueIconListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
AbstractApplicationNode.this.iconChanged();
}
public String toString() {
return StringTools.buildToStringFor(this, "icon listener");
}
};
}
private PropertyChangeListener buildValueDirtyListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
AbstractApplicationNode.this.dirtyChanged();
}
public String toString() {
return StringTools.buildToStringFor(this, "dirty listener");
}
};
}
private ListChangeListener buildValueBranchProblemsListener() {
return new ListChangeListener() {
public void itemsAdded(ListChangeEvent e) {
AbstractApplicationNode.this.branchProblemsChanged();
}
public void itemsRemoved(ListChangeEvent e) {
AbstractApplicationNode.this.branchProblemsChanged();
}
public void itemsReplaced(ListChangeEvent e) {
AbstractApplicationNode.this.branchProblemsChanged();
}
public void listChanged(ListChangeEvent e) {
AbstractApplicationNode.this.branchProblemsChanged();
}
public String toString() {
return StringTools.buildToStringFor(this, "branch problems listener");
}
};
}
private PropertyChangeListener buildValuePropertiesPageTitleTextListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
AbstractApplicationNode.this.propertiesPageTitleTextChanged();
}
public String toString() {
return StringTools.buildToStringFor(this, "properties page title text listener");
}
};
}
private PropertyChangeListener buildValuePropertiesPageTitleIconListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
AbstractApplicationNode.this.propertiesPageTitleIconChanged();
}
public String toString() {
return StringTools.buildToStringFor(this, "properties page title icon listener");
}
};
}
// ********** AbstractTreeNodeValueModel implementation **********
/**
* when we get here we know there are no "state" change listeners;
* now check for apect-specific listeners before engaging the
* various aspects of the value
*/
protected void engageValue() {
this.engageValueProperties();
this.engageValueLists();
}
protected void engageValueProperties() {
if (this.hasNoPropertyChangeListeners(DISPLAY_STRING_PROPERTY)) {
this.engageValueDisplayString();
}
if (this.hasNoPropertyChangeListeners(ICON_PROPERTY)) {
this.engageValueIcon();
}
if (this.hasNoPropertyChangeListeners(DIRTY_PROPERTY)) {
this.engageValueDirty();
}
if (this.hasNoPropertyChangeListeners(PROPERTIES_PAGE_TITLE_TEXT_PROPERTY)) {
this.engageValuePropertiesPageTitleText();
}
if (this.hasNoPropertyChangeListeners(PROPERTIES_PAGE_TITLE_ICON_PROPERTY)) {
this.engageValuePropertiesPageTitleIcon();
}
}
protected void engageValueLists() {
if (this.hasNoListChangeListeners(APPLICATION_PROBLEMS_LIST) && this.hasNoListChangeListeners(BRANCH_APPLICATION_PROBLEMS_LIST)) {
this.engageValueBranchProblems();
}
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
if (this.hasNoStateChangeListeners()) {
this.engageValueProperties();
}
super.addPropertyChangeListener(listener);
}
/**
* if a "state" change listener has not been previously added,
* check for property-specific listeners before engaging the
* various properties of the value
*/
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
if (this.hasNoStateChangeListeners()) {
if (this.hasNoPCLs(propertyName, DISPLAY_STRING_PROPERTY)) {
this.engageValueDisplayString();
}
if (this.hasNoPCLs(propertyName, ICON_PROPERTY)) {
this.engageValueIcon();
}
if (this.hasNoPCLs(propertyName, DIRTY_PROPERTY)) {
this.engageValueDirty();
}
if (this.hasNoPCLs(propertyName, PROPERTIES_PAGE_TITLE_TEXT_PROPERTY)) {
this.engageValuePropertiesPageTitleText();
}
if (this.hasNoPCLs(propertyName, PROPERTIES_PAGE_TITLE_ICON_PROPERTY)) {
this.engageValuePropertiesPageTitleIcon();
}
}
super.addPropertyChangeListener(propertyName, listener);
}
/**
* return whether the properties match and we have no
* listeners for the property
*/
protected boolean hasNoPCLs(String listenerPropertyName, String propertyName) {
return (listenerPropertyName == propertyName) &&
this.hasNoPropertyChangeListeners(propertyName);
}
public void addListChangeListener(ListChangeListener listener) {
if (this.hasNoStateChangeListeners()) {
this.engageValueLists();
}
super.addListChangeListener(listener);
}
/**
* if a "state" change listener has not been previously added,
* check for list-specific listeners before engaging the
* various lists of the value
*/
public void addListChangeListener(String listName, ListChangeListener listener) {
if (this.hasNoStateChangeListeners()) {
if (this.hasNoProblemListeners(listName)) {
this.engageValueBranchProblems();
}
}
super.addListChangeListener(listName, listener);
}
private boolean hasNoProblemListeners(String propertyName) {
if ((propertyName == APPLICATION_PROBLEMS_LIST) || (propertyName == BRANCH_APPLICATION_PROBLEMS_LIST)) {
return this.hasNoListChangeListeners(APPLICATION_PROBLEMS_LIST) &&
this.hasNoListChangeListeners(BRANCH_APPLICATION_PROBLEMS_LIST);
}
return false;
}
protected void engageValue(String[] propertyNames, PropertyChangeListener listener) {
this.engage(this.value, propertyNames, listener);
}
protected void engage(Model model, String[] propertyNames, PropertyChangeListener listener) {
for (int i = propertyNames.length; i-- > 0; ) {
model.addPropertyChangeListener(propertyNames[i], listener);
}
}
protected void engageValueDisplayString() {
this.engageValue(this.displayStringPropertyNames(), this.valueDisplayStringListener);
this.rebuildDisplayString();
}
/**
* Return the names of the value's properties that affect
* the node's label.
*/
protected String[] displayStringPropertyNames() {
return DEFAULT_DISPLAY_STRING_PROPERTY_NAMES;
}
protected void engageValueIcon() {
this.engageValue(this.iconPropertyNames(), this.valueIconListener);
this.rebuildIconBuilder();
this.rebuildIcon();
}
/**
* Return the names of the value's properties that affect
* the node's icon.
*/
protected String[] iconPropertyNames() {
return DEFAULT_ICON_PROPERTY_NAMES;
}
protected void engageValueDirty() {
this.engageValue(this.dirtyPropertyNames(), this.valueDirtyListener);
this.rebuildDirtyFlag();
}
/**
* Return the names of the value's properties that affect whether
* the node is "dirty".
*/
protected String[] dirtyPropertyNames() {
return DEFAULT_DIRTY_PROPERTY_NAMES;
}
protected void engageValuePropertiesPageTitleText() {
this.engageValue(this.propertiesPageTitleTextPropertyNames(), this.valuePropertiesPageTitleTextListener);
this.rebuildPropertiesPageTitleText();
}
/**
* Return the names of the value's properties that affect
* the node's properties page title text.
*/
protected String[] propertiesPageTitleTextPropertyNames() {
return this.displayStringPropertyNames();
}
protected void engageValuePropertiesPageTitleIcon() {
this.engageValue(this.propertiesPageTitleIconPropertyNames(), this.valuePropertiesPageTitleIconListener);
this.rebuildPropertiesPageTitleIconBuilder();
this.rebuildPropertiesPageTitleIcon();
}
/**
* Return the names of the value's properties that affect
* the node's properties page title icon.
*/
protected String[] propertiesPageTitleIconPropertyNames() {
return this.iconPropertyNames();
}
protected void engageValue(String[] listNames, ListChangeListener listener) {
this.engage(this.value, listNames, listener);
}
protected void engage(Model model, String[] listNames, ListChangeListener listener) {
for (int i = listNames.length; i-- > 0; ) {
model.addListChangeListener(listNames[i], listener);
}
}
/**
* Return the names of the value's lists that affect the
* node's branch problems.
*/
protected String[] branchProblemsListNames() {
return DEFAULT_BRANCH_PROBLEMS_LIST_NAMES;
}
protected void engageValueBranchProblems() {
this.engageValue(this.branchProblemsListNames(), this.valueBranchProblemsListener);
this.rebuildApplicationProblems();
this.rebuildBranchApplicationProblems();
}
/**
* when we get here we know there are no "state" change listeners;
* now check for aspect-specific listeners before disengaging the
* various aspects of the value
*/
protected void disengageValue() {
this.disengageValueLists();
this.disengageValueProperties();
}
protected void disengageValueLists() {
if (this.hasNoListChangeListeners(APPLICATION_PROBLEMS_LIST) && this.hasNoListChangeListeners(BRANCH_APPLICATION_PROBLEMS_LIST)) {
this.disengageValueBranchProblems();
}
}
protected void disengageValueProperties() {
if (this.hasNoPropertyChangeListeners(PROPERTIES_PAGE_TITLE_ICON_PROPERTY)) {
this.disengageValuePropertiesPageTitleIcon();
}
if (this.hasNoPropertyChangeListeners(PROPERTIES_PAGE_TITLE_TEXT_PROPERTY)) {
this.disengageValuePropertiesPageTitleText();
}
if (this.hasNoPropertyChangeListeners(DIRTY_PROPERTY)) {
this.disengageValueDirty();
}
if (this.hasNoPropertyChangeListeners(ICON_PROPERTY)) {
this.disengageValueIcon();
}
if (this.hasNoPropertyChangeListeners(DISPLAY_STRING_PROPERTY)) {
this.disengageValueDisplayString();
}
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
super.removePropertyChangeListener(listener);
if (this.hasNoStateChangeListeners()) {
this.disengageValueProperties();
}
}
/**
* if there are no more "state" change listeners remaining,
* check for property-specific listeners before disengaging the
* various properties of the value
*/
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
super.removePropertyChangeListener(propertyName, listener);
if (this.hasNoStateChangeListeners()) {
if (this.hasNoPCLs(propertyName, PROPERTIES_PAGE_TITLE_ICON_PROPERTY)) {
this.disengageValuePropertiesPageTitleIcon();
}
if (this.hasNoPCLs(propertyName, PROPERTIES_PAGE_TITLE_TEXT_PROPERTY)) {
this.disengageValuePropertiesPageTitleText();
}
if (this.hasNoPCLs(propertyName, DIRTY_PROPERTY)) {
this.disengageValueDirty();
}
if (this.hasNoPCLs(propertyName, ICON_PROPERTY)) {
this.disengageValueIcon();
}
if (this.hasNoPCLs(propertyName, DISPLAY_STRING_PROPERTY)) {
this.disengageValueDisplayString();
}
}
}
public void removeListChangeListener(ListChangeListener listener) {
super.removeListChangeListener(listener);
if (this.hasNoStateChangeListeners()) {
this.disengageValueLists();
}
}
/**
* if there are no more "state" change listeners remaining,
* check for list-specific listeners before disengaging the
* various lists of the value
*/
public void removeListChangeListener(String listName, ListChangeListener listener) {
super.removeListChangeListener(listName, listener);
if (this.hasNoStateChangeListeners()) {
if (this.hasNoProblemListeners(listName)) {
this.disengageValueBranchProblems();
}
}
}
protected void disengageValue(String[] propertyNames, PropertyChangeListener listener) {
this.disengage(this.value, propertyNames, listener);
}
protected void disengage(Model model, String[] propertyNames, PropertyChangeListener listener) {
for (int i = propertyNames.length; i-- > 0; ) {
model.removePropertyChangeListener(propertyNames[i], listener);
}
}
protected void disengageValue(String[] listNames, ListChangeListener listener) {
this.disengage(this.value, listNames, listener);
}
protected void disengage(Model model, String[] listNames, ListChangeListener listener) {
for (int i = listNames.length; i-- > 0; ) {
model.removeListChangeListener(listNames[i], listener);
}
}
protected void disengageValueBranchProblems() {
this.disengageValue(this.branchProblemsListNames(), this.valueBranchProblemsListener);
this.applicationProblems.clear();
this.branchApplicationProblems.clear();
}
protected void disengageValuePropertiesPageTitleIcon() {
this.disengageValue(this.propertiesPageTitleIconPropertyNames(), this.valuePropertiesPageTitleIconListener);
this.propertiesPageTitleIconBuilder = null;
this.propertiesPageTitleIcon = null;
}
protected void disengageValuePropertiesPageTitleText() {
this.disengageValue(this.propertiesPageTitleTextPropertyNames(), this.valuePropertiesPageTitleTextListener);
this.propertiesPageTitleText = null;
}
protected void disengageValueDirty() {
this.disengageValue(this.dirtyPropertyNames(), this.valueDirtyListener);
this.dirty = false;
}
protected void disengageValueIcon() {
this.disengageValue(this.iconPropertyNames(), this.valueIconListener);
this.iconBuilder = null;
this.icon = null;
}
protected void disengageValueDisplayString() {
this.disengageValue(this.displayStringPropertyNames(), this.valueDisplayStringListener);
this.displayString = null;
}
protected PropertyChangeListener getValueDisplayStringListener() {
return this.valueDisplayStringListener;
}
protected PropertyChangeListener getValueIconListener() {
return this.valueIconListener;
}
protected PropertyChangeListener getValueDirtyListener() {
return this.valueDirtyListener;
}
protected ListChangeListener getValueBranchProblemsListener() {
return this.valueBranchProblemsListener;
}
protected PropertyChangeListener getValuePropertiesPageTitleTextListener() {
return this.valuePropertiesPageTitleTextListener;
}
protected PropertyChangeListener getValuePropertiesPageTitleIconListener() {
return this.valuePropertiesPageTitleIconListener;
}
// ********** factories **********
protected final void rebuildDisplayString() {
this.displayString = this.buildDisplayString();
}
/**
* The default is to take a display string directly from the value.
*/
protected String buildDisplayString() {
return this.value.displayString();
}
protected final void rebuildIconBuilder() {
this.iconBuilder = this.buildIconBuilder();
}
/**
* The default is to have no icon and to indicate
* whether the node has any "branch" problems.
*/
protected IconBuilder buildIconBuilder() {
return new CompositeIconBuilder(
this.buildBaseIconBuilder(),
this.valueHasBranchProblems(),
PROBLEM_ICON,
-21, // gap - overlay the "leading" edge of the unadorned icon
SwingConstants.HORIZONTAL, // orientation
SwingConstants.BOTTOM, // alignment
null // description
);
}
/**
* The default is to have no icon and to indicate
* whether the node has any "branch" problems.
*/
protected IconBuilder buildBaseIconBuilder() {
return new SimpleIconBuilder(this.resourceRepository().getIcon(this.buildIconKey()));
}
/**
* The default is to have no icon.
*/
protected String buildIconKey() {
return null;
}
protected final void rebuildIcon() {
this.icon = this.buildIcon();
}
/**
* The default is to have no icon and to indicate
* whether the node has any "branch" problems.
*/
protected final Icon buildIcon() {
return this.iconBuilder.buildIcon();
}
protected final void rebuildDirtyFlag() {
this.dirty = this.buildDirtyFlag();
}
/**
* The default is to use the value's dirty branch setting.
*/
protected boolean buildDirtyFlag() {
return this.value.isDirtyBranch();
}
protected final void rebuildPropertiesPageTitleText() {
this.propertiesPageTitleText = this.buildPropertiesPageTitleText();
}
/**
* The default is to use the display string.
*/
protected String buildPropertiesPageTitleText() {
return this.buildDisplayString();
}
protected final void rebuildPropertiesPageTitleIconBuilder() {
this.propertiesPageTitleIconBuilder = this.buildPropertiesPageTitleIconBuilder();
}
/**
* The default is to use the normal icon builder.
*/
protected IconBuilder buildPropertiesPageTitleIconBuilder() {
return this.buildIconBuilder();
}
protected final void rebuildPropertiesPageTitleIcon() {
this.propertiesPageTitleIcon = this.buildPropertiesPageTitleIcon();
}
/**
* The default builder will build the normal icon.
*/
protected final Icon buildPropertiesPageTitleIcon() {
return this.propertiesPageTitleIconBuilder.buildIcon();
}
/**
* Rebuild the list of "exclusive" application problems based on the value's
* branch problems.
*/
protected final void rebuildApplicationProblems() {
this.applicationProblems.clear();
this.addExclusiveApplicationProblemsTo(this.applicationProblems);
}
protected void addExclusiveApplicationProblemsTo(List list) {
for (ListIterator stream = this.value.branchProblems(); stream.hasNext(); ) {
Problem problem = (Problem) stream.next();
if (this.ownsExclusively(problem)) {
list.add(this.buildApplicationProblem(problem));
}
}
}
/**
* Return whether the application node is the "exclusive owner" of
* the specified problem; i.e. the problem belongs to the node's branch
* but not to any children application nodes either.
* NB: There is NOT a 1:1 correspondence between application nodes
* and model nodes. There are many more model nodes than there are
* application nodes; but the application node containment hierarchies
* is roughly parallel to the model node containment hierarchy (with
* the exception of MWClass, which requires review until we have a
* visible Class Repository).
*/
private boolean ownsExclusively(Problem problem) {
return this.value.containsBranchProblem(problem) && ( ! this.childrenContain(problem));
}
/**
* Return whether the application node's children "contain"
* the specified problem.
*/
private boolean childrenContain(Problem problem) {
for (Iterator stream = this.children(); stream.hasNext(); ) {
if (((ApplicationProblemContainer) stream.next()).containsBranchApplicationProblemFor(problem)) {
return true;
}
}
return false;
}
/**
* Return an application problem for the specified model problem.
*/
ApplicationProblem buildApplicationProblem(Problem problem) {
return new DefaultApplicationProblem(
this,
problem.getMessageKey(),
this.resourceRepository().getString(problem.getMessageKey(), problem.getMessageArguments())
);
}
/**
* Rebuild the list of "branch" application problems based on the value's
* branch problems.
*/
protected final void rebuildBranchApplicationProblems() {
this.branchApplicationProblems.clear();
this.addBranchApplicationProblemsTo(this.branchApplicationProblems);
}
// ********** queries **********
public Iterator children() {
return (Iterator) this.getChildrenModel().getValue();
}
protected boolean valueHasBranchProblems() {
return this.value.hasBranchProblems();
}
public NodeManager nodeManager() {
return this.getApplicationContext().getNodeManager();
}
protected ResourceRepository resourceRepository() {
return this.getApplicationContext().getResourceRepository();
}
protected Preferences preferences() {
return this.getApplicationContext().getPreferences();
}
// ********** behavior **********
/**
* Select the descendant node with the specified value.
*/
public void selectDescendantNodeForValue(Node node, NavigatorSelectionModel nsm) {
nsm.setSelectedNode(this.descendantNodeForValue(node));
}
/**
* replace the cached value and notify listeners
*/
protected void displayStringChanged() {
Object old = this.displayString;
this.rebuildDisplayString();
this.firePropertyChanged(DISPLAY_STRING_PROPERTY, old, this.displayString);
if (this.attributeValueHasChanged(old, this.displayString)) {
this.fireStateChanged();
}
}
/**
* replace the cached value and notify listeners
*/
protected void iconChanged() {
Object oldBuilder = this.iconBuilder;
this.rebuildIconBuilder();
if ( ! this.iconBuilder.equals(oldBuilder)) {
Object oldIcon = this.icon;
this.rebuildIcon();
this.firePropertyChanged(ICON_PROPERTY, oldIcon, this.icon);
this.fireStateChanged();
}
}
/**
* replace the cached value and notify listeners
*/
protected void dirtyChanged() {
boolean old = this.dirty;
this.rebuildDirtyFlag();
this.firePropertyChanged(DIRTY_PROPERTY, old, this.dirty);
if (this.dirty != old) {
this.fireStateChanged();
}
}
/**
* replace the cached value and notify listeners
*/
protected void propertiesPageTitleTextChanged() {
Object old = this.propertiesPageTitleText;
this.rebuildPropertiesPageTitleText();
this.firePropertyChanged(PROPERTIES_PAGE_TITLE_TEXT_PROPERTY, old, this.propertiesPageTitleText);
if (this.attributeValueHasChanged(old, this.propertiesPageTitleText)) {
this.fireStateChanged();
}
}
/**
* replace the cached value and notify listeners
*/
protected void propertiesPageTitleIconChanged() {
Object oldBuilder = this.propertiesPageTitleIconBuilder;
this.rebuildPropertiesPageTitleIconBuilder();
if ( ! this.propertiesPageTitleIconBuilder.equals(oldBuilder)) {
Object oldIcon = this.propertiesPageTitleIcon;
this.rebuildPropertiesPageTitleIcon();
this.firePropertyChanged(PROPERTIES_PAGE_TITLE_ICON_PROPERTY, oldIcon, this.propertiesPageTitleIcon);
this.fireStateChanged();
}
}
/**
* rebuild the cached values and notify listeners;
* completely rebuild the lists (we should only get
* here when the list of problems has *actually* changed)
*/
protected void branchProblemsChanged() {
this.rebuildApplicationProblems();
this.fireListChanged(APPLICATION_PROBLEMS_LIST);
this.rebuildBranchApplicationProblems();
this.fireListChanged(BRANCH_APPLICATION_PROBLEMS_LIST);
}
// ********** ValueModel implementation **********
public Object getValue() {
return this.value;
}
// ********** TreeNodeValueModel implementation **********
public TreeNodeValueModel getParent() {
return this.parent;
}
public ListValueModel getChildrenModel() {
return NullListValueModel.instance();
}
// ********** ApplicationNode implementation **********
public ApplicationContext getApplicationContext() {
return this.context;
}
public Plugin getPlugin() {
return this.plugin;
}
/**
* The project root nodes are the direct children of
* the "real" root node.
*/
public ApplicationNode getProjectRoot() {
if (this.getParent() == this.nodeManager().getRootNode()) {
return this;
}
return ((ApplicationNode) this.getParent()).getProjectRoot();
}
public final boolean isDirty() {
return this.dirty;
}
public String helpTopicID() {
return "default";
}
public boolean save(File mostRecentSaveDirectory, WorkbenchContext workbenchContext) {
return this.getProjectRoot().save(mostRecentSaveDirectory, workbenchContext);
}
public boolean saveAs(File mostRecentSaveDirectory, WorkbenchContext workbenchContext) {
return this.getProjectRoot().saveAs(mostRecentSaveDirectory, workbenchContext);
}
public File saveFile() {
return this.getProjectRoot().saveFile();
}
public void addValuePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
this.value.addPropertyChangeListener(propertyName, listener);
}
public void removeValuePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
this.value.removePropertyChangeListener(propertyName, listener);
}
public final ApplicationNode descendantNodeForValue(Node node) {
if (this.value == node) {
return this;
}
for (Iterator stream = this.children(); stream.hasNext(); ) {
ApplicationNode appNode = ((ApplicationNode) stream.next()).descendantNodeForValue(node); // recurse
if (appNode != null) {
return appNode;
}
}
return null;
}
// ********** ApplicationProblemContainer implementation **********
public final ListIterator applicationProblems() {
return this.applicationProblems.listIterator();
}
public final int applicationProblemsSize() {
return this.applicationProblems.size();
}
public final void addApplicationProblemsTo(List list) {
list.addAll(this.applicationProblems);
}
public final ListIterator branchApplicationProblems() {
return this.branchApplicationProblems.listIterator();
}
public final int branchApplicationProblemsSize() {
return this.branchApplicationProblems.size();
}
public void addBranchApplicationProblemsTo(List list) {
this.addApplicationProblemsTo(list);
for (Iterator stream = this.children(); stream.hasNext(); ) {
((ApplicationProblemContainer) stream.next()).addBranchApplicationProblemsTo(list); // recurse
}
}
public boolean containsBranchApplicationProblemFor(Problem problem) {
return this.value.containsBranchProblem(problem);
}
/**
* Print the node's problem title, if appropriate; print the node's problems;
* then print the node's children's problems, recursively.
*/
public void printBranchApplicationProblemsOn(IndentingPrintWriter writer) {
if (this.branchApplicationProblems.size() == 0) {
return;
}
this.printBranchApplicationProblemsHeaderOn(writer);
writer.println();
writer.indent();
for (Iterator stream = this.applicationProblems(); stream.hasNext(); ) {
((ApplicationProblem) stream.next()).printOn(writer);
writer.println();
}
for (Iterator stream = this.children(); stream.hasNext(); ) {
((ApplicationProblemContainer) stream.next()).printBranchApplicationProblemsOn(writer); // recurse
}
writer.undent();
}
/**
* Write a "problem header", if appropriate.
*/
protected void printBranchApplicationProblemsHeaderOn(IndentingPrintWriter writer) {
writer.print(this.displayString());
}
// ********** EditorNode implementation **********
public final String propertiesPageTitleText() {
return this.propertiesPageTitleText;
}
public final Icon propertiesPageTitleIcon() {
return this.propertiesPageTitleIcon;
}
// ********** Displayable implementation **********
public final String displayString() {
return this.displayString;
}
public final Icon icon() {
return this.icon;
}
// ********** Comparable implementation **********
public int compareTo(Object o) {
// use the Comparator defined in Displayable, which compares displayStrings
return DEFAULT_COMPARATOR.compare(this, o);
}
// ********** AccessibleNode implementation **********
/**
* Returns a string that can add more description to the rendered object when
* the text is not sufficient, if <code>null</code> is returned, then the
* text is used as the accessible text.
*/
public String accessibleName() {
return this.resourceRepository().getString(this.accessibleNameKey(), this.displayString());
}
protected String accessibleNameKey() {
return "ACCESSIBLE_NODE";
}
}