View Javadoc
1   /*
2    * Copyright (c) 2020 bahlef.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v2.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v20.html
7    * Contributors:
8    * markiewb - initial API and implementation and/or initial documentation
9    * bahlef
10   */
11  package de.funfried.netbeans.plugins.external.formatter.eclipse;
12  
13  import java.util.SortedSet;
14  import java.util.prefs.Preferences;
15  
16  import javax.swing.SwingUtilities;
17  import javax.swing.text.BadLocationException;
18  import javax.swing.text.StyledDocument;
19  
20  import org.apache.commons.lang3.tuple.Pair;
21  import org.netbeans.api.annotations.common.NonNull;
22  import org.netbeans.editor.BaseDocument;
23  import org.openide.DialogDisplayer;
24  import org.openide.NotifyDescriptor;
25  import org.openide.awt.NotificationDisplayer;
26  import org.openide.awt.StatusDisplayer;
27  
28  import de.funfried.netbeans.plugins.external.formatter.AbstractFormatJob;
29  import de.funfried.netbeans.plugins.external.formatter.exceptions.CannotLoadConfigurationException;
30  import de.funfried.netbeans.plugins.external.formatter.exceptions.ConfigReadException;
31  import de.funfried.netbeans.plugins.external.formatter.exceptions.FormattingFailedException;
32  import de.funfried.netbeans.plugins.external.formatter.exceptions.ProfileNotFoundException;
33  import de.funfried.netbeans.plugins.external.formatter.ui.Icons;
34  import de.funfried.netbeans.plugins.external.formatter.ui.options.Settings;
35  
36  /**
37   * Abstract Eclipse formatter implementation of the {@link AbstractFormatJob} as a base
38   * class for any type of Eclipse Formatter.
39   *
40   * @author bahlef
41   */
42  public abstract class AbstractEclipseFormatJob extends AbstractFormatJob {
43  	/**
44  	 * Protected constructor to create a new instance of {@link AbstractEclipseFormatJob}.
45  	 *
46  	 * @param document the {@link StyledDocument} which sould be formatted
47  	 * @param changedElements the ranges which should be formatted
48  	 */
49  	protected AbstractEclipseFormatJob(StyledDocument document, SortedSet<Pair<Integer, Integer>> changedElements) {
50  		super(document, changedElements);
51  	}
52  
53  	/**
54  	 * Returns the formatted content.
55  	 *
56  	 * @param pref the {@link Preferences}
57  	 * @param formatterFile the path to the formatter configuration file
58  	 * @param formatterProfile the name of the formatter configuration profile
59  	 * @param code the current (unformatted) code
60  	 *
61  	 * @return the formatted content
62  	 *
63  	 * @throws ConfigReadException if there is an issue parsing the formatter configuration
64  	 * @throws ProfileNotFoundException if the given {@code profile} could not be found
65  	 * @throws CannotLoadConfigurationException if there is any issue accessing or reading the formatter configuration
66  	 * @throws FormattingFailedException if the external formatter failed to format the given code
67  	 */
68  	protected abstract String getFormattedContent(Preferences pref, String formatterFile, String formatterProfile, String code)
69  			throws ConfigReadException, ProfileNotFoundException, CannotLoadConfigurationException, FormattingFailedException;
70  
71  	/**
72  	 * Returns the configured formatter configuration file.
73  	 *
74  	 * @param pref the {@link Preferences}
75  	 *
76  	 * @return the configured formatter configuration file
77  	 */
78  	protected abstract String getFormatterFile(Preferences pref);
79  
80  	/**
81  	 * Returns the configured formatter profile name.
82  	 *
83  	 * @param pref the {@link Preferences}
84  	 *
85  	 * @return the configured formatter profile name
86  	 */
87  	protected abstract String getFormatterProfile(Preferences pref);
88  
89  	/**
90  	 * Returns the configured line feed.
91  	 *
92  	 * @param pref the {@link Preferences}
93  	 *
94  	 * @return the configured line feed
95  	 */
96  	protected abstract String getLineFeed(Preferences pref);
97  
98  	/**
99  	 * Returns the content of the {@code document}.
100 	 *
101 	 * @param pref the {@link Preferences}
102 	 *
103 	 * @return The content of the {@code document}
104 	 */
105 	protected String getCode(Preferences pref) {
106 		String lineFeed = getLineFeed(pref);
107 
108 		//save with configured linefeed
109 		if (null != lineFeed) {
110 			document.putProperty(BaseDocument.READ_LINE_SEPARATOR_PROP, lineFeed);
111 			document.putProperty(BaseDocument.WRITE_LINE_SEPARATOR_PROP, lineFeed);
112 		}
113 
114 		return getCode();
115 	}
116 
117 	/**
118 	 * {@inheritDoc}
119 	 */
120 	@Override
121 	public final void format() throws BadLocationException {
122 		Preferences pref = Settings.getActivePreferences(document);
123 
124 		String formatterFile = getFormatterFile(pref);
125 		String formatterProfile = getFormatterProfile(pref);
126 
127 		String code = getCode(pref);
128 
129 		try {
130 			String formattedContent = getFormattedContent(pref, formatterFile, formatterProfile, code);
131 			if (setFormattedCode(code, formattedContent)) {
132 				String msg = getNotificationMessageForEclipseFormatterConfigurationFileType(formatterFile, formatterProfile);
133 
134 				SwingUtilities.invokeLater(() -> {
135 					if (pref.getBoolean(Settings.SHOW_NOTIFICATIONS, false)) {
136 						NotificationDisplayer.getDefault().notify("Format using Eclipse formatter", Icons.ICON_ECLIPSE, msg, null);
137 					}
138 
139 					StatusDisplayer.getDefault().setStatusText("Format using Eclipse formatter: " + msg);
140 				});
141 			}
142 		} catch (ProfileNotFoundException ex) {
143 			SwingUtilities.invokeLater(() -> {
144 				NotifyDescriptor notify = new NotifyDescriptor.Message(
145 						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,
146 								formatterFile),
147 						NotifyDescriptor.ERROR_MESSAGE);
148 				DialogDisplayer.getDefault().notify(notify);
149 
150 				StatusDisplayer.getDefault().setStatusText(String.format("Profile '%s' not found in %s", formatterProfile, formatterFile));
151 			});
152 
153 			throw ex;
154 		} catch (CannotLoadConfigurationException ex) {
155 			SwingUtilities.invokeLater(() -> {
156 				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),
157 						NotifyDescriptor.ERROR_MESSAGE);
158 				DialogDisplayer.getDefault().notify(notify);
159 
160 				StatusDisplayer.getDefault().setStatusText(String.format("Could not find configuration file %s. Make sure the file exists and it can be read.", formatterFile));
161 			});
162 
163 			throw ex;
164 		} catch (FormattingFailedException ex) {
165 			SwingUtilities.invokeLater(() -> {
166 				StatusDisplayer.getDefault().setStatusText("Failed to format using Eclipse formatter: " + ex.getMessage());
167 			});
168 
169 			throw ex;
170 		}
171 	}
172 
173 	/**
174 	 * Returns the message which should be shown in a notification after the formatting is done.
175 	 *
176 	 * @param formatterFile the used formatter configuration file
177 	 * @param formatterProfile the used formatter profile
178 	 *
179 	 * @return the message which should be shown in a notification after the formatting is done
180 	 */
181 	@NonNull
182 	protected abstract String getNotificationMessageForEclipseFormatterConfigurationFileType(String formatterFile, String formatterProfile);
183 }