diff: detect invalid encodings

* show error message if contend was decoded with errors
This commit is contained in:
Aleksey Pivovarov
2015-12-23 15:05:13 +03:00
parent f2af97c09f
commit 573f2bc03d
3 changed files with 73 additions and 11 deletions
@@ -1,5 +1,7 @@
package com.intellij.diff.contents;
import com.intellij.diff.tools.util.DiffNotifications;
import com.intellij.diff.util.DiffUserDataKeys;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
@@ -11,11 +13,16 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.LightColors;
import com.intellij.util.LineSeparator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
public class FileAwareDocumentContent extends DocumentContentImpl {
@Nullable private final Project myProject;
@@ -63,6 +70,8 @@ public class FileAwareDocumentContent extends DocumentContentImpl {
private VirtualFile myHighlightFile;
private LineSeparator mySeparator;
private Charset myCharset;
private Charset mySuggestedCharset;
private boolean myMalformedContent;
public Builder(@Nullable Project project) {
myProject = project;
@@ -76,7 +85,7 @@ public class FileAwareDocumentContent extends DocumentContentImpl {
private Builder init(@NotNull FilePath path) {
myHighlightFile = path.getVirtualFile();
myFileType = path.getFileType();
myCharset = path.getCharset(myProject);
mySuggestedCharset = path.getCharset(myProject);
return this;
}
@@ -84,7 +93,7 @@ public class FileAwareDocumentContent extends DocumentContentImpl {
private Builder init(@NotNull VirtualFile highlightFile) {
myHighlightFile = highlightFile;
myFileType = highlightFile.getFileType();
myCharset = highlightFile.getCharset();
mySuggestedCharset = highlightFile.getCharset();
return this;
}
@@ -98,14 +107,37 @@ public class FileAwareDocumentContent extends DocumentContentImpl {
@NotNull
private Builder create(@NotNull byte[] content) {
assert myCharset != null;
return create(CharsetToolkit.decodeString(content, myCharset));
assert mySuggestedCharset != null;
myCharset = mySuggestedCharset;
try {
String text = CharsetToolkit.tryDecodeString(content, mySuggestedCharset);
return create(text);
}
catch (CharacterCodingException e) {
String text = CharsetToolkit.decodeString(content, mySuggestedCharset);
myMalformedContent = true;
return create(text);
}
}
@Nullable
private List<JComponent> createNotifications() {
if (!myMalformedContent) return null;
assert mySuggestedCharset != null;
String text = "Content was decoded with errors (using " + "'" + mySuggestedCharset.name() + "' charset)";
JComponent notification = DiffNotifications.createNotification(text, LightColors.RED);
return Collections.singletonList(notification);
}
@NotNull
public FileAwareDocumentContent build() {
if (FileTypes.UNKNOWN.equals(myFileType)) myFileType = PlainTextFileType.INSTANCE;
return new FileAwareDocumentContent(myProject, myDocument, myFileType, myHighlightFile, mySeparator, myCharset);
FileAwareDocumentContent content
= new FileAwareDocumentContent(myProject, myDocument, myFileType, myHighlightFile, mySeparator, myCharset);
content.putUserData(DiffUserDataKeys.NOTIFICATIONS, createNotifications());
return content;
}
}
}
@@ -350,6 +350,7 @@ public class DiffUtil {
@NotNull
public static List<JComponent> createSimpleTitles(@NotNull ContentDiffRequest request) {
List<DiffContent> contents = request.getContents();
List<String> titles = request.getContentTitles();
if (!ContainerUtil.exists(titles, Condition.NOT_NULL)) {
@@ -357,8 +358,10 @@ public class DiffUtil {
}
List<JComponent> components = new ArrayList<JComponent>(titles.size());
for (String title : titles) {
components.add(createTitle(StringUtil.notNullize(title)));
for (int i = 0; i < contents.size(); i++) {
JComponent title = createTitle(StringUtil.notNullize(titles.get(i)));
title = createTitleWithNotifications(title, contents.get(i));
components.add(title);
}
return components;
@@ -394,12 +397,26 @@ public class DiffUtil {
}
for (int i = 0; i < contents.size(); i++) {
result.add(createTitle(StringUtil.notNullize(titles.get(i)), contents.get(i), equalCharsets, equalSeparators, editors.get(i)));
JComponent title = createTitle(StringUtil.notNullize(titles.get(i)), contents.get(i), equalCharsets, equalSeparators, editors.get(i));
title = createTitleWithNotifications(title, contents.get(i));
result.add(title);
}
return result;
}
@Nullable
private static JComponent createTitleWithNotifications(@Nullable JComponent title,
@NotNull DiffContent content) {
List<JComponent> notifications = getCustomNotifications(content);
if (notifications.isEmpty()) return title;
List<JComponent> components = new ArrayList<JComponent>();
if (title != null) components.add(title);
components.addAll(notifications);
return createStackedComponents(components, TITLE_GAP);
}
private static boolean isEqualElements(@NotNull List elements) {
for (int i = 0; i < elements.size(); i++) {
for (int j = i + 1; j < elements.size(); j++) {
@@ -1064,6 +1081,11 @@ public class DiffUtil {
return ContainerUtil.concat(ContainerUtil.notNullize(contextComponents), ContainerUtil.notNullize(requestComponents));
}
@NotNull
public static List<JComponent> getCustomNotifications(@NotNull DiffContent content) {
return ContainerUtil.notNullize(content.getUserData(DiffUserDataKeys.NOTIFICATIONS));
}
//
// DataProvider
//
@@ -24,9 +24,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.charset.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
@@ -308,6 +306,16 @@ public class CharsetToolkit {
return charBuffer.toString();
}
@NotNull
public static String tryDecodeString(@NotNull byte[] bytes, @NotNull final Charset charset) throws CharacterCodingException {
int bomLength = CharsetToolkit.getBOMLength(bytes, charset);
ByteBuffer buffer = ByteBuffer.wrap(bytes, bomLength, bytes.length - bomLength);
CharsetDecoder decoder = charset.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT);
return decoder.decode(buffer).toString();
}
public enum GuessedEncoding {
SEVEN_BIT, // ASCII
VALID_UTF8, // UTF-8