package org.jboss.windup.rules.apps.java.reporting.freemarker;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.FileLocationModel;
import org.jboss.windup.graph.model.LinkModel;
import org.jboss.windup.graph.model.ProjectModel;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.model.resource.ReportResourceFileModel;
import org.jboss.windup.reporting.freemarker.WindupFreeMarkerTemplateDirective;
import org.jboss.windup.reporting.model.association.LinkableModel;
import org.jboss.windup.reporting.model.source.SourceReportModel;
import org.jboss.windup.reporting.service.SourceReportService;
import org.jboss.windup.rules.apps.java.model.AbstractJavaSourceModel;
import org.jboss.windup.rules.apps.java.model.JavaClassFileModel;
import org.jboss.windup.rules.apps.java.model.JavaClassModel;
import org.jboss.windup.rules.apps.java.model.JavaSourceFileModel;
import org.jboss.windup.rules.apps.java.service.JavaClassService;
import org.jboss.windup.util.Logging;
import freemarker.core.Environment;
import freemarker.ext.beans.StringModel;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import org.jboss.windup.util.IterableConverter;
/**
* Renders linkable elements as a list of links.
*
* @author <a href="mailto:bradsdavis@gmail.com">Brad Davis</a>
* @author <a href="mailto:zizka@seznam.cz">Ondrej Zizka</a>
*/
public class RenderLinkDirective implements WindupFreeMarkerTemplateDirective
{
private static final Logger LOG = Logging.get(RenderLinkDirective.class);
public static final String NAME = "render_link";
public static final String MODEL = "model";
public static final String TEXT = "text";
public static final String PROJECT = "project";
private GraphContext context;
private SourceReportService sourceReportService;
private JavaClassService javaClassService;
@Override
public String getDescription()
{
return "Takes the following parameters: FileModel (a " + FileModel.class.getSimpleName() + ")";
}
@Override
public void execute(Environment env, @SuppressWarnings("rawtypes") Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException
{
final Writer writer = env.getOut();
StringModel stringModel = (StringModel) params.get(MODEL);
SimpleScalar defaultTextModel = (SimpleScalar) params.get(TEXT);
String defaultText = defaultTextModel == null ? null : defaultTextModel.getAsString();
if (stringModel == null || stringModel.getWrappedObject() == null)
{
if (StringUtils.isNotBlank(defaultText))
writer.append(defaultText);
else
{
writer.append("unknown");
LOG.warning("Failed to resolve name or text for " + getClass().getName() + ". " + env);
}
return;
}
StringModel projectStringModel = (StringModel) params.get(PROJECT);
ProjectModel project = null;
if (projectStringModel != null && projectStringModel.getWrappedObject() instanceof ProjectModel)
project = (ProjectModel)projectStringModel.getWrappedObject();
Object model = stringModel.getWrappedObject();
LayoutType layoutType = resolveLayoutType(params);
String cssClass = resolveCssClass(params);
if (model instanceof FileLocationModel)
{
processFileLocationModel(writer, cssClass, project, (FileLocationModel) model, defaultText);
}
else if (model instanceof FileModel)
{
processFileModel(writer, cssClass, project, (FileModel) model, defaultText);
}
else if (model instanceof JavaClassModel)
{
processJavaClassModel(writer, cssClass, project, (JavaClassModel) model, defaultText);
}
else if (model instanceof LinkableModel)
{
processLinkableModel(writer, layoutType, cssClass, project, (LinkableModel) model, defaultText);
}
else
{
throw new TemplateException("Object type not permitted: " + model.getClass().getSimpleName(), env);
}
}
private String resolveCssClass(Map params)
{
SimpleScalar css = (SimpleScalar) params.get("class");
if (css == null)
return "";
else
return css.getAsString();
}
private LayoutType resolveLayoutType(Map params) throws TemplateException
{
LayoutType layoutType = LayoutType.HORIZONTAL;
SimpleScalar layoutModel = (SimpleScalar) params.get("layout");
if (layoutModel != null)
{
String lt = layoutModel.getAsString();
try
{
layoutType = LayoutType.valueOf(lt.toUpperCase());
}
catch (IllegalArgumentException e)
{
throw new TemplateException("Layout: " + lt + " is not supported.", e, null);
}
}
return layoutType;
}
private void processFileLocationModel(Writer writer, String cssClass, ProjectModel project, FileLocationModel obj, String defaultText) throws IOException
{
String position = " (" + obj.getLineNumber() + ", " + obj.getColumnNumber() + ")";
String linkText = StringUtils.isBlank(defaultText) ? getPrettyPathForFile(obj.getFile()) + position : defaultText;
String anchor = obj.asVertex().getId().toString();
SourceReportModel result = sourceReportService.getSourceReportForFileModel(obj.getFile());
if (result == null)
writer.write(linkText);
else
renderLink(writer, cssClass, project, result.getReportFilename() + "#" + anchor, linkText);
}
private void processLinkableModel(Writer writer, LayoutType layoutType, String cssClass, ProjectModel project, LinkableModel obj, String defaultText) throws IOException
{
Iterable<Link> links = new IterableConverter<LinkModel, Link>(obj.getLinks())
{
public Link from(LinkModel m) { return new Link(m.getLink(), m.getDescription()); }
};
renderLinks(writer, layoutType, project, links);
}
private void processFileModel(Writer writer, String cssClass, ProjectModel project, FileModel fileModel, String defaultText) throws IOException
{
String linkText = StringUtils.isBlank(defaultText) ? getPrettyPathForFile(fileModel) : defaultText;
SourceReportModel result = sourceReportService.getSourceReportForFileModel(fileModel);
if (result == null)
writer.write(linkText);
else
renderLink(writer, cssClass, project, result.getReportFilename(), linkText);
}
private void processJavaClassModel(Writer writer, String cssClass, ProjectModel project, JavaClassModel clz, String defaultText)
throws IOException
{
Iterator<AbstractJavaSourceModel> results = javaClassService.getJavaSource(clz.getQualifiedName()).iterator();
if (!results.hasNext())
{
writer.write(clz.getQualifiedName());
return;
}
String linkText = StringUtils.isBlank(defaultText) ? clz.getQualifiedName() : defaultText;
int i = 2;
while (results.hasNext())
{
AbstractJavaSourceModel source = results.next();
SourceReportModel result = sourceReportService.getSourceReportForFileModel(source);
if (result == null)
writer.write(linkText);
else
renderLink(writer, cssClass, project, result.getReportFilename(), linkText);
linkText = " (" + i++ + ")";
}
}
private void renderLinks(Writer writer, LayoutType layoutType, ProjectModel project, Iterable<Link> linkIterable) throws IOException
{
Iterator<Link> links = linkIterable.iterator();
if (null == layoutType)
layoutType = LayoutType.HORIZONTAL;
switch (layoutType)
{
case UL:
renderAsLI(writer, project, links, true);
break;
case LI:
renderAsLI(writer, project, links, false);
break;
case DL:
renderAsDT(writer, project, links, true);
break;
case DT:
renderAsDT(writer, project, links, false);
break;
default:
renderAsHorizontal(writer, project, links);
break;
}
}
private void renderLink(Writer writer, String cssClass, ProjectModel project, String href, String linkText) throws IOException
{
writer.append("<a");
if (cssClass != null)
writer.append(" class='" + cssClass + "'");
writer.append(" href='").append(href);
appendProject(writer, project);
writer.append("'>").append(linkText).append("</a>");
}
/**
* Renders in LI tags, Wraps with UL tags optionally.
*/
private void renderAsLI(Writer writer, ProjectModel project, Iterator<Link> links, boolean wrap) throws IOException
{
if (!links.hasNext())
return;
if (wrap)
writer.append("<ul>");
while (links.hasNext())
{
Link link = links.next();
writer.append("<li>");
renderLink(writer, project, link);
writer.append("</li>");
}
if (wrap)
writer.append("</ul>");
}
/*
* Renders as DT elements, optionally wraps in DL
*/
private void renderAsDT(Writer writer, ProjectModel project, Iterator<Link> links, boolean wrap) throws IOException
{
if (!links.hasNext())
return;
if (wrap)
writer.append("<dl>");
while (links.hasNext())
{
Link link = links.next();
writer.append("<dt>").append(link.getDescription());
writer.append("</dt><dd><a href='").append(link.getLink());
appendProject(writer, project);
writer.append("'>Link</a></dd>");
}
if (wrap)
writer.append("</dl>");
}
private void appendProject(Writer writer, ProjectModel project) throws IOException {
if (project != null)
writer.append("?project=").append(String.valueOf(project.asVertex().getId()));
}
private void renderAsHorizontal(Writer writer, ProjectModel project, Iterator<Link> links) throws IOException
{
if (links.hasNext())
return;
renderLink(writer, project, links.next());
while (links.hasNext())
{
writer.append(" | ");
renderLink(writer, project, links.next());
}
}
private void renderLink(Writer writer, ProjectModel project, Link link) throws IOException
{
writer.append("<a href='").append(link.getLink());
appendProject(writer, project);
writer.append("' target='_blank'>");
writer.append(link.getDescription());
writer.append("</a>");
}
private String getPrettyPathForFile(FileModel fileModel)
{
if (fileModel instanceof JavaClassFileModel)
{
JavaClassFileModel jcfm = (JavaClassFileModel) fileModel;
if (jcfm.getJavaClass() == null)
return fileModel.getPrettyPathWithinProject();
else
return jcfm.getJavaClass().getQualifiedName();
}
else if (fileModel instanceof ReportResourceFileModel)
{
return "resources/" + fileModel.getPrettyPath();
}
else if (fileModel instanceof JavaSourceFileModel)
{
JavaSourceFileModel javaSourceModel = (JavaSourceFileModel) fileModel;
String filename = StringUtils.removeEndIgnoreCase(fileModel.getFileName(), ".java");
String packageName = javaSourceModel.getPackageName();
return packageName == null || packageName.isEmpty() ? filename : packageName + "." + filename;
}
else
{
return fileModel.getPrettyPathWithinProject();
}
}
@Override
public String getDirectiveName()
{
return NAME;
}
@Override
public void setContext(GraphRewrite event)
{
this.context = event.getGraphContext();
this.sourceReportService = new SourceReportService(this.context);
this.javaClassService = new JavaClassService(this.context);
}
private static enum LayoutType
{
HORIZONTAL, UL, DL, LI, DT
}
private static class Link
{
private final String link;
private final String description;
Link(String link, String description)
{
this.link = link;
this.description = description;
}
private static List<Link> fromLinkModels(Iterable<LinkModel> links)
{
List<Link> links2 = new LinkedList<>();
for (LinkModel model : links)
links2.add(new Link(model.getLink(), model.getDescription()));
return links2;
}
public String getLink()
{
return link;
}
public String getDescription()
{
return description;
}
}
}