org.eclipse.mat.parser.index.IndexWriter Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (c) 2008 SAP AG.
* 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:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.mat.parser.index;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.NoSuchElementException;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.collect.ArrayIntCompressed;
import org.eclipse.mat.collect.ArrayLong;
import org.eclipse.mat.collect.ArrayLongCompressed;
import org.eclipse.mat.collect.ArrayUtils;
import org.eclipse.mat.collect.BitField;
import org.eclipse.mat.collect.HashMapIntLong;
import org.eclipse.mat.collect.HashMapIntObject;
import org.eclipse.mat.collect.IteratorInt;
import org.eclipse.mat.collect.IteratorLong;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.parser.index.IIndexReader.IOne2LongIndex;
import org.eclipse.mat.parser.index.IIndexReader.IOne2OneIndex;
import org.eclipse.mat.parser.io.BitInputStream;
import org.eclipse.mat.parser.io.BitOutputStream;
import org.eclipse.mat.util.IProgressListener;
public abstract class IndexWriter {
public static final int PAGE_SIZE_INT = 1000000;
public static final int PAGE_SIZE_LONG = 500000;
public interface KeyWriter {
public void storeKey(int index, Serializable key);
}
// //////////////////////////////////////////////////////////////
// integer based indices
// //////////////////////////////////////////////////////////////
public static class Identifier implements IIndexReader.IOne2LongIndex {
long[] identifiers;
int size;
public void add(long id) {
if (identifiers == null) {
identifiers = new long[10000];
size = 0;
}
if (size + 1 > identifiers.length) {
int newCapacity = (identifiers.length * 3) / 2 + 1;
if (newCapacity < size + 1)
newCapacity = size + 1;
identifiers = copyOf(identifiers, newCapacity);
}
identifiers[size++] = id;
}
public void sort() {
Arrays.sort(identifiers, 0, size);
}
public int size() {
return size;
}
public long get(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException();
return identifiers[index];
}
public int reverse(long val) {
int a, c;
for (a = 0, c = size; a < c; ) {
// Avoid overflow problems by using unsigned divide by 2
int b = (a + c) >>> 1;
long probeVal = get(b);
if (val < probeVal) {
c = b;
} else if (probeVal < val) {
a = b + 1;
} else {
return b;
}
}
// Negative index indicates not found (and where to insert)
return -1 - a;
}
public IteratorLong iterator() {
return new IteratorLong() {
int index = 0;
public boolean hasNext() {
return index < size;
}
public long next() {
return identifiers[index++];
}
};
}
public long[] getNext(int index, int length) {
long answer[] = new long[length];
for (int ii = 0; ii < length; ii++)
answer[ii] = identifiers[index + ii];
return answer;
}
public void close() throws IOException {
}
public void delete() {
identifiers = null;
}
public void unload() throws IOException {
throw new UnsupportedOperationException();
}
}
public static class IntIndexCollectorUncompressed {
int[] dataElements;
public IntIndexCollectorUncompressed(int size) {
dataElements = new int[size];
}
public void set(int index, int value) {
dataElements[index] = value;
}
public int get(int index) {
return dataElements[index];
}
public IIndexReader.IOne2OneIndex writeTo(File indexFile) throws IOException {
return new IntIndexStreamer().writeTo(indexFile, dataElements);
}
}
static class Pages {
int size;
Object[] elements;
public Pages(int initialSize) {
elements = new Object[initialSize];
size = 0;
}
private void ensureCapacity(int minCapacity) {
int oldCapacity = elements.length;
if (minCapacity > oldCapacity) {
int newCapacity = (oldCapacity * 3) / 2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
Object[] copy = new Object[newCapacity];
System.arraycopy(elements, 0, copy, 0, Math.min(elements.length, newCapacity));
elements = copy;
}
}
@SuppressWarnings("unchecked")
public V get(int key) {
return (key >= elements.length) ? null : (V) elements[key];
}
public void put(int key, V value) {
ensureCapacity(key + 1);
elements[key] = value;
size = Math.max(size, key + 1);
}
public int size() {
return size;
}
}
abstract static class IntIndex {
int pageSize;
int size;
Pages pages;
protected IntIndex() {
}
protected IntIndex(int size) {
init(size, PAGE_SIZE_INT);
}
protected void init(int size, int pageSize) {
this.size = size;
this.pageSize = pageSize;
this.pages = new Pages(size / pageSize + 1);
}
public int get(int index) {
ArrayIntCompressed array = getPage(index / pageSize);
return array.get(index % pageSize);
}
public int[] getNext(int index, int length) {
int answer[] = new int[length];
int page = index / pageSize;
int pageIndex = index % pageSize;
ArrayIntCompressed array = getPage(page);
for (int ii = 0; ii < length; ii++) {
answer[ii] = array.get(pageIndex++);
if (pageIndex >= pageSize) {
array = getPage(++page);
pageIndex = 0;
}
}
return answer;
}
@SuppressWarnings("null")
public int[] getAll(int index[]) {
int[] answer = new int[index.length];
int page = -1;
ArrayIntCompressed array = null;
for (int ii = 0; ii < answer.length; ii++) {
int p = index[ii] / pageSize;
if (p != page)
array = getPage(page = p);
answer[ii] = array.get(index[ii] % pageSize);
}
return answer;
}
public void set(int index, int value) {
ArrayIntCompressed array = getPage(index / pageSize);
array.set(index % pageSize, value);
}
protected abstract ArrayIntCompressed getPage(int page);
public synchronized void unload() {
this.pages = new Pages(size / pageSize + 1);
}
public int size() {
return size;
}
public IteratorInt iterator() {
return new IntIndexIterator(this);
}
}
static class IntIndexIterator implements IteratorInt {
IntIndex> intArray;
int nextIndex = 0;
public IntIndexIterator(IntIndex> intArray) {
this.intArray = intArray;
}
public int next() {
return intArray.get(nextIndex++);
}
public boolean hasNext() {
return nextIndex < intArray.size();
}
}
public static class IntIndexCollector extends IntIndex implements IOne2OneIndex {
int mostSignificantBit;
public IntIndexCollector(int size, int mostSignificantBit) {
super(size);
this.mostSignificantBit = mostSignificantBit;
}
@Override
protected ArrayIntCompressed getPage(int page) {
ArrayIntCompressed array = pages.get(page);
if (array == null) {
int ps = page < (size / pageSize) ? pageSize : size % pageSize;
array = new ArrayIntCompressed(ps, 31 - mostSignificantBit, 0);
pages.put(page, array);
}
return array;
}
public IIndexReader.IOne2OneIndex writeTo(File indexFile) throws IOException {
// needed to re-compress
return new IntIndexStreamer().writeTo(indexFile, this.iterator());
}
public IIndexReader.IOne2OneIndex writeTo(DataOutputStream out, long position) throws IOException {
return new IntIndexStreamer().writeTo(out, position, this.iterator());
}
public void close() throws IOException {
}
public void delete() {
pages = null;
}
}
public static class IntIndexStreamer extends IntIndex> {
DataOutputStream out;
ArrayLong pageStart;
int[] page;
int left;
public IIndexReader.IOne2OneIndex writeTo(File indexFile, IteratorInt iterator) throws IOException {
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(indexFile)));
openStream(out, 0);
addAll(iterator);
closeStream();
out.close();
return getReader(indexFile);
}
public IIndexReader.IOne2OneIndex writeTo(File indexFile, int[] array) throws IOException {
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(indexFile)));
openStream(out, 0);
addAll(array);
closeStream();
out.close();
return getReader(indexFile);
}
public IIndexReader.IOne2OneIndex writeTo(DataOutputStream out, long position, IteratorInt iterator)
throws IOException {
openStream(out, position);
addAll(iterator);
closeStream();
return getReader(null);
}
public IIndexReader.IOne2OneIndex writeTo(DataOutputStream out, long position, int[] array) throws IOException {
openStream(out, position);
addAll(array);
closeStream();
return getReader(null);
}
void openStream(DataOutputStream out, long position) {
this.out = out;
init(0, PAGE_SIZE_INT);
this.page = new int[pageSize];
this.pageStart = new ArrayLong();
this.pageStart.add(position);
this.left = page.length;
}
/**
* @return total bytes written to index file
*/
long closeStream() throws IOException {
if (left < page.length)
addPage();
// write header information
for (int jj = 0; jj < pageStart.size(); jj++)
out.writeLong(pageStart.get(jj));
out.writeInt(pageSize);
out.writeInt(size);
this.page = null;
this.out = null;
return this.pageStart.lastElement() + (8 * pageStart.size()) + 8 - this.pageStart.firstElement();
}
IndexReader.IntIndexReader getReader(File indexFile) {
return new IndexReader.IntIndexReader(indexFile, pages, size, pageSize, pageStart.toArray());
}
void addAll(IteratorInt iterator) throws IOException {
while (iterator.hasNext())
add(iterator.next());
}
void add(int value) throws IOException {
if (left == 0)
addPage();
page[page.length - left--] = value;
size++;
}
void addAll(int[] values) throws IOException {
addAll(values, 0, values.length);
}
void addAll(int[] values, int offset, int length) throws IOException {
while (length > 0) {
if (left == 0)
addPage();
int chunk = Math.min(left, length);
System.arraycopy(values, offset, page, page.length - left, chunk);
left -= chunk;
size += chunk;
length -= chunk;
offset += chunk;
}
}
private void addPage() throws IOException {
ArrayIntCompressed array = new ArrayIntCompressed(page, 0, page.length - left);
byte[] buffer = array.toByteArray();
out.write(buffer);
int written = buffer.length;
pages.put(pages.size(), new SoftReference(array));
pageStart.add(pageStart.lastElement() + written);
left = page.length;
}
@Override
protected ArrayIntCompressed getPage(int page) {
throw new UnsupportedOperationException();
}
}
public static class IntArray1NWriter {
int[] header;
File indexFile;
DataOutputStream out;
IntIndexStreamer body;
public IntArray1NWriter(int size, File indexFile) throws IOException {
this.header = new int[size];
this.indexFile = indexFile;
this.out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(indexFile)));
this.body = new IntIndexStreamer();
this.body.openStream(this.out, 0);
}
public void log(Identifier identifer, int index, ArrayLong references) throws IOException {
// remove duplicates and convert to identifiers
// keep pseudo reference as first one
long pseudo = references.firstElement();
references.sort();
int[] objectIds = new int[references.size()];
int length = 1;
long current = 0, last = references.firstElement() - 1;
for (int ii = 0; ii < objectIds.length; ii++) {
current = references.get(ii);
if (last != current) {
int objectId = identifer.reverse(current);
if (objectId >= 0) {
int jj = (current == pseudo) ? 0 : length++;
objectIds[jj] = objectId;
}
}
last = current;
}
this.set(index, objectIds, 0, length);
}
/**
* must not contain duplicates!
*/
public void log(int index, ArrayInt references) throws IOException {
this.set(index, references.toArray(), 0, references.size());
}
public void log(int index, int[] values) throws IOException {
this.set(index, values, 0, values.length);
}
protected void set(int index, int[] values, int offset, int length) throws IOException {
header[index] = body.size();
body.add(length);
body.addAll(values, offset, length);
}
public IIndexReader.IOne2ManyIndex flush() throws IOException {
long divider = body.closeStream();
IIndexReader.IOne2OneIndex headerIndex = new IntIndexStreamer().writeTo(out, divider, header);
out.writeLong(divider);
out.close();
out = null;
return createReader(headerIndex, body.getReader(null));
}
/**
* @throws IOException
*/
protected IIndexReader.IOne2ManyIndex createReader(IIndexReader.IOne2OneIndex headerIndex,
IIndexReader.IOne2OneIndex bodyIndex) throws IOException {
return new IndexReader.IntIndex1NReader(this.indexFile, headerIndex, bodyIndex);
}
public void cancel() {
try {
if (out != null) {
out.close();
body = null;
out = null;
}
} catch (IOException ignore) {
} finally {
if (indexFile.exists())
indexFile.delete();
}
}
public File getIndexFile() {
return indexFile;
}
}
public static class IntArray1NSortedWriter extends IntArray1NWriter {
public IntArray1NSortedWriter(int size, File indexFile) throws IOException {
super(size, indexFile);
}
protected void set(int index, int[] values, int offset, int length) throws IOException {
header[index] = body.size() + 1;
body.addAll(values, offset, length);
}
protected IIndexReader.IOne2ManyIndex createReader(IIndexReader.IOne2OneIndex headerIndex,
IIndexReader.IOne2OneIndex bodyIndex) throws IOException {
return new IndexReader.IntIndex1NSortedReader(this.indexFile, headerIndex, bodyIndex);
}
}
public static class InboundWriter {
int size;
File indexFile;
int bitLength;
int pageSize;
BitOutputStream[] segments;
int[] segmentSizes;
/**
* @throws IOException
*/
public InboundWriter(int size, File indexFile) throws IOException {
this.size = size;
this.indexFile = indexFile;
int requiredSegments = (size / 500000) + 1;
int segments = 1;
while (segments < requiredSegments)
segments <<= 1;
this.bitLength = mostSignificantBit(size) + 1;
this.pageSize = (size / segments) + 1;
this.segments = new BitOutputStream[segments];
this.segmentSizes = new int[segments];
}
public void log(int objectIndex, int refIndex, boolean isPseudo) throws IOException {
int segment = objectIndex / pageSize;
if (segments[segment] == null) {
File segmentFile = new File(this.indexFile.getAbsolutePath() + segment + ".log");//$NON-NLS-1$
segments[segment] = new BitOutputStream(new FileOutputStream(segmentFile));
}
segments[segment].writeBit(isPseudo ? 1 : 0);
segments[segment].writeInt(objectIndex, bitLength);
segments[segment].writeInt(refIndex, bitLength);
segmentSizes[segment]++;
}
public IIndexReader.IOne2ManyObjectsIndex flush(IProgressListener monitor, KeyWriter keyWriter)
throws IOException {
close();
int[] header = new int[size];
DataOutputStream index = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream(this.indexFile), 1024 * 256));
BitInputStream segmentIn = null;
try {
IntIndexStreamer body = new IntIndexStreamer();
body.openStream(index, 0);
for (int segment = 0; segment < segments.length; segment++) {
if (monitor.isCanceled())
throw new IProgressListener.OperationCanceledException();
File segmentFile = new File(this.indexFile.getAbsolutePath() + segment + ".log");//$NON-NLS-1$
if (!segmentFile.exists())
continue;
// read & sort payload
segmentIn = new BitInputStream(new FileInputStream(segmentFile));
int objIndex[] = new int[segmentSizes[segment]];
int refIndex[] = new int[segmentSizes[segment]];
for (int ii = 0; ii < segmentSizes[segment]; ii++) {
boolean isPseudo = segmentIn.readBit() == 1;
objIndex[ii] = segmentIn.readInt(bitLength);
refIndex[ii] = segmentIn.readInt(bitLength);
if (isPseudo)
refIndex[ii] = -1 - refIndex[ii]; // 0 is a valid!
}
segmentIn.close();
segmentIn = null;
if (monitor.isCanceled())
throw new IProgressListener.OperationCanceledException();
// delete segment log
segmentFile.delete();
segmentFile = null;
processSegment(monitor, keyWriter, header, body, objIndex, refIndex);
}
// write header
long divider = body.closeStream();
IIndexReader.IOne2OneIndex headerIndex = new IntIndexStreamer().writeTo(index, divider, header);
index.writeLong(divider);
index.flush();
index.close();
index = null;
// return index reader
return new IndexReader.InboundReader(this.indexFile, headerIndex, body.getReader(null));
} finally {
try {
if (index != null)
index.close();
} catch (IOException ignore) {
}
try {
if (segmentIn != null)
segmentIn.close();
} catch (IOException ignore) {
}
if (monitor.isCanceled())
cancel();
}
}
private void processSegment(IProgressListener monitor, KeyWriter keyWriter, int[] header,
IntIndexStreamer body, int[] objIndex, int[] refIndex) throws IOException {
// sort (only by objIndex though)
ArrayUtils.sort(objIndex, refIndex);
// write index body
int start = 0;
int previous = -1;
for (int ii = 0; ii <= objIndex.length; ii++) {
if (ii == 0) {
start = ii;
previous = objIndex[ii];
} else if (ii == objIndex.length || previous != objIndex[ii]) {
if (monitor.isCanceled())
throw new IProgressListener.OperationCanceledException();
header[previous] = body.size() + 1;
processObject(keyWriter, header, body, previous, refIndex, start, ii);
if (ii < objIndex.length) {
previous = objIndex[ii];
start = ii;
}
}
}
}
private void processObject(KeyWriter keyWriter, int[] header, IntIndexStreamer body, int objectId,
int[] refIndex, int fromIndex, int toIndex) throws IOException {
Arrays.sort(refIndex, fromIndex, toIndex);
int endPseudo = fromIndex;
if ((toIndex - fromIndex) > 100000) {
BitField duplicates = new BitField(size);
int jj = fromIndex;
for (; jj < toIndex; jj++) // pseudo references
{
if (refIndex[jj] >= 0)
break;
endPseudo++;
refIndex[jj] = -refIndex[jj] - 1;
if (!duplicates.get(refIndex[jj])) {
body.add(refIndex[jj]);
duplicates.set(refIndex[jj]);
}
}
for (; jj < toIndex; jj++) // other references
{
if ((jj == fromIndex || refIndex[jj - 1] != refIndex[jj]) && !duplicates.get(refIndex[jj])) {
body.add(refIndex[jj]);
}
}
} else {
SetInt duplicates = new SetInt(toIndex - fromIndex);
int jj = fromIndex;
for (; jj < toIndex; jj++) // pseudo references
{
if (refIndex[jj] >= 0)
break;
endPseudo++;
refIndex[jj] = -refIndex[jj] - 1;
if (duplicates.add(refIndex[jj]))
body.add(refIndex[jj]);
}
for (; jj < toIndex; jj++) // other references
{
if ((jj == fromIndex || refIndex[jj - 1] != refIndex[jj]) && !duplicates.contains(refIndex[jj])) {
body.add(refIndex[jj]);
}
}
}
if (endPseudo > fromIndex) {
keyWriter.storeKey(objectId, new int[]{header[objectId] - 1, endPseudo - fromIndex});
}
}
public synchronized void cancel() {
try {
close();
if (segments != null) {
for (int ii = 0; ii < segments.length; ii++) {
new File(this.indexFile.getAbsolutePath() + ii + ".log").delete();//$NON-NLS-1$
}
}
} catch (IOException ignore) {
} finally {
indexFile.delete();
}
}
public synchronized void close() throws IOException {
if (segments != null) {
for (int ii = 0; ii < segments.length; ii++) {
if (segments[ii] != null) {
segments[ii].flush();
segments[ii].close();
segments[ii] = null;
}
}
}
}
public File getIndexFile() {
return indexFile;
}
}
public static class IntArray1NUncompressedCollector {
int[][] elements;
File indexFile;
/**
* @throws IOException
*/
public IntArray1NUncompressedCollector(int size, File indexFile) throws IOException {
this.elements = new int[size][];
this.indexFile = indexFile;
}
public void log(int classId, int methodId) {
if (elements[classId] == null) {
elements[classId] = new int[]{methodId};
} else {
int[] newChildren = new int[elements[classId].length + 1];
System.arraycopy(elements[classId], 0, newChildren, 0, elements[classId].length);
newChildren[elements[classId].length] = methodId;
elements[classId] = newChildren;
}
}
public File getIndexFile() {
return indexFile;
}
public IIndexReader.IOne2ManyIndex flush() throws IOException {
IntArray1NSortedWriter writer = new IntArray1NSortedWriter(elements.length, indexFile);
for (int ii = 0; ii < elements.length; ii++) {
if (elements[ii] != null)
writer.log(ii, elements[ii]);
}
return writer.flush();
}
}
// //////////////////////////////////////////////////////////////
// long based indices
// //////////////////////////////////////////////////////////////
public static class LongIndexCollectorUncompressed {
long[] dataElements;
public LongIndexCollectorUncompressed(int size) {
dataElements = new long[size];
}
public void set(int index, long value) {
dataElements[index] = value;
}
public long get(int index) {
return dataElements[index];
}
public IIndexReader.IOne2LongIndex writeTo(File indexFile) throws IOException {
return new LongIndexStreamer().writeTo(indexFile, dataElements);
}
}
abstract static class LongIndex {
private static final int DEPTH = 10;
int pageSize;
int size;
// pages are either IntArrayCompressed or
// SoftReference
HashMapIntObject