diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java index be69482d6e6f..93cbb2792411 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/StreamApiMigrationInspection.java @@ -44,6 +44,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * User: anna @@ -123,17 +124,22 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo PsiBreakStatement.class, PsiReturnStatement.class, PsiThrowStatement.class); if (exitPoints.isEmpty()) { - final List usedVariables = ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset); - for (PsiVariable variable : usedVariables) { - if (!HighlightControlFlowUtil.isEffectivelyFinal(variable, body, null)) { - return; - } - } - if (ExceptionUtil.getThrownCheckedExceptions(new PsiElement[]{body}).isEmpty()) { TerminalBlock tb = TerminalBlock.from(statement.getIterationParameter(), body); List operations = tb.extractOperations(); + final List nonFinalVariables = ControlFlowUtil.getUsedVariables(controlFlow, startOffset, endOffset) + .stream().filter(variable -> !HighlightControlFlowUtil.isEffectivelyFinal(variable, body, null)) + .collect(Collectors.toList()); + + if(getCounter(statement, tb, operations, nonFinalVariables) != null) { + holder.registerProblem(iteratedValue, "Can be replaced with count() call", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + new ReplaceWithCountFix()); + } + if(!nonFinalVariables.isEmpty()) { + return; + } if ((isArray || !isRawSubstitution(iteratedValueType, collectionClass)) && isCollectCall(tb, operations)) { boolean addAll = operations.isEmpty() && isAddAllCall(tb); holder.registerProblem(iteratedValue, "Can be replaced with " + (addAll ? "addAll call" : "collect call"), @@ -169,6 +175,43 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo }; } + private static PsiExpression extractIncrementedExpression(PsiStatement statement) { + if(!(statement instanceof PsiExpressionStatement)) return null; + PsiExpression expression = ((PsiExpressionStatement)statement).getExpression(); + PsiExpression operand; + if(expression instanceof PsiPostfixExpression) { + if(!JavaTokenType.PLUSPLUS.equals(((PsiPostfixExpression)expression).getOperationTokenType())) return null; + operand = ((PsiPostfixExpression)expression).getOperand(); + } else if(expression instanceof PsiPrefixExpression) { + if(!JavaTokenType.PLUSPLUS.equals(((PsiPrefixExpression)expression).getOperationTokenType())) return null; + operand = ((PsiPrefixExpression)expression).getOperand(); + } else return null; // TODO: support i = i+1; + return operand; + } + + @Nullable + private static PsiLocalVariable getCounter(PsiForeachStatement foreachStatement, + TerminalBlock tb, + List operations, + List variables) { + // have only one non-final variable + if(variables.size() != 1) return null; + + // have single expression which is either ++x or x++ + PsiExpression operand = extractIncrementedExpression(tb.getSingleStatement()); + if(!(operand instanceof PsiReferenceExpression)) return null; + PsiElement element = ((PsiReferenceExpression)operand).resolve(); + + // the referred variable is the same as non-final variable + if(!(element instanceof PsiLocalVariable) || !variables.contains(element)) return null; + + // the referred variable is not used in intermediate operations + for(Operation operation : operations) { + if(ReferencesSearch.search(element, new LocalSearchScope(operation.getExpression())).findFirst() != null) return null; + } + return (PsiLocalVariable)element; + } + private static boolean isAddAllCall(TerminalBlock tb) { final PsiVariable variable = tb.getVariable(); final PsiMethodCallExpression methodCallExpression = tb.getSingleMethodCall(); @@ -288,6 +331,12 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo return mapperCall instanceof PsiReferenceExpression && ((PsiReferenceExpression)mapperCall).resolve() == variable; } + private static void reformatWhenNeeded(@NotNull Project project, PsiElement result) { + if (result != null) { + CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result)); + } + } + private static class ReplaceWithForeachCallFix implements LocalQuickFix { private final String myForEachMethodName; @@ -476,12 +525,6 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } } - private static void reformatWhenNeeded(@NotNull Project project, PsiElement result) { - if (result != null) { - CodeStyleManager.getInstance(project).reformat(JavaCodeStyleManager.getInstance(project).shortenClassReferences(result)); - } - } - private static String createInitializerReplacementText(PsiType varType, PsiExpression initializer) { final PsiType initializerType = initializer.getType(); final PsiClassType rawType = initializerType instanceof PsiClassType ? ((PsiClassType)initializerType).rawType() : null; @@ -513,6 +556,65 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo } + private static class ReplaceWithCountFix implements LocalQuickFix { + + @NotNull + @Override + public String getName() { + return getFamilyName(); + } + + @NotNull + @Override + public String getFamilyName() { + return "Replace with count()"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiForeachStatement foreachStatement = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiForeachStatement.class); + if (foreachStatement != null) { + if (!FileModificationService.getInstance().preparePsiElementForWrite(foreachStatement)) return; + final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); + PsiStatement body = foreachStatement.getBody(); + final PsiExpression iteratedValue = foreachStatement.getIteratedValue(); + if (body != null && iteratedValue != null) { + final PsiParameter parameter = foreachStatement.getIterationParameter(); + TerminalBlock tb = TerminalBlock.from(parameter, body); + List intermediateOps = tb.extractOperationReplacements(elementFactory); + PsiExpression operand = extractIncrementedExpression(tb.getSingleStatement()); + if(!(operand instanceof PsiReferenceExpression)) return; + PsiElement element = ((PsiReferenceExpression)operand).resolve(); + if(!(element instanceof PsiLocalVariable)) return; + PsiLocalVariable var = (PsiLocalVariable)element; + final StringBuilder builder = generateStream(iteratedValue, intermediateOps); + builder.append(".count()"); + PsiElement declaration = var.getParent(); + if(declaration instanceof PsiDeclarationStatement) { + PsiElement[] elements = ((PsiDeclarationStatement)declaration).getDeclaredElements(); + if(elements[elements.length-1] == var && foreachStatement.equals( + PsiTreeUtil.skipSiblingsForward(declaration, PsiWhiteSpace.class, PsiComment.class))) { + PsiExpression initializer = var.getInitializer(); + if(initializer != null && initializer.getText().equals("0")) { + String typeStr = var.getType().getCanonicalText(); + String replacement = (typeStr.equals("long") ? "" : "(" + typeStr + ") ") + builder; + initializer.replace(elementFactory.createExpressionFromText(replacement, foreachStatement)); + simplifyRedundantCast(var); + foreachStatement.delete(); + reformatWhenNeeded(project, var); + return; + } + } + } + PsiElement result = foreachStatement.replace(elementFactory.createStatementFromText(var.getName()+"+="+builder+";", foreachStatement)); + simplifyRedundantCast(result); + reformatWhenNeeded(project, result); + } + } + } + + } + /** * Intermediate stream operation representation */ diff --git a/java/java-impl/src/com/intellij/codeInsight/javadoc/JavaDocExternalFilter.java b/java/java-impl/src/com/intellij/codeInsight/javadoc/JavaDocExternalFilter.java index e3efd9e1fd2c..2f759866bf64 100644 --- a/java/java-impl/src/com/intellij/codeInsight/javadoc/JavaDocExternalFilter.java +++ b/java/java-impl/src/com/intellij/codeInsight/javadoc/JavaDocExternalFilter.java @@ -23,11 +23,12 @@ import com.intellij.lang.java.JavaDocumentationProvider; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.NullableComputable; -import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; +import com.intellij.util.Url; +import com.intellij.util.Urls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -35,6 +36,7 @@ import org.jetbrains.builtInWebServer.BuiltInServerOptions; import org.jetbrains.builtInWebServer.WebServerPathToFileManager; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -111,14 +113,13 @@ public class JavaDocExternalFilter extends AbstractExternalFilter { public String getExternalDocInfoForElement(@NotNull String docURL, final PsiElement element) throws Exception { String externalDoc = null; myElement = element; - String builtInServer = "http://localhost:" + BuiltInServerOptions.getInstance().getEffectiveBuiltInServerPort() + "/" + myProject.getName() + "/"; + String projectPath = "/" + myProject.getName() + "/"; + String builtInServer = "http://localhost:" + BuiltInServerOptions.getInstance().getEffectiveBuiltInServerPort() + projectPath; if (docURL.startsWith(builtInServer)) { - int refPosition = docURL.lastIndexOf('#'); - VirtualFile file = WebServerPathToFileManager.getInstance(myProject).findVirtualFile( - docURL.substring(builtInServer.length(), refPosition < builtInServer.length() ? docURL.length() : refPosition) - ); + Url url = Urls.parseFromIdea(docURL); + VirtualFile file = url == null ? null : WebServerPathToFileManager.getInstance(myProject).findVirtualFile(url.getPath().substring(projectPath.length())); if (file != null) { - InputStreamReader reader = new InputStreamReader(file.getInputStream(), CharsetToolkit.UTF8_CHARSET); + InputStreamReader reader = new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8); StringBuilder result = new StringBuilder(); try { doBuildFromStream(docURL, reader, result); diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCountArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCountArray.java new file mode 100644 index 000000000000..a05d937ac819 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCountArray.java @@ -0,0 +1,10 @@ +// "Replace with count()" "true" + +import java.util.Arrays; + +public class Main { + public long test(String[] array) { + long longStrings = Arrays.stream(array).map(String::trim).filter(trimmed -> trimmed.length() > 10).count(); + return longStrings; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCountInner.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCountInner.java new file mode 100644 index 000000000000..12c45a103a95 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCountInner.java @@ -0,0 +1,14 @@ +// "Replace with count()" "true" +import java.util.List; +import java.util.Set; + +public class Main { + public void test(List> nested) { + int count = 0; + for(Set element : nested) { + if(element != null) { + count += element.stream().filter(str -> str.startsWith("xyz")).count(); + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCountOuter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCountOuter.java new file mode 100644 index 000000000000..5d2889738b9f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/afterCountOuter.java @@ -0,0 +1,10 @@ +// "Replace with count()" "true" +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public class Main { + public void test(List> nested) { + int count = (int) nested.stream().filter(element -> element != null).flatMap(Collection::stream).filter(str -> str.startsWith("xyz")).count(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCountArray.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCountArray.java new file mode 100644 index 000000000000..5d1ed4169250 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCountArray.java @@ -0,0 +1,14 @@ +// "Replace with count()" "true" + +public class Main { + public long test(String[] array) { + long longStrings = 0; + for(String str : array) { + String trimmed = str.trim(); + if(trimmed.length() > 10) { + longStrings++; + } + } + return longStrings; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCountInner.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCountInner.java new file mode 100644 index 000000000000..825262c52ac5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCountInner.java @@ -0,0 +1,18 @@ +// "Replace with count()" "true" +import java.util.List; +import java.util.Set; + +public class Main { + public void test(List> nested) { + int count = 0; + for(Set element : nested) { + if(element != null) { + for(String str : element) { + if(str.startsWith("xyz")) { + count++; + } + } + } + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCountOuter.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCountOuter.java new file mode 100644 index 000000000000..4da30a7d6f1f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/streamApiMigration/beforeCountOuter.java @@ -0,0 +1,18 @@ +// "Replace with count()" "true" +import java.util.List; +import java.util.Set; + +public class Main { + public void test(List> nested) { + int count = 0; + for(Set element : nested) { + if(element != null) { + for(String str : element) { + if(str.startsWith("xyz")) { + count++; + } + } + } + } + } +} \ No newline at end of file diff --git a/platform/configuration-store-impl/src/DefaultProjectStoreImpl.kt b/platform/configuration-store-impl/src/DefaultProjectStoreImpl.kt index bdb7fa0c70a7..c808b68f560f 100644 --- a/platform/configuration-store-impl/src/DefaultProjectStoreImpl.kt +++ b/platform/configuration-store-impl/src/DefaultProjectStoreImpl.kt @@ -53,7 +53,7 @@ internal class DefaultProjectStoreImpl(override val project: ProjectImpl, privat override fun createSaveSession(states: StateMap) = object : FileBasedStorage.FileSaveSession(states, this) { override fun saveLocally(element: Element?) { - super.saveLocally(Element("application").addContent(Element("component").setAttribute("name", "ProjectManager").addContent(element))) + super.saveLocally(element?.let { Element("application").addContent(Element("component").setAttribute("name", "ProjectManager").addContent(it))}) } } } diff --git a/platform/configuration-store-impl/src/StateMap.kt b/platform/configuration-store-impl/src/StateMap.kt index 4a28cbb49a7c..c89569b3d6af 100644 --- a/platform/configuration-store-impl/src/StateMap.kt +++ b/platform/configuration-store-impl/src/StateMap.kt @@ -77,7 +77,7 @@ fun stateToElement(key: String, state: Any?, newLiveStates: Map return state.clone() } else { - return newLiveStates?.get(key) ?: (state as? ByteArray)?.let { unarchiveState(it) } + return newLiveStates?.get(key) ?: (state as? ByteArray)?.let(::unarchiveState) } } diff --git a/platform/configuration-store-impl/src/XmlElementStorage.kt b/platform/configuration-store-impl/src/XmlElementStorage.kt index 10002f157bb2..f39b0fc9289e 100644 --- a/platform/configuration-store-impl/src/XmlElementStorage.kt +++ b/platform/configuration-store-impl/src/XmlElementStorage.kt @@ -201,7 +201,15 @@ private fun save(states: StateMap, rootElementName: String?, newLiveStates: Map< val rootElement = if (rootElementName == null) null else Element(rootElementName) for (componentName in states.keys()) { - val element = states.getElement(componentName, newLiveStates) ?: continue + val element: Element + try { + element = states.getElement(componentName, newLiveStates) ?: continue + } + catch (e: Exception) { + LOG.error("Cannot save \"$componentName\" data", e) + continue + } + // name attribute should be first val elementAttributes = element.attributes if (elementAttributes.isEmpty()) { diff --git a/platform/platform-api/src/com/intellij/credentialStore/CredentialAttributes.kt b/platform/platform-api/src/com/intellij/credentialStore/CredentialAttributes.kt index 2a9345c7f485..27d55c4e8ff0 100644 --- a/platform/platform-api/src/com/intellij/credentialStore/CredentialAttributes.kt +++ b/platform/platform-api/src/com/intellij/credentialStore/CredentialAttributes.kt @@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicReference /** * requestor is deprecated. Never use it in new code. */ -data class CredentialAttributes(val serviceName: String, val userName: String? = null, val requestor: Class<*>? = null) { +data class CredentialAttributes @JvmOverloads constructor(val serviceName: String, val userName: String? = null, val requestor: Class<*>? = null) { } // user cannot be empty, but password can be @@ -38,6 +38,8 @@ class Credentials(user: String?, val password: OneTimeString? = null) { constructor(user: String?, password: CharArray?) : this(user, password?.let { OneTimeString(it) }) + constructor(user: String?, password: ByteArray?) : this(user, password?.let { OneTimeString(password) }) + val userName = user.nullize() fun getPasswordAsString() = password?.toString() diff --git a/platform/platform-api/src/com/intellij/ui/HighlightedText.java b/platform/platform-api/src/com/intellij/ui/HighlightedText.java index 8e5c46b3dd5d..27d51e0882de 100644 --- a/platform/platform-api/src/com/intellij/ui/HighlightedText.java +++ b/platform/platform-api/src/com/intellij/ui/HighlightedText.java @@ -19,14 +19,14 @@ package com.intellij.ui; import com.intellij.openapi.editor.markup.TextAttributes; import java.util.ArrayList; +import java.util.List; public class HighlightedText { - private final StringBuffer myBuffer; - private final ArrayList myHighlightedRegions; + private final StringBuilder myBuffer; + private final List myHighlightedRegions = new ArrayList<>(); public HighlightedText() { - myBuffer = new StringBuffer(); - myHighlightedRegions = new ArrayList(3); + myBuffer = new StringBuilder(); } public void appendText(String text, TextAttributes attributes) { @@ -37,16 +37,11 @@ public class HighlightedText { } } - public void insertTextAtStart(String text, TextAttributes attributes) { - int textLength = text.length(); - for (int i=0; i < myHighlightedRegions.size(); i++) { - HighlightedRegion info = (HighlightedRegion)myHighlightedRegions.get(i); - info.startOffset += textLength; - info.endOffset += textLength; - } - myBuffer.insert(0, text); + public void appendText(char[] text, TextAttributes attributes) { + int startOffset = myBuffer.length(); + myBuffer.append(text); if (attributes != null) { - myHighlightedRegions.add(new HighlightedRegion(0, textLength, attributes)); + myHighlightedRegions.add(new HighlightedRegion(startOffset, myBuffer.length(), attributes)); } } @@ -67,10 +62,8 @@ public class HighlightedText { public void applyToComponent(HighlightableComponent renderer) { renderer.setText(myBuffer.toString()); - for (int i=0; i < myHighlightedRegions.size(); i++) { - HighlightedRegion info = (HighlightedRegion)myHighlightedRegions.get(i); + for (HighlightedRegion info : myHighlightedRegions) { renderer.addHighlighter(info.startOffset, info.endOffset, info.textAttributes); } } - } diff --git a/platform/platform-api/src/com/intellij/ui/HyperlinkLabel.java b/platform/platform-api/src/com/intellij/ui/HyperlinkLabel.java index 002e2abda263..b907c9955d4b 100644 --- a/platform/platform-api/src/com/intellij/ui/HyperlinkLabel.java +++ b/platform/platform-api/src/com/intellij/ui/HyperlinkLabel.java @@ -16,20 +16,18 @@ package com.intellij.ui; -import javax.accessibility.AccessibleAction; -import javax.accessibility.AccessibleContext; -import javax.accessibility.AccessibleRole; import com.intellij.ide.BrowserUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.markup.EffectType; import com.intellij.openapi.editor.markup.TextAttributes; -import com.intellij.util.NotNullProducer; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.PlatformColors; import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.accessibility.AccessibleAction; +import javax.accessibility.AccessibleContext; +import javax.accessibility.AccessibleRole; import javax.swing.*; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; @@ -204,7 +202,7 @@ public class HyperlinkLabel extends HighlightableComponent { @Override public void handleText(char[] data, int pos) { - highlightedText.appendText(new String(data), currentAttributes); + highlightedText.appendText(data, currentAttributes); } @Override diff --git a/platform/platform-api/src/com/intellij/util/net/AuthenticationDialog.java b/platform/platform-api/src/com/intellij/util/net/AuthenticationDialog.java index 0f8c2495f929..1000283a0333 100644 --- a/platform/platform-api/src/com/intellij/util/net/AuthenticationDialog.java +++ b/platform/platform-api/src/com/intellij/util/net/AuthenticationDialog.java @@ -41,21 +41,6 @@ public class AuthenticationDialog extends DialogWrapper { init(); } - public AuthenticationDialog(String title, String description, final String login, final String password, final boolean rememberPassword) { - super(JOptionPane.getRootFrame(), true); - setTitle(title); - - MnemonicHelper.init(getContentPane()); - panel = new AuthenticationPanel(description, login, password, rememberPassword); - - final Window window = getWindow(); - if (window instanceof JDialog) { - ((JDialog) window).setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - } - - init(); - } - @Nullable @Override public JComponent getPreferredFocusedComponent() { diff --git a/platform/platform-impl/src/com/intellij/diagnostic/ErrorReportConfigurable.java b/platform/platform-impl/src/com/intellij/diagnostic/ErrorReportConfigurable.java index 3f865dd19abd..e738a646c5e3 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/ErrorReportConfigurable.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/ErrorReportConfigurable.java @@ -15,58 +15,43 @@ */ package com.intellij.diagnostic; -import com.intellij.openapi.components.NamedComponent; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.util.DefaultJDOMExternalizer; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.JDOMExternalizable; -import com.intellij.openapi.util.WriteExternalException; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.util.Base64; -import org.jdom.Element; -import org.jetbrains.annotations.NotNull; +import com.intellij.credentialStore.CredentialAttributes; +import com.intellij.credentialStore.Credentials; +import com.intellij.ide.passwordSafe.PasswordSafe; +import com.intellij.openapi.components.*; +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.Nullable; -public class ErrorReportConfigurable implements JDOMExternalizable, NamedComponent { - public String ITN_LOGIN = ""; - public String ITN_PASSWORD_CRYPT = ""; - public boolean KEEP_ITN_PASSWORD = false; +import java.util.Base64; - public String EMAIL = ""; +@State(name = "ErrorReportConfigurable", storages = @Storage(value = "other.xml", deprecated = true, roamingType = RoamingType.DISABLED)) +class ErrorReportConfigurable implements PersistentStateComponent { + public static final String SERVICE_NAME = "IntelliJ Platform — JetBrains Account"; + + static class State { + public String ITN_LOGIN; + public String ITN_PASSWORD_CRYPT; + } public static ErrorReportConfigurable getInstance() { return ServiceManager.getService(ErrorReportConfigurable.class); } + @Nullable @Override - public void readExternal(Element element) throws InvalidDataException { - DefaultJDOMExternalizer.readExternal(this, element); - if (!KEEP_ITN_PASSWORD) { - ITN_PASSWORD_CRYPT = ""; + public State getState() { + return new State(); + } + + @Override + public void loadState(State state) { + if (!StringUtil.isEmpty(state.ITN_LOGIN) || !StringUtil.isEmpty(state.ITN_PASSWORD_CRYPT)) { + PasswordSafe.getInstance().set(new CredentialAttributes(SERVICE_NAME, state.ITN_LOGIN), new Credentials(state.ITN_LOGIN, Base64.getDecoder().decode(state.ITN_PASSWORD_CRYPT))); } } - @Override - public void writeExternal(Element element) throws WriteExternalException { - String itnPassword = ITN_PASSWORD_CRYPT; - if (!KEEP_ITN_PASSWORD) { - ITN_PASSWORD_CRYPT = ""; - } - DefaultJDOMExternalizer.writeExternal(this, element); - - ITN_PASSWORD_CRYPT = itnPassword; - } - - @Override - @NotNull - public String getComponentName() { - return "ErrorReportConfigurable"; - } - - public String getPlainItnPassword() { - return new String(Base64.decode(getInstance().ITN_PASSWORD_CRYPT), CharsetToolkit.UTF8_CHARSET); - } - - public void setPlainItnPassword(String password) { - ITN_PASSWORD_CRYPT = Base64.encode(password.getBytes(CharsetToolkit.UTF8_CHARSET)); + @Nullable + public static Credentials getCredentials() { + return PasswordSafe.getInstance().get(new CredentialAttributes(SERVICE_NAME)); } } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.java b/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.java index 85dd18e9add8..4fae72516f46 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/ITNReporter.java @@ -16,6 +16,8 @@ package com.intellij.diagnostic; import com.intellij.CommonBundle; +import com.intellij.credentialStore.CredentialAttributesKt; +import com.intellij.credentialStore.Credentials; import com.intellij.errorreport.bean.ErrorBean; import com.intellij.errorreport.error.InternalEAPException; import com.intellij.errorreport.error.NoSuchEAPUserException; @@ -28,7 +30,6 @@ import com.intellij.idea.IdeaLogger; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.ErrorReportSubmitter; import com.intellij.openapi.diagnostic.IdeaLoggingEvent; @@ -77,15 +78,13 @@ public class ITNReporter extends ErrorReportSubmitter { final Consumer callback, final ErrorBean errorBean, final String description) { - final DataContext dataContext = DataManager.getInstance().getDataContext(parentComponent); - final Project project = CommonDataKeys.PROJECT.getData(dataContext); - - ErrorReportConfigurable settings = ErrorReportConfigurable.getInstance(); - if (!settings.KEEP_ITN_PASSWORD && !StringUtil.isEmpty(settings.ITN_LOGIN) && StringUtil.isEmpty(settings.getPlainItnPassword())) { - JetBrainsAccountDialog dlg = new JetBrainsAccountDialog(parentComponent); - if (!dlg.showAndGet()) { + Credentials credentials = ErrorReportConfigurable.getCredentials(); + if (!CredentialAttributesKt.isFulfilled(credentials)) { + if (!new JetBrainsAccountDialog(parentComponent).showAndGet()) { return false; } + + credentials = ErrorReportConfigurable.getCredentials(); } errorBean.setDescription(description); @@ -95,17 +94,7 @@ public class ITNReporter extends ErrorReportSubmitter { errorBean.setPreviousException(previousExceptionThreadId); } - Throwable t = event.getThrowable(); - if (t != null) { - final PluginId pluginId = IdeErrorsDialog.findPluginId(t); - if (pluginId != null) { - final IdeaPluginDescriptor ideaPluginDescriptor = PluginManager.getPlugin(pluginId); - if (ideaPluginDescriptor != null && (!ideaPluginDescriptor.isBundled() || ideaPluginDescriptor.allowBundledUpdate())) { - errorBean.setPluginName(ideaPluginDescriptor.getName()); - errorBean.setPluginVersion(ideaPluginDescriptor.getVersion()); - } - } - } + setPluginInfo(event, errorBean); Object data = event.getData(); if (data instanceof AbstractMessage) { @@ -113,13 +102,14 @@ public class ITNReporter extends ErrorReportSubmitter { errorBean.setAttachments(((AbstractMessage)data).getIncludedAttachments()); } - String login = settings.ITN_LOGIN; - String password = settings.getPlainItnPassword(); + String login = credentials == null ? null : credentials.getUserName(); + String password = credentials == null ? null : credentials.getPasswordAsString(); if (StringUtil.isEmptyOrSpaces(login) && StringUtil.isEmptyOrSpaces(password)) { login = "idea_anonymous"; password = "guest"; } + Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(parentComponent)); ITNProxy.sendError(project, login, password, errorBean, threadId -> { updatePreviousThreadId(threadId); String url = ITNProxy.getBrowseUrl(threadId); @@ -175,6 +165,20 @@ public class ITNReporter extends ErrorReportSubmitter { return true; } + public static void setPluginInfo(IdeaLoggingEvent event, ErrorBean errorBean) { + Throwable t = event.getThrowable(); + if (t != null) { + final PluginId pluginId = IdeErrorsDialog.findPluginId(t); + if (pluginId != null) { + final IdeaPluginDescriptor ideaPluginDescriptor = PluginManager.getPlugin(pluginId); + if (ideaPluginDescriptor != null && (!ideaPluginDescriptor.isBundled() || ideaPluginDescriptor.allowBundledUpdate())) { + errorBean.setPluginName(ideaPluginDescriptor.getName()); + errorBean.setPluginVersion(ideaPluginDescriptor.getVersion()); + } + } + } + } + private static void updatePreviousThreadId(Integer threadId) { previousExceptionThreadId = threadId; } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java index bab7901c42af..739db6876806 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/IdeErrorsDialog.java @@ -17,6 +17,8 @@ package com.intellij.diagnostic; import com.intellij.CommonBundle; import com.intellij.ExtensionPoints; +import com.intellij.credentialStore.CredentialAttributesKt; +import com.intellij.credentialStore.Credentials; import com.intellij.diagnostic.errordialog.*; import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; @@ -52,7 +54,6 @@ import com.intellij.openapi.wm.IdeFrame; import com.intellij.ui.HeaderlessTabbedPane; import com.intellij.ui.HyperlinkLabel; import com.intellij.ui.IdeBorderFactory; -import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.DateFormatUtil; import com.intellij.util.ui.UIUtil; @@ -71,6 +72,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; @@ -80,7 +82,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene private static final Logger LOG = Logger.getInstance(IdeErrorsDialog.class.getName()); private final boolean myInternalMode; @NonNls private static final String ACTIVE_TAB_OPTION = IdeErrorsDialog.class.getName() + "activeTab"; - public static DataKey CURRENT_TRACE_KEY = DataKey.create("current_stack_trace_key"); + public static final DataKey CURRENT_TRACE_KEY = DataKey.create("current_stack_trace_key"); public static final int COMPONENTS_WIDTH = 670; public static Collection ourDevelopersList = Collections.emptyList(); @@ -287,7 +289,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene } }; if (!myInternalMode) { - myDetailsTabForm = new DetailsTabForm(null, myInternalMode); + myDetailsTabForm = new DetailsTabForm(null, false); myCommentsTabForm = new CommentsTabForm(); myCommentsTabForm.addCommentsListener(commentsListener); myTabs.addTab(DiagnosticBundle.message("error.comments.tab.title"), myCommentsTabForm.getContentPane()); @@ -298,7 +300,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene if (analyzePlatformAction != null) { myAnalyzeAction = new AnalyzeAction(analyzePlatformAction); } - myDetailsTabForm = new DetailsTabForm(myAnalyzeAction, myInternalMode); + myDetailsTabForm = new DetailsTabForm(myAnalyzeAction, true); myDetailsTabForm.setCommentsAreaVisible(true); myDetailsTabForm.addCommentsListener(commentsListener); } @@ -465,15 +467,16 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene private void updateCredentialsPane(AbstractMessage message) { if (message != null) { - final ErrorReportSubmitter submitter = getSubmitter(message.getThrowable()); + ErrorReportSubmitter submitter = getSubmitter(message.getThrowable()); if (submitter instanceof ITNReporter) { myCredentialsPanel.setVisible(true); - String userName = ErrorReportConfigurable.getInstance().ITN_LOGIN; - if (StringUtil.isEmpty(userName)) { - myCredentialsLabel.setHtmlText(DiagnosticBundle.message("diagnostic.error.report.submit.error.anonymously")); + Credentials credentials = ErrorReportConfigurable.getCredentials(); + if (CredentialAttributesKt.isFulfilled(credentials)) { + assert credentials != null; + myCredentialsLabel.setHtmlText(DiagnosticBundle.message("diagnostic.error.report.submit.report.as", credentials.getUserName())); } else { - myCredentialsLabel.setHtmlText(DiagnosticBundle.message("diagnostic.error.report.submit.report.as", userName)); + myCredentialsLabel.setHtmlText(DiagnosticBundle.message("diagnostic.error.report.submit.error.anonymously")); } return; } @@ -647,7 +650,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene myDetailsTabForm.setAssigneeId(message == null ? null : message.getAssigneeId()); - List attachments = message != null ? message.getAllAttachments() : Collections.emptyList(); + List attachments = message != null ? message.getAllAttachments() : Collections.emptyList(); if (!attachments.isEmpty()) { if (myTabs.indexOfComponent(myAttachmentsTabForm.getContentPane()) == -1) { myTabs.addTab(DiagnosticBundle.message("error.attachments.tab.title"), myAttachmentsTabForm.getContentPane()); @@ -672,7 +675,7 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene return throwable.getMessage(); } else { - return new StringBuffer().append(message.getMessage()).append("\n").append(message.getThrowableText()).toString(); + return message.getMessage() + "\n" + message.getThrowableText(); } } @@ -767,8 +770,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene } } - if (PluginManager.isPluginClass(className)) { - return PluginManager.getPluginByClassName(className); + if (PluginManagerCore.isPluginClass(className)) { + return PluginManagerCore.getPluginByClassName(className); } } } @@ -777,8 +780,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene if (t.getMessage() != null) { String className = t.getMessage(); - if (PluginManager.isPluginClass(className)) { - return PluginManager.getPluginByClassName(className); + if (PluginManagerCore.isPluginClass(className)) { + return PluginManagerCore.getPluginByClassName(className); } } } @@ -791,8 +794,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene pos = s.lastIndexOf('.'); if (pos >= 0) { s = s.substring(0, pos); - if (PluginManager.isPluginClass(s)) { - return PluginManager.getPluginByClassName(s); + if (PluginManagerCore.isPluginClass(s)) { + return PluginManagerCore.getPluginByClassName(s); } } } @@ -800,8 +803,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene else if (t instanceof ExtensionException) { String className = ((ExtensionException)t).getExtensionClass().getName(); - if (PluginManager.isPluginClass(className)) { - return PluginManager.getPluginByClassName(className); + if (PluginManagerCore.isPluginClass(className)) { + return PluginManagerCore.getPluginByClassName(className); } } @@ -1016,8 +1019,8 @@ public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListene private static String md5(String buffer, @NonNls String key) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.update(buffer.getBytes()); - byte[] code = md5.digest(key.getBytes()); + md5.update(buffer.getBytes(StandardCharsets.UTF_8)); + byte[] code = md5.digest(key.getBytes(StandardCharsets.UTF_8)); BigInteger bi = new BigInteger(code).abs(); return bi.abs().toString(16); } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountDialog.java b/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountDialog.java index fb34ba2342cf..d8d04b8b88bc 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountDialog.java +++ b/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountDialog.java @@ -15,9 +15,13 @@ */ package com.intellij.diagnostic; +import com.intellij.credentialStore.CredentialAttributes; +import com.intellij.credentialStore.Credentials; import com.intellij.ide.BrowserUtil; +import com.intellij.ide.passwordSafe.PasswordSafe; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.ClickListener; import com.intellij.util.net.HttpConfigurable; import org.jetbrains.annotations.NotNull; @@ -28,20 +32,8 @@ import java.awt.event.MouseEvent; public class JetBrainsAccountDialog extends DialogWrapper { private JTextField myItnLoginTextField; - private JPasswordField myItnPasswordTextField; - private JCheckBox myRememberITNPasswordCheckBox; - - public void storeInfo() { - ErrorReportConfigurable.getInstance().ITN_LOGIN = myItnLoginTextField.getText(); - ErrorReportConfigurable.getInstance().setPlainItnPassword(new String(myItnPasswordTextField.getPassword())); - ErrorReportConfigurable.getInstance().KEEP_ITN_PASSWORD = myRememberITNPasswordCheckBox.isSelected(); - } - - public void loadInfo() { - myItnLoginTextField.setText(ErrorReportConfigurable.getInstance().ITN_LOGIN); - myItnPasswordTextField.setText(ErrorReportConfigurable.getInstance().getPlainItnPassword()); - myRememberITNPasswordCheckBox.setSelected(ErrorReportConfigurable.getInstance().KEEP_ITN_PASSWORD); - } + private JPasswordField myPasswordText; + private JCheckBox myRememberCheckBox; public JetBrainsAccountDialog(Component parent) throws HeadlessException { super(parent, false); @@ -82,7 +74,13 @@ public class JetBrainsAccountDialog extends DialogWrapper { mySendingSettingsLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); - loadInfo(); + Credentials credentials = ErrorReportConfigurable.getCredentials(); + String userName = credentials == null ? null : credentials.getUserName(); + myItnLoginTextField.setText(userName); + String password = credentials == null ? null : credentials.getPasswordAsString(); + myPasswordText.setText(password); + // if no user name - never stored and so, defaults to remember. if user name set, but no password, so, previously was stored without password + myRememberCheckBox.setSelected(StringUtil.isEmpty(userName) || !StringUtil.isEmpty(password)); new ClickListener() { @Override @@ -98,7 +96,11 @@ public class JetBrainsAccountDialog extends DialogWrapper { @Override protected void doOKAction() { - storeInfo(); + String userName = myItnLoginTextField.getText(); + if (!StringUtil.isEmpty(userName)) { + PasswordSafe.getInstance().set(new CredentialAttributes(ErrorReportConfigurable.SERVICE_NAME, userName), + new Credentials(userName, myRememberCheckBox.isSelected() ? myPasswordText.getPassword() : null)); + } super.doOKAction(); } diff --git a/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountForm.form b/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountForm.form index c5f3697acb11..e6fc473ec7d1 100644 --- a/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountForm.form +++ b/platform/platform-impl/src/com/intellij/diagnostic/JetBrainsAccountForm.form @@ -8,7 +8,7 @@ - + @@ -17,55 +17,11 @@ - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -73,12 +29,46 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform/platform-impl/src/com/intellij/ui/EditorTextFieldCellRenderer.java b/platform/platform-impl/src/com/intellij/ui/EditorTextFieldCellRenderer.java index f648dae9e293..2033ccc82b92 100644 --- a/platform/platform-impl/src/com/intellij/ui/EditorTextFieldCellRenderer.java +++ b/platform/platform-impl/src/com/intellij/ui/EditorTextFieldCellRenderer.java @@ -16,7 +16,6 @@ package com.intellij.ui; import com.intellij.openapi.Disposable; -import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.editor.colors.EditorColors; @@ -142,11 +141,14 @@ public abstract class EditorTextFieldCellRenderer implements TableCellRenderer, public abstract static class RendererComponent extends CellRendererPanel implements Disposable { private final EditorEx myEditor; + private final EditorTextField myTextField; protected TextAttributes myTextAttributes; private boolean mySelected; public RendererComponent(Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) { - myEditor = createEditor(project, fileType, inheritFontFromLaF); + Pair pair = createEditor(project, fileType, inheritFontFromLaF); + myTextField = pair.first; + myEditor = pair.second; add(myEditor.getContentComponent()); } @@ -155,7 +157,7 @@ public abstract class EditorTextFieldCellRenderer implements TableCellRenderer, } @NotNull - private static EditorEx createEditor(Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) { + private static Pair createEditor(Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) { EditorTextField field = new EditorTextField(new MyDocument(), project, fileType, false, false); field.setSupplementary(true); field.setFontInheritedFromLAF(inheritFontFromLaF); @@ -169,7 +171,7 @@ public abstract class EditorTextFieldCellRenderer implements TableCellRenderer, editor.getScrollPane().setBorder(null); - return editor; + return Pair.create(field, editor); } public void setText(String text, @Nullable TextAttributes textAttributes, boolean selected) { @@ -191,8 +193,8 @@ public abstract class EditorTextFieldCellRenderer implements TableCellRenderer, @Override public void dispose() { - myEditor.getComponent().removeNotify(); - EditorFactory.getInstance().releaseEditor(myEditor); + remove(myEditor.getContentComponent()); + myTextField.removeNotify(); } protected void setTextToEditor(String text) { diff --git a/resources-en/src/inspectionDescriptions/Convert2streamapi.html b/resources-en/src/inspectionDescriptions/Convert2streamapi.html index 55eacf61a9e8..ff190f897875 100644 --- a/resources-en/src/inspectionDescriptions/Convert2streamapi.html +++ b/resources-en/src/inspectionDescriptions/Convert2streamapi.html @@ -1,7 +1,7 @@ -This inspection reports foreach loops which can be replaced with stream api calls. +This inspection reports foreach loops which can be replaced with stream API calls.

- Stream api is not available under Java 1.7 or earlier JVMs. + Stream API is not available under Java 1.7 or earlier JVMs. \ No newline at end of file