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    * bahlef - initial API and implementation and/or initial documentation
9    */
10  package de.funfried.netbeans.plugins.external.formatter.java.spring;
11  
12  import java.io.IOException;
13  import java.io.InputStream;
14  import java.util.Objects;
15  import java.util.Properties;
16  import java.util.SortedSet;
17  import java.util.logging.Level;
18  import java.util.logging.Logger;
19  import java.util.prefs.Preferences;
20  
21  import javax.swing.text.BadLocationException;
22  import javax.swing.text.Document;
23  import javax.swing.text.StyledDocument;
24  
25  import org.apache.commons.lang3.tuple.Pair;
26  import org.netbeans.api.annotations.common.CheckForNull;
27  import org.netbeans.api.annotations.common.NonNull;
28  import org.netbeans.api.project.Project;
29  import org.openide.util.NbBundle;
30  import org.openide.util.lookup.ServiceProvider;
31  
32  import de.funfried.netbeans.plugins.external.formatter.FormatterService;
33  import de.funfried.netbeans.plugins.external.formatter.java.base.AbstractJavaFormatterService;
34  import de.funfried.netbeans.plugins.external.formatter.java.spring.ui.SpringJavaFormatterOptionsPanel;
35  import de.funfried.netbeans.plugins.external.formatter.ui.options.FormatterOptionsPanel;
36  import de.funfried.netbeans.plugins.external.formatter.ui.options.Settings;
37  import io.spring.javaformat.formatter.eclipse.EclipseCodeFormatter;
38  
39  /**
40   * Spring implementation of the {@link AbstractJavaFormatterService}.
41   *
42   * @author bahlef
43   */
44  @NbBundle.Messages({
45  		"FormatterName=Spring Java Code Formatter"
46  })
47  @ServiceProvider(service = FormatterService.class, position = 1500)
48  public class SpringJavaFormatterService extends AbstractJavaFormatterService<SpringFormatJob> {
49  	/** {@link Logger} of this class. */
50  	private static final Logger log = Logger.getLogger(SpringJavaFormatterService.class.getName());
51  
52  	/** The ID of this formatter service. */
53  	public static final String ID = "spring-java-formatter";
54  
55  	/** * The {@link SpringJavaFormatterWrapper} implementation. */
56  	private final SpringJavaFormatterWrapper formatter = new SpringJavaFormatterWrapper();
57  
58  	/**
59  	 * {@inheritDoc}
60  	 */
61  	@NonNull
62  	@Override
63  	public String getDisplayName() {
64  		return NbBundle.getMessage(SpringJavaFormatterService.class, "FormatterName");
65  	}
66  
67  	/**
68  	 * {@inheritDoc}
69  	 */
70  	@NonNull
71  	@Override
72  	public String getId() {
73  		return ID;
74  	}
75  
76  	/**
77  	 * {@inheritDoc}
78  	 */
79  	@Override
80  	public FormatterOptionsPanel createOptionsPanel(Project project) {
81  		return new SpringJavaFormatterOptionsPanel(project);
82  	}
83  
84  	/**
85  	 * {@inheritDoc}
86  	 */
87  	@CheckForNull
88  	@Override
89  	public Integer getContinuationIndentSize(Document document) {
90  		if (document == null) {
91  			return null;
92  		}
93  
94  		Integer ret = null;
95  
96  		Preferences preferences = Settings.getActivePreferences(document);
97  		if (isUseFormatterIndentationSettings(preferences)) {
98  			String propKey = "core.formatter.continuation_indentation";
99  			String prop = this.getSpringFormatterProperty(propKey);
100 			if (prop != null) {
101 				try {
102 					ret = Integer.parseInt(prop);
103 
104 					Integer indentSize = getIndentSize(document);
105 					if (indentSize != null) {
106 						ret *= indentSize;
107 					}
108 				} catch (NumberFormatException ex) {
109 					log.log(Level.WARNING, "Property '" + propKey + "' is not an integer: " + prop, ex);
110 				}
111 			}
112 
113 			if (ret == null) {
114 				ret = 2;
115 			}
116 		}
117 
118 		return ret;
119 	}
120 
121 	/**
122 	 * {@inheritDoc}
123 	 */
124 	@CheckForNull
125 	@Override
126 	public Integer getIndentSize(Document document) {
127 		if (document == null) {
128 			return null;
129 		}
130 
131 		Integer ret = null;
132 
133 		Preferences preferences = Settings.getActivePreferences(document);
134 		if (isUseFormatterIndentationSettings(preferences)) {
135 			String tabChar = getSpringFormatterProperty("core.formatter.tabulation.char");
136 			if (Objects.equals(tabChar, "mixed")) {
137 				String propKey = "core.formatter.indentation.size";
138 				String prop = this.getSpringFormatterProperty(propKey);
139 				if (prop != null) {
140 					try {
141 						ret = Integer.parseInt(prop);
142 					} catch (NumberFormatException ex) {
143 						log.log(Level.WARNING, "Property '" + propKey + "' is not an integer: " + prop, ex);
144 					}
145 				}
146 			} else {
147 				ret = getSpacesPerTab(document);
148 			}
149 
150 			if (ret == null) {
151 				ret = 4;
152 			}
153 		}
154 
155 		return ret;
156 	}
157 
158 	/**
159 	 * {@inheritDoc}
160 	 */
161 	@CheckForNull
162 	@Override
163 	public Integer getRightMargin(Document document) {
164 		if (document == null) {
165 			return null;
166 		}
167 
168 		Integer ret = 120;
169 
170 		String propKey = "core.formatter.lineSplit";
171 		String prop = this.getSpringFormatterProperty(propKey);
172 		if (prop != null) {
173 			try {
174 				ret = Integer.parseInt(prop);
175 			} catch (NumberFormatException ex) {
176 				log.log(Level.WARNING, "Property '" + propKey + "' is not an integer: " + prop, ex);
177 			}
178 		}
179 
180 		return ret;
181 	}
182 
183 	/**
184 	 * {@inheritDoc}
185 	 */
186 	@Override
187 	protected SpringFormatJob getFormatJob(StyledDocument document, SortedSet<Pair<Integer, Integer>> changedElements) {
188 		return new SpringFormatJob(document, formatter, changedElements);
189 	}
190 
191 	/**
192 	 * {@inheritDoc}
193 	 */
194 	@CheckForNull
195 	@Override
196 	public Integer getSpacesPerTab(Document document) {
197 		if (document == null) {
198 			return null;
199 		}
200 
201 		Integer ret = null;
202 
203 		Preferences preferences = Settings.getActivePreferences(document);
204 		if (isUseFormatterIndentationSettings(preferences)) {
205 			if (!isExpandTabToSpaces(document, preferences) && preferences.getBoolean(Settings.OVERRIDE_TAB_SIZE, true)) {
206 				ret = preferences.getInt(Settings.OVERRIDE_TAB_SIZE_VALUE, 4);
207 			} else {
208 				String propKey = "core.formatter.tabulation.size";
209 				String prop = this.getSpringFormatterProperty(propKey);
210 				if (prop != null) {
211 					try {
212 						ret = Integer.parseInt(prop);
213 					} catch (NumberFormatException ex) {
214 						log.log(Level.WARNING, "Property '" + propKey + "' is not an integer: " + prop, ex);
215 					}
216 				}
217 
218 				if (ret == null) {
219 					ret = 4;
220 				}
221 			}
222 		}
223 
224 		return ret;
225 	}
226 
227 	/**
228 	 * Reads the configuration value of the internal Spring formatter configuration for the
229 	 * given {@code  key}.
230 	 *
231 	 * @param key the key of the value which should be read
232 	 *
233 	 * @return the configuration value of the internal Spring formatter configuration for
234 	 *         the given {@code  key}
235 	 */
236 	private String getSpringFormatterProperty(String key) {
237 		Properties props = new Properties();
238 		try (InputStream is = EclipseCodeFormatter.class.getResourceAsStream("formatter.prefs")) {
239 			props.load(is);
240 		} catch (IOException ex) {
241 			log.log(Level.SEVERE, "Could not read internal Spring formatter configuration", ex);
242 		}
243 
244 		return props.getProperty(key);
245 	}
246 
247 	/**
248 	 * {@inheritDoc}
249 	 */
250 	@CheckForNull
251 	@Override
252 	public Boolean isExpandTabToSpaces(Document document) {
253 		if (document == null) {
254 			return null;
255 		}
256 
257 		return isExpandTabToSpaces(document, Settings.getActivePreferences(document));
258 	}
259 
260 	@CheckForNull
261 	private Boolean isExpandTabToSpaces(Document document, Preferences preferences) {
262 		if (document == null || preferences == null) {
263 			return null;
264 		}
265 
266 		Boolean ret = null;
267 
268 		if (isUseFormatterIndentationSettings(preferences)) {
269 			String propKey = "core.formatter.tabulation.char";
270 			String prop = this.getSpringFormatterProperty(propKey);
271 			if (prop != null) {
272 				ret = Objects.equals(prop, "space");
273 			}
274 
275 			if (ret == null) {
276 				ret = false;
277 			}
278 		}
279 
280 		return ret;
281 	}
282 
283 	/**
284 	 * Returns {@code true} if using the formatter indentation settings from the external
285 	 * formatter is activated, otherwise {@code false}.
286 	 *
287 	 * @param prefs the {@link Preferences} where to check
288 	 *
289 	 * @return {@code true} if using the formatter indentation settings from the external
290 	 *         formatter is activated, otherwise {@code false}
291 	 */
292 	private boolean isUseFormatterIndentationSettings(Preferences prefs) {
293 		return prefs.getBoolean(Settings.ENABLE_USE_OF_INDENTATION_SETTINGS, true);
294 	}
295 
296 	/**
297 	 * {@inheritDoc}
298 	 */
299 	@Override
300 	@CheckForNull
301 	public Boolean organizeImports(StyledDocument document, boolean afterFixImports) throws BadLocationException {
302 		return null;
303 	}
304 }