All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.qualitycheck.immutableobject.util.SourceCodeFormatter Maven / Gradle / Ivy

package net.sf.qualitycheck.immutableobject.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

import javax.annotation.Nonnull;

import net.sf.qualitycheck.Check;

import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.TextEdit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class SourceCodeFormatter {

	public enum LineEnding {

		KEEP(""),

		CR("\r"),

		CRLF("\r\n"),

		LF("\n");

		/**
		 * Default line ending
		 */
		public static final LineEnding DEFAULT_LINE_ENDING = LineEnding.LF;

		/**
		 * Determines the most occurring line-ending characters in the file text or null if no line-ending occurs the
		 * most.
		 * 
		 * @param code
		 *            source code
		 * @return most occurring line ending or {@code LineEnding#KEEP}
		 */
		@Nonnull
		private static LineEnding determine(final String code) {
			Check.notNull(code, "code");
			int lfCount = 0;
			int crCount = 0;
			int crlfCount = 0;
			for (int i = 0; i < code.length(); i++) {
				final char c = code.charAt(i);
				if (c == '\r') {
					if (i + 1 < code.length() && code.charAt(i + 1) == '\n') {
						crlfCount++;
						i++;
					} else {
						crCount++;
					}
				} else if (c == '\n') {
					lfCount++;
				}
			}
			if (lfCount > crCount && lfCount > crlfCount) {
				return LineEnding.LF;
			} else if (crlfCount > lfCount && crlfCount > crCount) {
				return LineEnding.CRLF;
			} else if (crCount > lfCount && crCount > crlfCount) {
				return LineEnding.CR;
			}
			return LineEnding.KEEP;
		}

		/**
		 * Returns the line ending parameter as characters when the value is known (LF, CRLF, CR) or it will trying to
		 * detect it from the source code (KEEP), otherwise the default line ending will be returned.
		 * 
		 * @param lineEnding
		 *            line ending
		 * @param code
		 *            source code
		 * @return line ending parameter as characters
		 */
		@Nonnull
		public static String find(@Nonnull final LineEnding lineEnding, @Nonnull final String code) {
			Check.notNull(lineEnding, "lineEnding");
			Check.notNull(code, "code");
			String ret = DEFAULT_LINE_ENDING.asString();
			if (LineEnding.KEEP == lineEnding) {
				final LineEnding determined = determine(code);
				ret = LineEnding.KEEP == determined ? DEFAULT_LINE_ENDING.asString() : determined.asString();
			} else if (LineEnding.LF == lineEnding) {
				ret = lineEnding.asString();
			} else if (LineEnding.CRLF == lineEnding) {
				ret = lineEnding.asString();
			} else if (LineEnding.CR == lineEnding) {
				ret = lineEnding.asString();
			}
			return ret;
		}

		private final String _chars;

		private LineEnding(final String chars) {
			_chars = chars;
		}

		public String asString() {
			return _chars;
		}

		public String find(final String code) {
			return find(this, code);
		}

	}

	/**
	 * Default options to be passed when no custom properties are given
	 */
	public static final Properties DEFAULT_PROPERTIES = findDefaultProperties();

	private static final String DEFAULT_PROPERTIES_PATH = "org.eclipse.jdt.core.prefs";

	private static final Logger LOG = LoggerFactory.getLogger(SourceCodeFormatter.class);

	/**
	 * Gets the default options to be passed when no custom properties are given.
	 * 
	 * @return properties with formatter options
	 */
	@Nonnull
	private static Properties findDefaultProperties() {
		final InputStream in = SourceCodeFormatter.class.getClassLoader().getResourceAsStream(DEFAULT_PROPERTIES_PATH);
		final Properties p = new Properties();
		try {
			p.load(in);
		} catch (final IOException e) {
			throw new RuntimeException(String.format("Can not load resource %s", DEFAULT_PROPERTIES_PATH));
		}
		return p;
	}

	/**
	 * Pretty prints the given source code.
	 * 
	 * @param code
	 *            source code to format
	 * @return formatted source code
	 */
	public static String format(final String code) {
		return format(code, DEFAULT_PROPERTIES, LineEnding.DEFAULT_LINE_ENDING);
	}

	/**
	 * Pretty prints the given source code.
	 * 
	 * @param code
	 *            source code to format
	 * @param options
	 *            formatter options
	 * @param lineEnding
	 *            desired line ending
	 * @return formatted source code
	 */
	public static String format(final String code, final Properties options, final LineEnding lineEnding) {
		Check.notEmpty(code, "code");
		Check.notEmpty(options, "options");
		Check.notNull(lineEnding, "lineEnding");

		final CodeFormatter formatter = ToolFactory.createCodeFormatter(options);
		final String lineSeparator = LineEnding.find(lineEnding, code);
		TextEdit te = null;
		try {
			te = formatter.format(CodeFormatter.K_COMPILATION_UNIT, code, 0, code.length(), 0, lineSeparator);
		} catch (final Exception formatFailed) {
			LOG.warn("Formatting failed", formatFailed);
		}

		String formattedCode = code;
		if (te == null) {
			LOG.info("Code cannot be formatted. Possible cause is unmatched source/target/compliance version.");
		} else {

			final IDocument doc = new Document(code);
			try {
				te.apply(doc);
			} catch (final Exception e) {
				LOG.warn(e.getLocalizedMessage(), e);
			}
			formattedCode = doc.get();
		}
		return formattedCode;
	}

	/**
	 * Attention: This class is not intended to create objects from it.
	 */
	private SourceCodeFormatter() {
		// This class is not intended to create objects from it.
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy