com.sangupta.resumemaker.export.HtmlExport.java Source code

Java tutorial

Introduction

Here is the source code for com.sangupta.resumemaker.export.HtmlExport.java

Source

/**
 *
 * Resume Maker
 * Copyright (c) 2011, Sandeep Gupta
 * 
 * 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 com.sangupta.resumemaker.export;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.resource.loader.FileResourceLoader;
import org.apache.velocity.tools.generic.AlternatorTool;

import com.google.code.linkedinapi.schema.Education;
import com.google.code.linkedinapi.schema.Position;
import com.sangupta.jerry.util.AssertUtils;
import com.sangupta.resumemaker.Exporter;
import com.sangupta.resumemaker.export.svg.Circle;
import com.sangupta.resumemaker.export.svg.Line;
import com.sangupta.resumemaker.export.svg.Path;
import com.sangupta.resumemaker.export.svg.Rectangle;
import com.sangupta.resumemaker.export.svg.SVGBuilder;
import com.sangupta.resumemaker.export.svg.Text;
import com.sangupta.resumemaker.github.GitHubCommitData;
import com.sangupta.resumemaker.github.GitHubRepositoryData;
import com.sangupta.resumemaker.linkedin.LinkedInHelper;
import com.sangupta.resumemaker.model.Event;
import com.sangupta.resumemaker.model.UserData;
import com.sangupta.resumemaker.util.DateUtils;
import com.sangupta.resumemaker.util.HtmlUtils;
import com.sangupta.resumemaker.velocity.directives.LinkedInDatesDirective;
import com.sangupta.resumemaker.velocity.directives.MarkdownDirective;

public class HtmlExport implements Exporter {

    private static final int RADIUS_Y = 50;

    private static final String TEMPLATE_NAME = "resume.template.html";

    private static final String TEMPLATE_FOLDER = "./templates/" + TEMPLATE_NAME;

    private static final int GRAPHIC_WIDTH = 800;

    private static final VelocityEngine engine = new VelocityEngine();

    static {
        final String[] customDirectives = { LinkedInDatesDirective.class.getName(),
                MarkdownDirective.class.getName() };

        StringBuilder builder = new StringBuilder();
        for (String directive : customDirectives) {
            builder.append(directive).append(",");
        }
        builder.deleteCharAt(builder.length() - 1);

        final String directives = builder.toString();

        File file = new File(TEMPLATE_FOLDER);
        if (!file.exists()) {
            throw new RuntimeException("Cannot find template at " + file.getAbsolutePath());
        }

        file = file.getAbsoluteFile();

        // initialize the velocity engine
        Properties properties = new Properties();
        properties.setProperty(VelocityEngine.RESOURCE_LOADER, "file");
        properties.setProperty("file" + VelocityEngine.RESOURCE_LOADER + ".class",
                FileResourceLoader.class.getName());
        properties.setProperty("userdirective", directives);

        properties.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, file.getParentFile().getAbsolutePath());

        engine.init(properties);
    }

    @Override
    public void export(UserData userData, File exportFile) {
        Template template = engine.getTemplate(TEMPLATE_NAME);

        VelocityContext pageModel = getModel(userData);

        StringWriter writer = new StringWriter();
        try {
            template.merge(pageModel, writer);
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try {
            String unformattedHTML = writer.toString();
            String formattedHTML = HtmlUtils.tidyHtml(unformattedHTML);
            FileUtils.write(exportFile, formattedHTML);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private VelocityContext getModel(UserData userData) {
        VelocityContext context = new VelocityContext();

        context.put("name", userData.linkedInUserData.getName());
        context.put("createdOn", new Date());
        context.put("linkedin", userData.linkedInUserData);
        context.put("gravatarURL", userData.gravatarImageURL);

        // build linkedin jobs timeline
        List<Event> events = new ArrayList<Event>();
        for (Position position : userData.linkedInUserData.getPositions()) {
            Event event = new Event(position.getCompany().getName(),
                    LinkedInHelper.fromStartDate(position.getStartDate()),
                    LinkedInHelper.fromEndDate(position.getEndDate()));
            events.add(event);
            event.setDescription(position.getTitle());
        }

        context.put("positions", events);
        context.put("positionsTimeLine", createSVGTimeLineCode(events));

        // build education time line
        events = new ArrayList<Event>();
        for (Education education : userData.linkedInUserData.getEducations()) {
            Event event = new Event(education.getSchoolName(),
                    LinkedInHelper.fromStartDate(education.getStartDate()),
                    LinkedInHelper.fromEndDate(education.getEndDate()));
            event.setDescription(education.getDegree());
            events.add(event);
        }
        context.put("educations", events);
        context.put("educationTimeLine", createSVGTimeLineCode(events));

        // set the github data
        userData.gitHubData.sortRepositories();
        context.put("github", userData.gitHubData);
        context.put("githubGraph", createGithubGraph(userData.gitHubData.getCommitDatas()));

        // for each repository - prepare a separate SVG graph
        // of 52 week commit history
        final Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) - 1);
        final Date oneYearPrevious = cal.getTime();

        for (GitHubRepositoryData repo : userData.gitHubData.getRepositories()) {
            final String repoName = repo.getName();

            List<GitHubCommitData> commits = new ArrayList<GitHubCommitData>();
            // build a list of commits
            for (GitHubCommitData commit : userData.gitHubData.getCommitDatas()) {
                if (commit.repositoryID.equals(repoName) && commit.createdAt.after(oneYearPrevious)) {
                    commits.add(commit);
                }
            }

            // build the SVG graph
            String graph = createGithubWeeklyGraph(commits);
            repo.setGithubCommitGraph(graph);
        }

        // for alternating rows
        context.put("alternator", new AlternatorTool());

        // return the final context back
        return context;
    }

    private String createGithubWeeklyGraph(List<GitHubCommitData> commits) {
        if (commits == null) {
            return null;
        }

        Collections.sort(commits);

        int[] weeklyValues = new int[52];

        final int GRAPH_WIDTH = 412;
        final int GRAPH_HEIGHT = 70;

        final int BAR_WIDTH = 8;

        final int ORIGIN_X = GRAPH_WIDTH - (52 * BAR_WIDTH);
        final int ORIGIN_Y = GRAPH_HEIGHT - 50;

        // find max Y
        for (GitHubCommitData commit : commits) {
            int linesCommitted = commit.additions + commit.deletions;
            int week = DateUtils.getWeekOfYear(commit.createdAt);
            weeklyValues[week] = weeklyValues[week] + linesCommitted;
        }

        int maxCommits = 0;
        for (int i = 0; i < weeklyValues.length; i++) {
            int linesCommitted = weeklyValues[i];
            maxCommits += linesCommitted;
        }

        float yScalingFactor = 0f;
        if (maxCommits > 0) {
            yScalingFactor = ((float) ORIGIN_Y) / maxCommits;
        }

        // start building the graph
        SVGBuilder svgBuilder = new SVGBuilder(GRAPH_WIDTH, GRAPH_HEIGHT);

        // build the timeline
        for (int i = 0; i < 52; i++) {
            float startX = ORIGIN_X + i * 8;
            float y = ORIGIN_Y + 1;
            Line line = new Line(startX, y, startX + 7, y, "graphWeekMark");
            svgBuilder.addLine(line);
        }

        // the data bars
        for (int i = 0; i < weeklyValues.length; i++) {
            int week = i;
            int sum = weeklyValues[i];

            if (sum > 0) {
                float startX = ORIGIN_X + (week - 1) * 8;
                float topY = ORIGIN_Y - (yScalingFactor * sum);

                Rectangle rectangle = new Rectangle(startX + 1, topY, 6, ORIGIN_Y - topY, "weeklyGraphBar");
                svgBuilder.addRectangle(rectangle);
            }
        }

        // create the legend
        Rectangle rectangle = new Rectangle(10 + ORIGIN_X, ORIGIN_Y + 15, 10, 10);
        svgBuilder.addRectangle(rectangle);

        Text text = new Text(25 + ORIGIN_X, ORIGIN_Y + 25, "commits by this user", "start", "graphLegendText");
        svgBuilder.addText(text);

        text = new Text(GRAPH_WIDTH - 150, ORIGIN_Y + 25, "52 week participation", "start", "graphLegendText");
        svgBuilder.addText(text);

        return svgBuilder.toString();
    }

    /**
     * Create an area graph of commit data
     * 
     * @param commitDatas
     * @return
     */
    private String createGithubGraph(List<GitHubCommitData> commits) {
        if (AssertUtils.isEmpty(commits)) {
            return null;
        }

        Collections.sort(commits);

        final int startYear = DateUtils.getYear(commits.get(0).createdAt);
        final int endYear = DateUtils.getYear(commits.get(commits.size() - 1).createdAt) + 2; // add TWO to make sure that we if we are nearing the end of the current year, then we have enough space at the end of the graph
        float totalYearSegments = endYear - startYear;

        final float yearSegmentWidth = GRAPHIC_WIDTH / totalYearSegments;
        final float weekSegmentWidth = yearSegmentWidth / 52;

        final int X_AXIS_MOVED = 100;
        final int HEIGHT_OF_GRAPH = 250;
        final int THICKNESS = 3;

        SVGBuilder svgBuilder = new SVGBuilder(GRAPHIC_WIDTH + X_AXIS_MOVED, HEIGHT_OF_GRAPH + 50);

        // create the basic timeline
        Rectangle rectangle = new Rectangle(0 + X_AXIS_MOVED, HEIGHT_OF_GRAPH, GRAPHIC_WIDTH, THICKNESS,
                "graphGridLines");
        svgBuilder.addRectangle(rectangle);

        // add the caption
        Text text = new Text(GRAPHIC_WIDTH / 2, HEIGHT_OF_GRAPH + 50, "Commit Timeline");
        svgBuilder.addText(text);

        // add X-axis labels
        final float textAdditive = yearSegmentWidth * 0.5f;

        for (int year = startYear; year < endYear; year++) {
            int index = year - startYear;
            float x = yearSegmentWidth * index;

            // add the year vertical bar distinguisher
            rectangle = new Rectangle(x + X_AXIS_MOVED, HEIGHT_OF_GRAPH, THICKNESS, 10, "graphGridLines");
            svgBuilder.addRectangle(rectangle);

            // add the year number
            text = new Text(x + textAdditive + X_AXIS_MOVED, HEIGHT_OF_GRAPH + 20f, String.valueOf(year), "middle",
                    "xAxisLabels");
            svgBuilder.addText(text);
        }

        // determine max value for y-axis
        float maxLines = 0;
        for (GitHubCommitData commit : commits) {
            maxLines += commit.additions - commit.deletions;
        }

        // normalize the maximum value
        final float lineFactor = HEIGHT_OF_GRAPH / maxLines;

        // create the Y-AXIS
        rectangle = new Rectangle(X_AXIS_MOVED, 0, THICKNESS, HEIGHT_OF_GRAPH, "graphGridLines");
        svgBuilder.addRectangle(rectangle);

        final int Y_AXIS_DIVISIONS = 5;
        float yInterval = maxLines / Y_AXIS_DIVISIONS;
        for (int count = 0; count < Y_AXIS_DIVISIONS; count++) {
            float lines = count * yInterval;
            float y = lines * lineFactor;
            rectangle = new Rectangle(X_AXIS_MOVED - 10, y, 10, THICKNESS, "graphGridLines");
            svgBuilder.addRectangle(rectangle);

            text = new Text(X_AXIS_MOVED - 15, HEIGHT_OF_GRAPH - y + 5, String.valueOf(((int) lines)), "end",
                    "yAxisLabels");
            svgBuilder.addText(text);
        }

        // System.out.println("Line factor: " + lineFactor);

        float totalLines = 0;
        int lastMyYear = 0;
        int lastMyWeek = 0;

        float lastX = 0f;
        float lastY = 0f;

        for (GitHubCommitData commit : commits) {
            final int myYear = DateUtils.getYear(commit.createdAt);
            final int myWeek = DateUtils.getWeekOfYear(commit.createdAt);

            if (myYear == lastMyYear && myWeek == lastMyWeek) {
                totalLines += commit.additions + commit.deletions;
            } else {
                float x = ((myYear - startYear) * yearSegmentWidth) + ((myWeek - 1) * weekSegmentWidth);
                float y = totalLines * lineFactor;

                if (!(y == lastY && y == 0.0)) {
                    Line line = new Line(lastX + X_AXIS_MOVED, HEIGHT_OF_GRAPH - lastY, x + X_AXIS_MOVED,
                            HEIGHT_OF_GRAPH - y, "trendLine");
                    svgBuilder.addLine(line);
                }

                lastMyYear = myYear;
                lastMyWeek = myWeek;

                // System.out.println("Week " + myWeek + " of year " + myYear + " had totalLines of " + totalLines + " as (" + x + ", " + y + ")");

                lastX = x;
                lastY = y;
            }
        }

        return svgBuilder.toString();
    }

    private String createSVGTimeLineCode(List<Event> events) {
        if (events == null) {
            return null;
        }

        // sort the collection based on the dates
        Collections.sort(events);

        // start finding the segment widths
        // and build up the array
        final int startYear = DateUtils.getYear(events.get(0).getStartDate());
        final int endYear = DateUtils.getYear(events.get(events.size() - 1).getEndDate()) + 2; // add TWO to make sure that we if we are nearing the end of the current year, then we have enough space at the end of the graph

        float totalYearSegments = endYear - startYear;
        final float yearSegmentWidth = GRAPHIC_WIDTH / totalYearSegments;
        final float monthSegmentWidth = yearSegmentWidth / 12;

        final int BASE_LINE = 200;
        final int THICKNESS = 3;

        SVGBuilder svgBuilder = new SVGBuilder(GRAPHIC_WIDTH, 250);

        // create the basic timeline
        Rectangle rectangle = new Rectangle(0, BASE_LINE, GRAPHIC_WIDTH, THICKNESS, "graphGridLines");
        svgBuilder.addRectangle(rectangle);

        final float textAdditive = yearSegmentWidth * 0.5f;

        for (int year = startYear; year < endYear; year++) {
            int index = year - startYear;
            float x = yearSegmentWidth * index;

            // add the year vertical bar distinguisher
            rectangle = new Rectangle(x, 200, THICKNESS, 10, "graphGridLines");
            svgBuilder.addRectangle(rectangle);

            // add the year number
            Text text = new Text(x + textAdditive, 220f, String.valueOf(year), "middle", "xAxisLabels");
            svgBuilder.addText(text);
        }

        // start building the shape for each year
        for (int index = 0; index < events.size(); index++) {
            Event event = events.get(index);

            int myStartYear = DateUtils.getYear(event.getStartDate());
            int myStartMonth = DateUtils.getMonth(event.getStartDate());

            // compute the arc dimensions
            float startX = ((myStartYear - startYear) * yearSegmentWidth) + (myStartMonth * monthSegmentWidth);

            float endX;
            Date endDate;
            if (event.getEndDate() != null) {
                endDate = event.getEndDate();
            } else {
                endDate = Calendar.getInstance().getTime();
            }

            int myEndYear = DateUtils.getYear(endDate);
            int myEndMonth = DateUtils.getMonth(endDate);

            endX = ((myEndYear - startYear) * yearSegmentWidth) + (myEndMonth * monthSegmentWidth);

            final float mid = (endX + startX) / 2;

            String styleClassName = "fillColor" + String.valueOf((index % 8) + 1);

            Path path = new Path();
            path.setStyleClassName(styleClassName);
            path.moveTo(startX, 199).arc(endX, 199, ((endX - startX) / 2), RADIUS_Y, 0, false, true).close();
            svgBuilder.addPath(path);

            // compute the label path
            Line line = new Line(mid, 112, mid, 160, "graphGridLines");
            svgBuilder.addLine(line);

            // add the star or circle around the end
            if (event.getEndDate() != null) {
                Circle circle = new Circle(mid, 160, 3);
                svgBuilder.addCircle(circle);
            } else {
                // for the last event on the timeline
                // add a star
                svgBuilder.addPath(createStarPath(mid, 160));
            }

            // add the position name over this line
            Text text = new Text(mid, 100, event.getName(), "middle", "xAxisLabels");
            svgBuilder.addText(text);
        }

        return svgBuilder.toString();
    }

    private Path createStarPath(float x, float y) {
        final float x1 = 2.938926261f;
        final float x2 = 1.816517946f;
        final float x3 = 4.755282581f;
        final float x4 = 1.122669832f;
        final float x5 = 0f;

        final float y1 = 2.135084972f;
        final float y2 = -1.319777541f;
        final float y3 = -3.455084972f;
        final float y4 = -3.455222459f;
        final float y5 = -6.91f;

        Path path = new Path();

        path.moveTo(x, y).lineTo(x + x1, y + y1).lineTo(x + x2, y + y2).lineTo(x + x3, y + y3)
                .lineTo(x + x4, y + y4).lineTo(x + x5, y + y5).lineTo(x - x4, y + y4).lineTo(x - x3, y + y3)
                .lineTo(x - x2, y + y2).lineTo(x - x1, y + y1).close();

        return path;
    }

}