@Override
    public void paint(Graphics g0)
    {
        super.paint(g0);
        Graphics2D g = (Graphics2D)g0.create();

        ArrayList<Point> points = computePoints();
        if (points.isEmpty()) {
            return;      // defensive; should not happen
        }

        if (points.size() == 1) {
            this.paintSelfLoop(g, points.get(0));
            return;
        }

        // Choose line width.
        int lineWidth = 1;
        if (this.relation.lineWidth != null) {
            lineWidth = this.relation.lineWidth.intValue();
        }
        else if (this.relation.end.isInheritance()) {
            lineWidth = InheritanceController.inheritLineWidth;
        }

        // Dashed line?
        if (!this.relation.dashStructure.isEmpty()) {
            // Determine how many segments to pass to BasicStroke.
            int numSegments = this.relation.dashStructure.size();
            if ((numSegments & 1) == 1) {
                // BasicStroke has (to me) unexpected behavior with an
                // odd number of segments: it repeats the segments, but
                // swapping what is opaque and what is transparent.
                // Therefore, I will add one more zero-length segment
                // to preserve the opaque and transparent roles.
                numSegments++;
            }

            // Copy the integers to a float array for BasicStroke.
            float[] segments = new float[numSegments];    // zero-initialized
            for (int i=0; i < this.relation.dashStructure.size(); i++) {
                segments[i] = (float)this.relation.dashStructure.get(i);
            }

            // Create a dashed stroke.
            g.setStroke(new BasicStroke(
                lineWidth,
                BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_MITER,
                10.0f,       // miter limit (default)
                segments,
                0.0f));      // dash phase
        }
        else {
            // Solid line.
            g.setStroke(new BasicStroke(
                lineWidth,
                BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_MITER));
        }

        // Choose line color.
        g.setColor(this.getLineColor());

        // Draw the line segments.
        int nPoints = points.size();
        {
            // Construct a pair of arrays containing the points along
            // the line, in order to pass to drawPolyline.
            int xPoints[] = new int[nPoints];
            int yPoints[] = new int[nPoints];
            for (int i=0; i < nPoints; i++) {
                xPoints[i] = points.get(i).x;
                yPoints[i] = points.get(i).y;
            }

            // Draw them all with one call.  This is important in order
            // to draw the corners correctly (JOIN_MITER) for thick lines.
            g.drawPolyline(xPoints, yPoints, nPoints);
        }

        // Solid line for arrow heads.
        g.setStroke(new BasicStroke(lineWidth));

        // Arrowhead at start.
        {
            Point first = points.get(0);
            Point second = points.get(1);
            drawArrowhead(g, second, first, this.relation.start.arrowStyle);
        }

        // Arrowhead at end, if not an inheritance.
        if (!this.relation.end.isInheritance()) {
            Point secondToLast = points.get(nPoints-2);
            Point last = points.get(nPoints-1);
            drawArrowhead(g, secondToLast, last, this.relation.end.arrowStyle);
        }

        // Label near midpoint of first segment.
        g.setColor(this.getTextColor());
        this.drawLabelAtSegment(g, points.get(0), points.get(1), this.relation.label);
    }