// Copyright (c) 2011, 2020, Oracle and/or its affiliates. 

/**
 * @constructor
 */
dvt.AdfDiagramLink = function(context, linkData, diagram)
{
  this.Init(context, linkData, diagram);
};

dvt.Obj.createSubclass(dvt.AdfDiagramLink, dvt.BaseDiagramLink, 'dvt.AdfDiagramLink');

/**
 * Default link width
 * @const
 * @private
 */
dvt.AdfDiagramLink._DEFAULT_LINK_WIDTH = 1;
/**
 * Default link length used by palette
 * @const
 * @private
 */
dvt.AdfDiagramLink._DEFAULT_LINK_LENGTH = 28;

dvt.AdfDiagramLink._START_CONNECTOR = 0;
dvt.AdfDiagramLink._END_CONNECTOR = 1;

//Bug 15867125: padding to add to link width to make it easier to click
dvt.AdfDiagramLink._HIT_DETECTION_WIDTH_OFFSET = 4;

// keys for retrieving style properties


/**
 * Style key for the color of the link
 * @const
 */
dvt.AdfDiagramLink.COLOR = 'linkColor';


/**
 * Style key for the color of the selected link state
 * @const
 */
dvt.AdfDiagramLink.SELECTED_COLOR = 'linkSelectColor';


/**
 * Style key for the inner color of the hover link state
 * @const
 */
dvt.AdfDiagramLink.HOVER_INNER_COLOR = 'linkHoverInnerColor';


/**
 * Style key for the outer color of the hover link state
 * @const
 */
dvt.AdfDiagramLink.HOVER_OUTER_COLOR = 'linkHoverOuterColor';


/**
 * Style key for the color of promoted links
 * @const
 */
dvt.AdfDiagramLink.PROMOTED_COLOR = 'linkPromotedColor';


/**
 * Style key for the linkStyle of promoted links
 * @const
 */
dvt.AdfDiagramLink.PROMOTED_STYLE = 'linkPromotedStyle';


/**
 * Style key for the width of promoted links
 * @const
 */
dvt.AdfDiagramLink.PROMOTED_WIDTH = 'linkPromotedWidth';


/**
 * Style key for the link label style
 * @const
 */
dvt.AdfDiagramLink.LABEL_STYLE = 'linkLabelStyle';


/**
 * Initialization method called by the constructor
 *
 * @param {dvt.Context} context The rendering context
 * @param {DvtAdfDiagramLinkData} linkData the data for this Link
 * @param {dvt.AdfDiagram} diagram The Diagram component to which this link belongs
 */
dvt.AdfDiagramLink.prototype.Init = function(context, linkData, diagram)
{
  dvt.AdfDiagramLink.superclass.Init.call(this, context, linkData.getId(), diagram);

  this._data = linkData;

  this._linkDef;

  this._linkColor;
  this._linkWidth;
  this._linkType;
  this._linkStyle;
  this._shortDesc;
  this._endConnectorType;
  this._startConnectorType;
  this._label;
  this._bRendered = true;
  this._categories;

  this._initialPoints;
  this._hover = false;
  this._updateEnabled = false;
  this._deleteEnabled = false;

  this._dragItems = new Object();

  //these will be set from dvt.AdfDiagramNode.addChildLink or
  //dvt.AdfDiagramNode.addCrossChildLink
  this._linkParent;
  this._bCrossContainerLink = false;

  this._maxAlpha = 1.0;

  this._linkUnderlay;
  this._savedStroke;
  //Bug 15867125: underlay wider than link itself to make it easier
  //for mouse to interact with link
  this._hitDetectionUnderlay;

  this._bFocused = false;
  this._bFocusEffect = false;

  this._showPopupBehaviors;

  this._bPromoted = false;
  this._bDirty = false;

  //associate link with itself so that all children automatically
  //default to the link as the logical object
  this.GetDiagram().getEventManager().associate(this, this);

  //keyboard navigation
  this._keyboardNavNode;
  this._tabStopsConnectorArray = [];

  this._bFirefox = dvt.Agent.isPlatformGecko();
  this._bIE = (dvt.Agent.isPlatformIE());

  //skinning
  var linkStyles = this._diagram.getLinkStyles();
  this._defaultColor = linkStyles[dvt.AdfDiagramLink.COLOR];
  this._selectColor = linkStyles[dvt.AdfDiagramLink.SELECTED_COLOR];
  this._hoverInnerColor = linkStyles[dvt.AdfDiagramLink.HOVER_INNER_COLOR];
  this._hoverOuterColor = linkStyles[dvt.AdfDiagramLink.HOVER_OUTER_COLOR];
  this._linkLabelStyles = linkStyles[dvt.AdfDiagramLink.LABEL_STYLE];
  this._promotedColor = linkStyles[dvt.AdfDiagramLink.PROMOTED_COLOR];
  this._promotedStyle = linkStyles[dvt.AdfDiagramLink.PROMOTED_STYLE];
  this._promotedWidth = linkStyles[dvt.AdfDiagramLink.PROMOTED_WIDTH] ? linkStyles[dvt.AdfDiagramLink.PROMOTED_WIDTH] : dvt.AdfDiagramLink._DEFAULT_LINK_WIDTH;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.GetDiagram = function()
{
  if (this._diagram) {
    return this._diagram;
  }
  if (this._cachedDiagram) {
    return this._cachedDiagram;
  }
  var parent = this.getParent();
  while (parent)
  {
    if (parent instanceof dvt.AdfDiagram)
    {
      return parent;
    }
    parent = parent.getParent();
  }
};

/**
 * Returns bounds of link with stroke
 * @param {dvt.Animator} animator (optional) the animator into which this node should be rendered
 * @return {dvt.Rectangle} bounds The Diagram component to which this link belongs
 */
dvt.AdfDiagramLink.prototype.getDimensionsWithStroke = function(animator)
{
  var points = animator ? animator.getDestVal(this, this.getPoints) : this.getPoints();
  var dims = dvt.PathUtils.getDimensions(points);
  var width = this._hitDetectionUnderlay.getStroke().getWidth();
  dims.x -= width / 2;
  dims.y -= width / 2;
  dims.h += width;
  dims.w += width;
  if (this.getStartConnector()) {
    dims = this.getStartConnector().getDimensions(this).getUnion(dims);
  }
  if (this.getEndConnector()) {
    dims = this.getEndConnector().getDimensions(this).getUnion(dims);
  }
  return dims;
};


dvt.AdfDiagramLink.prototype.showHoverEffect = function() {
  this._showFeedback(true, this._selected);
};

dvt.AdfDiagramLink.prototype.hideHoverEffect = function() {
  if (this._bFocusEffect || this._selected) {
    this._showFeedback(this._bFocusEffect, this._selected); // keep hover effect if we're displaying keyboard focus
  }
  else if (this._bHighlight) {
    this._hideFeedback();
  }
};
/**
 * Show link roll over effect
 *
 * @param {dvt.MouseEvent | dvt.KeyboardEvent} event the event triggering the roll over
 */
dvt.AdfDiagramLink.prototype.showRollOverEffect = function(event) {
  //don't set flag here because when view is re-rendered when crossing
  //a zoom threshold, the node might show the hover effect by mistake
  //this._hover = true;
  this.GetDiagram().ShowLinkHoverEffect(this, event instanceof dvt.KeyboardEvent);
};
/**
 * Hide link roll over effect
 */
dvt.AdfDiagramLink.prototype.hideRollOverEffect = function() {
  //don't set flag here because when view is re-rendered when crossing
  //a zoom threshold, the node might lose the hover effect by mistake
  //this._hover = false;
  this.GetDiagram().HideLinkHoverEffect(this);
};


/**
 * Applies hover and selection feedback to the link underlay object
 *
 * @param {boolean} bHover whether or not hover feedback should be displayed
 * @param {boolean} bSelect whether or not selection feedback should be displayed
 */
dvt.AdfDiagramLink.prototype.ApplyStrokeToUnderlay = function(bHover, bSelect) {
  var color = bSelect ? this._selectColor : this._hoverOuterColor;

  var strokeOffset = 2;

  if (this._linkUnderlay) {
    var stroke = this._linkUnderlay.getStroke().clone();
    stroke.setColor(color);
    stroke.setWidth(strokeOffset + this.GetAppliedLinkWidth());
    this.ApplyLinkStyle(stroke, true);
    this._linkUnderlay.setStroke(stroke, strokeOffset);
  }
};

dvt.AdfDiagramLink.prototype.HandleClick = function(event) {
  this.GetDiagram().HandleLinkClick(this);
};


/**
 * Applies hover and selection feedback to the link
 * @param {boolean} bHover whether or not hover feedback should be displayed
 * @param {boolean} bSelect whether or not selection feedback should be displayed
 * @private
 */
dvt.AdfDiagramLink.prototype._showFeedback = function(bHover, bSelect) {
  this._bHighlight = true;
  this._preserveKeyboardFocusEffect = false;
  if (bHover) {
    if (!this._savedStroke) {
      this._savedStroke = this.getShape().getStroke();
    }
    var hoverStroke = this.getShape().getStroke().clone();
    if (hoverStroke.setColor) {
      hoverStroke.setColor(this._hoverInnerColor);
    }
    this.getShape().setStroke(hoverStroke);

    if (this.getStartConnector() && this.IsStandardConnector(dvt.AdfDiagramLink._START_CONNECTOR))
      this.ReplaceConnectorColor(this.getStartConnector(), hoverStroke);
    if (this.getEndConnector() && this.IsStandardConnector(dvt.AdfDiagramLink._END_CONNECTOR))
      this.ReplaceConnectorColor(this.getEndConnector(), hoverStroke);
  }
  else if (this._savedStroke) {
    this.getShape().setStroke(this._savedStroke);

    if (this.getStartConnector() && this.IsStandardConnector(dvt.AdfDiagramLink._START_CONNECTOR))
      this.ReplaceConnectorColor(this.getStartConnector(), this._savedStroke);
    if (this.getEndConnector() && this.IsStandardConnector(dvt.AdfDiagramLink._END_CONNECTOR))
      this.ReplaceConnectorColor(this.getEndConnector(), this._savedStroke);
    this._savedStroke = null;
  }

  //may need to create the underlay
  if (!this._linkUnderlay) {
    this._linkUnderlay = this.CreateFeedbackUnderlay();
    this.addChildAt(this._linkUnderlay, 0);
  }
  //change the stroke on the underlay
  this.ApplyStrokeToUnderlay(bHover, bSelect);

};


/**
 * Removes hover and selection feedback from the link
 * @private
 */
dvt.AdfDiagramLink.prototype._hideFeedback = function()
{
  this._bHighlight = false;

  if (this._savedStroke) {
    this.getShape().setStroke(this._savedStroke);

    if (this.getStartConnector() && this.IsStandardConnector(dvt.AdfDiagramLink._START_CONNECTOR))
      this.ReplaceConnectorColor(this.getStartConnector(), this._savedStroke);
    if (this.getEndConnector() && this.IsStandardConnector(dvt.AdfDiagramLink._END_CONNECTOR))
      this.ReplaceConnectorColor(this.getEndConnector(), this._savedStroke);

    this._savedStroke = null;
  }

  //may need to remove the underlay
  if (this._linkUnderlay) {// && !this._hover) {
    this.removeChild(this._linkUnderlay);
    if (this._linkUnderlay.destroy) {
      this._linkUnderlay.destroy();
    }
    this._linkUnderlay = null;
  }
  if (!this._preserveKeyboardFocusEffect)
    this._updateKeyboardFocusEffect(null);
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getDatatip = function() {
  return this.getShortDesc();
};

/**
 * Sets short description for the link
 * @param {string} shortDesc link short description
 */
dvt.AdfDiagramLink.prototype.setShortDesc = function(shortDesc) {
  this._shortDesc = shortDesc;
};

/**
 * Gets the link short description
 * @return {string} link short description
 */
dvt.AdfDiagramLink.prototype.getShortDesc = function() {
  if (this._bPromoted) {
    var format = null;
    if (this._promotionCount > 1) {
      format = this._diagram.getResourcesMap()['promotedLinksTip']; // plural
    }
    else if (!this._shortDesc) {
      format = this._diagram.getResourcesMap()['promotedLinkTip']; // singular
    }
    if (format) {
      return dvt.Bundle.format(format, [this._promotionCount]);
    }
  }
  return this._shortDesc;
};

dvt.AdfDiagramLink.prototype.getLayoutAttributes = function(layout) {
  var layoutAttrs = this.getData().getLayoutAttributes();
  if (layoutAttrs && layout) {
    return layoutAttrs[layout];
  }
  return {};
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.setEndConnectorType = function(type) {
  this._endConnectorType = type;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getEndConnectorType = function() {
  return this._endConnectorType;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.setStartConnectorType = function(type) {
  this._startConnectorType = type;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getStartConnectorType = function() {
  return this._startConnectorType;
};

dvt.AdfDiagramLink.prototype.getData = function()
{
  return this._data;
};

dvt.AdfDiagramLink.prototype.setLinkDef = function(linkDef)
{
  this._linkDef = linkDef;

  if (linkDef)
  {
    linkDef.stamp(this.getData().getElAttributes(), this);
    this._refreshCursor();
  }
  this._bDirty = true;
};

dvt.AdfDiagramLink.prototype.getLinkDef = function()
{
  return this._linkDef;
};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.setLinkColor = function(lc)
{
  this._linkColor = lc;
};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getLinkColor = function()
{
  if (this._linkColor)
  {
    return this._linkColor;
  }
  return this._defaultColor;
};


/**
 * Sets links width
 * @param {number} lw link width
 */
dvt.AdfDiagramLink.prototype.setLinkWidth = function(lw)
{
  this._linkWidth = lw;
};

/**
 * Gets link width
 * @return {number} link width
 */
dvt.AdfDiagramLink.prototype.getLinkWidth = function()
{
  if (this._linkWidth)
  {
    return this._linkWidth;
  }
  return dvt.AdfDiagramLink._DEFAULT_LINK_WIDTH;
};


/**
 * Sets links length
 * @param {number} ll link length
 */
dvt.AdfDiagramLink.prototype.setLinkLength = function(ll)
{
  this._linkLength = ll;
};


/**
 * Gets link length
 * @return {number} link length
 */
dvt.AdfDiagramLink.prototype.getLinkLength = function()
{
  if (this._linkLength)
  {
    return this._linkLength;
  }
  return dvt.AdfDiagramLink._DEFAULT_LINK_LENGTH;
};

dvt.AdfDiagramLink.prototype.setLinkType = function(lt)
{
  this._linkType = lt;
};

dvt.AdfDiagramLink.prototype.getLinkType = function()
{
  if (this._linkType)
  {
    return this._linkType;
  }
  return this.GetDefaultLinkType();
};


/**
 * @protected
 */
dvt.AdfDiagramLink.prototype.GetDefaultLinkType = function()
{
  return dvt.AdfDiagramLinkDef.LINK_TYPE_DIRECT;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.setLinkStyle = function(ls)
{
  this._linkStyle = ls;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getLinkStyle = function()
{
  if (this._linkStyle)
  {
    return this._linkStyle;
  }
  return dvt.AdfDiagramLinkDef.LINK_STYLE_SOLID;
};

dvt.AdfDiagramLink.prototype.setLabel = function(label)
{
  this._label = label;
};

/**
 * Get the link label
 * @return {String}  Diagram link label
 */
dvt.AdfDiagramLink.prototype.getLabel = function()
{
  return this._label;
};

dvt.AdfDiagramLink.prototype.setRendered = function(bRendered)
{
  this._bRendered = bRendered;
};

dvt.AdfDiagramLink.prototype.isRendered = function()
{
  return this._bRendered;
};

dvt.AdfDiagramLink.prototype.setUpdateEnabled = function(enabled)
{
  this._updateEnabled = enabled;
};

dvt.AdfDiagramLink.prototype.isUpdateEnabled = function()
{
  return this._updateEnabled;
};

dvt.AdfDiagramLink.prototype.setDeleteEnabled = function(enabled)
{
  this._deleteEnabled = enabled;
};

dvt.AdfDiagramLink.prototype.isDeleteEnabled = function()
{
  return this._deleteEnabled;
};

dvt.AdfDiagramLink.prototype.DestroyContent = function() {
  //Bug 14813637: connectors are children of shape, so destroy them before destroying shape
  if (this.getEndConnector())
  {
    var ecParent = this.getEndConnector().getParent();
    if (ecParent) {
      ecParent.removeChild(this.getEndConnector());
    }
    if (this.getEndConnector().destroy)
    {
      this.getEndConnector().destroy();
    }
    this.setEndConnector(null);
  }
  if (this.getStartConnector())
  {
    var scParent = this.getStartConnector().getParent();
    if (scParent) {
      scParent.removeChild(this.getStartConnector());
    }
    if (this.getStartConnector().destroy)
    {
      this.getStartConnector().destroy();
    }
    this.setStartConnector(null);
  }
  if (this.getShape())
  {
    this.removeChild(this.getShape());
    if (this.getShape().destroy)
    {
      this.getShape().destroy();
    }
    this.setShape(null);
  }
  if (this._linkUnderlay) {
    this.removeChild(this._linkUnderlay);
    if (this._linkUnderlay.destroy) {
      this._linkUnderlay.destroy();
    }
    this._linkUnderlay = null;
  }
  if (this._hitDetectionUnderlay) {
    this.removeChild(this._hitDetectionUnderlay);
    if (this._hitDetectionUnderlay.destroy) {
      this._hitDetectionUnderlay.destroy();
    }
    this._hitDetectionUnderlay = null;
  }
  this._hover = false;
  this._savedStroke = null;
};


/**
 * Renders this link
 */
dvt.AdfDiagramLink.prototype.renderLink = function()
{
  if (this.getShape() && !this._bDirty) {
    return;  // No need to rerender if not dirty
  }
  this.DestroyContent();

  //save old label bounds so that we can apply them to a new label
  var oldLabelBounds = null;
  var oldLabelPos = null;
  var oldLabelRotAngle = null;
  var oldLabelRotPoint = null;
  var oldLabelAlpha = null;
  if (this._labelObj)
  {
    oldLabelBounds = this.getLabelBounds();
    oldLabelPos = this.getLabelPosition();
    oldLabelRotAngle = this.getLabelRotationAngle();
    oldLabelRotPoint = this.getLabelRotationPoint();
    oldLabelAlpha = this._labelObj.getAlpha();
    this.removeChild(this._labelObj);
    if (this._labelObj.destroy)
    {
      this._labelObj.destroy();
    }
    this._labelObj = null;
  }
  var bCanDrawArrow = true;
  var points = this._pathCmds;
  if (!points)
  {
    points = ['M', 0, 0, 'L', 0, 0];
    // bCanDrawArrow = false;
  }
  var id = this.getData().getId();
  this.setShape(new dvt.Path(this.getCtx(), points, id));
  this.getShape().setFill(null);
  //add shape underneath any custom connectors
  //Bug 14813637: add shape to link before creating connectors, so that when connectors are added to shape they
  //are on display list and we can get dimensions for them
  this.addChildAt(this.getShape(), 0);

  //this.GetDiagram().getEventManager().associate(this.getShape(), this);
  var linkColor = this.GetAppliedLinkColor();
  var linkWidth = this.GetAppliedLinkWidth();
  var stroke = new dvt.SolidStroke(linkColor, 1, linkWidth);
  // Bug 15868719 - use non-scaling stroke on the link on older devices with iOS and Safari
  if (dvt.Agent.isTouchDevice() && dvt.Agent.isBrowserSafari() && dvt.Agent.getDevicePixelRatio() == 1)
    stroke.setFixedWidth(true);
  this.ApplyLinkStyle(stroke);
  this.getShape().setStroke(stroke);

  if (bCanDrawArrow)
  {
    var endConnectorType = this.getEndConnectorType();
    var endConnectorTemplate = this.getConnectorTemplate(dvt.DiagramLinkConnectorUtils.END_CONNECTOR);
    this.setEndConnector(this.CreateConnector(this._points, endConnectorType, 1, endConnectorTemplate));
    var startConnectorType = this.getStartConnectorType();
    var startConnectorTemplate = this.getConnectorTemplate(dvt.DiagramLinkConnectorUtils.START_CONNECTOR);
    this.setStartConnector(this.CreateConnector(this._points, startConnectorType, 0, startConnectorTemplate));
  }

  this._refreshSelectionEffect(); //Maintain selection state through layout change

  if (!this._bPromoted && this.HasLabel())
  {
    this._labelObj = this.CreateLabel();
    this.addChild(this._labelObj);
    //initially apply old label bounds so that later layout transition
    //looks more natural
    if (oldLabelBounds)
    {
      this.setLabelBounds(oldLabelBounds);
    }
    if (oldLabelPos) {
      this.setLabelPosition(oldLabelPos);
    }
    if (oldLabelRotAngle != null) {
      this.setLabelRotationAngle(oldLabelRotAngle);
    }
    if (oldLabelRotPoint) {
      this.setLabelRotationPoint(oldLabelRotPoint);
    }
    if (oldLabelAlpha != null) { // need to explicitly check against null because 0 will be interpreted as false
      this._labelObj.setAlpha(oldLabelAlpha);
    }
  }

  //Bug 15867125: create a transparent underlay wider than the link
  //in order to make it easier to interact with the link
  if (!this._hitDetectionUnderlay) {
    this._hitDetectionUnderlay = this.CreateUnderlay('#000000', 0, dvt.AdfDiagramLink._HIT_DETECTION_WIDTH_OFFSET);
    this.addChildAt(this._hitDetectionUnderlay, 0);
  }

  this.addAccessibilityAttributes();
  this._bDirty = false;
};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.GetAppliedLinkColor = function() {
  return this._bPromoted ? this._promotedColor : this.getLinkColor();
};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.GetAppliedLinkStyle = function() {
  return this._bPromoted ? this._promotedStyle : this.getLinkStyle();
};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.GetAppliedLinkWidth = function() {
  return this._bPromoted ? this._promotedWidth : this.getLinkWidth();
};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getStartConnectorOffset = function() {

  var debug = false;
  if (this.getStartConnector())
  {
    var stroke;

    var connectorTemplate = this.getConnectorTemplate(dvt.DiagramLinkConnectorUtils.START_CONNECTOR);
    if (!connectorTemplate)
    {
      stroke = this.getShape().getStroke().clone();
      stroke.setType(dvt.Stroke.SOLID);
    }
    if (debug) alert('is start Conn: get connector end offset');

    return dvt.DiagramLinkConnectorUtils.GetConnectorOffset(this.getStartConnector(), this.getStartConnectorType(), connectorTemplate, stroke, this);
  }
  return 0;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getEndConnectorOffset = function() {

  var debug = false;
  if (debug) alert('get end connector end offset');

  if (this.getEndConnector())
  {

    var stroke;

    var connectorTemplate = this.getConnectorTemplate(dvt.DiagramLinkConnectorUtils.END_CONNECTOR);
    if (!connectorTemplate)
    {
      stroke = this.getShape().getStroke().clone();
      stroke.setType(dvt.Stroke.SOLID);
    }

    if (debug) alert('is endConn: get connector end offset');

    return dvt.DiagramLinkConnectorUtils.GetConnectorOffset(this.getEndConnector(), this.getEndConnectorType(), connectorTemplate, stroke, this);
  }
  return 0;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.setSelected = function(selected) {
  dvt.AdfDiagramLink.superclass.setSelected.call(this, selected);
  this._refreshSelectionEffect();
  this.UpdateAccessibilityAttributes();
  if (!this.GetDiagram().isReadOnly() && this.isUpdateEnabled()) {
    if (selected) {
      this.hideConnectors();
      this.showUpdateHandles();
    } else {
      this.hideUpdateHandles();
      this.showConnectors();
    }
  }
};

dvt.AdfDiagramLink.prototype._refreshSelectionEffect = function() {
  // We usually only update the hover effect via the selection handler, which already checks whether we're selectable.
  // _refreshSelectionEffect happens outside of this mechanism, so we need to make the check explicitly
  var hover = this.isSelectable() && this._hover;
  if (hover || this._bFocusEffect || this._selected) {
    this._showFeedback(hover || this._bFocusEffect, this._selected);
  }
  else if (this._bHighlight) {
    this._hideFeedback();
  }
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getCategories = function() {
  if (!this._categories) {
    this._categories = new Array();
    var parentId = this.getData().getStartId();
    var childId = this.getData().getEndId();
    var parent = this.GetDiagram().getNodeById(parentId);
    var child = this.GetDiagram().getNodeById(childId);
    var parentCategories = parent.getCategories();
    var childCategories = child.getCategories();
    if (parentCategories && childCategories) {
      for (var i = 0; i < parentCategories.length; i++) {
        if (dvt.ArrayUtils.getIndex(childCategories, parentCategories[i]) >= 0) {
          this._categories.push(parentCategories[i]);
        }
      }
    }
  }
  return this._categories;
};

dvt.AdfDiagramLink.prototype.getDisplayables = function() {
  var ar = [this];
  return ar;
};

/**
 * @protected
 * Inserts link into a parent container
 * @param {dvt.Container} parent parent container
 */
dvt.AdfDiagramLink.prototype.InsertLinkIntoParent = function(parent) {
  if (!parent)
    return;
  if (this._bCrossContainerLink) {
    parent.addChild(this);
  }
  else {
    parent.addChildAt(this, 0);
  }
};

dvt.AdfDiagramLink.prototype.GetParentContainer = function() {
  var parent = null;
  var diagram = this.GetDiagram();
  if (diagram) {
    var parent = this._linkParent;
    if (!parent) {
      parent = diagram._linksPane;
    }
  }
  return parent;
};

/**
 * Sets visibility of the link.
 * @param {boolean} bVisible True to make the link visible
 */
dvt.AdfDiagramLink.prototype.setVisible = function(bVisible) {
  //Store the visible state before changing it
  if (this._bVisible != bVisible)
    this.GetDiagram().setAutomationState(this.getData().getId(), 'visible', this._bVisible);
  dvt.AdfDiagramLink.superclass.setVisible.call(this, bVisible);

  var diagram = this.GetDiagram();
  if (diagram) {
    var parent = this.GetParentContainer();
    var contained = parent.contains(this);
    if (bVisible && !contained) {
      this.InsertLinkIntoParent(parent);
      this._cachedDiagram = null;
    }
    else if (!bVisible && contained) {
      parent.removeChild(this);
      this._cachedDiagram = diagram;
    }
  }
};


/**
 * Create label
 * @protected
 * @return {dvt.Text} label text
 */
dvt.AdfDiagramLink.prototype.CreateLabel = function() {
  var label = null;
  var labelTemplate = this.GetLabelTemplate();
  if (labelTemplate)
  {
    var afContext = this.GetDiagram().createAfContext();

    afContext.setELContext(this.getData().getElAttributes());
    label = dvt.AfComponentFactory.parseAndLayout(afContext, labelTemplate, this);
  }
  else if (this._label)
  {
    label = new dvt.OutputText(this.getCtx(), this._label, 0, 0);
    if (this._linkLabelStyles)
      label.setCSSStyle(this._linkLabelStyles);
    //this.GetDiagram().getEventManager().associate(label, this);
  }
  return label;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getLabelBounds = function() {
  var bounds = null;
  if (this._labelObj)
  {
    bounds = this._labelObj.getDimensions();
  }
  return bounds;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getConnectorTemplate = function(connectP) {
  var linkDef = this.getLinkDef();
  return linkDef ? linkDef.getConnectorTemplate(connectP) : null;
};

dvt.AdfDiagramLink.prototype.GetLabelTemplate = function() {
  var linkDef = this.getLinkDef();
  return linkDef ? linkDef.getLabelTemplate() : null;
};

dvt.AdfDiagramLink.prototype.HasLabel = function() {
  return (this._label || this.GetLabelTemplate());
};

/**
 * Get the Af Component in link
 * @param {String} componentId component id
 * @return {DvtAfObject}  Af Object
 */
dvt.AdfDiagramLink.prototype.getAfComponent = function(componentId) {
  var element;
  //check end connector
  var afComp = dvt.AfComponentFactory.getAfComponent(this.getEndConnector());
  if (afComp && afComp.getId() == componentId) {
    element = afComp;
  } else {
    // check start connector
    afComp = dvt.AfComponentFactory.getAfComponent(this.getStartConnector());
    if (afComp && afComp.getId() == componentId) {
      element = afComp;
    } else {
      //check link label object
      if (this.GetLabelTemplate() && this._labelObj) {
        afComp = dvt.AfComponentFactory.getAfComponent(this._labelObj);
        if (afComp && afComp.getId() == componentId)
          element = afComp;
      }
    }
  }
  return element;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.setPromoted = function(bPromoted) {
  if (this._bPromoted != bPromoted) {
    this._bDirty = true;
  }
  this._bPromoted = bPromoted;
};

/**
 *
 */
dvt.AdfDiagramLink.prototype.setPromotionCount = function(count) {
  this._promotionCount = count;
};


/**
 *
 */
dvt.AdfDiagramLink.prototype.getPromotionCount = function() {
  return this._promotionCount;
};


/**
 * Adds link editing handles to the link
 */
dvt.AdfDiagramLink.prototype.showUpdateHandles = function() {
  if (!this._startHandle) {
    var startHandle = new DvtAdfDiagramLinkEndpoint(this.getCtx(), this, DvtAdfDiagramLinkEndpoint.START_TYPE, this.getLinkStart(), this.getId(), this._diagram.getEditorStyles());
    this._startHandle = startHandle;
    this.addChild(startHandle);
  }
  if (!this._endHandle) {
    var endHandle = new DvtAdfDiagramLinkEndpoint(this.getCtx(), this, DvtAdfDiagramLinkEndpoint.END_TYPE, this.getLinkEnd(), this.getId(), this._diagram.getEditorStyles());
    this._endHandle = endHandle;
    this.addChild(endHandle);
  }
  this.GetDiagram().moveToDiagramFront(this);
};

dvt.AdfDiagramLink.prototype.hideUpdateHandles = function() {
  if (this._startHandle)
    this.removeChild(this._startHandle);
  if (this._endHandle)
    this.removeChild(this._endHandle);
  this._startHandle = null;
  this._endHandle = null;
  this.GetDiagram().moveToLinks(this);
};

dvt.AdfDiagramLink.prototype.hideConnectors = function() {
  if (this.getStartConnector()) {
    this.getShape().removeChild(this.getStartConnector());
    if (this._linkUnderlay)
      this._linkUnderlay.hideUnderlayStart();
  }
  if (this.getEndConnector()) {
    this.getShape().removeChild(this.getEndConnector());
    if (this._linkUnderlay)
      this._linkUnderlay.hideUnderlayEnd();
  }
};

dvt.AdfDiagramLink.prototype.showConnectors = function() {
  if (this.getStartConnector()) {
    this.getShape().addChild(this.getStartConnector());
    if (this._linkUnderlay)
      this._linkUnderlay.showUnderlayStart();
  }
  if (this.getEndConnector()) {
    this.getShape().addChild(this.getEndConnector());
    if (this._linkUnderlay)
      this._linkUnderlay.showUnderlayEnd();
  }
};

dvt.AdfDiagramLink.prototype.startLinkChange = function(type, point) {
  this._initialPoints = this.getPoints();
  this._dragItems[type] = true;
  this.setMouseEnabled(false);
};

dvt.AdfDiagramLink.prototype.updateLinkChange = function(type, point) {
  var start;
  var end;
  if (type == DvtAdfDiagramLinkEndpoint.START_TYPE) {
    start = point;
    end = this.getLinkEnd();
  } else {
    start = this.getLinkStart();
    end = point;
  }
  this.setPoints([start.x, start.y, end.x, end.y]);
  this.renderLink();
};
dvt.AdfDiagramLink.prototype.endLinkChange = function(type) {
  this._dragItems[type] = false;
};
dvt.AdfDiagramLink.prototype.endLinkChangeNewNode = function(type, centerPoint) {
  this.endLinkChange(type);
  this.updateLinkChange(type, centerPoint);
  this.setMouseEnabled(true);
};

dvt.AdfDiagramLink.prototype.endLinkChangeRestore = function(type) {
  this.endLinkChange(type);
  var stillDragging = false;
  for (var type in this._dragItems) {
    if (this._dragItems[type])
      stillDragging = true;
  }
  if (!stillDragging) {
    this.restoreLinkPosition();
    this._initialPoints = null;
  }
  this.setMouseEnabled(true);
};

dvt.AdfDiagramLink.prototype.restoreLinkPosition = function() {
  this.setPoints(this._initialPoints);
  this.renderLink();
};

/**
 * @protected
 * Get label bounds relative to this link, taking rotation and position into account.
 */
dvt.AdfDiagramLink.prototype.GetRelativeLabelBounds = function(pos, bounds, rotAngle, rotPoint) {
//Bug 31943600 :Added reference of the base class as current class does not include CalcLabelMatrix 
//Base class name is refactor from  BaseDiagramLink to dvt.BaseDiagramLink 
  var mat = dvt.BaseDiagramLink.CalcLabelMatrix(pos, bounds, rotAngle, rotPoint);
  var topLeft = mat.transformPoint(new dvt.Point(bounds.x, bounds.y));
  var topRight = mat.transformPoint(new dvt.Point(bounds.x + bounds.w, bounds.y));
  var bottomLeft = mat.transformPoint(new dvt.Point(bounds.x, bounds.y + bounds.h));
  var bottomRight = mat.transformPoint(new dvt.Point(bounds.x + bounds.w, bounds.y + bounds.h));
  var minX = Math.min(Math.min(topLeft.x, topRight.x), Math.min(bottomLeft.x, bottomRight.x));
  var maxX = Math.max(Math.max(topLeft.x, topRight.x), Math.max(bottomLeft.x, bottomRight.x));
  var minY = Math.min(Math.min(topLeft.y, topRight.y), Math.min(bottomLeft.y, bottomRight.y));
  var maxY = Math.max(Math.max(topLeft.y, topRight.y), Math.max(bottomLeft.y, bottomRight.y));
  return new dvt.Rectangle(minX, minY, maxX - minX, maxY - minY);
};

dvt.AdfDiagramLink.prototype.SetMaxAlpha = function(maxAlpha) {
  this._maxAlpha = maxAlpha;
};

dvt.AdfDiagramLink.prototype.GetMaxAlpha = function() {
  return this._maxAlpha;
};

// Implementation of DvtKeyboardNavigable interface


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getNextNavigable = function(event) 
{
  var nodes = [];
  if (event.type == dvt.MouseEvent.CLICK)
  {
    return this;
  }
  else if (event.keyCode == dvt.KeyboardEvent.SPACE && event.ctrlKey)
  {
    // multi-select node with current focus; so we navigate to ourself and then let the selection handler take
    // care of the selection
    return this;
  }
  else if (event.keyCode == dvt.KeyboardEvent.UP_ARROW || event.keyCode == dvt.KeyboardEvent.DOWN_ARROW)
  {
    //if the link got focus via keyboard, get the node where the focus came from
    //we'll navigate around that node
    //if the focus was set through mouse click, set start node as a center of navigation
    this._updateKeyboardFocusEffect(null);
    var node = this.getKeyboardFocusNode();
    if (!node)
      node = this._diagram.getNodeById(this._diagram.getLinkStartId(this));

    //find next link - if up counter-clockwise, down - clockwise
    var nextLink = this;
    var links = this._diagram.getNavigableLinksForNodeId(node.getData().getId());
    var keyboardHandler = this._diagram.getEventManager().getKeyboardHandler();
    if (keyboardHandler && keyboardHandler.getNextNavigableLink)
      nextLink = keyboardHandler.getNextNavigableLink(node, this, event, links);

    nextLink.setKeyboardFocusNode(node);
    return nextLink;
  }
  else if (event.keyCode == dvt.KeyboardEvent.RIGHT_ARROW || event.keyCode == dvt.KeyboardEvent.LEFT_ARROW)
  {
    // select a connector or jump to the adjacent node
    var connector = this._findConnectorByDirection(event.keyCode);
    if (connector) {
      this._updateKeyboardFocusEffect(connector);
      this._preserveKeyboardFocusEffect = true; //to preserve the connector focus - event manager is going to reset it
      return this;
    }
    else {
      this._updateKeyboardFocusEffect(null);
      var nodeId;
      if (this._movingToStart(event.keyCode))
        nodeId = this._diagram.getLinkStartId(this);
      else
        nodeId = this._diagram.getLinkEndId(this);
      return this._diagram.getNodeById(nodeId);
    }
  }

  //TO DO: implement correctly (this was just copied from dvt.AdfDiagramNode)
  var nodesByGroupDepth = this._diagram.getSortedVisibleNodesByGroupDepth();
  nodes = nodesByGroupDepth[0];

  // TODO: choose correct depth
  return dvt.KeyboardHandler.getNextAdjacentNavigable(this, event, nodes);
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.showKeyboardFocusEffect = function() 
{
  this.showHoverEffect();
  this._bFocusEffect = true;
  this.setFocused(true);

};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.hideKeyboardFocusEffect = function() 
{
  this._bFocusEffect = false;
  if (!this._hover) {
    this.hideHoverEffect();
  }
  this.setFocused(false);
};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.setFocused = function(bFocused) {
  this._bFocused = bFocused;
};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.isFocused = function() {
  return this._bFocused;
};


/**
 * @override
 */
dvt.AdfDiagramLink.prototype.isShowingKeyboardFocusEffect = function() 
{
  return this._bFocusEffect;
};

/**
 * store context information in a dvt.BaseComponentEvent event.
 * @param {dvt.BaseComponentEvent} event
 *
 */
dvt.AdfDiagramLink.prototype.eventContextCallback = function(event) {
  // store clientRowKey in this event
  if (event) {
    event[dvt.BaseComponentEvent.CLIENT_ROW_KEY] = this.getData().getId();
  }

  this.GetDiagram().HideNodeTransientToolbar();
};


/**
 * Get show popup behaviors of diagram link
 * @return {array}  Array of ShowPopupBehavior
 */
dvt.AdfDiagramLink.prototype.getShowPopupBehaviors = function() {
  if (!this._showPopupBehaviors && this._linkDef && this._linkDef.getShowPopupBehaviors()) {
    var afShowPopupBehaviors = this._linkDef.getShowPopupBehaviors();
    var afContext = this.GetDiagram().createAfContext();

    afContext.setELContext(this.getData().getElAttributes());
    this._showPopupBehaviors = [];
    for (var i = 0; i < afShowPopupBehaviors.length; i++) {
      this._showPopupBehaviors[i] = dvt.AfComponentFactory.parseAndStamp(afContext, afShowPopupBehaviors[i], null);
    }
  }
  return this._showPopupBehaviors;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getAriaLabel = function() {
  return dvt.Displayable.generateAriaLabel(this.getShortDesc(), this.getAccessibilityStates());
};


/**
 * Gets states for the link that are going to be used for WAI-ARIA label
 * @return {array} array of states for the link
 */
dvt.AdfDiagramLink.prototype.getAccessibilityStates = function() {
  var states = [];
  if (this.isSelectable()) {
    states.push(this.isSelected() ? dvt.Bundle.getTranslatedString(dvt.Bundle.UTIL_PREFIX, 'STATE_SELECTED') :
        dvt.Bundle.getTranslatedString(dvt.Bundle.UTIL_PREFIX, 'STATE_UNSELECTED'));
  }
  if (this.isPromoted()) {
    states.push(dvt.Bundle.getTranslatedString(dvt.Bundle.ADF_DIAGRAM_PREFIX, 'PROMOTED_LINK_ARIA_DESC'));
  }
  return states;
};


/**
 * Updates WAI-ARIA attributes for the link
 * @protected
 */
dvt.AdfDiagramLink.prototype.UpdateAccessibilityAttributes = function() {
  if (!dvt.Agent.deferAriaCreation()) {
    var desc = this.getAriaLabel();
    if (desc) {
      this.setAriaProperty('label', desc);
    }
  }
};


/**
 * Adds WAI-ARIA attributes to the node
 */
dvt.AdfDiagramLink.prototype.addAccessibilityAttributes = function() {
  // WAI-ARIA
  var desc = this.getAriaLabel();
  if (desc) {
    this.setAriaRole('img');
    this.setAriaProperty('label', desc);
  }
};

/**
 * @protected
 * Copy the state to this diagram link from the passed link
 * @param {dvt.AdfDiagramLink} fromLink  Diagram link to copy from
 */
dvt.AdfDiagramLink.prototype.CopyState = function(fromLink) {
  this._categories = dvt.ArrayUtils.copy(fromLink._categories);

  this._selected = fromLink._selected;
  this._hover = fromLink._hover;

  this._bFocused = fromLink._bFocused;
  this._bFocusEffect = fromLink._bFocusEffect;
  this._bVisible = fromLink._bVisible;

  this._arPoints = dvt.ArrayUtils.copy(fromLink._arPoints);
  this._points = dvt.ArrayUtils.copy(fromLink._points);
  this._pathCmds = dvt.ArrayUtils.copy(fromLink._pathCmds);
};

dvt.AdfDiagramLink.prototype.destroy = function() {
  this.DestroyContent();

  dvt.AdfDiagramLink.superclass.destroy.call(this);
};

dvt.AdfDiagramLink.prototype.CreateCustomAnimPlayable = function() {
  return new dvt.CustomAnimation(this.getCtx(), null, this.GetDiagram().getAnimationDuration());
};


/**
 * Shows animated transformation of the link from old state to new
 * @param {DvtAdfDiagramDataAnimationHandler} dataAnimHandler Animation handler
 * @param {dvt.AdfDiagramLink} oldLink Old link
 */
dvt.AdfDiagramLink.prototype.AnimateUpdate = function(dataAnimHandler, oldLink) {
  var playable = null;

  //if visibility is changing, treat the update like an insert or delete,
  //and don't have to consider any other changes to the link
  var oldVisible = oldLink.getVisible();
  var oldPoints = oldLink.getPoints();
  var newVisible = this.getVisible();
  if (oldVisible != newVisible || !oldPoints) {
    if (!playable) {
      playable = this.CreateCustomAnimPlayable();
    }
    if (oldVisible && oldPoints) {
      //treat like a delete
      var linkParent = this.GetParentContainer();
      oldLink.InsertLinkIntoParent(linkParent);
      playable.getAnimator().addProp(dvt.Animator.TYPE_NUMBER, oldLink, oldLink.getAlpha, oldLink.setAlpha, 0);
      dvt.Playable.appendOnEnd(playable, function() {
        oldLink.getParent().removeChild(oldLink);
        oldLink.destroy();
      });
      dataAnimHandler.add(playable, DvtAdfDiagramDataAnimationHandler.PHASE_DELETE);
    }
    else if (newVisible) {
      //treat like an insert
      this.setAlpha(0);
      playable.getAnimator().addProp(dvt.Animator.TYPE_NUMBER, this, this.getAlpha, this.setAlpha, this.GetMaxAlpha());
      dataAnimHandler.add(playable, DvtAdfDiagramDataAnimationHandler.PHASE_INSERT);
    }

    //the link is either appearing or disappearing, so no need to
    //worry about other changes
    //playable = null;
    return;
  }

  var oldPoints = oldLink.getPoints();
  var newPoints = this.getPoints();
  if (!dvt.ArrayUtils.equals(oldPoints, newPoints)) {
    if (!playable) {
      playable = this.CreateCustomAnimPlayable();
    }
    this.setPoints(oldPoints);
    var linkAnimType = dvt.Animator.TYPE_POLYLINE;
    if (newPoints.length > 0 && isNaN(newPoints[0])) {
      linkAnimType = dvt.Animator.TYPE_PATH;
    }
    playable.getAnimator().addProp(linkAnimType, this, this.getPoints, this.setPoints, newPoints);
  }

  if (oldLink._labelObj && this._labelObj) {
    var oldMat = oldLink._labelObj.getMatrix();
    var newMat = this._labelObj.getMatrix();
    if (!oldMat.equals(newMat)) {
      if (!playable) {
        playable = this.CreateCustomAnimPlayable();
      }
      this._labelObj.setMatrix(oldMat);
      playable.getAnimator().addProp(dvt.Animator.TYPE_MATRIX, this._labelObj, this._labelObj.getMatrix, this._labelObj.setMatrix, newMat);
    }
  }

  if (oldLink.getShape() && this.getShape()) {
    var oldStroke = oldLink.getShape().getStroke();
    var newStroke = this.getShape().getStroke();
    if (oldStroke && newStroke &&
        oldStroke instanceof dvt.SolidStroke &&
        newStroke instanceof dvt.SolidStroke &&
        (oldStroke.getColor() != newStroke.getColor() ||
         oldStroke.getWidth() != newStroke.getWidth())) {
      if (!playable) {
        playable = this.CreateCustomAnimPlayable();
      }
      this.getShape().setStroke(oldStroke);
      playable.getAnimator().addProp(dvt.Animator.TYPE_STROKE, this.getShape(), this.getShape().getStroke, this.getShape().setStroke, newStroke);
    }
  }

  if (playable) {
    dataAnimHandler.add(playable, DvtAdfDiagramDataAnimationHandler.PHASE_UPDATE);
  }
};

/**
 * @protected
 * Animates insertion of a diagram node
 * @param {DvtAdfDiagramDataAminationHandler} dataAnimHandler daigram data animation handler
 */
dvt.AdfDiagramLink.prototype.AnimateInsert = function(dataAnimHandler) {
  var bAnim = true;
  var groupId = this.getData().getGroupId();
  if (groupId) {
    var groupNode = this.GetDiagram()._oldNodes[groupId];
    //if the group didn't exist in the old diagram, then this node
    //is a descendant of a group being inserted, so no need to
    //animate it separately because the top level inserted group
    //will handle it
    if (!groupNode) {
      bAnim = false;
    }
  }
  if (bAnim) {
    var playable = new dvt.CustomAnimation(this.getCtx(), this.GetDiagram().getAnimationDuration());
    var anim = playable.getAnimator();
    this.setAlpha(0);
    anim.addProp(dvt.Animator.TYPE_NUMBER, this, this.getAlpha, this.setAlpha, this._maxAlpha);
    dataAnimHandler.add(playable, DvtAdfDiagramDataAnimationHandler.PHASE_INSERT);
  }
};

/**
 * @protected
 * Animates deletion of a diagram link
 * @param {DvtAdfDiagramDataAminationHandler} dataAnimHandler daigram data animation handler
 * @param {dvt.AdfDiagram} newDiagram new diagram
 */
dvt.AdfDiagramLink.prototype.AnimateDelete = function(dataAnimHandler, newDiagram) {
  var bAnim = true;
  var groupId = this.getData().getGroupId();
  if (groupId) {
    var groupNode = newDiagram.getNodeById(groupId);
    //if the group node exists in the new diagram, then reparent
    //this link to it and animate it
    if (groupNode && groupNode.GetChildNodePane()) {
      this.InsertLinkIntoParent(groupNode.GetChildNodePane());
    }
    //if the group node doesn't exist in the new diagram, then
    //this link must be a descendant of a group being deleted, so
    //don't need to reparent or animate this link separately because
    //the top level group being deleted will take care of it
    else {
      bAnim = false;
    }
  }
  else {
    //if not in a group, then reparent this link and animate it
    this.InsertLinkIntoParent(newDiagram._linksPane);
  }
  if (bAnim) {
    var playable = new dvt.CustomAnimation(this.getCtx(), newDiagram.getAnimationDuration());
    var anim = playable.getAnimator();
    anim.addProp(dvt.Animator.TYPE_NUMBER, this, this.getAlpha, this.setAlpha, 0);
    var thisRef = this;
    var removeFunc = function() {
      thisRef.getParent().removeChild(thisRef);
      thisRef.destroy();
    };
    dvt.Playable.appendOnEnd(playable, removeFunc);
    dataAnimHandler.add(playable, DvtAdfDiagramDataAnimationHandler.PHASE_DELETE);
  }
};

dvt.AdfDiagramLink.prototype.setKeyboardFocusConnector = function(connector) {
  this._keyboardNavConnector = connector;
};

dvt.AdfDiagramLink.prototype.getKeyboardFocusConnector = function() {
  return this._keyboardNavConnector;
};

dvt.AdfDiagramLink.prototype._updateKeyboardFocusEffect = function(focusable) {

  var prevFocus = this.getKeyboardFocusConnector();
  if (prevFocus && prevFocus.hideKeyboardFocusEffect)
    prevFocus.hideKeyboardFocusEffect();
  if (focusable && focusable.showKeyboardFocusEffect)
    focusable.showKeyboardFocusEffect();
  this.setKeyboardFocusConnector(focusable);
};

dvt.AdfDiagramLink.prototype._movingToStart = function(direction) {
  var start = this.getLinkStart();
  var end = this.getLinkEnd();
  var linkDirectionL2R = (start.x < end.x) ? true : false;

  if ((direction == dvt.KeyboardEvent.RIGHT_ARROW && linkDirectionL2R) ||
      (direction == dvt.KeyboardEvent.LEFT_ARROW && !linkDirectionL2R))
    return false;
  else
    return true;
};

dvt.AdfDiagramLink.prototype._findConnectorByDirection = function(direction) {

  var connector = null;
  if (this._movingToStart(direction))
    connector = this.getStartConnector();
  else
    connector = this.getEndConnector();

  //we have a connector, but is it a focusable connector ?
  var focusable = this.findFocusableObject(connector);

  // if this is the same connector, we should not select it
  // but move focus to the node instead
  if (this.getKeyboardFocusConnector() == focusable)
    focusable = null;
  return focusable;
};

/**
 * Keyboard event handler
 * @param {dvt.KeyboardEvent} event keyboard event
 * @protected
 */
dvt.AdfDiagramLink.prototype.HandleKeyboardEvent = function(event) {
  var keyCode = event.keyCode;

  if (keyCode == dvt.KeyboardEvent.ENTER) {
    var eventManager = this.GetDiagram().getEventManager();
    var target = this.getShape();
    var point = target.localToStage(this.CalPointForMouseEvent());
    var mouseEvent = dvt.DomEventFactory.generateMouseEventFromKeyboardEvent(event, this.getCtx(), dvt.MouseEvent.CLICK,
        this.getCtx().getStage(),
        point.x, point.y);
    mouseEvent.target = target;
    eventManager.PreOnClick(mouseEvent);
  }
};

dvt.AdfDiagramLink.prototype.CalPointForMouseEvent = function() {
  var points = this._points;
  var x = 0, y = 0;
  if (points.length > 4) {
    var mid = points.length / 2;
    if (mid % 2 == 0) {
      x = points[mid];
      y = points[mid + 1];
    }
    else {
      x = points[mid - 1];
      y = points[mid];
    }
  }
  else if (points.length == 4) {
    x = (points[0] + points[2]) / 2;
    y = (points[1] + points[3]) / 2;
  }
  return new dvt.Point(x, y);
};

dvt.AdfDiagramLink.prototype.IsStandardConnector = function(position) {
  var connectorType = (position == 0) ? this.getStartConnectorType() : this.getEndConnectorType();
  if (connectorType)
    return true;
  else
    return false;
};

dvt.AdfDiagramLink.prototype.findFocusableObject = function(connector) {

  for (var i = 0; i < this._tabStopsConnectorArray.length; i++) {
    var focusable = this._tabStopsConnectorArray[i];
    if ((focusable instanceof dvt.AfComponent) && (focusable.getDisplayObj() == connector))
      return focusable;
  }
  return null;
};

dvt.AdfDiagramLink.prototype.getTabStopsArray = function() {
  if (!this._tabStopsConnectorArray)
    this._tabStopsConnectorArray = [];
  return this._tabStopsConnectorArray;
};

dvt.AdfDiagramLink.prototype.getCurrentTabStop = function() {
  return this.getKeyboardFocusConnector();
};

dvt.AdfDiagramLink.prototype.resetTabStops = function() {
  this.setKeyboardFocusConnector(null);
};


/**
 * Sets the cursor for this link
 */
dvt.AdfDiagramLink.prototype._refreshCursor = function() {
  var selectingCursor = dvt.SelectionEffectUtils.getSelectingCursor();
  if (this.isSelectable()) {
    this.setCursor(selectingCursor);
    return;
  }
  var spbs = this.getShowPopupBehaviors();
  if (dvt.ArrayUtils.isArray(spbs)) {
    for (var i = 0; i < spbs.length; i++) {
      if (spbs[i] && spbs[i].getTriggerType() == 'click') {
        this.setCursor(selectingCursor);
        return;
      }
    }
  }
  this.setCursor(null);
};

/**
 * Get the automation state of the link for the passed state key
 * This will return pre-transition state value until the link animation is complete.
 * @param {string} state   State key
 * @return {boolean}  state value of the diagram link
 */
dvt.AdfDiagramLink.prototype.getAutomationState = function(state) {
  var value = this.GetDiagram().getAutomationState(this.getData().getId(), state);
  if (value == null) {
    if (state == 'visible')
      return this.getVisible();
  }
  return value;
};

/**
 * @override
 */
dvt.AdfDiagramLink.prototype.getGroupId = function() {
  return this.getData().getGroupId();
};
