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