org.coursera.courier.lang.PoorMansCStyleSourceFormatter.java Source code

Java tutorial

Introduction

Here is the source code for org.coursera.courier.lang.PoorMansCStyleSourceFormatter.java

Source

/*
 * Copyright 2015 Coursera Inc.
 *
 * 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 org.coursera.courier.lang;

import org.apache.commons.lang3.StringUtils;

import java.util.Stack;

/**
 * Cleans up C style source code generated by a string template engine.
 *
 * Ideally, we prefer to use a grammar aware formatter for all target languages.
 * For C Style languages where we can't find something something good, we'll use these basic
 * heuristics:
 *
 * - To sanitize whitespace, collapse consecutive empty lines to a single empty line.
 * - To keep doc strings and annotations directly above class and field definitions, collapse any
 *   empty lines after a line starting with '*' or '@'.
 * - Auto-indent '{', '}' delimited code blocks correctly so long as '{' is always the last and '}'
 *   is always the first non-whitespace char on a line.
 * - Removes any tailing whitespace from the ends of lines.
 * - Treat all lines starting with a '*' as a continuation of a doc string and indents them
 *   one additional space.
 *
 * This routine only modifies whitespace that is either on an empty line or that precedes or trails
 * the code on any particular line. The code from the fist non-whitespace character to the last on
 * each line of code is left unmodified.
 *
 */
public class PoorMansCStyleSourceFormatter {

    private enum Scope {
        ROOT, UNCATEGORIZED, SWITCH, COMMENT, PARAMS, BLOCK
    }

    private final String indent;
    private final DocCommentStyle docCommentStyle;

    public PoorMansCStyleSourceFormatter(int indentSpaces, DocCommentStyle docCommentStyle) {
        this.indent = StringUtils.repeat(" ", indentSpaces);
        this.docCommentStyle = docCommentStyle;
    }

    public String format(String code) {
        String lines[] = code.split("\\r?\\n");
        StringBuilder result = new StringBuilder();
        Stack<Scope> scope = new Stack<>();
        scope.push(Scope.ROOT);

        int indentLevel = 0;
        boolean isPreviousLineEmpty = true;
        boolean isPreviousLinePreamble = false;

        for (String line : lines) {
            line = line.trim();

            // skip repeated empty lines
            boolean isEmpty = (line.length() == 0);
            if (isEmpty && ((isPreviousLineEmpty || isPreviousLinePreamble) || scope.size() > 2))
                continue;

            if (scope.peek() == Scope.COMMENT && line.startsWith("*")
                    && docCommentStyle == DocCommentStyle.ASTRISK_MARGIN) {
                result.append(" "); // align javadoc continuation
            }

            if ((scope.peek() == Scope.BLOCK || scope.peek() == Scope.SWITCH) && line.startsWith("}")) {
                scope.pop();
                indentLevel--;
            } else if (scope.peek() == Scope.PARAMS && line.startsWith(")")) {
                scope.pop();
                indentLevel--;
            } else if (scope.peek() == Scope.COMMENT && line.startsWith("*/")) {
                scope.pop();
                if (docCommentStyle == DocCommentStyle.NO_MARGIN)
                    indentLevel--;
            }

            boolean isCaseStmt = scope.peek() == Scope.SWITCH
                    && (line.startsWith("case ") || line.startsWith("default"));

            boolean isDeclContinuation = line.startsWith("extends") || line.startsWith("with")
                    || line.startsWith("implements");

            result.append(
                    StringUtils.repeat(indent, indentLevel - (isCaseStmt ? 1 : 0) + (isDeclContinuation ? 1 : 0)));

            result.append(line);
            result.append('\n');

            if (line.endsWith("{")) {
                indentLevel++;
                if (line.startsWith("switch ")) {
                    scope.push(Scope.SWITCH);
                } else {
                    scope.push(Scope.BLOCK);
                }
            } else if (line.endsWith("(")) {
                indentLevel++;
                scope.push(Scope.PARAMS);
            } else if (line.startsWith("/**")) {
                scope.push(Scope.COMMENT);
                if (docCommentStyle == DocCommentStyle.NO_MARGIN)
                    indentLevel++;
            }

            isPreviousLinePreamble = (line.startsWith("@") || line.startsWith("*"));
            isPreviousLineEmpty = isEmpty;
        }
        return result.toString();
    }
}