diff options
author | Sorin Basca <sorinbasca@google.com> | 2024-01-16 12:42:22 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2024-01-16 12:42:22 +0000 |
commit | 627f7e9e05bd10152525ff7a5f4337c1ab52efbb (patch) | |
tree | d592898fe81e13c3f80b13796f41ac7dc8417424 /idea_plugin/src/main/java/com/google/googlejavaformat | |
parent | 23900a4073caf1243f551a2b9a1a2c13eab80dcf (diff) | |
parent | 897a1820eb727e6daab691616114df17d04bc66d (diff) | |
download | google-java-format-627f7e9e05bd10152525ff7a5f4337c1ab52efbb.tar.gz |
Merge commit 'v1.19.0' am: 9b3a28805d am: 26c8b23036 am: 897a1820eb
Original change: https://android-review.googlesource.com/c/platform/external/google-java-format/+/2887766
Change-Id: Ie42d6efd5e39b9dffa497a5252e12b4875a6679b
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
Diffstat (limited to 'idea_plugin/src/main/java/com/google/googlejavaformat')
9 files changed, 846 insertions, 0 deletions
diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form new file mode 100644 index 0000000..1db1d79 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.google.googlejavaformat.intellij.GoogleJavaFormatConfigurable"> + <grid id="27dc6" binding="panel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <xy x="20" y="20" width="500" height="400"/> + </constraints> + <properties/> + <border type="none"/> + <children> + <component id="4a87f" class="javax.swing.JCheckBox" binding="enable" default-binding="true"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Enable google-java-format"/> + </properties> + </component> + <vspacer id="19e83"> + <constraints> + <grid row="2" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/> + </constraints> + </vspacer> + <component id="c93e1" class="javax.swing.JLabel"> + <constraints> + <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text value="Code style"/> + </properties> + </component> + <component id="31761" class="javax.swing.JComboBox" binding="styleComboBox" custom-create="true"> + <constraints> + <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="1" use-parent-layout="false"/> + </constraints> + <properties/> + </component> + </children> + </grid> +</form> diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java new file mode 100644 index 0000000..2f34b74 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java @@ -0,0 +1,206 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * 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 com.google.googlejavaformat.intellij; + +import com.google.googlejavaformat.intellij.GoogleJavaFormatSettings.EnabledState; +import com.intellij.openapi.options.BaseConfigurable; +import com.intellij.openapi.options.ConfigurationException; +import com.intellij.openapi.options.SearchableConfigurable; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.uiDesigner.core.GridConstraints; +import com.intellij.uiDesigner.core.GridLayoutManager; +import com.intellij.uiDesigner.core.Spacer; +import java.awt.Insets; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +class GoogleJavaFormatConfigurable extends BaseConfigurable implements SearchableConfigurable { + + private final Project project; + private JPanel panel; + private JCheckBox enable; + private JComboBox styleComboBox; + + public GoogleJavaFormatConfigurable(Project project) { + this.project = project; + } + + @NotNull + @Override + public String getId() { + return "google-java-format.settings"; + } + + @Nullable + @Override + public Runnable enableSearch(String option) { + return null; + } + + @Nls + @Override + public String getDisplayName() { + return "google-java-format Settings"; + } + + @Nullable + @Override + public String getHelpTopic() { + return null; + } + + @Nullable + @Override + public JComponent createComponent() { + return panel; + } + + @Override + public void apply() throws ConfigurationException { + GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project); + settings.setEnabled(enable.isSelected() ? EnabledState.ENABLED : getDisabledState()); + settings.setStyle(((UiFormatterStyle) styleComboBox.getSelectedItem()).convert()); + } + + private EnabledState getDisabledState() { + // The default settings (inherited by new projects) are either 'enabled' or + // 'show notification'. There's no way to default new projects to disabled. If someone wants + // that, we can add another checkbox, I suppose. + return project.isDefault() ? EnabledState.UNKNOWN : EnabledState.DISABLED; + } + + @Override + public void reset() { + GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project); + enable.setSelected(settings.isEnabled()); + styleComboBox.setSelectedItem(UiFormatterStyle.convert(settings.getStyle())); + } + + @Override + public boolean isModified() { + GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project); + return enable.isSelected() != settings.isEnabled() + || !styleComboBox.getSelectedItem().equals(UiFormatterStyle.convert(settings.getStyle())); + } + + @Override + public void disposeUIResources() {} + + private void createUIComponents() { + styleComboBox = new ComboBox<>(UiFormatterStyle.values()); + } + + { + // GUI initializer generated by IntelliJ IDEA GUI Designer + // >>> IMPORTANT!! <<< + // DO NOT EDIT OR ADD ANY CODE HERE! + $$$setupUI$$$(); + } + + /** + * Method generated by IntelliJ IDEA GUI Designer >>> IMPORTANT!! <<< DO NOT edit this method OR + * call it in your code! + * + * @noinspection ALL + */ + private void $$$setupUI$$$() { + createUIComponents(); + panel = new JPanel(); + panel.setLayout(new GridLayoutManager(3, 2, new Insets(0, 0, 0, 0), -1, -1)); + enable = new JCheckBox(); + enable.setText("Enable google-java-format"); + panel.add( + enable, + new GridConstraints( + 0, + 0, + 1, + 2, + GridConstraints.ANCHOR_WEST, + GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, + null, + null, + null, + 0, + false)); + final Spacer spacer1 = new Spacer(); + panel.add( + spacer1, + new GridConstraints( + 2, + 0, + 1, + 2, + GridConstraints.ANCHOR_CENTER, + GridConstraints.FILL_VERTICAL, + 1, + GridConstraints.SIZEPOLICY_WANT_GROW, + null, + null, + null, + 0, + false)); + final JLabel label1 = new JLabel(); + label1.setText("Code style"); + panel.add( + label1, + new GridConstraints( + 1, + 0, + 1, + 1, + GridConstraints.ANCHOR_WEST, + GridConstraints.FILL_NONE, + GridConstraints.SIZEPOLICY_FIXED, + GridConstraints.SIZEPOLICY_FIXED, + null, + null, + null, + 0, + false)); + panel.add( + styleComboBox, + new GridConstraints( + 1, + 1, + 1, + 1, + GridConstraints.ANCHOR_WEST, + GridConstraints.FILL_HORIZONTAL, + GridConstraints.SIZEPOLICY_CAN_GROW, + GridConstraints.SIZEPOLICY_FIXED, + null, + null, + null, + 1, + false)); + } + + /** @noinspection ALL */ + public JComponent $$$getRootComponent$$$() { + return panel; + } +} diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java new file mode 100644 index 0000000..9d2d7a5 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java @@ -0,0 +1,141 @@ +/* + * Copyright 2023 Google Inc. All Rights Reserved. + * + * 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 com.google.googlejavaformat.intellij; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Range; +import com.google.googlejavaformat.java.Formatter; +import com.google.googlejavaformat.java.FormatterException; +import com.google.googlejavaformat.java.JavaFormatterOptions; +import com.google.googlejavaformat.java.JavaFormatterOptions.Style; +import com.intellij.formatting.service.AsyncDocumentFormattingService; +import com.intellij.formatting.service.AsyncFormattingRequest; +import com.intellij.ide.highlighter.JavaFileType; +import com.intellij.lang.ImportOptimizer; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiFile; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import org.jetbrains.annotations.NotNull; + +/** Uses {@code google-java-format} to reformat code. */ +public class GoogleJavaFormatFormattingService extends AsyncDocumentFormattingService { + + public static final ImmutableSet<ImportOptimizer> IMPORT_OPTIMIZERS = + ImmutableSet.of(new GoogleJavaFormatImportOptimizer()); + + @Override + protected FormattingTask createFormattingTask(AsyncFormattingRequest request) { + Project project = request.getContext().getProject(); + + if (!JreConfigurationChecker.checkJreConfiguration(project)) { + return null; + } + + Style style = GoogleJavaFormatSettings.getInstance(project).getStyle(); + Formatter formatter = createFormatter(style, request.canChangeWhitespaceOnly()); + return new GoogleJavaFormatFormattingTask(formatter, request); + } + + @Override + protected String getNotificationGroupId() { + return Notifications.PARSING_ERROR_NOTIFICATION_GROUP; + } + + @Override + protected String getName() { + return "google-java-format"; + } + + private static Formatter createFormatter(Style style, boolean canChangeWhiteSpaceOnly) { + JavaFormatterOptions.Builder optBuilder = JavaFormatterOptions.builder().style(style); + if (canChangeWhiteSpaceOnly) { + optBuilder.formatJavadoc(false).reorderModifiers(false); + } + return new Formatter(optBuilder.build()); + } + + @Override + public @NotNull Set<Feature> getFeatures() { + return Set.of(Feature.FORMAT_FRAGMENTS, Feature.OPTIMIZE_IMPORTS); + } + + @Override + public boolean canFormat(@NotNull PsiFile file) { + return JavaFileType.INSTANCE.equals(file.getFileType()) + && GoogleJavaFormatSettings.getInstance(file.getProject()).isEnabled(); + } + + @Override + public @NotNull Set<ImportOptimizer> getImportOptimizers(@NotNull PsiFile file) { + return IMPORT_OPTIMIZERS; + } + + private static final class GoogleJavaFormatFormattingTask implements FormattingTask { + private final Formatter formatter; + private final AsyncFormattingRequest request; + + private GoogleJavaFormatFormattingTask(Formatter formatter, AsyncFormattingRequest request) { + this.formatter = formatter; + this.request = request; + } + + @Override + public void run() { + try { + String formattedText = formatter.formatSource(request.getDocumentText(), toRanges(request)); + request.onTextReady(formattedText); + } catch (FormatterException e) { + request.onError( + Notifications.PARSING_ERROR_TITLE, + Notifications.parsingErrorMessage(request.getContext().getContainingFile().getName())); + } + } + + private static Collection<Range<Integer>> toRanges(AsyncFormattingRequest request) { + if (isWholeFile(request)) { + // The IDE sometimes passes invalid ranges when the file is unsaved before invoking the + // formatter. So this is a workaround for that issue. + return ImmutableList.of(Range.closedOpen(0, request.getDocumentText().length())); + } + return request.getFormattingRanges().stream() + .map(textRange -> Range.closedOpen(textRange.getStartOffset(), textRange.getEndOffset())) + .collect(ImmutableList.toImmutableList()); + } + + private static boolean isWholeFile(AsyncFormattingRequest request) { + List<TextRange> ranges = request.getFormattingRanges(); + return ranges.size() == 1 + && ranges.get(0).getStartOffset() == 0 + // using greater than or equal because ranges are sometimes passed inaccurately + && ranges.get(0).getEndOffset() >= request.getDocumentText().length(); + } + + @Override + public boolean isRunUnderProgress() { + return true; + } + + @Override + public boolean cancel() { + return false; + } + } +} diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java new file mode 100644 index 0000000..4251242 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 Google Inc. All Rights Reserved. + * + * 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 com.google.googlejavaformat.intellij; + +import com.google.common.util.concurrent.Runnables; +import com.google.googlejavaformat.java.FormatterException; +import com.google.googlejavaformat.java.ImportOrderer; +import com.google.googlejavaformat.java.JavaFormatterOptions; +import com.google.googlejavaformat.java.RemoveUnusedImports; +import com.intellij.ide.highlighter.JavaFileType; +import com.intellij.lang.ImportOptimizer; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import org.jetbrains.annotations.NotNull; + +/** Uses {@code google-java-format} to optimize imports. */ +public class GoogleJavaFormatImportOptimizer implements ImportOptimizer { + + @Override + public boolean supports(@NotNull PsiFile file) { + return JavaFileType.INSTANCE.equals(file.getFileType()) + && GoogleJavaFormatSettings.getInstance(file.getProject()).isEnabled(); + } + + @Override + public @NotNull Runnable processFile(@NotNull PsiFile file) { + Project project = file.getProject(); + + if (!JreConfigurationChecker.checkJreConfiguration(file.getProject())) { + return Runnables.doNothing(); + } + + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + Document document = documentManager.getDocument(file); + + if (document == null) { + return Runnables.doNothing(); + } + + JavaFormatterOptions.Style style = GoogleJavaFormatSettings.getInstance(project).getStyle(); + + final String origText = document.getText(); + String text; + try { + text = ImportOrderer.reorderImports(RemoveUnusedImports.removeUnusedImports(origText), style); + } catch (FormatterException e) { + Notifications.displayParsingErrorNotification(project, file.getName()); + return Runnables.doNothing(); + } + + // pointless to change document text if it hasn't changed, plus this can interfere with + // e.g. GoogleJavaFormattingService's output, i.e. it can overwrite the results from the main + // formatter. + if (text.equals(origText)) { + return Runnables.doNothing(); + } + + return () -> { + if (documentManager.isDocumentBlockedByPsi(document)) { + documentManager.doPostponedOperationsAndUnblockDocument(document); + } + + // similarly to above, don't overwrite new document text if it has changed - we use + // getCharsSequence() as we should have `writeAction()` (which I think means effectively a + // write-lock) and it saves calling getText(), which apparently is expensive. + CharSequence newText = document.getCharsSequence(); + if (CharSequence.compare(origText, newText) != 0) { + return; + } + + document.setText(text); + }; + } +} diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java new file mode 100644 index 0000000..ee187c0 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * 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 com.google.googlejavaformat.intellij; + +import com.google.googlejavaformat.java.JavaFormatterOptions; +import com.intellij.openapi.components.PersistentStateComponent; +import com.intellij.openapi.components.State; +import com.intellij.openapi.components.Storage; +import com.intellij.openapi.project.Project; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; + +@State( + name = "GoogleJavaFormatSettings", + storages = {@Storage("google-java-format.xml")}) +class GoogleJavaFormatSettings implements PersistentStateComponent<GoogleJavaFormatSettings.State> { + + private final Project project; + + private State state = new State(); + + GoogleJavaFormatSettings(Project project) { + this.project = project; + } + + static GoogleJavaFormatSettings getInstance(Project project) { + return project.getService(GoogleJavaFormatSettings.class); + } + + @Nullable + @Override + public State getState() { + return state; + } + + @Override + public void loadState(@NotNull State state) { + this.state = state; + } + + boolean isEnabled() { + return state.enabled.equals(EnabledState.ENABLED); + } + + void setEnabled(boolean enabled) { + setEnabled(enabled ? EnabledState.ENABLED : EnabledState.DISABLED); + } + + void setEnabled(EnabledState enabled) { + if (enabled.equals(EnabledState.ENABLED)) { + JreConfigurationChecker.checkJreConfiguration(project); + } + state.enabled = enabled; + } + + boolean isUninitialized() { + return state.enabled.equals(EnabledState.UNKNOWN); + } + + JavaFormatterOptions.Style getStyle() { + return state.style; + } + + void setStyle(JavaFormatterOptions.Style style) { + state.style = style; + } + + enum EnabledState { + UNKNOWN, + ENABLED, + DISABLED + } + + static class State { + + private EnabledState enabled = EnabledState.UNKNOWN; + public JavaFormatterOptions.Style style = JavaFormatterOptions.Style.GOOGLE; + + // enabled used to be a boolean so we use bean property methods for backwards compatibility + public void setEnabled(@Nullable String enabledStr) { + if (enabledStr == null) { + enabled = EnabledState.UNKNOWN; + } else if (Boolean.parseBoolean(enabledStr)) { + enabled = EnabledState.ENABLED; + } else { + enabled = EnabledState.DISABLED; + } + } + + public String getEnabled() { + switch (enabled) { + case ENABLED: + return "true"; + case DISABLED: + return "false"; + default: + return null; + } + } + } +} diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java new file mode 100644 index 0000000..940def6 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * 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 com.google.googlejavaformat.intellij; + +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationGroup; +import com.intellij.notification.NotificationGroupManager; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.startup.StartupActivity; +import org.jetbrains.annotations.NotNull; + +final class InitialConfigurationStartupActivity implements StartupActivity.Background { + + private static final String NOTIFICATION_TITLE = "Enable google-java-format"; + private static final NotificationGroup NOTIFICATION_GROUP = + NotificationGroupManager.getInstance().getNotificationGroup(NOTIFICATION_TITLE); + + @Override + public void runActivity(@NotNull Project project) { + GoogleJavaFormatSettings settings = GoogleJavaFormatSettings.getInstance(project); + + if (settings.isUninitialized()) { + settings.setEnabled(false); + displayNewUserNotification(project, settings); + } else if (settings.isEnabled()) { + JreConfigurationChecker.checkJreConfiguration(project); + } + } + + private void displayNewUserNotification(Project project, GoogleJavaFormatSettings settings) { + Notification notification = + new Notification( + NOTIFICATION_GROUP.getDisplayId(), + NOTIFICATION_TITLE, + "The google-java-format plugin is disabled by default. " + + "<a href=\"enable\">Enable for this project</a>.", + NotificationType.INFORMATION); + notification.setListener( + (n, e) -> { + settings.setEnabled(true); + n.expire(); + }); + notification.notify(project); + } +} diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java new file mode 100644 index 0000000..5084b6a --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java @@ -0,0 +1,104 @@ +/* + * Copyright 2023 Google Inc. All Rights Reserved. + * + * 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 com.google.googlejavaformat.intellij; + +import com.google.common.base.Suppliers; +import com.intellij.ide.ui.IdeUiService; +import com.intellij.notification.Notification; +import com.intellij.notification.NotificationType; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import java.util.function.Supplier; + +class JreConfigurationChecker { + + private static final Supplier<Boolean> hasAccess = + Suppliers.memoize(JreConfigurationChecker::checkJreConfiguration); + private static final Logger logger = Logger.getInstance(JreConfigurationChecker.class); + + private final Project project; + + public JreConfigurationChecker(Project project) { + this.project = project; + } + + static boolean checkJreConfiguration(Project project) { + var success = hasAccess.get(); + if (!success) { + project.getService(JreConfigurationChecker.class).displayConfigurationErrorNotification(); + } + return success; + } + + /** + * Determine whether the JRE is configured to work with the google-java-format plugin. If not, + * display a notification with instructions and return false. + */ + private static boolean checkJreConfiguration() { + try { + return testClassAccess( + "com.sun.tools.javac.api.JavacTrees", + "com.sun.tools.javac.code.Flags", + "com.sun.tools.javac.file.JavacFileManager", + "com.sun.tools.javac.parser.JavacParser", + "com.sun.tools.javac.tree.JCTree", + "com.sun.tools.javac.util.Log"); + } catch (ClassNotFoundException e) { + logger.error("Error checking jre configuration for google-java-format", e); + return false; + } + } + + private static boolean testClassAccess(String... classNames) throws ClassNotFoundException { + for (String className : classNames) { + if (!testClassAccess(className)) { + return false; + } + } + return true; + } + + private static boolean testClassAccess(String className) throws ClassNotFoundException { + Class<?> klass = Class.forName(className); + return klass + .getModule() + // isExported returns true if the package is either open or exported. Either one is + // sufficient + // to run the google-java-format code (even though the documentation specifies --add-opens). + .isExported( + klass.getPackageName(), + JreConfigurationChecker.class.getClassLoader().getUnnamedModule()); + } + + private void displayConfigurationErrorNotification() { + Notification notification = + new Notification( + "Configure JRE for google-java-format", + "Configure the JRE for google-java-format", + "The google-java-format plugin needs additional configuration before it can be used. " + + "<a href=\"instructions\">Follow the instructions here</a>.", + NotificationType.INFORMATION); + notification.setListener( + (n, e) -> { + IdeUiService.getInstance() + .browse( + "https://github.com/google/google-java-format/blob/master/README.md#intellij-jre-config"); + n.expire(); + }); + notification.notify(project); + } +} diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java new file mode 100644 index 0000000..d32aa98 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Google Inc. All Rights Reserved. + * + * 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 com.google.googlejavaformat.intellij; + +import com.intellij.formatting.service.FormattingNotificationService; +import com.intellij.openapi.project.Project; + +class Notifications { + + static final String PARSING_ERROR_NOTIFICATION_GROUP = "google-java-format parsing error"; + static final String PARSING_ERROR_TITLE = PARSING_ERROR_NOTIFICATION_GROUP; + + static String parsingErrorMessage(String filename) { + return "google-java-format failed. Does " + filename + " have syntax errors?"; + } + + static void displayParsingErrorNotification(Project project, String filename) { + FormattingNotificationService.getInstance(project) + .reportError( + Notifications.PARSING_ERROR_NOTIFICATION_GROUP, + Notifications.PARSING_ERROR_TITLE, + Notifications.parsingErrorMessage(filename)); + } +} diff --git a/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/UiFormatterStyle.java b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/UiFormatterStyle.java new file mode 100644 index 0000000..24ed6f6 --- /dev/null +++ b/idea_plugin/src/main/java/com/google/googlejavaformat/intellij/UiFormatterStyle.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * 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 com.google.googlejavaformat.intellij; + +import com.google.googlejavaformat.java.JavaFormatterOptions; +import com.google.googlejavaformat.java.JavaFormatterOptions.Style; +import java.util.Arrays; +import java.util.Objects; + +/** Configuration options for the formatting style. */ +enum UiFormatterStyle { + GOOGLE("Default Google Java style", Style.GOOGLE), + AOSP("Android Open Source Project (AOSP) style", Style.AOSP); + + private final String description; + private final JavaFormatterOptions.Style style; + + UiFormatterStyle(String description, JavaFormatterOptions.Style style) { + this.description = description; + this.style = style; + } + + @Override + public String toString() { + return description; + } + + public JavaFormatterOptions.Style convert() { + return style; + } + + static UiFormatterStyle convert(JavaFormatterOptions.Style style) { + return Arrays.stream(UiFormatterStyle.values()) + .filter(value -> Objects.equals(value.style, style)) + .findFirst() + .get(); + } +} |