/**
* 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;
}
}