Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -893,7 +893,11 @@ private IDocument createDocument() {

try {
String encoding = internalGetEncoding();
s = Utilities.readString(sca, encoding);
// The background job may have read the contents already; decoding is
// all that is left then.
byte[] prefetched = CompareUIPlugin.takePrefetchedContents(fElement);
s = prefetched != null ? Utilities.readString(prefetched, encoding)
: Utilities.readString(sca, encoding);
} catch (CoreException ex) {
this.fViewer.setError(fLeg, ex.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.eclipse.compare.internal;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.Reference;
Expand All @@ -32,6 +33,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -77,6 +79,7 @@
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeManager;
Expand Down Expand Up @@ -285,6 +288,15 @@ private static final class SniffedElement {
}
}

/**
* Contents read ahead while preparing an input, keyed by element identity. They
* are written in the background job and read on the UI thread, and an entry is
* dropped as soon as it became a document, so at most one input's contents are
* held at a time.
*/
private static final Map<ITypedElement, byte[]> fPrefetchedContents= Collections
.synchronizedMap(new IdentityHashMap<>());

public static final int NO_DIFFERENCE = 10000;

/**
Expand Down Expand Up @@ -900,6 +912,9 @@ public IStatus prepareInput(CompareEditorInput input, IProgressMonitor monitor)
if (input.getCompareResult() == null) {
return new Status(IStatus.ERROR, CompareUIPlugin.PLUGIN_ID, NO_DIFFERENCE, Utilities.getString("CompareUIPlugin.noDifferences"), null); //$NON-NLS-1$
}
// Still off the UI thread here, so this is the place to read the contents
// that the viewer would otherwise read while the user waits.
prefetchContents(input.getCompareResult(), monitor);
return Status.OK_STATUS;
} catch (InterruptedException e) {
throw new OperationCanceledException();
Expand Down Expand Up @@ -1565,6 +1580,60 @@ private static IContentType getContentType(ITypedElement element) {
return sniffed.contentType;
}

/**
* Reads the contents of the given input's elements once, so that the sniffing
* and the merge viewer do not have to open the streams again on the UI thread.
* Elements that come with a shared document are left alone: those are served
* from the file buffer and never read a stream.
*/
public static void prefetchContents(Object compareResult, IProgressMonitor monitor) {
if (!(compareResult instanceof ICompareInput input)) {
return;
}
fPrefetchedContents.clear();
SubMonitor progress= SubMonitor.convert(monitor, 3);
prefetchContents(input.getAncestor(), progress.split(1));
prefetchContents(input.getLeft(), progress.split(1));
prefetchContents(input.getRight(), progress.split(1));
}

private static void prefetchContents(ITypedElement element, IProgressMonitor monitor) {
if (!(element instanceof IStreamContentAccessor accessor) || hasSharedDocument(element)) {
return;
}
try (InputStream stream= accessor.getContents()) {
if (stream != null) {
fPrefetchedContents.put(element, stream.readAllBytes());
}
} catch (CoreException | IOException | OutOfMemoryError e) {
// not fatal: whoever needs the content reads the stream itself
} finally {
monitor.done();
}
}

private static boolean hasSharedDocument(ITypedElement element) {
ISharedDocumentAdapter adapter= SharedDocumentAdapterWrapper.getAdapter(element);
return adapter != null && adapter.getDocumentKey(element) != null;
}

/**
* Returns the prefetched contents of the given element and forgets them, or
* <code>null</code> if nothing was read ahead for it.
*/
public static byte[] takePrefetchedContents(Object element) {
return fPrefetchedContents.remove(element);
}

/** The contents of the given element, read ahead if that already happened. */
private static InputStream contentsOf(ITypedElement element) throws CoreException {
byte[] prefetched= fPrefetchedContents.get(element);
if (prefetched != null) {
return new ByteArrayInputStream(prefetched);
}
return element instanceof IStreamContentAccessor accessor ? accessor.getContents() : null;
}

private static IContentType computeContentType(ITypedElement element) {
String name= element.getName();
IContentType ct= null;
Expand All @@ -1579,9 +1648,9 @@ private static IContentType computeContentType(ITypedElement element) {
}
}
}
if (element instanceof IStreamContentAccessor isa) {
if (element instanceof IStreamContentAccessor) {
try {
InputStream is= isa.getContents();
InputStream is= contentsOf(element);
if (is != null) {
try (InputStream bis = new BufferedInputStream(is)) {
ct= fgContentTypeManager.findContentTypeFor(is, name);
Expand Down Expand Up @@ -1711,10 +1780,10 @@ private static String guessType(ITypedElement input) {
}

private static String computeGuessType(ITypedElement input) {
if (input instanceof IStreamContentAccessor sca) {
if (input instanceof IStreamContentAccessor) {
InputStream is= null;
try {
is= sca.getContents();
is= contentsOf(input);
if (is == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
Expand Down Expand Up @@ -582,6 +583,18 @@ private static IStatus addStatus(IStatus status, IStatus entry) {

// encoding

/**
* Decodes already read contents, with the same encoding fallback as
* {@link #readString(IStreamContentAccessor, String)}.
*/
public static String readString(byte[] contents, String encoding) {
try {
return new String(contents, encoding);
} catch (UnsupportedEncodingException e) {
return new String(contents, Charset.defaultCharset());
}
}

public static String readString(IStreamContentAccessor sca, String encoding) throws CoreException {
String s = null;
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ public class CompareOpenEfficiencyTest {

/**
* Upper bound for the {@code getContents()} calls per side on one compare editor
* open: content-type detection, text heuristic, and the document itself.
* open: the background job reads the contents once and everything else is served
* from it.
*/
private static final int MAX_GET_CONTENTS_PER_SIDE = 3;
private static final int MAX_GET_CONTENTS_PER_SIDE = 1;

private static final long TIMEOUT_MILLIS = 30_000;

Expand Down
Loading