/*******************************************************************************
* 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.descriptor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.persistence.tools.workbench.framework.action.FrameworkAction;
import org.eclipse.persistence.tools.workbench.framework.app.ApplicationProblemContainer;
import org.eclipse.persistence.tools.workbench.framework.app.GroupContainerDescription;
import org.eclipse.persistence.tools.workbench.framework.app.MenuGroupDescription;
import org.eclipse.persistence.tools.workbench.framework.app.RootMenuDescription;
import org.eclipse.persistence.tools.workbench.framework.app.ToolBarDescription;
import org.eclipse.persistence.tools.workbench.framework.context.WorkbenchContext;
import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.MWDescriptor;
import org.eclipse.persistence.tools.workbench.mappingsplugin.AutomappableNode;
import org.eclipse.persistence.tools.workbench.mappingsplugin.RemovableNode;
import org.eclipse.persistence.tools.workbench.mappingsplugin.ui.common.MappingsApplicationNode;
import org.eclipse.persistence.tools.workbench.mappingsplugin.ui.project.ProjectNode;
import org.eclipse.persistence.tools.workbench.uitools.app.ItemPropertyListValueModelAdapter;
import org.eclipse.persistence.tools.workbench.uitools.app.ListValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.SimpleCollectionValueModel;
import org.eclipse.persistence.tools.workbench.uitools.app.SortedListValueModelAdapter;
import org.eclipse.persistence.tools.workbench.utility.CollectionTools;
import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator;
import org.eclipse.persistence.tools.workbench.utility.node.Problem;
/**
* This is not a "typical" node in that it is a "virtual" node that does
* not have a corresponding object in the model. Descriptor Package
* Nodes are created and destroyed by the Project Node as it monitors
* the adding, removing, and renaming of Descriptors in a Project.
*/
public abstract class DescriptorPackageNode
extends MappingsApplicationNode
implements ProjectNode.Child, UnmappablePackageNode, AutomappableNode, RemovableNode
{
/**
* We don't have a corresponding object in the model,
* we just have a name.
*/
private String name;
/**
* Clients must supply us with a descriptor node builder
* that will build the appropriate descriptor nodes for us.
*/
private DescriptorNodeBuilder descriptorNodeBuilder;
/**
* The descriptor nodes are added and removed by our parent
* project node, which is monitoring the project's 'descriptors'
* collection.
*/
private SimpleCollectionValueModel descriptorNodesHolder;
/**
* This is a wrapper around the descriptorNodesHolder that
* is sorted and re-sorts whenever a descriptor's name changes.
*/
private ListValueModel childrenModel;
/**
* We track whether a descriptor node has been removed.
* If it has, we mark the package node dirty, even if none of
* the remaining descriptor nodes are dirty.
*/
private boolean descriptorNodeHasBeenRemoved;
/**
* When the project node has been marked clean, we can
* reset the descriptorNodeHasBeenRemoved flag.
*/
private PropertyChangeListener projectNodeDirtyFlagListener;
// ********** constructor/initialization ************
protected DescriptorPackageNode(String name, ProjectNode parent, DescriptorNodeBuilder descriptorNodeBuilder) {
// no model!
super(null, parent, parent.getPlugin(), parent.getApplicationContext());
this.name = name;
this.descriptorNodeBuilder = descriptorNodeBuilder;
}
protected void initialize() {
super.initialize();
this.descriptorNodesHolder = new SimpleCollectionValueModel();
this.childrenModel = this.buildChildrenModel();
this.descriptorNodeHasBeenRemoved = false;
this.projectNodeDirtyFlagListener = this.buildProjectNodeDirtyFlagListener();
}
/**
* keep the descriptor nodes sorted
*/
protected ListValueModel buildChildrenModel() {
return new SortedListValueModelAdapter(this.buildDescriptorsDisplayStringAdapter());
}
/**
* the display string (name) of each descriptor node can change
* and trigger a re-sort of the list
*/
protected ListValueModel buildDescriptorsDisplayStringAdapter() {
return new ItemPropertyListValueModelAdapter(this.descriptorNodesHolder, DISPLAY_STRING_PROPERTY);
}
protected PropertyChangeListener buildProjectNodeDirtyFlagListener() {
return new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
DescriptorPackageNode.this.projectNodeDirtyFlagChanged(e);
}
public String toString() {
return "project node dirty flag listener";
}
};
}
// ********** AbstractTreeNodeValueModel overrides **********
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o.getClass() != this.getClass()) {
return false;
}
DescriptorPackageNode other = (DescriptorPackageNode) o;
return this.getParent().equals(other.getParent()) &&
this.name.equals(other.name);
}
public int hashCode() {
return this.name.hashCode();
}
public void toString(StringBuffer sb) {
sb.append(this.name);
}
// ********** AbstractApplicationNode overrides **********
/**
* this node does not have a value; do not call this method
* willy-nilly on a collection of heterogeneous nodes ~bjv
*/
public Object getValue() {
throw new UnsupportedOperationException();
}
public ListValueModel getChildrenModel() {
return this.childrenModel;
}
public String helpTopicID() {
return "package";
}
/**
* the name will temporarily be null during intialization
*/
protected String buildDisplayString() {
return (this.name == null) ? "" : (this.name.length() == 0) ? this.resourceRepository().getString("DEFAULT_PACKAGE") : this.name;
}
protected String buildIconKey() {
return "package";
}
/**
* delegate to the descriptor nodes; if none of the descriptor nodes is dirty,
* check whether a descriptor node was removed earlier
*/
protected boolean buildDirtyFlag() {
for (Iterator stream = this.descriptors(); stream.hasNext(); ) {
if (((MWDescriptor) stream.next()).isDirtyBranch()) {
return true;
}
}
return this.descriptorNodeHasBeenRemoved;
}
/**
* delegate to the descriptors
*/
protected boolean valueHasBranchProblems() {
for (Iterator stream = this.descriptors(); stream.hasNext(); ) {
if (((MWDescriptor) stream.next()).hasBranchProblems()) {
return true;
}
}
return false;
}
/**
* packages don't have "exclusive" application problems,
* they only have "branch" application problems
*/
protected void addExclusiveApplicationProblemsTo(List list) {
// no problems
}
/**
* delegate to the descriptor nodes
*/
public boolean containsBranchApplicationProblemFor(Problem problem) {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
if (((DescriptorNode) stream.next()).containsBranchApplicationProblemFor(problem)) {
return true;
}
}
return false;
}
/**
* we don't have a value, so engage the descriptor nodes
*/
protected void engageValueDisplayString() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.engageDescriptorNodeDisplayString((DescriptorNode) stream.next());
}
this.rebuildDisplayString();
}
protected void engageDescriptorNodeDisplayString(DescriptorNode descriptorNode) {
descriptorNode.addPropertyChangeListener(DISPLAY_STRING_PROPERTY, this.getValueDisplayStringListener());
}
/**
* we don't have a value, so engage the descriptor nodes
*/
protected void engageValueIcon() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.engageDescriptorNodeIcon((DescriptorNode) stream.next());
}
this.rebuildIconBuilder();
this.rebuildIcon();
}
protected void engageDescriptorNodeIcon(DescriptorNode descriptorNode) {
descriptorNode.addPropertyChangeListener(ICON_PROPERTY, this.getValueIconListener());
}
/**
* we don't have a value, so engage the descriptor nodes;
* in certain situations, the project node being marked clean
* can cause our dirty flag to be cleared also
*/
protected void engageValueDirty() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.engageDescriptorNodeDirty((DescriptorNode) stream.next());
}
this.getProjectNode().addPropertyChangeListener(DIRTY_PROPERTY, this.projectNodeDirtyFlagListener);
this.rebuildDirtyFlag();
}
protected void engageDescriptorNodeDirty(DescriptorNode descriptorNode) {
descriptorNode.addPropertyChangeListener(DIRTY_PROPERTY, this.getValueDirtyListener());
}
/**
* we don't have a value, so engage the descriptor nodes
*/
protected void engageValuePropertiesPageTitleIcon() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.engageDescriptorNodePropertiesPageTitleIcon((DescriptorNode) stream.next());
}
this.rebuildPropertiesPageTitleIconBuilder();
this.rebuildPropertiesPageTitleIcon();
}
protected void engageDescriptorNodePropertiesPageTitleIcon(DescriptorNode descriptorNode) {
descriptorNode.addPropertyChangeListener(PROPERTIES_PAGE_TITLE_ICON_PROPERTY, this.getValuePropertiesPageTitleIconListener());
}
/**
* we don't have a value, so engage the descriptor nodes
*/
protected void engageValuePropertiesPageTitleText() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.engageDescriptorNodePropertiesPageTitleText((DescriptorNode) stream.next());
}
this.rebuildPropertiesPageTitleText();
}
protected void engageDescriptorNodePropertiesPageTitleText(DescriptorNode descriptorNode) {
descriptorNode.addPropertyChangeListener(PROPERTIES_PAGE_TITLE_TEXT_PROPERTY, this.getValuePropertiesPageTitleTextListener());
}
/**
* we don't have a value, so engage the descriptor nodes;
* Listen to the node instead of the model. The problems are built based
* on the nodes, so it is possible that the package node could be informed
* before the descriptor node. This would cause the problems to be out of synch.
*/
protected void engageValueBranchProblems() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.engageDescriptorNodeBranchProblems((DescriptorNode) stream.next());
}
this.rebuildApplicationProblems();
this.rebuildBranchApplicationProblems();
}
protected void engageDescriptorNodeBranchProblems(DescriptorNode descriptorNode) {
descriptorNode.addListChangeListener(ApplicationProblemContainer.BRANCH_APPLICATION_PROBLEMS_LIST, this.getValueBranchProblemsListener());
}
/**
* we don't have a value, so disengage the descriptor nodes
*/
protected void disengageValueDisplayString() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.disengageDescriptorNodeDisplayString((DescriptorNode) stream.next());
}
}
protected void disengageDescriptorNodeDisplayString(DescriptorNode descriptorNode) {
descriptorNode.removePropertyChangeListener(DISPLAY_STRING_PROPERTY, this.getValueDisplayStringListener());
}
/**
* we don't have a value, so disengage the descriptor nodes
*/
protected void disengageValueIcon() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.disengageDescriptorNodeIcon((DescriptorNode) stream.next());
}
}
protected void disengageDescriptorNodeIcon(DescriptorNode descriptorNode) {
descriptorNode.removePropertyChangeListener(ICON_PROPERTY, this.getValueIconListener());
}
/**
* we don't have a value, so disengage the descriptor nodes
*/
protected void disengageValueDirty() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.disengageDescriptorNodeDirty((DescriptorNode) stream.next());
}
this.getProjectNode().removePropertyChangeListener(DIRTY_PROPERTY, this.projectNodeDirtyFlagListener);
}
protected void disengageDescriptorNodeDirty(DescriptorNode descriptorNode) {
descriptorNode.removePropertyChangeListener(DIRTY_PROPERTY, this.getValueDirtyListener());
}
/**
* we don't have a value, so disengage the descriptor nodes
*/
protected void disengageValuePropertiesPageTitleIcon() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.disengageDescriptorNodePropertiesPageTitleIcon((DescriptorNode) stream.next());
}
}
protected void disengageDescriptorNodePropertiesPageTitleIcon(DescriptorNode descriptorNode) {
descriptorNode.removePropertyChangeListener(PROPERTIES_PAGE_TITLE_ICON_PROPERTY, this.getValuePropertiesPageTitleIconListener());
}
/**
* we don't have a value, so disengage the descriptor nodes
*/
protected void disengageValuePropertiesPageTitleText() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.disengageDescriptorNodePropertiesPageTitleText((DescriptorNode) stream.next());
}
}
protected void disengageDescriptorNodePropertiesPageTitleText(DescriptorNode descriptorNode) {
descriptorNode.removePropertyChangeListener(PROPERTIES_PAGE_TITLE_TEXT_PROPERTY, this.getValuePropertiesPageTitleTextListener());
}
/**
* we don't have a value, so disengage the descriptor nodes
*/
protected void disengageValueBranchProblems() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
this.disengageDescriptorNodeBranchProblems((DescriptorNode) stream.next());
}
}
protected void disengageDescriptorNodeBranchProblems(DescriptorNode descriptorNode) {
descriptorNode.removeListChangeListener(ApplicationProblemContainer.BRANCH_APPLICATION_PROBLEMS_LIST, this.getValueBranchProblemsListener());
}
public void addValuePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
// do nothing, no model to listen to
}
public void removeValuePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
// do nothing, no model to unlisten :) to
}
/**
* 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() {
if ((this.name == null) || (this.name.length() == 0)) {
return this.resourceRepository().getString("DEFAULT_PACKAGE");
}
return this.resourceRepository().getString("ACCESSIBLE_PACKAGE_NODE", this.displayString());
}
// ********** MappingsApplicationNode overrides **********
protected Class propertiesPageClass() {
return DescriptorPackagePropertiesPage.class;
}
public String candidatePackageName() {
return this.name;
}
public boolean isAutoMappable() {
return true;
}
public void addDescriptorsTo(Collection descriptors) {
CollectionTools.addAll(descriptors, this.descriptors());
}
public Iterator descriptors() {
return new TransformationIterator(this.descriptorNodes()) {
protected Object transform(Object next) {
return ((DescriptorNode) next).getDescriptor();
}
};
}
// ********** ApplicationNode implementation **********
public GroupContainerDescription buildMenuDescription(WorkbenchContext workbenchContext) {
RootMenuDescription desc = new RootMenuDescription();
this.addToMenuDescription(desc, buildLocalWorkbenchContext(workbenchContext));
return desc;
}
protected abstract void addToMenuDescription(GroupContainerDescription menuDescription, WorkbenchContext context);
protected MenuGroupDescription buildClassActionGroup(WorkbenchContext context) {
MenuGroupDescription classActionGroup = new MenuGroupDescription();
classActionGroup.add(this.getMappingsPlugin().getRefreshClassesAction(context));
classActionGroup.add(this.getMappingsPlugin().getAddOrRefreshClassesAction(context));
classActionGroup.add(this.getMappingsPlugin().getCreateNewClassAction(context));
return classActionGroup;
}
protected MenuGroupDescription buildRemoveActionGroup(WorkbenchContext context) {
MenuGroupDescription removeActionGroup = new MenuGroupDescription();
removeActionGroup.add(this.getMappingsPlugin().getRemoveAction(context));
removeActionGroup.add(this.getRenamePackageAction(context));
return removeActionGroup;
}
protected MenuGroupDescription buildUnmapActionGroup(WorkbenchContext context) {
MenuGroupDescription unMapGroup = new MenuGroupDescription();
unMapGroup.add(this.getUnmapAllDescriptorsInPackageAction(context));
return unMapGroup;
}
protected MenuGroupDescription buildExportJavaSourceActionGroup(WorkbenchContext context) {
MenuGroupDescription exportJavaSourceGroup = new MenuGroupDescription();
exportJavaSourceGroup.add(this.getMappingsPlugin().getExportSpecificDescriptorModelJavaSourceAction(context));
return exportJavaSourceGroup;
}
public GroupContainerDescription buildToolBarDescription(WorkbenchContext workbenchContext) {
return new ToolBarDescription();
}
private FrameworkAction getUnmapAllDescriptorsInPackageAction(WorkbenchContext context) {
return new UnmapAllDescriptorsInPackageAction(context);
}
private FrameworkAction getRenamePackageAction(WorkbenchContext context) {
return new RenamePackageAction(context);
}
// *********** ProjectNode.Child implementation ***********
/**
* packages go first under the project
*/
public int getProjectNodeChildPriority() {
return 0;
}
// ********** AutomappableNode implementation **********
public String getAutomapSuccessfulStringKey() {
return "AUTOMAP_PACKAGE_SUCCESSFUL";
}
// ********** UnmappablePackageNode implementation **********
public void unmapEntirePackage() {
this.unmap();
}
public void unmap() {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
((DescriptorNode) stream.next()).unmap();
}
}
// ********** RemovableNode implementation **********
public void remove() {
for (Iterator stream = CollectionTools.collection(this.descriptorNodes()).iterator(); stream.hasNext(); ) {
((DescriptorNode) stream.next()).remove();
}
}
public String getName() {
return this.name;
}
// ********** accessors **********
public Iterator descriptorNodes() {
return (Iterator) this.descriptorNodesHolder.getValue();
}
public int descriptorNodesSize() {
return this.descriptorNodesHolder.size();
}
private void addDescriptorNode(DescriptorNode descriptorNode) {
this.descriptorNodesHolder.addItem(descriptorNode);
}
private void removeDescriptorNode(DescriptorNode descriptorNode) {
this.descriptorNodesHolder.removeItem(descriptorNode);
}
public DescriptorNode descriptorNodeFor(MWDescriptor descriptor) {
for (Iterator stream = this.descriptorNodes(); stream.hasNext(); ) {
DescriptorNode node = (DescriptorNode) stream.next();
if (node.getDescriptor() == descriptor) {
return node;
}
}
return null;
}
public void addDescriptorNodeFor(MWDescriptor descriptor) {
DescriptorNode descriptorNode = this.descriptorNodeBuilder.buildDescriptorNode(descriptor, this);
this.addDescriptorNode(descriptorNode);
boolean hasAnyStateChangeListeners = this.hasAnyStateChangeListeners();
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(DISPLAY_STRING_PROPERTY)) {
this.engageDescriptorNodeDisplayString(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(ICON_PROPERTY)) {
this.engageDescriptorNodeIcon(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(DIRTY_PROPERTY)) {
this.engageDescriptorNodeDirty(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(PROPERTIES_PAGE_TITLE_ICON_PROPERTY)) {
this.engageDescriptorNodePropertiesPageTitleIcon(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(PROPERTIES_PAGE_TITLE_TEXT_PROPERTY)) {
this.engageDescriptorNodePropertiesPageTitleText(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyListChangeListeners(BRANCH_APPLICATION_PROBLEMS_LIST)) {
this.engageDescriptorNodeBranchProblems(descriptorNode);
}
this.iconChanged();
this.dirtyChanged();
this.propertiesPageTitleIconChanged();
this.propertiesPageTitleTextChanged();
this.branchProblemsChanged();
}
public void removeDescriptorNodeFor(MWDescriptor descriptor) {
DescriptorNode descriptorNode = this.descriptorNodeFor(descriptor);
boolean hasAnyStateChangeListeners = this.hasAnyStateChangeListeners();
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(DISPLAY_STRING_PROPERTY)) {
this.disengageDescriptorNodeDisplayString(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(ICON_PROPERTY)) {
this.disengageDescriptorNodeIcon(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(DIRTY_PROPERTY)) {
this.disengageDescriptorNodeDirty(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(PROPERTIES_PAGE_TITLE_ICON_PROPERTY)) {
this.disengageDescriptorNodePropertiesPageTitleIcon(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyPropertyChangeListeners(PROPERTIES_PAGE_TITLE_TEXT_PROPERTY)) {
this.disengageDescriptorNodePropertiesPageTitleText(descriptorNode);
}
if (hasAnyStateChangeListeners || this.hasAnyListChangeListeners(BRANCH_APPLICATION_PROBLEMS_LIST)) {
this.disengageDescriptorNodeBranchProblems(descriptorNode);
}
this.removeDescriptorNode(descriptorNode);
this.descriptorNodeHasBeenRemoved = true;
this.iconChanged();
this.dirtyChanged();
this.propertiesPageTitleIconChanged();
this.propertiesPageTitleTextChanged();
this.branchProblemsChanged();
}
/**
* If the project has been saved and the project node marked clean;
* reset the flag indicating whether a descriptor node has been removed.
*/
void projectNodeDirtyFlagChanged(PropertyChangeEvent e) {
boolean oldDirtyFlagValue = ((Boolean) e.getOldValue()).booleanValue();
boolean newDirtyFlagValue = ((Boolean) e.getNewValue()).booleanValue();
if ((oldDirtyFlagValue == true) && (newDirtyFlagValue == false)) {
// dirty => clean
this.descriptorNodeHasBeenRemoved = false;
this.dirtyChanged();
}
}
// ********** nested interface **********
/**
* This allows a descriptor package node to be configured to
* build different child descriptor nodes, depending on the client
* (which is typically a project node).
*/
public interface DescriptorNodeBuilder {
/**
* Build and return a node for the specified descriptor.
*/
DescriptorNode buildDescriptorNode(MWDescriptor descriptor, DescriptorPackageNode descriptorPackageNode);
}
}