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.google;
11  
12  import java.util.SortedSet;
13  import java.util.prefs.Preferences;
14  
15  import javax.swing.text.BadLocationException;
16  import javax.swing.text.Document;
17  import javax.swing.text.StyledDocument;
18  
19  import org.apache.commons.lang3.tuple.Pair;
20  import org.netbeans.api.annotations.common.CheckForNull;
21  import org.netbeans.api.annotations.common.NonNull;
22  import org.netbeans.api.editor.guards.GuardedSectionManager;
23  import org.netbeans.api.project.Project;
24  import org.openide.util.NbBundle;
25  import org.openide.util.lookup.ServiceProvider;
26  
27  import com.google.googlejavaformat.java.JavaFormatterOptions;
28  
29  import de.funfried.netbeans.plugins.external.formatter.FormatterService;
30  import de.funfried.netbeans.plugins.external.formatter.MimeType;
31  import de.funfried.netbeans.plugins.external.formatter.exceptions.FormattingFailedException;
32  import de.funfried.netbeans.plugins.external.formatter.java.base.AbstractJavaFormatterService;
33  import de.funfried.netbeans.plugins.external.formatter.java.google.ui.GoogleJavaFormatterOptionsPanel;
34  import de.funfried.netbeans.plugins.external.formatter.ui.options.FormatterOptionsPanel;
35  import de.funfried.netbeans.plugins.external.formatter.ui.options.Settings;
36  
37  /**
38   * Google implementation of the {@link AbstractJavaFormatterService}.
39   *
40   * @author bahlef
41   */
42  @NbBundle.Messages({
43  		"FormatterName=Google Java Code Formatter"
44  })
45  @ServiceProvider(service = FormatterService.class, position = 500)
46  public class GoogleJavaFormatterService extends AbstractJavaFormatterService<GoogleFormatJob> {
47  	/** The ID of this formatter service. */
48  	public static final String ID = "google-java-formatter";
49  
50  	/** * The {@link GoogleJavaFormatterWrapper} implementation. */
51  	private final GoogleJavaFormatterWrapper formatter = new GoogleJavaFormatterWrapper();
52  
53  	/**
54  	 * {@inheritDoc}
55  	 */
56  	@Override
57  	public boolean canHandle(Document document) {
58  		// Cannot handle guarded blocks properly due to a bug in the Google Java Code Formatter:
59  		// https://github.com/google/google-java-format/issues/433
60  		if (document instanceof StyledDocument) {
61  			StyledDocument styledDoc = (StyledDocument) document;
62  
63  			if (GuardedSectionManager.getInstance(styledDoc) != null) {
64  				return false;
65  			}
66  		}
67  
68  		return super.canHandle(document);
69  	}
70  
71  	/**
72  	 * {@inheritDoc}
73  	 */
74  	@NonNull
75  	@Override
76  	public String getDisplayName() {
77  		return NbBundle.getMessage(GoogleJavaFormatterService.class, "FormatterName");
78  	}
79  
80  	/**
81  	 * {@inheritDoc}
82  	 */
83  	@NonNull
84  	@Override
85  	public String getId() {
86  		return ID;
87  	}
88  
89  	/**
90  	 * {@inheritDoc}
91  	 */
92  	@Override
93  	public FormatterOptionsPanel createOptionsPanel(Project project) {
94  		return new GoogleJavaFormatterOptionsPanel(project);
95  	}
96  
97  	/**
98  	 * {@inheritDoc}
99  	 */
100 	@CheckForNull
101 	@Override
102 	public Integer getContinuationIndentSize(Document document) {
103 		if (document == null) {
104 			return null;
105 		}
106 
107 		Integer ret = null;
108 
109 		Preferences preferences = Settings.getActivePreferences(document);
110 		if (isUseFormatterIndentationSettings(preferences)) {
111 			String codeStylePref = preferences.get(GoogleJavaFormatterSettings.CODE_STYLE, JavaFormatterOptions.Style.GOOGLE.name());
112 			JavaFormatterOptions.Style codeStyle = JavaFormatterOptions.Style.valueOf(codeStylePref);
113 			if (JavaFormatterOptions.Style.GOOGLE.equals(codeStyle)) {
114 				// see: https://google.github.io/styleguide/javaguide.html#s4.5.2-line-wrapping-indent
115 				ret = 4;
116 			} else {
117 				// see: https://source.android.com/setup/contribute/code-style#use-spaces-for-indentation
118 				ret = 8;
119 			}
120 		}
121 
122 		return ret;
123 	}
124 
125 	/**
126 	 * {@inheritDoc}
127 	 */
128 	@CheckForNull
129 	@Override
130 	public Integer getIndentSize(Document document) {
131 		if (document == null) {
132 			return null;
133 		}
134 
135 		Integer ret = null;
136 
137 		Preferences preferences = Settings.getActivePreferences(document);
138 		if (isUseFormatterIndentationSettings(preferences)) {
139 			String codeStylePref = preferences.get(GoogleJavaFormatterSettings.CODE_STYLE, JavaFormatterOptions.Style.GOOGLE.name());
140 			JavaFormatterOptions.Style codeStyle = JavaFormatterOptions.Style.valueOf(codeStylePref);
141 			if (JavaFormatterOptions.Style.GOOGLE.equals(codeStyle)) {
142 				// see: https://google.github.io/styleguide/javaguide.html#s4.2-block-indentation
143 				ret = 2;
144 			} else {
145 				// see: https://source.android.com/setup/contribute/code-style#use-spaces-for-indentation
146 				ret = 4;
147 			}
148 		}
149 
150 		return ret;
151 	}
152 
153 	/**
154 	 * {@inheritDoc}
155 	 */
156 	@CheckForNull
157 	@Override
158 	public Integer getRightMargin(Document document) {
159 		if (document == null) {
160 			return null;
161 		}
162 
163 		// see: https://google.github.io/styleguide/javaguide.html#s4.4-column-limit
164 		// and https://source.android.com/setup/contribute/code-style#limit-line-length
165 		return 100;
166 	}
167 
168 	/**
169 	 * {@inheritDoc}
170 	 */
171 	@Override
172 	protected GoogleFormatJob getFormatJob(StyledDocument document, SortedSet<Pair<Integer, Integer>> changedElements) {
173 		return new GoogleFormatJob(document, formatter, changedElements);
174 	}
175 
176 	/**
177 	 * {@inheritDoc}
178 	 */
179 	@CheckForNull
180 	@Override
181 	public Integer getSpacesPerTab(Document document) {
182 		if (document == null) {
183 			return null;
184 		}
185 
186 		Integer ret = null;
187 
188 		Preferences preferences = Settings.getActivePreferences(document);
189 		if (isUseFormatterIndentationSettings(preferences)) {
190 			String codeStylePref = preferences.get(GoogleJavaFormatterSettings.CODE_STYLE, JavaFormatterOptions.Style.GOOGLE.name());
191 			JavaFormatterOptions.Style codeStyle = JavaFormatterOptions.Style.valueOf(codeStylePref);
192 			if (JavaFormatterOptions.Style.GOOGLE.equals(codeStyle)) {
193 				// see: https://google.github.io/styleguide/javaguide.html#s4.2-block-indentation
194 				ret = 2;
195 			} else {
196 				// see: https://source.android.com/setup/contribute/code-style#use-spaces-for-indentation
197 				ret = 4;
198 			}
199 		}
200 
201 		return ret;
202 	}
203 
204 	/**
205 	 * {@inheritDoc}
206 	 */
207 	@CheckForNull
208 	@Override
209 	public Boolean isExpandTabToSpaces(Document document) {
210 		if (document == null) {
211 			return null;
212 		}
213 
214 		Boolean ret = null;
215 
216 		Preferences preferences = Settings.getActivePreferences(document);
217 		if (isUseFormatterIndentationSettings(preferences)) {
218 			// see: https://google.github.io/styleguide/javaguide.html#s4.2-block-indentation
219 			// and https://source.android.com/setup/contribute/code-style#use-spaces-for-indentation
220 			ret = true;
221 		}
222 
223 		return ret;
224 	}
225 
226 	/**
227 	 * Returns {@code true} if using the formatter indentation settings from the external
228 	 * formatter is activated, otherwise {@code false}.
229 	 *
230 	 * @param prefs the {@link Preferences} where to check
231 	 *
232 	 * @return {@code true} if using the formatter indentation settings from the external
233 	 *         formatter is activated, otherwise {@code false}
234 	 */
235 	private boolean isUseFormatterIndentationSettings(Preferences prefs) {
236 		return prefs.getBoolean(Settings.ENABLE_USE_OF_INDENTATION_SETTINGS, true);
237 	}
238 
239 	/**
240 	 * {@inheritDoc}
241 	 */
242 	@Override
243 	@CheckForNull
244 	public Boolean organizeImports(StyledDocument document, boolean afterFixImports) throws BadLocationException {
245 		Preferences preferences = Settings.getActivePreferences(document);
246 		if (!preferences.getBoolean(GoogleJavaFormatterSettings.ORGANIZE_IMPORTS, false)) {
247 			return null;
248 		}
249 
250 		if (afterFixImports) {
251 			if (!preferences.getBoolean(GoogleJavaFormatterSettings.ORGANIZE_IMPORTS_AFTER_FIX_IMPORTS, false)) {
252 				return null;
253 			}
254 		}
255 
256 		if (!canHandle(document)) {
257 			throw new FormattingFailedException("The file type '" + MimeType.getMimeTypeAsString(document) + "' is not supported");
258 		}
259 
260 		getFormatJob(document, null).organizeImports();
261 
262 		return true;
263 	}
264 }