Java tutorial
/* * 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(); } }