aboutsummaryrefslogtreecommitdiff
path: root/idea_plugin/src/main/java/com/google/googlejavaformat
diff options
context:
space:
mode:
authorSorin Basca <sorinbasca@google.com>2024-01-16 12:42:22 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2024-01-16 12:42:22 +0000
commit627f7e9e05bd10152525ff7a5f4337c1ab52efbb (patch)
treed592898fe81e13c3f80b13796f41ac7dc8417424 /idea_plugin/src/main/java/com/google/googlejavaformat
parent23900a4073caf1243f551a2b9a1a2c13eab80dcf (diff)
parent897a1820eb727e6daab691616114df17d04bc66d (diff)
downloadgoogle-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')
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.form40
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatConfigurable.java206
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatFormattingService.java141
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatImportOptimizer.java90
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/GoogleJavaFormatSettings.java115
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/InitialConfigurationStartupActivity.java60
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/JreConfigurationChecker.java104
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/Notifications.java38
-rw-r--r--idea_plugin/src/main/java/com/google/googlejavaformat/intellij/UiFormatterStyle.java52
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();
+ }
+}