/******************************************************************************* * Copyright (c) 2007, 2012 Wind River Systems and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Wind River Systems - initial API and implementation *******************************************************************************/ package org.eclipse.cdt.dsf.debug.internal.ui.disassembly.text; import java.io.IOException; import java.io.PrintStream; import java.nio.CharBuffer; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.text.ITextStore; /** * Piece list text store implementation with scratch files. */ public final class REDTextStore implements ITextStore { private static final int SCRATCH_FILE_THRESHOLD = 1024 * 1024; private static final int MAX_SCRATCH_FILES = 4; private static final int RECYCLE_THRESHOLD = 20; private static final int IN_MEMORY_LIMIT = 1024 * 32; private final static int CHUNK_SIZE = 1024 * 4; private REDFileRider[] fScratchFiles = new REDFileRider[MAX_SCRATCH_FILES]; private LinkedRun fHead; private LinkedRun fSpare; private LinkedRun fCache; private int fCachePos; private int fLength; private int fDeadLength; private final RunSpec fRunSpec = new RunSpec(); private Job fSwapper; /** * This job swaps readonly IFileRider to disk. */ private final class TextStoreSwapper extends Job { private IFileRider fRider; private String fText; private TextStoreSwapper(IFileRider rider, String text) { super(""); //$NON-NLS-1$ fRider = rider; fText = text; setName("Swapping editor buffer to disk"); //$NON-NLS-1$ setPriority(Job.LONG); } @Override public IStatus run(IProgressMonitor monitor) { REDFileRider fileRider = null; if (!monitor.isCanceled()) { try { // System.out.println("TextStoreSwapper.run() creating swap file"); fileRider = new REDFileRider(new REDFile()); int size = fText.length(); monitor.beginTask(getName(), size+1); int written = 0; while (written < size && !monitor.isCanceled()) { int n = Math.min(size-written, CHUNK_SIZE); fileRider.writeChars(fText, written, n); monitor.worked(n); written += n; } } catch (IOException e) { cancel(); } } if (!monitor.isCanceled()) { // System.out.println("TextStoreSwapper.run() swapping"); fileRider = swap(fRider, fileRider); monitor.done(); } // something went wrong, dispose the file if (fileRider != null) { // System.out.println("TextStoreSwapper.run() disposing"); fileRider.getFile().dispose(); } // remove references fText = null; fRider = null; // System.out.println("TextStoreSwapper.run() done"); return Status.OK_STATUS; } } private final static class LinkedRun extends REDRun { LinkedRun fNext; LinkedRun fPrev; LinkedRun(IFileRider rider, String str) throws IOException { super(rider, str); } LinkedRun(IFileRider rider, char[] buf, int off, int n) throws IOException { super(rider, buf, off, n); } LinkedRun(IFileRider rider, int offset, int length) { super(rider, offset, length); } } /** * Create an empty text store. */ public REDTextStore() { } /** * Create a text store with intial content. */ public REDTextStore(String text) { set(text); } @Override protected void finalize() { dispose(); } /** * Free resources. * Can be reactivated by calling <code>set(String)</code>. */ public void dispose() { synchronized (fRunSpec) { if (fSwapper != null) { fSwapper.cancel(); fSwapper = null; } for (int i = 0; i < fScratchFiles.length; ++i) { if (fScratchFiles[i] != null) { fScratchFiles[i].getFile().dispose(); fScratchFiles[i] = null; } } fHead = null; fCache = null; fSpare = null; fRunSpec.fRun = null; fCachePos = 0; fLength = 0; fDeadLength = 0; } } // ---- ITextStore interface ------------------------------------ /* (non-Javadoc) * @see org.eclipse.jface.text.ITextStore#get(int) */ @Override public char get(int offset) { synchronized (fRunSpec) { RunSpec spec = findNextRun(offset, null); if (spec.fRun != null) { return spec.fRun.charAt(spec.fOff); } return 0; } } /* (non-Javadoc) * @see org.eclipse.jface.text.ITextStore#get(int, int) */ @Override public String get(int offset, int length) { synchronized (fRunSpec) { // special case: long in-memory text in full length (about to be swapped) if (length == fLength && fSwapper != null && fHead != null && fHead.fNext == null) { ((StringRider)fHead.fRider).fBuffer.position(0); return ((StringRider)fHead.fRider).fBuffer.toString(); } return toString(offset, offset + length); } } /* (non-Javadoc) * @see org.eclipse.jface.text.ITextStore#getLength() */ @Override public int getLength() { synchronized (fRunSpec) { return fLength; } } /* (non-Javadoc) * @see org.eclipse.jface.text.ITextStore#set(java.lang.String) */ @Override public void set(String text) { synchronized (fRunSpec) { dispose(); if (text != null) { fHead = new LinkedRun(new StringRider(text), 0, text.length()); fLength = text.length(); if (fLength > IN_MEMORY_LIMIT) { fSwapper = new TextStoreSwapper(fHead.fRider, text); fSwapper.schedule(1000); } } } } /* (non-Javadoc) * @see org.eclipse.jface.text.ITextStore#replace(int, int, java.lang.String) */ @Override public void replace(int offset, int length, String text) { synchronized (fRunSpec) { if (text == null || text.length() == 0) { // delete only replace(offset, length, null, 0, 0); } else { replace(offset, length, text, 0, text.length()); } } } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { synchronized (fRunSpec) { return toString(0, getLength()); } } // --- implementation ---------------------------------------------------- /** Get part of the text as string. * The parameters from and to are normalized to be in range: [0, fLength] * Not MT-safe! * @param from The beginning of the stretch of text to be returned; for from == n, the nth character is included. * @param to The end of the stretch of text to be returned; for to == n, the nth character is not included. * @return The stretch [from, to[ as String. */ private String toString(int from, int to) { assert from >= 0 && from <= to && to <= fLength; int len = to - from; StringBuffer strBuf = new StringBuffer(len); if (len > 0) { RunSpec spec = findPrevRun(from, fRunSpec); try { int done = spec.fRun.appendTo(strBuf, len, spec.fOff); while (done < len) { spec.fRun = spec.fRun.fNext; assert spec.fRun != null; done += spec.fRun.appendTo(strBuf, len - done, 0); } } catch (IOException e) { internalError(e); } } assert strBuf.length() == len; return strBuf.toString(); } /** * Replace [from;deleteLen[ with buf[off;insertLen[ * Not MT-safe! * @param from * @param deleteLen * @param buf * @param off * @param insertLen */ private void replace(int from, int deleteLen, Object buf, int off, int insertLen) { assert from >= 0 && from <= fLength; assert deleteLen >= 0; assert from + deleteLen <= fLength; RunPair split = null; if (deleteLen > 0) { split = delete(from, from + deleteLen); } if (buf == null || insertLen == 0) { return; } // assert off >= 0 && off < buf.length; // assert insertLen >= 0 && off+insertLen <= buf.length; if (split == null) { split = splitRun(from); } RunPair insert = makeRuns(split.fBefore, buf, off, insertLen); // assert runLength(insert.fBefore, insert.fAfter) == insertLen; insertRuns(split, insert.fBefore, insert.fAfter); fLength += insertLen; // assert runLength(fHead, null) == fLength; fCache = insert.fAfter; fCachePos = from+insertLen-insert.fAfter.fLength; // assert checkConsistency(); if (split.fBefore != null) { mergeRuns(split.fBefore, split.fAfter); } else { mergeRuns(fHead, split.fAfter); } if (fDeadLength > fLength / 10) { reconcile(); } } /** * Recreate text store by reinserting all runs. * Not MT-safe! */ public void reconcile() { LinkedRun run = fHead; REDFileRider[] scratchFiles = fScratchFiles; fScratchFiles = new REDFileRider[MAX_SCRATCH_FILES]; fHead = null; fCache = null; fCachePos = -1; fSpare = null; fLength = 0; fDeadLength = 0; char[] buf = new char[CHUNK_SIZE]; int offset = 0; int runOffset = 0; while (run != null) { int n; try { do { n = run.copyInto(buf, 0, buf.length, runOffset); replace(offset, 0, buf, 0, n); offset += n; runOffset += n; } while (runOffset < run.fLength); } catch (IOException e) { internalError(e); } run = run.fNext; runOffset = 0; } for (int i = 0; i < scratchFiles.length; ++i) { if (scratchFiles[i] != null) { scratchFiles[i].getFile().dispose(); scratchFiles[i] = null; } } } // ******************************************************************************************************************************************************* // P R I V A T E - L I N E // ******************************************************************************************************************************************************* /** * Create a new LinkedRun * @param before LinkedRun before new run * @param n length of content * @return new LinkedRun */ private LinkedRun createRun(LinkedRun before, int n) { IFileRider scratchFile; if (before != null && before.fRider.length() == before.fOffset + before.fLength && before.fRider.limit() >= before.fRider.length() + n) { scratchFile = before.fRider; } else { scratchFile = getScratchFile(); } return new LinkedRun(scratchFile, scratchFile.length(), n); } private REDFileRider getScratchFile() { REDFileRider rider = null; for (int i = 0; i < fScratchFiles.length; ++i) { rider = fScratchFiles[i]; if (rider == null) { try { rider = new REDFileRider(new REDFile()); } catch (IOException e) { internalError(e); } fScratchFiles[i] = rider; break; } else if (rider.length() < SCRATCH_FILE_THRESHOLD) { break; } } return rider; } /** * Save run for later recycling. * @param run */ private void spareRun(LinkedRun run, LinkedRun last) { // remove readonly runs first if (last != null) { last.fNext = null; } LinkedRun cur = run; LinkedRun prev = null; while (cur != null) { if (cur.fRider.isReadonly()) { if (prev != null) { prev.fNext = cur.fNext; } else { run = cur.fNext; } if (cur.fNext != null) { cur.fNext.fPrev = prev; } } else { prev = cur; } cur = cur.fNext; } if (run == null) { return; } last = prev; if (last != null) { last.fNext = fSpare; } if (fSpare != null) { fSpare.fPrev = last; } fSpare = run; fSpare.fPrev = null; } /** * Recycle a run. * @returns run */ private LinkedRun recycleRun() { LinkedRun recycled = fSpare; fSpare = null; return recycled; } /** * @param e */ private void internalError(Exception e) { throw new Error("Internal error", e); //$NON-NLS-1$ } /** * REDRunSpec represents a specification of a run, including the run itself, its origin and offset. * It is used for findRun - operations. */ private final static class RunSpec { public LinkedRun fRun = null; public int fOff = -1; public boolean isValid() { return fRun != null; } } /** * auxiliary class: pair of red runs */ private final static class RunPair { public LinkedRun fBefore; public LinkedRun fAfter; } /** * Auxiliary method to delete part of the text. * from and to have gap semantics. * @param from start of the stretch to be deleted. * @param to end of the stretch to be deleted. * @return split pos for insertion */ private RunPair delete(int from, int to) { RunPair start = splitRun(from); RunPair end = splitRun(to); if (start.fBefore != null) { start.fBefore.fNext = end.fAfter; } else { fHead = end.fAfter; } if (end.fAfter != null) { end.fAfter.fPrev = start.fBefore; } if (end.fAfter != null) { fCache = end.fAfter; fCachePos = from; } else { fCache = fHead; fCachePos = 0; } fLength -= (to - from); if (fLength == 0) { dispose(); return null; } spareRun(start.fAfter, end.fBefore); start.fAfter = end.fAfter; return start; } /** * Find the run which contains given position. * caveat: if the given position lies between run a and b, a is returned * @param pos The position to find the run for * @return A run specification representing the found run. May be invalid (if given position was larger than text) * @pre pos >= 0 * @pre pos <= length() * @post return != null * @post return.fOff > 0 || pos == 0 * @post return.fOff <= return.fRun.fLength * @post return.fOrg >= 0 */ private RunSpec findPrevRun(int pos, RunSpec spec) { assert pos >= 0 && pos <= fLength; LinkedRun cur; int curPos; if (fCache != null && fCachePos - pos < pos) { assert fCache != null; cur = fCache; curPos = fCachePos; } else { cur = fHead; curPos = 0; } while (cur != null && pos - curPos > cur.fLength) { curPos += cur.fLength; cur = cur.fNext; } if (pos != 0) { while (pos - curPos <= 0 && cur != null) { cur = cur.fPrev; curPos -= cur.fLength; } } fCache = cur; fCachePos = curPos; if (spec == null) { spec = fRunSpec; } spec.fRun = cur; spec.fOff = pos - curPos; return spec; } /** * Find the run which contains given position. * caveat: if the given position lies between run a and b, b is returned * @param pos The position to find the run for * @return A run specification representing the found run. May be invalid (if given position was larger than text) * @pre pos >= 0 * @pre pos <= length() * @post return != null * @post return.fOff >= 0 * @post return.fOff < return.fRun.fLength */ private RunSpec findNextRun(int pos, RunSpec spec) { if (pos < fLength) { spec = findPrevRun(pos + 1, spec); spec.fOff--; } else { spec = findPrevRun(pos, spec); } return spec; } /** Split run at pos and return pair of runs. */ private RunPair splitRun(int pos) { RunPair p = new RunPair(); if (pos == 0) { p.fBefore = null; p.fAfter = fHead; } else { RunSpec spec = findPrevRun(pos, null); assert spec.isValid(); p.fBefore = spec.fRun; int len = spec.fRun.length(); if (spec.fOff != len) { // need to split p.fAfter = new LinkedRun(p.fBefore.fRider, p.fBefore.fOffset + spec.fOff, p.fBefore.fLength - spec.fOff); p.fBefore.fLength = spec.fOff; p.fAfter.fNext = p.fBefore.fNext; if (p.fAfter.fNext != null) { p.fAfter.fNext.fPrev = p.fAfter; } p.fBefore.fNext = p.fAfter; p.fAfter.fPrev = p.fBefore; } else { // we already have a split p.fAfter = p.fBefore.fNext; } } return p; } /** * Merge all runs between start and end where possible. * @pre start != null */ private void mergeRuns(LinkedRun start, LinkedRun end) { LinkedRun cur = start; LinkedRun next = cur.fNext; while (cur != end && next != null) { if (cur.isMergeableWith(next)) { if (next == fCache) { fCache = cur; fCachePos -= cur.fLength; } cur.fLength += next.fLength; cur.fNext = next.fNext; if (cur.fNext != null) { cur.fNext.fPrev = cur; } if (next == end) { break; } } else { cur = next; } next = cur.fNext; } } private RunPair makeRuns(LinkedRun before, Object buf, int off, int n) { RunPair result = new RunPair(); LinkedRun run; LinkedRun recycled = recycleRun(); if (recycled != null) { result.fBefore = recycled; run = recycled; do { int count = Math.min(run.fLength, n); try { assert !run.fRider.isReadonly(); // safeguard if (run.fRider.isReadonly()) { run = null; break; } run.fRider.seek(run.fOffset); if (buf instanceof char[]) { run.fRider.writeChars((char[])buf, off, count); } else { run.fRider.writeChars((String)buf, off, count); } if (run.fLength - count >= RECYCLE_THRESHOLD) { LinkedRun next = run.fNext; LinkedRun newRun = new LinkedRun(run.fRider, run.fOffset+count, run.fLength-count); joinRuns(run, newRun); joinRuns(newRun, next); } else { fDeadLength += run.fLength - count; } run.fLength = count; off += count; n -= count; before = run; run = run.fNext; } catch (IOException e) { run = null; // internalError(e); break; } } while (run != null && n > 0); if (run != null) { // shortcut for spareRun(run, null) fSpare = run; } } if (n > 0) { run = createRun(before, n); if (buf instanceof char[]) { try { run.fRider.seek(run.fOffset); run.fRider.writeChars((char[])buf, off, n); } catch (IOException e) { // internalError(e); run = new LinkedRun(new StringRider(CharBuffer.wrap((char[])buf, off, off+n)), 0, n); } } else { try { run.fRider.seek(run.fOffset); run.fRider.writeChars((String)buf, off, n); } catch (IOException e) { // internalError(e); run = new LinkedRun(new StringRider(CharBuffer.wrap((String)buf, off, off+n)), 0, n); } } if (result.fBefore == null) { result.fBefore = run; } else { joinRuns(before, run); } result.fAfter = run; } else { result.fAfter = before; } return result; } private void joinRuns(LinkedRun start, LinkedRun next) { assert start != next; start.fNext = next; if (next != null) { next.fPrev = start; } } private void insertRuns(RunPair pos, LinkedRun start, LinkedRun end) { assert pos.fBefore == null || pos.fBefore != pos.fAfter; start.fPrev = pos.fBefore; if (pos.fBefore != null) { pos.fBefore.fNext = start; } else { fHead = start; } end.fNext = pos.fAfter; if (pos.fAfter != null) { pos.fAfter.fPrev = end; } } /** * Swap given old (readonly) rider with the new (writable) one. * @param oldRider * @param newRider * @return <code>null</code> if the new rider was consumed */ private REDFileRider swap(IFileRider oldRider, REDFileRider newRider) { synchronized(fRunSpec) { // search linked run list starting from head and replace // all instances of oldRider with newRider // in the general case, spare list should be searched, too LinkedRun cur = fHead; while (cur != null) { if (cur.fRider == oldRider) { cur.fRider = newRider; } cur = cur.fNext; } for (int i = 0; i < fScratchFiles.length; i++) { if (fScratchFiles[i] == null) { fScratchFiles[i] = newRider; newRider = null; } } if (newRider != null) { // unlikely, but possible: need to increase array REDFileRider[] scratchFiles = new REDFileRider[fScratchFiles.length+1]; System.arraycopy(fScratchFiles, 0, scratchFiles, 0, fScratchFiles.length); scratchFiles[fScratchFiles.length] = newRider; fScratchFiles = scratchFiles; newRider = null; } // clean out fRunSpec reference (just in case) fRunSpec.fRun = null; // clean out swapper reference fSwapper = null; } return newRider; } // ---- For debugging purposes public void printStatistics(PrintStream out) { int nRuns = 0; int nSpare = 0; int spareLength = 0; LinkedRun run = fHead; while(run != null) { ++nRuns; run = run.fNext; } run = fSpare; while(run != null) { ++nSpare; spareLength += run.fLength; run = run.fNext; } double runMean = nRuns > 0 ? (double)fLength / nRuns : Double.NaN; double spareMean = nSpare > 0 ? spareLength / nSpare : Double.NaN; out.println("Length: "+fLength); //$NON-NLS-1$ out.println("Number of runs: "+nRuns); //$NON-NLS-1$ out.println("Mean length of runs: " + runMean); //$NON-NLS-1$ out.println("Length of spare runs: "+spareLength); //$NON-NLS-1$ out.println("Number of spare runs: "+nSpare); //$NON-NLS-1$ out.println("Mean length of spare runs: " + spareMean); //$NON-NLS-1$ out.println("Length of dead runs: " + fDeadLength); //$NON-NLS-1$ } /** * Get structure. * Returns the structure (Runs) as a single string. The Runs are separated * by "->\n". At the end the string "null" is added to indicate, that no * more runs exist. This implies that the string "null" is returned for an * empty text. */ String getStructure() { LinkedRun cur = fHead; String structure = ""; //$NON-NLS-1$ while (cur != null) { try { structure += cur.asString() + "->\n"; //$NON-NLS-1$ } catch (IOException e) { internalError(e); } cur = cur.fNext; } structure += "null"; //$NON-NLS-1$ return structure; } /** * For debugging purposes only. */ boolean checkConsistency() { LinkedRun run = fHead; int length = 0; while (run != null) { LinkedRun prev = run.fPrev; LinkedRun next = run.fNext; assert prev != run; assert next != run; assert prev == null || prev.fNext == run; assert next == null || next.fPrev == run; while (prev != null) { prev = prev.fPrev; assert run != prev; } LinkedRun spare = fSpare; while (spare != null) { assert run != spare; spare = spare.fPrev; } length += run.fLength; run = next; } assert length == fLength; if (fCache != null) { int pos = fCachePos; run = fCache; while (run.fPrev != null) { run = run.fPrev; pos -= run.fLength; } assert pos == 0; pos = fCachePos; run = fCache; while (run != null) { pos += run.fLength; run = run.fNext; } assert pos == fLength; } return true; } int runLength(LinkedRun first, LinkedRun last) { LinkedRun run = first; int length = 0; while (run != null) { length += run.fLength; if (run == last) { break; } run = run.fNext; } return length; } }