package org.jboss.windup.rules.apps.java.reporting.freemarker;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.ProjectModel;
import org.jboss.windup.graph.traversal.ProjectModelTraversal;
import org.jboss.windup.reporting.freemarker.FreeMarkerUtil;
import org.jboss.windup.reporting.freemarker.WindupFreeMarkerTemplateDirective;
import org.jboss.windup.rules.apps.java.service.TypeReferenceService;
import freemarker.core.Environment;
import freemarker.ext.beans.StringModel;
import freemarker.template.SimpleScalar;
import freemarker.template.SimpleSequence;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import org.jboss.windup.util.exception.WindupException;
/**
* Renders a JavaScript block that calls <a href="http://www.flotcharts.org/">Flot</a>. This depends upon the template already loading the JQuery and
* Flot Charting Javascript files.
*
* The chart will present a distribution of packages that have been hinted by Windup.
*
* @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
*
*/
public class RenderApplicationPieChartDirective implements WindupFreeMarkerTemplateDirective
{
public static final String NAME = "render_pie";
private GraphContext context;
@Override
public String getDescription()
{
return "Renders a pie chart. Takes the following parameters: project (a " + ProjectModel.class.getSimpleName()
+ "), recursive (Boolean), and elementid (HTML ID of the element in which to render).";
}
@Override
public void execute(Environment env, @SuppressWarnings("rawtypes") Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body)
throws TemplateException, IOException
{
StringModel projectStringModel = (StringModel) params.get("project");
ProjectModel projectModel = null;
if (projectStringModel != null)
projectModel = (ProjectModel) projectStringModel.getWrappedObject();
StringModel projectTraversalStringModel = (StringModel) params.get("projectTraversal");
ProjectModelTraversal projectTraversal = null;
if (projectTraversalStringModel != null)
projectTraversal = (ProjectModelTraversal) projectTraversalStringModel.getWrappedObject();
if (projectModel != null && projectTraversal != null)
throw new WindupException(NAME + " both 'project' and 'projectTraversal' were specified. Only one of these values should be specified.");
else if (projectModel == null && projectTraversal == null)
throw new WindupException(NAME + " neither 'project' nor 'projectTraversal' were specified. At least one of these must be specified.");
TemplateBooleanModel recursiveBooleanModel = (TemplateBooleanModel) params.get("recursive");
boolean recursive = recursiveBooleanModel.getAsBoolean();
SimpleScalar elementIDStringModel = (SimpleScalar) params.get("elementID");
String elementID = elementIDStringModel.getAsString();
Set<String> includeTags = FreeMarkerUtil.simpleSequenceToSet((SimpleSequence) params.get("includeTags"));
Set<String> excludeTags = FreeMarkerUtil.simpleSequenceToSet((SimpleSequence) params.get("excludeTags"));
TypeReferenceService typeReferenceService = new TypeReferenceService(context);
Map<String, Integer> data;
if (projectModel != null)
data = typeReferenceService.getPackageUseFrequencies(projectModel, includeTags, excludeTags, 2, recursive);
else
data = typeReferenceService.getPackageUseFrequencies(projectTraversal, includeTags, excludeTags, 2, recursive);
if (data.keySet().size() > 0)
{
drawPie(env.getOut(), data, elementID);
}
else
{
// if we aren't drawing a pie, remove the element that would have held it
Writer writer = env.getOut();
writer.append("<script type='text/javascript'>");
writer.append("$('#" + elementID + "').parent().remove()");
writer.append("</script>");
}
}
private void drawPie(Writer writer, Map<String, Integer> data, String elementID) throws IOException
{
List<PieSort> pieList = topX(data, 9);
String dataVarName = "data_" + elementID;
writer.append("<script type='text/javascript'>");
writer.append("\nWINDUP_PACKAGE_PIE_DATA = typeof(WINDUP_PACKAGE_PIE_DATA) == 'undefined' ? {} : WINDUP_PACKAGE_PIE_DATA;");
writer.append("\nWINDUP_PACKAGE_PIE_DATA['").append(elementID).append("'] = [];");
for (PieSort p : pieList)
{
writer.append("\nWINDUP_PACKAGE_PIE_DATA['").append(elementID).append("'].push({label: '" + p.label + "', data: ")
.append(p.value.toString()).append("});");
}
writer.append("\n$(function () {");
writer.append("\n var " + dataVarName + " = [];");
for (PieSort p : pieList)
writer.append("\n").append(dataVarName).append(".push({ label: '").append(p.key).append("', data: ").append(p.value.toString()).append(" });");
writer.append("\n $.plot($('#" + elementID + "'), " + dataVarName + ", {");
writer.append("\n series: { pie: { show: true, innerRadius: 0.55, offset: { top: 0, left: -120 } } },");
writer.append("\n colors: $.map( " + dataVarName + ", function(item, index) {" +
"\n var len = " + dataVarName + ".length;" +
"\n return jQuery.Color({" +
"\n hue: ((index*0.95*360/len) + 90/len) % 360," +
"\n saturation: 0.95," +
"\n lightness: ((index%4 == 3 ? 1:0)/-4)+0.55, alpha: 1" +
"\n }).toHexString();" +
"\n })");
writer.append("\n });");
writer.append("\n});");
writer.append("</script>");
}
@Override
public String getDirectiveName()
{
return NAME;
}
private List<PieSort> topX(Map<String, Integer> map, int top)
{
List<PieSort> list = new ArrayList<>(map.keySet().size() + 1);
// Add the key/value pairs to the list containing the PieSort(key,value) object.
for (String key : map.keySet())
{
PieSort p = new PieSort(key + " - " + map.get(key) + "×", key, map.get(key));
list.add(p);
}
Collections.sort(list);
// Collect the bottom of the list for the "Other" category.
if (top < list.size())
{
// Add the "Other" category up.
int other = 0;
for (PieSort p : list.subList(top, list.size()))
other += p.value;
list = list.subList(0, top);
if (other > 0)
list.add(new PieSort("Other - " + other + "×", "Other", other));
}
return list;
}
private static class PieSort implements Comparable<PieSort>
{
public final String key;
public final String label;
public final Integer value;
PieSort(String k, String label, Integer v)
{
this.key = k;
this.label = label;
this.value = v;
}
@Override
public int compareTo(PieSort p)
{
return p.value - value;
}
}
@Override
public void setContext(GraphRewrite event)
{
this.context = event.getGraphContext();
}
}