/******************************************************************************* * 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.lang.reflect.Modifier; import java.util.Iterator; import org.eclipse.persistence.tools.workbench.framework.action.FrameworkAction; import org.eclipse.persistence.tools.workbench.framework.app.GroupContainerDescription; import org.eclipse.persistence.tools.workbench.framework.app.MenuDescription; import org.eclipse.persistence.tools.workbench.framework.app.MenuGroupDescription; import org.eclipse.persistence.tools.workbench.framework.app.NavigatorSelectionModel; 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.mappingsmodel.descriptor.MWMappingDescriptor; import org.eclipse.persistence.tools.workbench.mappingsmodel.descriptor.MWTransactionalPolicy; import org.eclipse.persistence.tools.workbench.mappingsmodel.mapping.MWMapping; import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClass; import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWClassAttribute; import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWMethod; import org.eclipse.persistence.tools.workbench.mappingsmodel.meta.MWModifiable; import org.eclipse.persistence.tools.workbench.mappingsplugin.AutomapAction; import org.eclipse.persistence.tools.workbench.mappingsplugin.ui.mapping.MappingNode; import org.eclipse.persistence.tools.workbench.uitools.app.CollectionValueModel; import org.eclipse.persistence.tools.workbench.uitools.app.CompositeCollectionValueModel; 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.Transformer; import org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeEvent; import org.eclipse.persistence.tools.workbench.utility.events.CollectionChangeListener; import org.eclipse.persistence.tools.workbench.utility.iterators.FilteringIterator; import org.eclipse.persistence.tools.workbench.utility.iterators.TransformationIterator; public abstract class MappingDescriptorNode extends DescriptorNode { /** * this holds both mapped and unmapped nodes */ private ListValueModel childrenModel; /** * we control the mapping nodes directly by listening to the * descriptor's 'mappings' collection */ private SimpleCollectionValueModel mappingNodesHolder; /** * we control the unmapped nodes directly by listening to the * 'inherited attributes', 'attributes', and 'EJB 2.0 attributes' collections */ private SimpleCollectionValueModel unmappedMappingNodesHolder; /** * this listens for mappings being added to or removed from * the descriptor */ private CollectionChangeListener mappingsListener; /** * this listens for attributes being added or removed * (to or from 3 different collections...) */ private CollectionChangeListener attributesListener; /** * this listens for an attribute's modifier being set * to static or final */ private PropertyChangeListener attributeModifierListener; /** * this listens for the stupid "unknown primary key thing" */ private PropertyChangeListener unknownPrimaryKeyListener; // ********** constructor/initialization ********** protected MappingDescriptorNode(MWDescriptor descriptor, DescriptorPackageNode parentNode) { super(descriptor, parentNode); } protected void initialize() { super.initialize(); this.mappingsListener = this.buildMappingsListener(); this.attributesListener = this.buildAttributesListener(); this.attributeModifierListener = this.buildAttributeModifierListener(); this.unknownPrimaryKeyListener = this.buildUnknownPrimaryKeyListener(); this.mappingNodesHolder = new SimpleCollectionValueModel(); this.unmappedMappingNodesHolder = new SimpleCollectionValueModel(); this.childrenModel = this.buildChildrenModel(); } private CollectionChangeListener buildMappingsListener() { return new CollectionChangeListener() { public void itemsAdded(CollectionChangeEvent e) { MappingDescriptorNode.this.mappingsAdded(e); } public void itemsRemoved(CollectionChangeEvent e) { MappingDescriptorNode.this.mappingsRemoved(e); } public void collectionChanged(CollectionChangeEvent e) { MappingDescriptorNode.this.mappingsChanged(e); } public String toString() { return "mappings listener"; } }; } private CollectionChangeListener buildAttributesListener() { return new CollectionChangeListener() { public void itemsAdded(CollectionChangeEvent e) { MappingDescriptorNode.this.attributesAdded(e); } public void itemsRemoved(CollectionChangeEvent e) { MappingDescriptorNode.this.attributesRemoved(e); } public void collectionChanged(CollectionChangeEvent e) { // since we listen to the model directly, and not through an // adapter, this "event" should never happen ~bjv throw new UnsupportedOperationException(); } public String toString() { return "attributes listener"; } }; } private PropertyChangeListener buildAttributeModifierListener() { return new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { MappingDescriptorNode.this.attributeModifierChanged(e); } public String toString() { return "attribute modifier listener"; } }; } private PropertyChangeListener buildUnknownPrimaryKeyListener() { return new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { MappingDescriptorNode.this.unknownPrimaryKeyChanged(e); } public String toString() { return "unknown primary key listener"; } }; } protected ListValueModel buildChildrenModel() { return new SortedListValueModelAdapter(this.buildDisplayStringAdapter()); } /** * the display string (name) of each mapping node can change */ protected ListValueModel buildDisplayStringAdapter() { return new ItemPropertyListValueModelAdapter(this.buildUnsortedChildrenModel(), DISPLAY_STRING_PROPERTY); } private CollectionValueModel buildUnsortedChildrenModel() { CollectionValueModel container = new SimpleCollectionValueModel(); container.addItem(this.mappingNodesHolder); container.addItem(this.unmappedMappingNodesHolder); return new CompositeCollectionValueModel(container, Transformer.NULL_INSTANCE); } // ********** queries ********** public MWMappingDescriptor getMappingDescriptor() { return (MWMappingDescriptor) this.getDescriptor(); } private Iterator mappingNodes() { return (Iterator) this.mappingNodesHolder.getValue(); } private MappingNode mappingNodeFor(MWMapping mapping) { for (Iterator stream = this.mappingNodes(); stream.hasNext(); ) { MappingNode node = (MappingNode) stream.next(); if (node.getValue() == mapping) { return node; } } return null; } private MappingNode mappingNodeFor(MWClassAttribute attribute) { for (Iterator stream = this.mappingNodes(); stream.hasNext(); ) { MappingNode node = (MappingNode) stream.next(); if (node.instanceVariable() == attribute) { return node; } } return null; } private Iterator unmappedMappingNodes() { return (Iterator) this.unmappedMappingNodesHolder.getValue(); } private MappingNode unmappedMappingNodeFor(MWClassAttribute attribute) { for (Iterator stream = this.unmappedMappingNodes(); stream.hasNext(); ) { MappingNode node = (MappingNode) stream.next(); if (node.instanceVariable() == attribute) { return node; } } return null; } /** * filter the specified attributes, returning only the attributes that * do NOT have a mapping in the descriptor */ private Iterator unmappedAttributes(Iterator attributes) { return new FilteringIterator(attributes) { protected boolean accept(Object o) { MWMappingDescriptor descriptor = MappingDescriptorNode.this.getMappingDescriptor(); return descriptor.mappingForAttribute((MWClassAttribute) o) == null; } }; } public abstract String mappingHelpTopicPrefix(); // ********** behavior ********** /** * mappings were added to the descriptor; * remove the unmapped nodes and add nodes for the new mappings */ void mappingsAdded(CollectionChangeEvent e) { this.removeUnmappedMappingNodesFor(this.attributes(e.items())); this.addMappingNodesFor(e.items()); } private void removeUnmappedMappingNodesFor(Iterator attributes) { while (attributes.hasNext()) { this.removeUnmappedMappingNodeFor((MWClassAttribute) attributes.next()); } } private void removeUnmappedMappingNodeFor(MWClassAttribute attribute) { this.unmappedMappingNodesHolder.removeItem(this.unmappedMappingNodeFor(attribute)); } private void addMappingNodesFor(Iterator mappings) { while (mappings.hasNext()) { this.addMappingNodeFor((MWMapping) mappings.next()); } } private void addMappingNodeFor(MWMapping mapping) { this.mappingNodesHolder.addItem(this.buildMappingNode(mapping)); } /** * mappings were removed from the descriptor; * in each case, either the attribute was removed or it was "unmapped" */ void mappingsRemoved(CollectionChangeEvent e) { this.removeMappingNodesFor(e.items()); for (Iterator stream = this.attributes(e.items()); stream.hasNext(); ) { MWClassAttribute attribute = (MWClassAttribute) stream.next(); String attributeName = attribute.getName(); // if the attribute remains, add an "unmapped" mapping node if (this.type().containsCombinedAttributeNamed(attributeName) || this.getMappingDescriptor().containsInheritedAttributeNamed(attributeName)) { this.addUnmappedMappingNodeFor(attribute); } } } private void removeMappingNodesFor(Iterator mappings) { while (mappings.hasNext()) { this.removeMappingNodeFor((MWMapping) mappings.next()); } } private void removeMappingNodeFor(MWMapping mapping) { this.mappingNodesHolder.removeItem(this.mappingNodeFor(mapping)); } private void addUnmappedMappingNodeFor(MWClassAttribute attribute) { this.unmappedMappingNodesHolder.addItem(this.buildUnmappedMappingNode(attribute)); } /** * return the attributes for the specified mappings */ private Iterator attributes(Iterator mappings) { return new TransformationIterator(mappings) { protected Object transform(Object next) { return ((MWMapping) next).getInstanceVariable(); } }; } /** * the descriptor's mappings were changed in some unknown fashion; * rebuild all the mapping nodes */ void mappingsChanged(CollectionChangeEvent e) { this.rebuildAllMappingNodes(); } void attributesAdded(CollectionChangeEvent e) { this.engageAttributes(e.items()); this.addUnmappedMappingNodesFor(e.items()); } private void addUnmappedMappingNodesFor(Iterator attributes) { while (attributes.hasNext()) { this.addUnmappedMappingNodeFor((MWClassAttribute) attributes.next()); } } void attributesRemoved(CollectionChangeEvent e) { this.removeUnmappedMappingNodesFor(e.items()); this.disengageAttributes(e.items()); } private void rebuildAllMappingNodes() { this.clearAllMappingNodes(); this.addAllMappingNodes(); } private void clearAllMappingNodes() { this.mappingNodesHolder.clear(); this.unmappedMappingNodesHolder.clear(); } private void addAllMappingNodes() { MWMappingDescriptor descriptor = this.getMappingDescriptor(); MWClass type = descriptor.getMWClass(); this.addMappingNodesFor(descriptor.mappings()); this.addUnmappedMappingNodesFor(this.unmappedAttributes(type.attributes())); this.addUnmappedMappingNodesFor(this.unmappedAttributes(type.ejb20Attributes())); this.addUnmappedMappingNodesFor(this.unmappedAttributes(descriptor.inheritedAttributes())); } /** * an attribute's modifier changed; * this may affect whether we keep an "unmapped" mapping node; * "mapped" mapping nodes are handled by the properties page directly, * by prompting the user as to whether it is OK to remove a mapping * when the attribute is no longer "mappable" */ void attributeModifierChanged(PropertyChangeEvent e) { MWClassAttribute attribute = (MWClassAttribute) e.getSource(); if (this.mappingNodeFor(attribute) != null) { // the validation thread will tell the user the modifier is invalid return; } int oldCode = ((Integer) e.getOldValue()).intValue(); int newCode = ((Integer) e.getNewValue()).intValue(); this.checkModifier(attribute, Modifier.isStatic(oldCode), Modifier.isStatic(newCode)); this.checkModifier(attribute, Modifier.isFinal(oldCode), Modifier.isFinal(newCode)); } /** * check whether the specified modifier has changed in a way that * allows the specified attribute to be mapped, or vice-versa * (not sure what the best way is to handle all the negative logic...) */ private void checkModifier(MWClassAttribute attribute, boolean oldModifierIsNotMappable, boolean newModifierIsNotMappable) { if (oldModifierIsNotMappable) { if ( ! newModifierIsNotMappable) { // unmappable -> mappable (e.g. static -> non-static) // a previous modifier check might have already added the "unmapped" mapping node if (this.unmappedMappingNodeFor(attribute) == null) { this.addUnmappedMappingNodeFor(attribute); } } } else { if (newModifierIsNotMappable) { // mappable -> unmappable (e.g. non-static -> static) this.removeUnmappedMappingNodeFor(attribute); } } } void unknownPrimaryKeyChanged(PropertyChangeEvent e) { if (e.getNewValue() == null) { this.removeUnmappedMappingNodeFor((MWClassAttribute) e.getOldValue()); } } public void selectMethod(MWMethod method, WorkbenchContext context) { ((MappingDescriptorTabbedPropertiesPage) context.getPropertiesPage()).selectMethod(method); } public void selectMappingNodeFor(MWClassAttribute attribute, NavigatorSelectionModel nsm) { MWMapping mapping = this.getMappingDescriptor().mappingForAttribute(attribute); this.selectMappingNodeFor(mapping, nsm); } public void selectMappingNodeFor(MWMapping mapping, NavigatorSelectionModel nsm) { this.selectDescendantNodeForValue(mapping, nsm); } // ********** factories ********** /** * Build the appropriate mapping node for the specified mapping. */ protected abstract MappingNode buildMappingNode(MWMapping mapping); /** * Build the appropriate mapping node for the specified unmapped attribute. */ protected abstract MappingNode buildUnmappedMappingNode(MWClassAttribute attribute); // ********** AbstractApplicationNode overrides ********** public ListValueModel getChildrenModel() { return this.childrenModel; } protected void engageValue() { super.engageValue(); MWMappingDescriptor descriptor = this.getMappingDescriptor(); MWClass type = descriptor.getMWClass(); descriptor.addCollectionChangeListener(MWMappingDescriptor.MAPPINGS_COLLECTION, this.mappingsListener); descriptor.addCollectionChangeListener(MWMappingDescriptor.INHERITED_ATTRIBUTES_COLLECTION, this.attributesListener); type.addCollectionChangeListener(MWClass.ATTRIBUTES_COLLECTION, this.attributesListener); type.addCollectionChangeListener(MWClass.EJB20_ATTRIBUTES_COLLECTION, this.attributesListener); type.addPropertyChangeListener(MWClass.UNKNOWN_PK_ATTRIBUTE_PROPERTY, this.unknownPrimaryKeyListener); this.engageAttributes(type.combinedAttributes()); this.engageAttributes(descriptor.inheritedAttributes()); this.addAllMappingNodes(); } protected void engageAttributes(Iterator attributes) { while (attributes.hasNext()) { this.engageAttribute((MWClassAttribute) attributes.next()); } } protected void engageAttribute(MWClassAttribute attribute) { attribute.addPropertyChangeListener(MWModifiable.MODIFIER_CODE_PROPERTY, this.attributeModifierListener); } protected void disengageValue() { MWMappingDescriptor descriptor = this.getMappingDescriptor(); MWClass type = descriptor.getMWClass(); this.clearAllMappingNodes(); this.disengageAttributes(descriptor.inheritedAttributes()); this.disengageAttributes(type.combinedAttributes()); type.removePropertyChangeListener(MWClass.UNKNOWN_PK_ATTRIBUTE_PROPERTY, this.unknownPrimaryKeyListener); type.removeCollectionChangeListener(MWClass.EJB20_ATTRIBUTES_COLLECTION, this.attributesListener); type.removeCollectionChangeListener(MWClass.ATTRIBUTES_COLLECTION, this.attributesListener); descriptor.removeCollectionChangeListener(MWMappingDescriptor.INHERITED_ATTRIBUTES_COLLECTION, this.attributesListener); descriptor.removeCollectionChangeListener(MWMappingDescriptor.MAPPINGS_COLLECTION, this.mappingsListener); super.disengageValue(); } protected void disengageAttributes(Iterator attributes) { while (attributes.hasNext()) { this.disengageAttribute((MWClassAttribute) attributes.next()); } } protected void disengageAttribute(MWClassAttribute attribute) { attribute.removePropertyChangeListener(MWModifiable.MODIFIER_CODE_PROPERTY, this.attributeModifierListener); } // ********** DescriptorNode implementation ********** protected boolean supportsAdvancedProperties() { return true; } // ********** ApplicationNode implementation ********** public GroupContainerDescription buildToolBarDescription(WorkbenchContext workbenchContext) { return new ToolBarDescription(); } // ********** actions ********** protected MenuDescription buildMapInheritedAttributesMenuDescription(WorkbenchContext workbenchContext) { MenuDescription menuDesc = new MenuDescription( this.resourceRepository().getString("MAP_INHERITED_ATTRIBUTES_MENU_ITEM"), this.resourceRepository().getString("MAP_INHERITED_ATTRIBUTES_MENU_ITEM"), this.resourceRepository().getMnemonic("MAP_INHERITED_ATTRIBUTES_MENU_ITEM"), EMPTY_ICON ); MenuGroupDescription mapGroupDesc = new MenuGroupDescription(); mapGroupDesc.add(this.getMapInheritedAttributesToSuperClassAction(workbenchContext)); mapGroupDesc.add(this.getMapInheritedAttributesToRootMinusOneAction(workbenchContext)); mapGroupDesc.add(this.getMapInheritedAttributesToSelectedClassAction(workbenchContext)); menuDesc.add(mapGroupDesc); MenuGroupDescription removeGroupDesc = new MenuGroupDescription(); removeGroupDesc.add(this.getRemoveInheritedAttributesAction(workbenchContext)); menuDesc.add(removeGroupDesc); return menuDesc; } // TODO Should this be moved to DescriptorNode? Will we allow unmap on an interface descriptor? protected MenuDescription buildUnmapMenuDescription(WorkbenchContext context) { MenuDescription menuDesc = new MenuDescription( this.resourceRepository().getString("UNMAP_MENU"), this.resourceRepository().getString("UNMAP_MENU"), this.resourceRepository().getMnemonic("UNMAP_MENU"), EMPTY_ICON ); MenuGroupDescription groupDesc = new MenuGroupDescription(); groupDesc.add(this.getUnmapDescriptorAction(context)); groupDesc.add(this.getUnmapAllDescriptorsInPackageAction(context)); menuDesc.add(groupDesc); return menuDesc; } protected AutomapAction getAutomapAction(WorkbenchContext workbenchContext) { return this.getMappingsPlugin().getAutomapAction(workbenchContext); } private FrameworkAction getMapInheritedAttributesToSuperClassAction(WorkbenchContext context) { return new MapInheritedAttributesToSuperclassAction(context); } private FrameworkAction getMapInheritedAttributesToRootMinusOneAction(WorkbenchContext context) { return new MapInheritedAttributesToRootMinusOneAction(context); } private FrameworkAction getMapInheritedAttributesToSelectedClassAction(WorkbenchContext context) { return new MapInheritedAttributesToSelectedClassAction(context); } protected FrameworkAction getRemoveInheritedAttributesAction(WorkbenchContext context) { return new RemoveInheritedAttributesAction(context); } // TODO this should probably be in DescriptorNode (InterfaceDescriptors can be unmapped also...) private FrameworkAction getUnmapAllDescriptorsInPackageAction(WorkbenchContext context) { return new UnmapAllDescriptorsInPackageAction(context); } private FrameworkAction getUnmapDescriptorAction(WorkbenchContext context) { return new UnmapDescriptorAction(context); } }