AbstractEclipseFormatJob.java

/*
 * Copyright (c) 2020 bahlef.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * Contributors:
 * markiewb - initial API and implementation and/or initial documentation
 * bahlef
 */
package de.funfried.netbeans.plugins.external.formatter.eclipse;

import java.util.SortedSet;
import java.util.prefs.Preferences;

import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.StyledDocument;

import org.apache.commons.lang3.tuple.Pair;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.editor.BaseDocument;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.awt.NotificationDisplayer;
import org.openide.awt.StatusDisplayer;

import de.funfried.netbeans.plugins.external.formatter.AbstractFormatJob;
import de.funfried.netbeans.plugins.external.formatter.exceptions.CannotLoadConfigurationException;
import de.funfried.netbeans.plugins.external.formatter.exceptions.ConfigReadException;
import de.funfried.netbeans.plugins.external.formatter.exceptions.FormattingFailedException;
import de.funfried.netbeans.plugins.external.formatter.exceptions.ProfileNotFoundException;
import de.funfried.netbeans.plugins.external.formatter.ui.Icons;
import de.funfried.netbeans.plugins.external.formatter.ui.options.Settings;

/**
 * Abstract Eclipse formatter implementation of the {@link AbstractFormatJob} as a base
 * class for any type of Eclipse Formatter.
 *
 * @author bahlef
 */
public abstract class AbstractEclipseFormatJob extends AbstractFormatJob {
	/**
	 * Protected constructor to create a new instance of {@link AbstractEclipseFormatJob}.
	 *
	 * @param document the {@link StyledDocument} which sould be formatted
	 * @param changedElements the ranges which should be formatted
	 */
	protected AbstractEclipseFormatJob(StyledDocument document, SortedSet<Pair<Integer, Integer>> changedElements) {
		super(document, changedElements);
	}

	/**
	 * Returns the formatted content.
	 *
	 * @param pref the {@link Preferences}
	 * @param formatterFile the path to the formatter configuration file
	 * @param formatterProfile the name of the formatter configuration profile
	 * @param code the current (unformatted) code
	 *
	 * @return the formatted content
	 *
	 * @throws ConfigReadException if there is an issue parsing the formatter configuration
	 * @throws ProfileNotFoundException if the given {@code profile} could not be found
	 * @throws CannotLoadConfigurationException if there is any issue accessing or reading the formatter configuration
	 * @throws FormattingFailedException if the external formatter failed to format the given code
	 */
	protected abstract String getFormattedContent(Preferences pref, String formatterFile, String formatterProfile, String code)
			throws ConfigReadException, ProfileNotFoundException, CannotLoadConfigurationException, FormattingFailedException;

	/**
	 * Returns the configured formatter configuration file.
	 *
	 * @param pref the {@link Preferences}
	 *
	 * @return the configured formatter configuration file
	 */
	protected abstract String getFormatterFile(Preferences pref);

	/**
	 * Returns the configured formatter profile name.
	 *
	 * @param pref the {@link Preferences}
	 *
	 * @return the configured formatter profile name
	 */
	protected abstract String getFormatterProfile(Preferences pref);

	/**
	 * Returns the configured line feed.
	 *
	 * @param pref the {@link Preferences}
	 *
	 * @return the configured line feed
	 */
	protected abstract String getLineFeed(Preferences pref);

	/**
	 * Returns the content of the {@code document}.
	 *
	 * @param pref the {@link Preferences}
	 *
	 * @return The content of the {@code document}
	 */
	protected String getCode(Preferences pref) {
		String lineFeed = getLineFeed(pref);

		//save with configured linefeed
		if (null != lineFeed) {
			document.putProperty(BaseDocument.READ_LINE_SEPARATOR_PROP, lineFeed);
			document.putProperty(BaseDocument.WRITE_LINE_SEPARATOR_PROP, lineFeed);
		}

		return getCode();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void format() throws BadLocationException {
		Preferences pref = Settings.getActivePreferences(document);

		String formatterFile = getFormatterFile(pref);
		String formatterProfile = getFormatterProfile(pref);

		String code = getCode(pref);

		try {
			String formattedContent = getFormattedContent(pref, formatterFile, formatterProfile, code);
			if (setFormattedCode(code, formattedContent)) {
				String msg = getNotificationMessageForEclipseFormatterConfigurationFileType(formatterFile, formatterProfile);

				SwingUtilities.invokeLater(() -> {
					if (pref.getBoolean(Settings.SHOW_NOTIFICATIONS, false)) {
						NotificationDisplayer.getDefault().notify("Format using Eclipse formatter", Icons.ICON_ECLIPSE, msg, null);
					}

					StatusDisplayer.getDefault().setStatusText("Format using Eclipse formatter: " + msg);
				});
			}
		} catch (ProfileNotFoundException ex) {
			SwingUtilities.invokeLater(() -> {
				NotifyDescriptor notify = new NotifyDescriptor.Message(
						String.format("<html>Profile '%s' not found in <tt>%s</tt><br><br>Please configure a valid one in the project properties OR at Tools|Options|Editor|External Formatter!", formatterProfile,
								formatterFile),
						NotifyDescriptor.ERROR_MESSAGE);
				DialogDisplayer.getDefault().notify(notify);

				StatusDisplayer.getDefault().setStatusText(String.format("Profile '%s' not found in %s", formatterProfile, formatterFile));
			});

			throw ex;
		} catch (CannotLoadConfigurationException ex) {
			SwingUtilities.invokeLater(() -> {
				NotifyDescriptor notify = new NotifyDescriptor.Message(String.format("<html>Could not find configuration file %s.<br>Make sure the file exists and it can be read.", formatterFile),
						NotifyDescriptor.ERROR_MESSAGE);
				DialogDisplayer.getDefault().notify(notify);

				StatusDisplayer.getDefault().setStatusText(String.format("Could not find configuration file %s. Make sure the file exists and it can be read.", formatterFile));
			});

			throw ex;
		} catch (FormattingFailedException ex) {
			SwingUtilities.invokeLater(() -> {
				StatusDisplayer.getDefault().setStatusText("Failed to format using Eclipse formatter: " + ex.getMessage());
			});

			throw ex;
		}
	}

	/**
	 * Returns the message which should be shown in a notification after the formatting is done.
	 *
	 * @param formatterFile the used formatter configuration file
	 * @param formatterProfile the used formatter profile
	 *
	 * @return the message which should be shown in a notification after the formatting is done
	 */
	@NonNull
	protected abstract String getNotificationMessageForEclipseFormatterConfigurationFileType(String formatterFile, String formatterProfile);
}