net.roboconf.doc.generator.internal.transformers.HierarchicalTransformer.java Source code

Java tutorial

Introduction

Here is the source code for net.roboconf.doc.generator.internal.transformers.HierarchicalTransformer.java

Source

/**
 * Copyright 2015-2017 Linagora, Universit Joseph Fourier, Floralis
 *
 * The present code is developed in the scope of the joint LINAGORA -
 * Universit Joseph Fourier - Floralis research program and is designated
 * as a "Result" pursuant to the terms and conditions of the LINAGORA
 * - Universit Joseph Fourier - Floralis research program. Each copyright
 * holder of Results enumerated here above fully & independently holds complete
 * ownership of the complete Intellectual Property rights applicable to the whole
 * of said Results, and may freely exploit it in any manner which does not infringe
 * the moral rights of the other copyright holders.
 *
 * 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 net.roboconf.doc.generator.internal.transformers;

import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.roboconf.core.model.beans.AbstractType;
import net.roboconf.core.model.beans.Component;
import net.roboconf.doc.generator.internal.GraphUtils;
import edu.uci.ics.jung.graph.DirectedOrderedSparseMultigraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.decorators.AbstractEdgeShapeTransformer;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;

/**
 * A transformer to find vertex positions for Roboconf's hierarchical relations.
 * @author Vincent Zurczak - Linagora
 */
public class HierarchicalTransformer extends AbstractRoboconfTransformer {

    private static final int MIN_H_MARGIN = 100;
    private static final int H_PADDING = 50;
    private static final int V_PADDING = 2 * GraphUtils.SHAPE_HEIGHT;
    private static final int V_MARGIN = 50;

    private final Component component;
    private final Map<AbstractType, Point2D> typeToLocation;
    private final int maxPerLine;
    private final List<AbstractType> aloneOnRow;

    private final Graph<AbstractType, String> graph;
    private int currentWidth = MIN_H_MARGIN;
    private int currentHeigth = V_MARGIN;
    private int maxRowWidth, hMargin;

    /**
     * Constructor.
     * @param component the component whose hierarchy must be displayed
     * @param ancestors its ancestors
     * @param children its children
     * @param maxPerLine the maximum number of vertices per line
     */
    public HierarchicalTransformer(Component component, Collection<AbstractType> ancestors,
            Collection<AbstractType> children, int maxPerLine) {

        // Store fields
        this.component = component;
        this.maxPerLine = maxPerLine;
        this.typeToLocation = new HashMap<AbstractType, Point2D>();
        this.aloneOnRow = new ArrayList<AbstractType>();

        // Compute the effective horizontal margin for this graph
        this.hMargin = computeHMargin(component);
        for (AbstractType t : ancestors)
            this.hMargin = Math.max(this.hMargin, computeHMargin(t));

        for (AbstractType t : children)
            this.hMargin = Math.max(this.hMargin, computeHMargin(t));

        // Builds the graph
        this.graph = new DirectedOrderedSparseMultigraph<AbstractType, String>();
        int cpt = 1;

        for (AbstractType t : ancestors)
            this.graph.addVertex(t);

        this.graph.addVertex(component);
        for (AbstractType t : ancestors)
            this.graph.addEdge("can contain" + cpt++, t, component);

        for (AbstractType t : children) {
            this.graph.addVertex(t);
            this.graph.addEdge("can contain" + cpt++, component, t);
        }

        // In these first steps, vertices are aligned on the left
        if (!ancestors.isEmpty()) {
            dealWithOthers(ancestors);
            this.currentHeigth += V_PADDING;
        }

        dealWithMainComponent();

        if (!children.isEmpty()) {
            this.currentHeigth += V_PADDING;
            dealWithOthers(children);
        }

        this.currentHeigth += V_MARGIN;

        // Center alone vertices
        for (AbstractType t : this.aloneOnRow) {
            int width = GraphUtils.computeShapeWidth(t);
            int newX = (this.maxRowWidth - width) / 2;

            if (newX > this.hMargin) {
                Point2D p = transform(t);
                p.setLocation(newX, p.getY());
            }
        }
    }

    /**
     * Finds the position for the main component.
     */
    private void dealWithMainComponent() {

        this.typeToLocation.put(this.component, new Point2D.Double(this.hMargin, this.currentHeigth));

        int width = H_PADDING + GraphUtils.computeShapeWidth(this.component);
        this.maxRowWidth = Math.max(this.maxRowWidth, width);
        this.aloneOnRow.add(this.component);
    }

    /**
     * Finds the position of other components.
     * @param others a non-null list of components
     */
    private void dealWithOthers(Collection<AbstractType> others) {

        int col = 1;
        this.currentWidth = this.hMargin;
        for (AbstractType t : others) {

            if (col > this.maxPerLine) {
                col = 1;
                this.currentHeigth += V_PADDING + GraphUtils.SHAPE_HEIGHT;
                this.currentWidth = this.hMargin;
            }

            this.typeToLocation.put(t, new Point2D.Double(this.currentWidth, this.currentHeigth));
            this.currentWidth += H_PADDING + GraphUtils.computeShapeWidth(t);
            col++;

            this.maxRowWidth = Math.max(this.maxRowWidth, this.currentWidth);
        }

        if (others.size() == 1)
            this.aloneOnRow.add(others.iterator().next());
    }

    /*
     * (non-Javadoc)
     * @see org.apache.commons.collections15.Transformer
     * #transform(java.lang.Object)
     */
    @Override
    public Point2D transform(AbstractType input) {
        return this.typeToLocation.get(input);
    }

    /*
     * (non-Javadoc)
     * @see net.roboconf.doc.generator.internal.transformers.AbstractRoboconfTransformer
     * #getGraphDimension()
     */
    @Override
    public Dimension getGraphDimension() {
        return new Dimension(this.maxRowWidth, this.currentHeigth);
    }

    /*
     * (non-Javadoc)
     * @see net.roboconf.doc.generator.internal.transformers.AbstractRoboconfTransformer
     * #getGraph()
     */
    @Override
    public Graph<AbstractType, String> getGraph() {
        return this.graph;
    }

    /*
     * (non-Javadoc)
     * @see net.roboconf.doc.generator.internal.transformers.AbstractRoboconfTransformer
     * #getEdgeShapeTransformer()
     */
    @Override
    public AbstractEdgeShapeTransformer<AbstractType, String> getEdgeShapeTransformer() {
        return new EdgeShape.Line<AbstractType, String>();
    }

    /**
     * Computes an horizontal margin for a given node.
     * <p>
     * We used to have a static horizontal margin, but images were
     * truncated for long names. See roboconf-platform#315
     * </p>
     *
     * @param input a node (can be null)
     * @return a positive integer
     */
    private static int computeHMargin(AbstractType input) {

        int basis = MIN_H_MARGIN;
        if (input != null && input.getName().length() > 17) {
            // Beyond 17 characters, we give 3 pixels for every new characters.
            basis += 3 * (input.getName().length() - 17);
        }

        return basis;
    }
}