/*******************************************************************************
* Copyright 2017 Capital One Services, LLC and Bitwise, Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package hydrograph.ui.graph.controller;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.eclipse.draw2d.ConnectionAnchor;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.TextUtilities;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ConnectionEditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.NodeEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gef.editpolicies.AbstractEditPolicy;
import org.eclipse.gef.requests.DropRequest;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.slf4j.Logger;
import hydrograph.ui.common.component.config.Policy;
import hydrograph.ui.common.component.config.Property;
import hydrograph.ui.common.datastructures.tooltip.PropertyToolTipInformation;
import hydrograph.ui.common.util.Constants;
import hydrograph.ui.common.util.XMLConfigUtil;
import hydrograph.ui.graph.editor.ELTGraphicalEditor;
import hydrograph.ui.graph.figure.ComponentBorder;
import hydrograph.ui.graph.figure.ComponentFigure;
import hydrograph.ui.graph.figure.ELTFigureConstants;
import hydrograph.ui.graph.figure.PortFigure;
import hydrograph.ui.graph.model.Component;
import hydrograph.ui.graph.model.ComponentLabel;
import hydrograph.ui.graph.model.Link;
import hydrograph.ui.graph.model.processor.DynamicClassProcessor;
import hydrograph.ui.graph.propertywindow.ELTPropertyWindow;
import hydrograph.ui.graph.utility.SubJobUtility;
import hydrograph.ui.logging.factory.LogFactory;
/**
* The Class ComponentEditPart.
* @author Bitwise
*/
public class ComponentEditPart extends AbstractGraphicalEditPart implements NodeEditPart, PropertyChangeListener {
private static final Logger logger = LogFactory.INSTANCE.getLogger(ComponentEditPart.class);
private static final String PHASE="phase";
private static final String BATCH="batch";
private Font componentLabelFont;
/**
* Upon activation, attach to the model element as a property change
* listener.
*/
@Override
public void activate() {
if (!isActive()) {
super.activate();
((Component) getModel()).addPropertyChangeListener(this);
((Component) getModel()).setComponentEditPart(this);
}
backwardCompatibilityForChangingPhaseToBatch();
backwardCompatibilityForLoadingComponentId();
}
// Temp method for loading component's Id.
private void backwardCompatibilityForLoadingComponentId() {
if(StringUtils.isBlank(getCastedModel().getComponentId())){
getCastedModel().setComponentId(getCastedModel().getComponentLabel().getLabelContents());
}
}
private void backwardCompatibilityForChangingPhaseToBatch() {
if(getCastedModel().getProperties().get(PHASE)!=null)
{
getCastedModel().getProperties().put(BATCH,getCastedModel().getProperties().remove(PHASE));
}
}
/**
* Upon deactivation, detach from the model element as a property change
* listener.
*/
@Override
public void deactivate() {
if (isActive()) {
if(componentLabelFont!=null){
componentLabelFont.dispose();
}
super.deactivate();
((Component) getModel()).removePropertyChangeListener(this);
getComponentFigure().disposeFonts();
}
}
@Override
protected void createEditPolicies() {
String componentName = DynamicClassProcessor.INSTANCE.getClazzName(getModel().getClass());
try {
for (hydrograph.ui.common.component.config.Component component : XMLConfigUtil.INSTANCE.getComponentConfig()) {
if (component.getName().equalsIgnoreCase(componentName)) {
applyGeneralPolicy(component);
}
}
} catch (Exception e) {
logger.error("Error while creating edit policies", e);
}
}
/**
* Apply general policy.
*
* @param component
* the component
* @throws Exception
* the exception
*/
public void applyGeneralPolicy(
hydrograph.ui.common.component.config.Component component)
throws Exception {
for (Policy generalPolicy : XMLConfigUtil.INSTANCE.getPoliciesForComponent(component)) {
try {
AbstractEditPolicy editPolicy = (AbstractEditPolicy) Class.forName(generalPolicy.getValue()).newInstance();
installEditPolicy(generalPolicy.getName(), editPolicy);
} catch (ClassNotFoundException| InstantiationException | IllegalAccessException exception) {
logger.error("Failed to apply policies", exception);
throw exception;
}
}
}
@Override
protected IFigure createFigure() {
IFigure figure = createFigureForModel();
figure.setOpaque(true); // non-transparent figure
updateSubjobComponent(figure);
return figure;
}
/**
* updates version and validity status icon of subjob component
* @param figure
*/
public void updateSubjobComponent(IFigure figure) {
LinkedHashMap<String, Object> properties = getCastedModel().getProperties();
if (StringUtils.equals(getCastedModel().getComponentName(), Constants.SUBJOB_COMPONENT)) {
SubJobUtility graphUtility=new SubJobUtility();
graphUtility.updateSubjobCompVersion(getCastedModel());
}
String status = (String) properties.get(Component.Props.VALIDITY_STATUS.getValue());
((ComponentFigure)figure).setPropertyStatus(status);
}
private IFigure createFigureForModel() {
String componentName = DynamicClassProcessor.INSTANCE
.getClazzName(getModel().getClass());
String canvasIconPath = XMLConfigUtil.INSTANCE.getComponent(componentName).getCanvasIconPath();
String label = (String) getCastedModel().getPropertyValue(Component.Props.NAME_PROP.getValue());
String acronym = XMLConfigUtil.INSTANCE.getComponent(componentName).getAcronym();
return new ComponentFigure(getCastedModel(), canvasIconPath, label, acronym, getCastedModel().getProperties());
}
public Component getCastedModel() {
return (Component) getModel();
}
@Override
protected List getModelChildren() {
// return a list of models
return ((Component)getModel()).getChildren();
}
/**
* Map connection anchor to terminal.
*
* @param c
* the c
* @return the string
*/
public final String mapConnectionAnchorToTerminal(ConnectionAnchor c) {
return getComponentFigure().getConnectionAnchorName(c);
}
@Override
protected List<Link> getModelSourceConnections() {
return getCastedModel().getSourceConnections();
}
@Override
protected List<Link> getModelTargetConnections() {
return getCastedModel().getTargetConnections();
}
@Override
public ConnectionAnchor getSourceConnectionAnchor(
ConnectionEditPart connection) {
Link wire = (Link) connection.getModel();
return getComponentFigure().getConnectionAnchor(
wire.getSourceTerminal());
}
@Override
public ConnectionAnchor getSourceConnectionAnchor(Request request) {
Point pt = new Point(((DropRequest) request).getLocation());
return getComponentFigure().getSourceConnectionAnchorAt(pt);
}
protected ComponentFigure getComponentFigure() {
return (ComponentFigure) getFigure();
}
@Override
public ConnectionAnchor getTargetConnectionAnchor(
ConnectionEditPart connection) {
Link wire = (Link) connection.getModel();
return getComponentFigure().getConnectionAnchor(
wire.getTargetTerminal());
}
@Override
public ConnectionAnchor getTargetConnectionAnchor(Request request) {
Point pt = new Point(((DropRequest) request).getLocation());
return getComponentFigure().getTargetConnectionAnchorAt(pt);
}
/*
* (non-Javadoc)
*
* @see java.beans.PropertyChangeListener#propertyChange(java.beans.
* PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
String prop = evt.getPropertyName();
if (Component.Props.SIZE_PROP.equalsTo(prop)
|| Component.Props.LOCATION_PROP.equalsTo(prop) || Component.Props.EXECUTION_STATUS.equalsTo(prop)) {
refreshVisuals();
} else if (Component.Props.OUTPUTS.equalsTo(prop)) {
refreshSourceConnections();
} else if (Component.Props.INPUTS.equalsTo(prop)) {
refreshTargetConnections();
}
}
@Override
protected void refreshVisuals() {
// notify parent container of changed position & location
// if this line is removed, the XYLayoutManager used by the parent
// container
// (the Figure of the ShapesDiagramEditPart), will not know the bounds
// of this figure and will not draw it correctly.
Component component = getCastedModel();
ComponentFigure componentFigure = getComponentFigure();
ComponentBorder componentBorder = new ComponentBorder(componentFigure.getBorderColor(), 0, componentFigure.getComponentLabelMargin());
componentFigure.setBorder(componentBorder);
List<AbstractGraphicalEditPart> childrenEditParts = getChildren();
if (!childrenEditParts.isEmpty()){
ComponentLabelEditPart lLabelEditPart = null;
PortEditPart portEditPart = null;
for(AbstractGraphicalEditPart part:childrenEditParts)
{
if(part instanceof ComponentLabelEditPart){
lLabelEditPart = (ComponentLabelEditPart) part;
lLabelEditPart.refreshVisuals();
}
if(part instanceof PortEditPart){
portEditPart = (PortEditPart) part;
portEditPart.refreshVisuals();
}
}
}
Rectangle bounds = new Rectangle(getCastedModel().getLocation(),
getCastedModel().getSize());
((GraphicalEditPart) getParent()).setLayoutConstraint(this,
getFigure(), bounds);
componentFigure.updateComponentStatus(getCastedModel().getStatus());
componentFigure.repaint();
if(component.getTooltipInformation() == null){
addTooltipInfoToComponent();
}
component.updateTooltipInformation();
componentFigure.setPropertyToolTipInformation(component.getTooltipInformation());
}
private void addTooltipInfoToComponent() {
String componentName = DynamicClassProcessor.INSTANCE.getClazzName(getModel().getClass());
hydrograph.ui.common.component.config.Component components = XMLConfigUtil.INSTANCE.getComponent(componentName);
//attach tooltip information to component
Map<String,PropertyToolTipInformation> tooltipInformation = new LinkedHashMap<>();
for(Property property : components.getProperty()){
tooltipInformation.put(property.getName(),new PropertyToolTipInformation(property.getName(), property.getShowAsTooltip().value(), property.getTooltipDataType().value()));
}
getCastedModel().setTooltipInformation(tooltipInformation);
}
@Override
public void performRequest(Request req) {
// Opens Property Window only on Double click.
if (req.getType().equals(RequestConstants.REQ_OPEN)) {
ComponentFigure componentFigure=((ComponentEditPart)this).getComponentFigure();
componentFigure.terminateToolTipTimer();
ELTPropertyWindow propertyWindow = new ELTPropertyWindow(getModel());
propertyWindow.open();
if(Constants.SUBJOB_COMPONENT.equalsIgnoreCase(getCastedModel().getComponentName())){
SubJobUtility subJobUtility=new SubJobUtility();
subJobUtility.updateSubjobPropertyAndGetSubjobContainer((ComponentEditPart)this,null,null,true);
}
if(propertyWindow.isPropertyChanged()){
updateSubjobVersion();
}
updateComponentView(propertyWindow);
super.performRequest(req);
}
}
private void refreshComponentStatusOfAllComponent() {
for(Component component:getCastedModel().getParent().getUIComponentList()){
if(component != null){
((ComponentEditPart)component.getComponentEditPart()).updateComponentStatus();
}
}
}
private void updateSubjobVersion() {
Component currentComponent=getCastedModel();
if(currentComponent!=null && currentComponent.getParent().isCurrentGraphSubjob()){
currentComponent.getParent().updateSubjobVersion();
}
}
public void changePortSettings() {
if(getCastedModel().isChangeInPortsCntDynamically()){
setInPortsCountDynamically();
}
if(getCastedModel().isChangeOutPortsCntDynamically()){
setOutPortsCountDynamically();
}
if(getCastedModel().isChangeUnusedPortsCntDynamically()){
setUnusedPortsCountDynamically();
}
refresh();
adjustExistingPorts();
}
private void setInPortsCountDynamically(){
int prevInPortCount = getCastedModel().getInPortCount();
int outPortCount = 0, newInPortCount = 0;
if (getCastedModel().getProperties().get("inPortCount") != null) {
newInPortCount = Integer.parseInt((String.valueOf(getCastedModel().getProperties().get("inPortCount"))));
}
if (getCastedModel().getProperties().get("outPortCount") != null) {
outPortCount = Integer.parseInt(String.valueOf(getCastedModel().getProperties().get("outPortCount")));
}
if(prevInPortCount != newInPortCount){
int inPortCountToBeApplied = newInPortCount!=prevInPortCount ? newInPortCount : prevInPortCount;
ComponentFigure compFig = (ComponentFigure)getFigure();
compFig.setHeight(inPortCountToBeApplied, outPortCount);
Dimension newSize = new Dimension(compFig.getWidth(), compFig.getHeight()+getCastedModel().getComponentLabelMargin());
getCastedModel().setSize(newSize);
refresh();
if(prevInPortCount < newInPortCount){
//Increment the ports
getCastedModel().changeInPortCount(newInPortCount);
adjustExistingPorts();
getCastedModel().incrementLeftSidePorts(newInPortCount, prevInPortCount);
}else{
//decrement the ports
List<String> portsToBeRemoved = populateInPortsToBeRemoved(prevInPortCount, newInPortCount);
getCastedModel().decrementPorts(portsToBeRemoved);
compFig.decrementAnchors(portsToBeRemoved);
getCastedModel().changeInPortCount(newInPortCount);
}
}
}
private void setOutPortsCountDynamically(){
int prevOutPortCount = getCastedModel().getOutPortCount();
int inPortCount = 0, newOutPortCount = 0;
if (getCastedModel().getProperties().get("inPortCount") != null)
{
inPortCount = Integer.parseInt(String.valueOf(getCastedModel().getProperties().get("inPortCount")));
}
if (getCastedModel().getProperties().get("outPortCount") != null)
{
newOutPortCount = Integer.parseInt(String.valueOf(getCastedModel().getProperties().get("outPortCount")));
}
if(prevOutPortCount != newOutPortCount){
int outPortCountToBeApplied = newOutPortCount!=prevOutPortCount ? newOutPortCount : prevOutPortCount;
ComponentFigure compFig = (ComponentFigure)getFigure();
compFig.setHeight(inPortCount, outPortCountToBeApplied);
Dimension newSize = new Dimension(compFig.getWidth(), compFig.getHeight()+getCastedModel().getComponentLabelMargin());
getCastedModel().setSize(newSize);
refresh();
if(prevOutPortCount < newOutPortCount){
//Increment the ports
getCastedModel().changeOutPortCount(newOutPortCount);
adjustExistingPorts();
getCastedModel().incrementRightSidePorts(newOutPortCount, prevOutPortCount);
}else{
//decrement the ports
List<String> portsToBeRemoved = populateOutPortsToBeRemoved(prevOutPortCount, newOutPortCount);
getCastedModel().decrementPorts(portsToBeRemoved);
compFig.decrementAnchors(portsToBeRemoved);
getCastedModel().changeOutPortCount(newOutPortCount);
}
}
}
private void setUnusedPortsCountDynamically(){
int prevUnusedportCount = getCastedModel().getUnusedPortCount();
int newUnunsedPortCount=0;
if(getCastedModel().getProperties().get("unusedPortCount")!=null)
{
newUnunsedPortCount=Integer.parseInt((String)getCastedModel().getProperties().get("unusedPortCount"));
}
if(prevUnusedportCount != newUnunsedPortCount){
int unusedPortCountToBeApplied = newUnunsedPortCount!=prevUnusedportCount ? newUnunsedPortCount : prevUnusedportCount;
ComponentFigure compFig = (ComponentFigure)getFigure();
compFig.setWidth(unusedPortCountToBeApplied);
Dimension newSize = new Dimension(compFig.getWidth(), compFig.getHeight()+getCastedModel().getComponentLabelMargin());
getCastedModel().setSize(newSize);
refresh();
if(prevUnusedportCount < newUnunsedPortCount){
//Increment the ports
getCastedModel().changeUnusedPortCount(newUnunsedPortCount);
adjustExistingPorts();
getCastedModel().incrementBottomSidePorts(newUnunsedPortCount, prevUnusedportCount);
}else{
//decrement the ports
List<String> portsToBeRemoved = populateUnusedPortsToBeRemoved(prevUnusedportCount, newUnunsedPortCount);
getCastedModel().decrementPorts(portsToBeRemoved);
compFig.decrementAnchors(portsToBeRemoved);
getCastedModel().changeUnusedPortCount(newUnunsedPortCount);
}
}
}
private void adjustExistingPorts(){
List<AbstractGraphicalEditPart> childrenEditParts = getChildren();
PortEditPart portEditPart = null;
for(AbstractGraphicalEditPart part:childrenEditParts)
{
if(part instanceof PortEditPart){
portEditPart = (PortEditPart) part;
portEditPart.adjustPortFigure(getCastedModel().getLocation());
}
}
}
private void selectPorts() {
ComponentFigure compFig = (ComponentFigure)getFigure();
List<Figure> childrenFigures = compFig.getChildren();
if (!childrenFigures.isEmpty()){
for(Figure figure:childrenFigures)
{
if(figure instanceof PortFigure)
((PortFigure) figure).selectPort();
}
}
}
private List<String> populateInPortsToBeRemoved(int prevCount, int newCount){
int keyCount = newCount;
List<String> oldKeys = new ArrayList<>();
List<String> oldInKeys = new ArrayList<>();
List<String> newInKeys = new ArrayList<>();
List<String> inKeysToBeRemoved = new ArrayList<>();
oldKeys.addAll(getCastedModel().getPorts().keySet());
for (String key : oldKeys) {
if(key.contains(Constants.INPUT_SOCKET_TYPE))
oldInKeys.add(key);
}
for(int i=0; i<keyCount; i++)
{
newInKeys.add(Constants.INPUT_SOCKET_TYPE+i);
}
for (String key : oldInKeys) {
if(!newInKeys.contains(key))
inKeysToBeRemoved.add(key);
}
return inKeysToBeRemoved;
}
private List<String> populateOutPortsToBeRemoved(int prevCount, int newCount){
int keyCount = newCount;
List<String> oldKeys = new ArrayList<>();
List<String> oldOutKeys = new ArrayList<>();
List<String> newOutKeys = new ArrayList<>();
List<String> outKeysToBeRemoved = new ArrayList<>();
oldKeys.addAll(getCastedModel().getPorts().keySet());
for (String key : oldKeys) {
if(key.contains(Constants.OUTPUT_SOCKET_TYPE))
oldOutKeys.add(key);
}
for(int i=0; i<keyCount; i++)
{
newOutKeys.add(Constants.OUTPUT_SOCKET_TYPE+i);
}
for (String key : oldOutKeys) {
if(!newOutKeys.contains(key))
outKeysToBeRemoved.add(key);
}
return outKeysToBeRemoved;
}
private List<String> populateUnusedPortsToBeRemoved(int prevCount, int newCount){
int keyCount = newCount;
List<String> oldKeys = new ArrayList<>();
List<String> oldUnusedKeys = new ArrayList<>();
List<String> newUnusedKeys = new ArrayList<>();
List<String> unusedKeysToBeRemoved = new ArrayList<>();
oldKeys.addAll(getCastedModel().getPorts().keySet());
for (String key : oldKeys) {
if(key.contains(Constants.UNUSED_SOCKET_TYPE))
oldUnusedKeys.add(key);
}
for(int i=0; i<keyCount; i++)
{
newUnusedKeys.add(Constants.UNUSED_SOCKET_TYPE+i);
}
for (String key : oldUnusedKeys) {
if(!newUnusedKeys.contains(key))
unusedKeysToBeRemoved.add(key);
}
return unusedKeysToBeRemoved;
}
private void adjustComponentFigure(Component component, ComponentFigure componentFigure){
Dimension d = null;
//an extra space has been added at the end of the component's name for handling component renaming issue#1370.
String label = (String) component.getPropertyValue(Component.Props.NAME_PROP.getValue());
ComponentLabel componentLabel = component.getComponentLabel();
if(componentLabelFont==null){
componentLabelFont = new Font( Display.getDefault(), ELTFigureConstants.labelFont, 10,
SWT.NORMAL );
}
int labelLength = TextUtilities.INSTANCE.getStringExtents(label, componentLabelFont).width;
component.setComponentLabel(label);
if(labelLength >= ELTFigureConstants.compLabelOneLineLengthLimit && !componentFigure.isIncrementedHeight()){
component.setSize(new Dimension(component.getSize().width, component.getSize().height +ELTFigureConstants.componentOneLineLabelMargin));
//component.setSize(new Dimension(100, 82));
componentLabel.setSize(new Dimension(componentLabel.getSize().width, componentLabel.getSize().height +ELTFigureConstants.componentOneLineLabelMargin));
componentFigure.setIncrementedHeight(true);
component.setComponentLabelMargin(ELTFigureConstants.componentTwoLineLabelMargin);
componentFigure.setComponentLabelMargin(ELTFigureConstants.componentTwoLineLabelMargin);
}else if(labelLength < ELTFigureConstants.compLabelOneLineLengthLimit && componentFigure.isIncrementedHeight()){
component.setSize(new Dimension(component.getSize().width, component.getSize().height-ELTFigureConstants.componentOneLineLabelMargin));
componentLabel.setSize(new Dimension(componentLabel.getSize().width, componentLabel.getSize().height -ELTFigureConstants.componentOneLineLabelMargin));
componentFigure.setIncrementedHeight(false);
component.setComponentLabelMargin(ELTFigureConstants.componentOneLineLabelMargin);
componentFigure.setComponentLabelMargin(ELTFigureConstants.componentOneLineLabelMargin);
}else if(labelLength < ELTFigureConstants.compLabelOneLineLengthLimit ){
component.setSize(new Dimension(component.getSize().width, component.getSize().height));
}
componentFigure.repaint();
}
private void adjustComponentLabelPosition(){
List<AbstractGraphicalEditPart> childrenEditParts = getChildren();
for(AbstractGraphicalEditPart part:childrenEditParts)
{
if(part instanceof ComponentLabelEditPart){
ComponentLabelEditPart componentLabelEditPart = (ComponentLabelEditPart) part;
componentLabelEditPart.adjustLabelFigure(getCastedModel().getLocation(), getCastedModel().getSize());
break;
}
}
}
/**
* Updates the status of a component.
*/
public void updateComponentStatus(){
Component component = this.getCastedModel();
LinkedHashMap<String, Object> properties = component.getProperties();
String statusName = Component.Props.VALIDITY_STATUS.getValue();
if(properties.containsKey(statusName)){
((ComponentFigure)this.getFigure()).setPropertyStatus((String)properties.get(statusName));
this.getFigure().repaint();
}
}
/**
* Updates/refresh's the status, label position, port settings and tool tip information of a component.
*
* @param eltPropertyWindow ELTPropertyWindow instance is passed
*/
public void updateComponentView(ELTPropertyWindow eltPropertyWindow)
{
String currentStatus=(String) getCastedModel().getProperties().get(Component.Props.VALIDITY_STATUS.getValue());
adjustComponentFigure(getCastedModel(), getComponentFigure());
changePortSettings();
adjustComponentLabelPosition();
if(!StringUtils.equals(Constants.UPDATE_AVAILABLE,currentStatus)){
updateComponentStatus();
}
refresh();
adjustExistingPorts();
selectPorts();
if(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor() instanceof ELTGraphicalEditor){
ELTGraphicalEditor eltGraphicalEditor=(ELTGraphicalEditor) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
if(eltPropertyWindow.isPropertyChanged()){
eltGraphicalEditor.setDirty(true);
getCastedModel().updateTooltipInformation();
}
}
refreshComponentStatusOfAllComponent();
}
}