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

eu.solven.cleanthat.engine.java.eclipse.EclipseJavaFormatter Maven / Gradle / Ivy

There is a newer version: 2.22
Show newest version
/*
 * Copyright 2023 Benoit Lacelle - SOLVEN
 *
 * 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 eu.solven.cleanthat.engine.java.eclipse;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

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

import eu.solven.cleanthat.formatter.ILintFixerWithId;
import eu.solven.cleanthat.formatter.LineEnding;
import eu.solven.pepper.logging.PepperLogHelper;

/**
 * Bridges to Eclipse formatting engine
 *
 * @author Benoit Lacelle
 */
// https://github.com/revelc/formatter-maven-plugin/blob/master/src/main/java/net/revelc/code/formatter/java/JavaFormatter.java
// Example configurations:
// https://raw.githubusercontent.com/spring-io/spring-javaformat/master/.eclipse/eclipse-code-formatter.xml
// https://raw.githubusercontent.com/solven-eu/pepper/master/static/src/main/resources/eclipse/eclipse_java_code_formatter.xml
public class EclipseJavaFormatter implements ILintFixerWithId {
	private static final Logger LOGGER = LoggerFactory.getLogger(EclipseJavaFormatter.class);

	public static final String ID = "eclipse_formatter";

	private final Map defaultSettings;

	// For statistics purposes
	private static final AtomicInteger NB_FORMATTED = new AtomicInteger();
	private static final AtomicLong TIME_FORMATTING = new AtomicLong();

	public EclipseJavaFormatter(EclipseJavaFormatterConfiguration configuration) {
		defaultSettings = configuration.getSettings();
	}

	@Override
	public String getId() {
		return ID;
	}

	@Override
	public String doFormat(String code) throws IOException {
		// Make a new formatter to enable thread-safety
		CodeFormatter formatter = makeFormatter();

		var start = System.currentTimeMillis();
		TextEdit textEdit;
		try {
			var eolChars = LineEnding.getOrGuess(LineEnding.NATIVE, () -> code);
			textEdit = formatter.format(CodeFormatter.K_COMPILATION_UNIT
					| CodeFormatter.F_INCLUDE_COMMENTS, code, 0, code.length(), 0, eolChars);
			if (textEdit == null) {
				LOGGER.warn("Code cannot be formatted. Possible cause is unmatched source/target/compliance version.");
				return null;
			}
		} catch (RuntimeException e) {
			LOGGER.warn("Code cannot be formatted", e);
			return null;
		} catch (AssertionError e) {
			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=543646
			LOGGER.warn("Bug in Eclipse Formatter?", e);
			return null;
		} finally {
			var time = System.currentTimeMillis() - start;
			var totalDuration = TIME_FORMATTING.addAndGet(time);
			var totalFormats = NB_FORMATTED.incrementAndGet();
			if (Integer.bitCount(totalFormats) == 1) {
				LOGGER.info("Total Eclipse Formats: {}. MeanTime: {}",
						PepperLogHelper.humanBytes(totalFormats),
						PepperLogHelper.humanDuration(totalDuration / totalFormats));
			}
		}
		IDocument doc = new Document(code);
		try {
			textEdit.apply(doc);
		} catch (MalformedTreeException | BadLocationException e) {
			throw new IllegalArgumentException(e);
		}
		String formattedCode = doc.get();
		if (formattedCode == null) {
			throw new IllegalStateException("This happened while accessing concurrently a single CodeFormatter");
		} else if (code.equals(formattedCode)) {
			LOGGER.debug("Formatting was a no-op");
		}
		return formattedCode;
	}

	private CodeFormatter makeFormatter() {
		return ToolFactory.createCodeFormatter(defaultSettings, ToolFactory.M_FORMAT_EXISTING);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy