blob: aeddc851e7591dd6529820c8e1a43e4129051693 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018, 2023 IBM Corporation
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Andrew Johnson/IBM Corporation - initial API and implementation
*******************************************************************************/
/**
* Create a HPROF format file from a snapshot, whatever the original dump format.
*
* Problems with current snapshot API:
* finding thread to GCroots linkage
* - have to presume a ROOT_THREAD_OBJECT and getOutboundReferences and use
* ThreadToLocalReference
* parsing stackframes to classes, methods
* - have to parse out string format
* converting class name in stackframe to class
* - could be multiple classes with the same name, information loss
* converting object and stack frame to GC root with type
* - If JNI local reference and Java local reference to same object in different frame, could get the type swapped.
*
*/
package org.eclipse.mat.hprof;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.ArrayLong;
import org.eclipse.mat.collect.BitField;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.hprof.AbstractParser.Constants;
import org.eclipse.mat.hprof.describer.HprofContentDescriber;
import org.eclipse.mat.hprof.describer.Version;
import org.eclipse.mat.hprof.ui.HprofPreferences;
import org.eclipse.mat.query.IQuery;
import org.eclipse.mat.query.IResult;
import org.eclipse.mat.query.annotations.Argument;
import org.eclipse.mat.query.annotations.Argument.Advice;
import org.eclipse.mat.query.annotations.CommandName;
import org.eclipse.mat.query.annotations.HelpUrl;
import org.eclipse.mat.query.annotations.Icon;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.SnapshotInfo;
import org.eclipse.mat.snapshot.model.Field;
import org.eclipse.mat.snapshot.model.FieldDescriptor;
import org.eclipse.mat.snapshot.model.GCRootInfo;
import org.eclipse.mat.snapshot.model.GCRootInfo.Type;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IInstance;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.IObjectArray;
import org.eclipse.mat.snapshot.model.IPrimitiveArray;
import org.eclipse.mat.snapshot.model.IStackFrame;
import org.eclipse.mat.snapshot.model.IThreadStack;
import org.eclipse.mat.snapshot.model.NamedReference;
import org.eclipse.mat.snapshot.model.ObjectReference;
import org.eclipse.mat.snapshot.model.ThreadToLocalReference;
import org.eclipse.mat.snapshot.query.IHeapObjectArgument;
import org.eclipse.mat.snapshot.query.SnapshotQuery;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.SilentProgressListener;
@CommandName("export_hprof")
@Icon("/icons/export_hprof.gif")
@HelpUrl("/org.eclipse.mat.ui.help/tasks/exportdump.html")
public class ExportHprof implements IQuery
{
/** A dummy stack trace */
private static final int UNKNOWN_STACK_TRACE_SERIAL = 1;
/** No stack frame available */
private static final int UNKNOWN_STACK_FRAME_SERIAL = -1;
private static final Charset UTF8 = Charset.forName("UTF-8"); //$NON-NLS-1$
@Argument
public ISnapshot snapshot;
@Argument(advice = Advice.SAVE)
public File output;
@Argument(isMandatory = false)
public boolean compress;
@Argument(isMandatory = false)
public boolean chunked;
public enum RedactType
{
NONE("none"), //$NON-NLS-1$
NAMES("names"), //$NON-NLS-1$
BASIC("basic"), //$NON-NLS-1$
FULL("full"); //$NON-NLS-1$
String type;
private RedactType(String s)
{
type = s;
}
}
@Argument(isMandatory = false)
public RedactType redact = RedactType.NONE;
@Argument(isMandatory = false, advice = Advice.SAVE, flag = "map")
public File mapFile;
@Argument(isMandatory = false, advice = Advice.CLASS_NAME_PATTERN, flag = "skip")
public Pattern skipPattern = Pattern.compile("java\\..*|boolean|byte|char|short|int|long|float|double|void|<[a-z ]+>"); //$NON-NLS-1$
@Argument(isMandatory = false, advice = Advice.CLASS_NAME_PATTERN, flag = "avoid")
public Pattern avoidPattern = Pattern.compile(Messages.ExportHprof_AvoidExample);
@Argument(isMandatory = false)
public boolean undo;
@Argument(flag = Argument.UNFLAGGED, isMandatory = false)
public IHeapObjectArgument objects;
/** Dump class fields as a instance dump */
@Argument(isMandatory = false, flag = "classInstance")
public boolean classesAsInstances = false;
@Argument(isMandatory = false)
/** How big a heap dump segment can grow before it needs to be split */
public long segsize = 0xffffffffL;
/** Strings to HPROF ID */
HashMap<String, Integer> stringToID = new HashMap<String, Integer>();
int nextStringID = 1;
/** Thread object ID to HPROF thread serial number */
HashMap<Integer, Integer> threadToSerial = new HashMap<Integer, Integer>();
/** Thread object ID to HPROF stack serial number */
HashMap<Integer, Integer> threadToStack = new HashMap<Integer, Integer>();
/** Whether to include this object in the dump */
BitField include;
/** Keep count for a final result */
int totalClasses;
/** Keep count for a final result */
int totalObjects;
/** Keep count for a final result */
int totalRoots;
/** Keep count for a final result */
int totalClassloaders;
/** Keep count for a final result */
long totalBytes;
/** keep track for totals */
SetInt classloaders = new SetInt();
/** Speed up first pass */
boolean skipData = false;
/**
* Stream which discards the output.
*/
static class NullStream extends OutputStream
{
@Override
public void write(int b) throws IOException
{
}
}
/**
* DataOutputStream which can have a long size.
*/
static class DataOutputStream3 implements DataOutput, Closeable
{
final DataOutputStream2 out;
protected long written = 0;
public DataOutputStream3(OutputStream out)
{
this.out = new DataOutputStream2(out);
}
public void write(int b) throws IOException
{
out.write(b);
written += 1;
}
public void write(byte[] b) throws IOException
{
out.write(b);
written += b.length;
}
public void write(byte[] b, int off, int len) throws IOException
{
out.write(b, off, len);
written += len;
}
public void writeBoolean(boolean v) throws IOException
{
out.writeBoolean(v);
written += 1;
}
public void writeByte(int v) throws IOException
{
out.writeByte(v);
written += 1;
}
public void writeShort(int v) throws IOException
{
out.writeShort(v);
written += 2;
}
public void writeChar(int v) throws IOException
{
out.writeChar(v);
written += 2;
}
public void writeInt(int v) throws IOException
{
out.writeInt(v);
written += 4;
}
public void writeLong(long v) throws IOException
{
out.writeLong(v);
written += 8;
}
public void writeFloat(float v) throws IOException
{
out.writeFloat(v);
written += 4;
}
public void writeDouble(double v) throws IOException
{
out.writeDouble(v);
written += 8;
}
public void writeBytes(String s) throws IOException
{
out.writeBytes(s);
written += s.length();
}
public void writeChars(String s) throws IOException
{
out.writeChars(s);
written += 2 * s.length();
}
public void writeUTF(String s) throws IOException
{
out.reset();
int m1 = out.size();
out.writeUTF(s);
int m2 = out.size();
written += m2 - m1;
}
public long size()
{
return written;
}
public void close() throws IOException
{
out.close();
}
}
/**
* DataOutputStream which can have bytes written reset
*/
static class DataOutputStream2 extends DataOutputStream {
public DataOutputStream2(OutputStream out)
{
super(out);
}
public void reset()
{
written = 0;
}
}
/** The size of the ID fields in the HPROF file */
int idsize = 8;
/** Progress monitor work per class for dumping objects */
private static final int WORK_OBJECT = 3;
/** Ready for new way of reading classes */
private final boolean NEWCLASSSIZE = HprofPreferences.useAdditionalClassReferences();
Remap remap;
public IResult execute(IProgressListener listener) throws Exception
{
int ct = snapshot.getSnapshotInfo().getNumberOfClasses();
/*
* Stages and work items per class
* load classes 1 11%
* prepare classes 1 22%
* dump classes 1 33%
* prepare objects 3 67%
* dump objects 3 100%
* Won't work so well for objects argument.
*/
int ct2 = initObjs();
if (ct2 <= 0)
ct2 = ct;
int totalWork = 3 * ct + 2 * WORK_OBJECT * ct2;
listener.beginTask(MessageUtil.format(Messages.ExportHprof_ExportTo, output.getName()), totalWork);
remap = new Remap(skipPattern, avoidPattern, redact == RedactType.NAMES, undo || mapFile == null);
remap.loadMapping(mapFile, undo);
long startTime;
OutputStream outstream = new BufferedOutputStream(new FileOutputStream(output), 1024 * 64);
if (compress)
{
if (chunked)
outstream = new ChunkedGZIPRandomAccessFile.ChunkedGZIPOutputStream(outstream);
else
outstream = new GZIPOutputStream(outstream);
// Speeds up compression
outstream = new BufferedOutputStream(outstream);
}
DataOutputStream3 os = new DataOutputStream3(outstream);
try
{
os.writeBytes(Version.JDK6.getLabel()+"\0"); //$NON-NLS-1$
idsize = snapshot.getSnapshotInfo().getIdentifierSize();
os.writeInt(idsize);
startTime = System.currentTimeMillis();
os.writeLong(startTime);
// os.writeByte(Constants.Record.HEAP_SUMMARY);
DataOutputStream3 os2 = new DataOutputStream3(new NullStream());
loadClasses(os, os2, startTime, listener);
// Keep track of new strings
int firstId = nextStringID;
long mark1 = os2.size();
listener.subTask(Messages.ExportHprof_PrepareClasses);
dumpClasses(os2, listener);
listener.subTask(Messages.ExportHprof_PrepareGCRoots);
gcRoots(os2);
dumpThreadRoots(os2);
long mark2 = os2.size();
long sizeseg1 = mark2 - mark1;
listener.subTask(Messages.ExportHprof_PrepareThreadStacks);
// Find all the strings
dumpThreadStacks(os2, startTime);
// Write out new Strings from the classes etc.
listener.subTask(Messages.ExportHprof_DumpStrings);
for (Map.Entry<String, Integer> e : stringToID.entrySet())
{
String ss = e.getKey();
int id = e.getValue();
if (id < firstId)
continue;
writeStringUTF(os, os2, startTime, ss, id);
}
listener.subTask(Messages.ExportHprof_DumpThreadStacks);
dumpThreadStacks(os, startTime);
int segnum = 1;
long seg1Start = os.size();
os.writeByte(Constants.Record.HEAP_DUMP_SEGMENT);
os.writeInt((int) (System.currentTimeMillis() - startTime));
os.writeInt((int)sizeseg1);
long markseg1a = os.size();
listener.subTask(MessageUtil.format(Messages.ExportHprof_DumpClasses, segnum));
dumpClasses(os, listener);
listener.subTask(MessageUtil.format(Messages.ExportHprof_DumpGCRoots, segnum));
gcRoots(os);
dumpThreadRoots(os);
long markseg1b = os.size();
long sizeseg1_a = markseg1b - markseg1a;
if (sizeseg1_a != sizeseg1)
{
listener.sendUserMessage(IProgressListener.Severity.WARNING,
MessageUtil.format(Messages.ExportHprof_SegmentSizeMismatch, segnum, sizeseg1, sizeseg1_a, Long.toHexString(seg1Start)), null);
}
/*
* Possibly multiple segments needed for objects
*/
int maxObjects = Integer.MAX_VALUE;
int st = 0;
do
{
++segnum;
// Segment length measurer
os2 = new DataOutputStream3(new NullStream());
// Object measurer
DataOutputStream3 os3 = new DataOutputStream3(new NullStream());
long m1 = os2.size();
listener.subTask(MessageUtil.format(Messages.ExportHprof_PrepareObjects, segnum));
boolean saveSkip = skipData;
skipData = true;
int end = dumpObjects(os2, os3, st, maxObjects, true, listener);
skipData = saveSkip;
long m2 = os2.size();
long s2 = m2 - m1;
os3.close();
long sizel = s2;
long segStart = os.size();
if (sizel > 0xffffffffL || sizel > segsize)
{
// Too big, but carry on
listener.sendUserMessage(IProgressListener.Severity.WARNING,
MessageUtil.format(Messages.ExportHprof_SegmentTooLong, segnum, Long.toHexString(segStart), sizel), null);
}
os.writeByte(Constants.Record.HEAP_DUMP_SEGMENT);
os.writeInt((int) (System.currentTimeMillis() - startTime));
os.writeInt((int)sizel);
long checkmark1 = os.size();
listener.subTask(MessageUtil.format(Messages.ExportHprof_DumpObjects, segnum));
st = dumpObjects(os, os2, st, end, false, listener);
long checkmark2 = os.size();
long size2 = checkmark2 - checkmark1;
if (size2 != sizel)
{
listener.sendUserMessage(IProgressListener.Severity.WARNING,
MessageUtil.format(Messages.ExportHprof_SegmentSizeMismatch, segnum, sizel, size2, Long.toHexString(segStart)), null);
}
} while (st > 0 && st < maxObjects);
os.writeByte(Constants.Record.HEAP_DUMP_END);
os.writeInt((int) (System.currentTimeMillis() - startTime));
os.writeInt(0);
os2.close();
}
finally
{
os.close();
}
String comments = MessageUtil.format(Messages.ExportHprof_RemapProperties, output.getName(), new File(snapshot.getSnapshotInfo().getPath()).getName());
remap.saveMapping(mapFile, undo, comments);
listener.done();
/*
* Report the result as a heap dump overview.
* Handle integer overflow as we count everything twice, once for sizing, once for output.
*/
int nclasses = totalClasses >>> 1;
int nobjects = totalObjects >>> 1;
int nroots = totalRoots >>> 1;
int nclassloaders = totalClassloaders >>> 1;
long nused = totalBytes >>> 1;
SnapshotQuery sq = SnapshotQuery.lookup("heap_dump_overview", snapshot); //$NON-NLS-1$
String name = output.getName();
int dot = name.lastIndexOf('.');
if (dot >= 0)
{
name = name.substring(0, dot);
}
String prefix = (new File(output.getParentFile(), name)).getPath();
SnapshotInfo si = new SnapshotInfo(output.getAbsolutePath(), prefix, null, idsize, new Date(startTime), nobjects, nroots, nclasses, nclassloaders, nused);
String format = (new HprofContentDescriber()).getSupportedOptions()[0].getLocalName();
si.setProperty("$heapFormat", format); //$NON-NLS-1$
if (Boolean.TRUE.equals(snapshot.getSnapshotInfo().getProperty("$useCompressedOops"))) //$NON-NLS-1$
{
si.setProperty("$useCompressedOops", true); //$NON-NLS-1$
}
sq.setArgument("info", si); //$NON-NLS-1$
IResult ret = sq.execute(new SilentProgressListener(listener));
return ret;
}
/**
* Include an object in the output dump?
* @param obj
* @return
*/
boolean includeObject(int obj)
{
return include == null || include.get(obj);
}
/**
* Set up the tests for whether to include objects.
*/
int initObjs()
{
int n = 0;
if (objects != null)
{
include = new BitField(snapshot.getSnapshotInfo().getNumberOfObjects());
for (int ia[] : objects)
{
++n;
for (int i : ia)
{
include.set(i);
}
}
}
return n;
}
/**
* Generate a dummy stack trace for when objects/classes
* etc. were allocated. Needed for jhat.
*/
private int dummyStackTrace(DataOutput os, int dummyThread, long startTime) throws IOException
{
os.writeByte(Constants.Record.STACK_TRACE);
os.writeInt((int) (System.currentTimeMillis() - startTime));
os.writeInt(3 * 4);
os.writeInt(UNKNOWN_STACK_TRACE_SERIAL);
os.writeInt(dummyThread);
os.writeInt(0); // No frames
return UNKNOWN_STACK_TRACE_SERIAL;
}
/**
* Dump the GC roots
*
* @param os
* @throws SnapshotException
* @throws IOException
*/
private void gcRoots(DataOutput os) throws SnapshotException, IOException
{
int nextThreadSerial = 1;
++nextThreadSerial;
for (int i : snapshot.getGCRoots())
{
int roots = 0;
if (!includeObject(i))
continue;
GCRootInfo gi[] = snapshot.getGCRootInfo(i);
for (GCRootInfo gri : gi)
{
int tp;
long contextAddr;
switch (gri.getType())
{
case Type.BUSY_MONITOR:
tp = Constants.DumpSegment.ROOT_MONITOR_USED;
os.writeByte(tp);
writeID(os, gri.getObjectAddress());
++roots;
break;
case Type.THREAD_OBJ:
tp = Constants.DumpSegment.ROOT_THREAD_OBJECT;
os.writeByte(tp);
writeID(os, gri.getObjectAddress());
os.writeInt(nextThreadSerial);
threadToSerial.put(gri.getObjectId(), nextThreadSerial);
// System.out.println("Thread "+gri.getObjectId()+"
// "+nextThreadSerial);
++nextThreadSerial;
Integer stackserial = threadToStack.get(gri.getObjectId());
if (stackserial == null)
stackserial = UNKNOWN_STACK_TRACE_SERIAL;
os.writeInt(stackserial); // Stack trace
++roots;
break;
case Type.JAVA_LOCAL:
contextAddr = gri.getContextAddress();
if (contextAddr == 0)
{
tp = Constants.DumpSegment.ROOT_JAVA_FRAME;
os.writeByte(tp);
writeID(os, gri.getObjectAddress());
os.writeInt(0); // thread serial
os.writeInt(UNKNOWN_STACK_FRAME_SERIAL); // stack frame number
++roots;
}
break;
case Type.NATIVE_LOCAL:
case Type.NATIVE_STATIC:
case Type.NATIVE_STACK:
contextAddr = gri.getContextAddress();
// Currently DTFJ parser generates some roots with context address == object address
// when no context address
if (contextAddr == 0 || contextAddr == gri.getObjectAddress())
{
tp = Constants.DumpSegment.ROOT_JNI_GLOBAL;
os.writeByte(tp);
writeID(os, gri.getObjectAddress());
writeID(os, 0); // JNI global ref ID
++roots;
}
break;
case Type.SYSTEM_CLASS:
tp = Constants.DumpSegment.ROOT_STICKY_CLASS;
os.writeByte(tp);
writeID(os, gri.getObjectAddress());
++roots;
break;
case Type.THREAD_BLOCK:
contextAddr = gri.getContextAddress();
if (contextAddr == 0)
{
tp = Constants.DumpSegment.ROOT_THREAD_BLOCK;
os.writeByte(tp);
writeID(os, gri.getObjectAddress());
os.writeInt(0); // thread serial
++roots;
}
break;
case Type.UNREACHABLE:
case Type.FINALIZABLE:
case Type.UNKNOWN:
default:
tp = Constants.DumpSegment.ROOT_UNKNOWN;
os.writeByte(tp);
writeID(os, gri.getObjectAddress());
++roots;
break;
}
}
if (roots > 0)
{
++totalRoots;
}
}
}
/**
* Class to hold results of parsing stack frames.
*/
static class Frame
{
String clazz;
String method;
String signature;
String sourceFile;
int lineNumber;
public Frame(String clazz, String method, String signature, String sourceFile, int lineNumber)
{
this.clazz = clazz;
this.method = method;
this.signature = signature;
this.sourceFile = sourceFile;
this.lineNumber = lineNumber;
}
/**
* Parse lines such as:
* at org.eclipse.core.internal.jobs.Worker.run()V (Worker.java:55)
* at org.eclipse.swt.widgets.Display.sleep()Z (Display.java:4534)
* at org.eclipse.ui.internal.Workbench.lambda$3(Lorg/eclipse/swt/widgets/Display;Lorg/eclipse/ui/application/WorkbenchAdvisor;[I)V (Workbench.java:683) at
* at org.eclipse.swt.internal.win32.OS.WaitMessage()Z (Native Method)
* at org.eclipse.swt.internal.win32.OS.WaitMessage()Z (Compiled Code)
* at org.eclipse.swt.internal.win32.OS.WaitMessage()Z (Unknown source)
* at org.eclipse.swt.widgets.Display.sleep()Z (Display.java:4534(Compiled Code))
* at java.io.FileInputStream.readBytes([BII)I (Native Method)
* at java.io.FileInputStream.read([BII)I (FileInputStream.java:256)
* at java.io.BufferedInputStream.fill()V (BufferedInputStream.java:246)
* at java.io.BufferedInputStream.read()I (BufferedInputStream.java:265)
* at java.io.BufferedInputStream.read()I (BufferedInputStream.java:265(Compiled Code))
* at
* The last sometimes appears from DTFJ dumps in error.
*
* @param frame
* @return a new Frame object holding the parsed components
*/
public static Frame parse(String frame)
{
String parts[] = frame.split("\\s+", 3); //$NON-NLS-1$
String mn = parts.length >= 2 ? parts[1] : ""; //$NON-NLS-1$
String source = parts.length >= 3 ? parts[2] : ""; //$NON-NLS-1$
int b = mn.indexOf('(');
String method;
String classname;
String sig;
if (b < 0)
{
b = mn.length();
}
int c = mn.lastIndexOf('.', b);
if (c >= 0)
{
classname = mn.substring(0, c);
}
else
{
classname = ""; //$NON-NLS-1$
}
method = mn.substring(c + 1, b);
sig = mn.substring(b);
if (source.startsWith("(")) //$NON-NLS-1$
source = source.substring(1);
if (source.endsWith(")")) //$NON-NLS-1$
source = source.substring(0, source.length() - 1);
int cl = source.indexOf(':');
String sourcefile;
int linenum = 0;
if (cl >= 0)
{
sourcefile = source.substring(0, cl);
int cn1 = cl + 1;
while (cn1 < source.length() && source.charAt(cn1) >= '0' && source.charAt(cn1) <= '9')
{
++cn1;
}
if (cn1 > cl + 1)
{
linenum = Integer.parseInt(source.substring(cl + 1, cn1));
}
}
else
{
int br = source.indexOf('(');
if (br >= 0)
sourcefile = source.substring(0, br);
else
sourcefile = ""; //$NON-NLS-1$
if (source.contains("Compiled Code")) //$NON-NLS-1$
{
linenum = -2;
}
else if (source.contains("Native Method")) //$NON-NLS-1$
{
linenum = -3;
}
else
{
linenum = -1;
}
}
return new Frame(classname, method, sig, sourcefile, linenum);
}
}
/**
* Dump the thread stacks
* @param os the main output stream
* @param startTime
* @throws SnapshotException
* @throws IOException
*/
private void dumpThreadStacks(DataOutput os, long startTime)
throws SnapshotException, IOException
{
dummyStackTrace(os, UNKNOWN_STACK_TRACE_SERIAL, 1);
long nextFrameid = 1;
int serialid = UNKNOWN_STACK_TRACE_SERIAL + 1;
// Find the threads
for (int i : snapshot.getGCRoots())
{
if (!includeObject(i))
continue;
for (GCRootInfo gr : snapshot.getGCRootInfo(i))
{
if (gr.getType() == GCRootInfo.Type.THREAD_OBJ)
{
IThreadStack its = snapshot.getThreadStack(i);
if (its == null)
continue;
// Find the stack frames
ArrayLong frameIds = new ArrayLong();
IObject thread = snapshot.getObject(i);
int frameNumber = 0;
for (IStackFrame isf : its.getStackFrames())
{
long frameid = 0;
for (NamedReference ref : thread.getOutboundReferences())
{
if (ref instanceof ThreadToLocalReference)
{
ThreadToLocalReference tlr = (ThreadToLocalReference)ref;
for (GCRootInfo rootInfo : tlr.getGcRootInfo())
{
if (rootInfo.getType() == GCRootInfo.Type.JAVA_STACK_FRAME)
{
if (Integer.valueOf(frameNumber).equals(tlr.getObject().resolveValue("frameNumber")))
{
frameid = tlr.getObjectAddress();
break;
}
}
}
}
}
if (frameid == 0)
{
frameid = nextFrameid++;
}
String frametext = isf.getText();
Frame f = Frame.parse(frametext);
String classname = f.clazz;
String method = f.method;
String sig = f.signature;
String sourcefile = f.sourceFile;
method = remap.renameMethodName(classname, method, false);
sig = remap.renameSignature(sig);
sourcefile = remap.renameFileName(classname, sourcefile);
int linenum = f.lineNumber;
Collection<IClass> cls = snapshot.getClassesByName(classname, false);
int clsid;
if (cls == null || cls.isEmpty())
{
clsid = 0;
}
else
{
IClass cls1 = cls.iterator().next();
clsid = cls1.getObjectId();
}
os.writeByte(Constants.Record.STACK_FRAME);
os.writeInt((int) (System.currentTimeMillis() - startTime));
os.writeInt(4 * idsize + 2 * 4);
writeID(os, frameid);
writeString(os, method);
writeString(os, sig);
writeString(os, sourcefile);
os.writeInt(clsid);
os.writeInt(linenum);
frameIds.add(frameid);
++frameNumber;
}
os.writeByte(Constants.Record.STACK_TRACE);
os.writeInt((int) (System.currentTimeMillis() - startTime));
os.writeInt(3 * 4 + frameIds.size() * idsize);
os.writeInt(serialid);
Integer prev = threadToStack.put(i, serialid);
//if (prev != null && prev != serialid)
// throw new IllegalStateException("thread " + i + "0x" + Long.toHexString(gr.getObjectAddress()) + " " + serialid + " != " + prev); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
++serialid;
os.writeInt(threadToSerial.get(i));
os.writeInt(frameIds.size());
for (long j : frameIds.toArray())
{
writeID(os, j);
}
}
}
}
}
/**
* Write out the body of a load class definition.
* @param os
* @param startTime
* @param cls
* @throws IOException
*/
private void loadClassBody(DataOutput os, long startTime, IClass cls) throws IOException
{
os.writeInt(cls.getObjectId()); // Class serial id
writeID(os, cls.getObjectAddress());
os.writeInt(UNKNOWN_STACK_TRACE_SERIAL); // stack trace serial number
String classname = cls.getName();
classname = remap.renameClassName(classname);
writeString(os, classname);
}
/**
* Write out the whole load class, including the header and size
* @param os
* @param os2 Uses to measure the size of the body
* @param startTime
* @param cls
* @throws IOException
*/
private void loadClass(DataOutput os, DataOutputStream3 os2, long startTime, IClass cls) throws IOException
{
int str = nextStringID;
long mark = os2.size();
loadClassBody(os2, startTime, cls);
long end = os2.size();
if (nextStringID != str)
{
String classname = cls.getName();
classname = remap.renameClassName(classname);
writeStringUTF(os, os2, startTime, classname, str);
}
loadClass(os, cls, startTime, (int) (end - mark));
}
/**
* Generate load class definitions for all the classes.
* @param os
* @param os2
* @param startTime
* @param listener
* @throws IOException
* @throws SnapshotException
*/
private void loadClasses(DataOutput os, DataOutputStream3 os2, long startTime, IProgressListener listener)
throws IOException, SnapshotException
{
for (IClass cls : snapshot.getClasses())
{
if (includeObject(cls.getObjectId()))
{
loadClass(os, os2, startTime, cls);
}
listener.worked(1);
if (listener.isCanceled())
throw new OperationCanceledException();
}
return;
}
/**
* Write out a single string, as UTF-8.
* @param os
* @param os2
* @param startTime
* @param ss
* @param id
* @throws IOException
*/
private void writeStringUTF(DataOutput os, DataOutputStream3 os2, long startTime, String ss, int id)
throws IOException
{
os.writeByte(Constants.Record.STRING_IN_UTF8);
os.writeInt((int) (System.currentTimeMillis() - startTime));
long mark = os2.size();
writeID(os2, id);
byte utf[] = ss.getBytes(UTF8);
os2.write(utf);
long reclen = os2.size() - mark;
os.writeInt((int)reclen);
writeID(os, id);
os.write(utf);
}
/**
* Dump all the classes into a heap dump segment.
* @param os
* @param listener
* @throws IOException
* @throws SnapshotException
*/
private void dumpClasses(DataOutput os, IProgressListener listener) throws IOException, SnapshotException
{
for (IClass cls : snapshot.getClasses())
{
if (includeObject(cls.getObjectId()))
{
dumpClass(os, cls);
++totalClasses;
}
listener.worked(1);
if (listener.isCanceled())
throw new OperationCanceledException();
}
// As well as actual used class loader types, include any classes extending the java classloader
Collection<IClass> loaders = snapshot.getClassesByName(IClass.JAVA_LANG_CLASSLOADER, true);
if (loaders != null)
{
for (IClass cls: loaders)
{
for (int i : cls.getObjectIds())
{
classloaders.add(i);
}
}
}
return;
}
private void loadClass(DataOutput os, IClass cls, long startTime, int len) throws IOException
{
os.writeByte(Constants.Record.LOAD_CLASS);
os.writeInt((int) (System.currentTimeMillis() - startTime));
os.writeInt(len);
loadClassBody(os, startTime, cls);
}
private void dumpClass(DataOutput os, IClass cls) throws IOException
{
os.writeByte(Constants.DumpSegment.CLASS_DUMP);
writeID(os, cls.getObjectAddress());
os.writeInt(UNKNOWN_STACK_TRACE_SERIAL); // stack trace serial number
IClass sup = cls.getSuperClass();
writeID(os, sup != null ? sup.getObjectAddress() : 0);
writeID(os, cls.getClassLoaderAddress());
// Remember the type of the loader as a possible type for all class loaders
classloaders.add(cls.getClassLoaderId());
// Calculate constant pool
List<Field> statics = cls.getStaticFields();
boolean skip[] = new boolean[statics.size()];
long signersId = 0;
long protectionDomainId = 0;
long reserved1Id = 0;
long reserved2Id = 0;
// count constant pool
int skipfields = 0;
int cpsize = 0;
Pattern cpName = Pattern.compile("constant pool\\[(\\d+)\\]"); //$NON-NLS-1$
int idx = 0;
for (Field fld : statics)
{
String fieldName = fld.getName();
if (NEWCLASSSIZE && cpName.matcher(fieldName).matches())
{
++cpsize;
++skipfields;
skip[idx] = true;
}
if (NEWCLASSSIZE && fld.getType() == IObject.Type.OBJECT)
{
if (fieldName.equals("<signers>")) //$NON-NLS-1$
{
++skipfields;
skip[idx] = true;
if (fld.getValue() instanceof IObject)
signersId = ((IObject)fld.getValue()).getObjectAddress();
}
if (fieldName.equals("<protectionDomain>")) //$NON-NLS-1$
{
++skipfields;
skip[idx] = true;
if (fld.getValue() instanceof IObject)
protectionDomainId = ((IObject)fld.getValue()).getObjectAddress();
}
if (fieldName.equals("<reserved1>")) //$NON-NLS-1$
{
++skipfields;
skip[idx] = true;
if (fld.getValue() instanceof IObject)
reserved1Id = ((IObject)fld.getValue()).getObjectAddress();
}
if (fieldName.equals("<reserved2>")) //$NON-NLS-1$
{
++skipfields;
skip[idx] = true;
if (fld.getValue() instanceof IObject)
reserved2Id = ((IObject)fld.getValue()).getObjectAddress();
}
}
++idx;
}
// Ignore static fields which will be dumped as class fields
if (classesAsInstances)
{
for (IClass cls1 = cls.getClazz(); cls1 != null; cls1 = cls1.getSuperClass())
{
for (FieldDescriptor fd : cls1.getFieldDescriptors())
{
int idx2 = 0;
for (Field fld : cls.getStaticFields())
{
if (!skip[idx2] && fd.getType() == fld.getType() && fld.getName().equals("<"+fd.getName()+">")) //$NON-NLS-1$ //$NON-NLS-2$
{
++skipfields;
skip[idx2] = true;
break;
}
++idx2;
}
}
}
}
writeID(os, signersId); // signers
writeID(os, protectionDomainId); // protection domain
writeID(os, reserved1Id); // reserved
writeID(os, reserved2Id); // reserved
// Calculate the HPROF instance size as the size of an instance dump record data
// not what this snapshot has as the whole instance size: cls.getHeapSizePerInstance();
int hprofInstanceSize = 0;
for (IClass cls1 = cls; cls1 != null; cls1 = cls1.getSuperClass())
{
for (FieldDescriptor fld : cls1.getFieldDescriptors())
{
int type = fld.getType();
if (type == IObject.Type.OBJECT)
hprofInstanceSize += idsize;
else
hprofInstanceSize += IPrimitiveArray.ELEMENT_SIZE[type];
}
}
os.writeInt(hprofInstanceSize);
// write constant pool
os.writeShort(cpsize); // constant pool
for (Field fld : statics)
{
String fieldName = fld.getName();
Matcher matcher = cpName.matcher(fieldName);
if (NEWCLASSSIZE && matcher.matches())
{
String id = matcher.group(1);
int cpidx = Integer.parseInt(id);
os.writeShort((short)cpidx);
writeField(os, fld, true);
}
}
// Static fields
os.writeShort((short) (statics.size() - skipfields));
idx = 0;
for (Field fld : statics)
{
String fieldName = fld.getName();
if (skip[idx++])
continue;
fieldName = remap.renameMethodName(cls.getName(), fieldName, true);
writeString(os, fieldName);
writeField(os, fld, true);
}
// Instance fields
List<FieldDescriptor> fields = cls.getFieldDescriptors();
os.writeShort((short) fields.size());
for (FieldDescriptor fld : fields)
{
String fieldName = fld.getName();
fieldName = remap.renameMethodName(cls.getName(), fieldName, false);
writeString(os, fieldName);
int ty = fld.getType();
switch (ty)
{
case IObject.Type.BOOLEAN:
os.writeByte(IObject.Type.BOOLEAN);
break;
case IObject.Type.BYTE:
os.writeByte(IObject.Type.BYTE);
break;
case IObject.Type.CHAR:
os.writeByte(IObject.Type.CHAR);
break;
case IObject.Type.SHORT:
os.writeByte(IObject.Type.SHORT);
break;
case IObject.Type.INT:
os.writeByte(IObject.Type.INT);
break;
case IObject.Type.FLOAT:
os.writeByte(IObject.Type.FLOAT);
break;
case IObject.Type.LONG:
os.writeByte(IObject.Type.LONG);
break;
case IObject.Type.DOUBLE:
os.writeByte(IObject.Type.DOUBLE);
break;
case IObject.Type.OBJECT:
os.writeByte(IObject.Type.OBJECT);
break;
default:
// Error
break;
}
}
// Check this
totalBytes += cls.getUsedHeapSize();
}
private void writeField(DataOutput os, Field fld, boolean addType) throws IOException
{
int ty = fld.getType();
switch (ty)
{
case IObject.Type.BOOLEAN:
if (addType)
os.writeByte(IObject.Type.BOOLEAN);
int booleanValue = skipData || redact == RedactType.FULL ? 0 : ((Boolean) fld.getValue()).booleanValue() ? 1 : 0;
os.writeByte(booleanValue);
break;
case IObject.Type.BYTE:
if (addType)
os.writeByte(IObject.Type.BYTE);
byte byteValue = skipData || redact != RedactType.NONE && redact != RedactType.NAMES ? 0 : ((Byte) fld.getValue()).byteValue();
os.writeByte(byteValue);
break;
case IObject.Type.CHAR:
if (addType)
os.writeByte(IObject.Type.CHAR);
char charValue = skipData || redact != RedactType.NONE && redact != RedactType.NAMES ? 0 : ((Character) fld.getValue()).charValue();
os.writeChar(charValue);
break;
case IObject.Type.SHORT:
if (addType)
os.writeByte(IObject.Type.SHORT);
short shortValue = skipData || redact == RedactType.FULL ? 0 : ((Short) fld.getValue()).shortValue();
os.writeShort(shortValue);
break;
case IObject.Type.INT:
if (addType)
os.writeByte(IObject.Type.INT);
int intValue = skipData || redact == RedactType.FULL ? 0 : ((Integer) fld.getValue()).intValue();
os.writeInt(intValue);
break;
case IObject.Type.FLOAT:
if (addType)
os.writeByte(IObject.Type.FLOAT);
float floatValue = skipData || redact == RedactType.FULL ? 0.0f : ((Float) fld.getValue()).floatValue();
os.writeFloat(floatValue);
break;
case IObject.Type.LONG:
if (addType)
os.writeByte(IObject.Type.LONG);
long longValue = skipData || redact == RedactType.FULL ? 0L : ((Long) fld.getValue()).longValue();
os.writeLong(longValue);
break;
case IObject.Type.DOUBLE:
if (addType)
os.writeByte(IObject.Type.DOUBLE);
double doubleValue = skipData || redact == RedactType.FULL ? 0.0 : ((Double) fld.getValue()).doubleValue();
os.writeDouble(doubleValue);
break;
case IObject.Type.OBJECT:
if (addType)
os.writeByte(IObject.Type.OBJECT);
ObjectReference value = skipData || !(fld.getValue() instanceof ObjectReference) ? null
: (ObjectReference) fld.getValue();
if (value != null)
writeID(os, value.getObjectAddress());
else
writeID(os, 0);
break;
default:
// Error
break;
}
}
private void dumpThreadRoots(DataOutput os)
throws IOException, SnapshotException
{
for (IClass cls : snapshot.getClasses())
{
dumpThreadRoots(os, cls);
}
}
/**
* Dump objects from start to end (exclusive).
* Returns early if segment is full.
* @param os the main out stream
* @param os2 a temporary length measuring stream
* @param start Start object (inclusive)
* @param end End object (exclusive), negative means no limit
* @param check whether to check for segment overflow
* @param listener
* @return next start position, (negative if no more objects to do)
* @throws IOException
* @throws SnapshotException
*/
private int dumpObjects(DataOutputStream3 os, DataOutputStream3 os2, int start, int end, boolean check, IProgressListener listener)
throws IOException, SnapshotException
{
int i = 0;
if (objects != null)
{
for (int objs[] : objects)
{
i = dumpObjects(os, os2, start, end, i, objs, check, listener);
if (end >= 0 && i >= end)
return i;
if (i < 0)
return -i;
if (listener.isCanceled())
throw new OperationCanceledException();
}
}
else
{
// Whether to output every object in address order or to go by classes
boolean sequential = true;
int count;
int step;
int st;
Iterator<IClass> it;
if (sequential)
{
count = snapshot.getSnapshotInfo().getNumberOfObjects();
step = 10000;
// Advance directly to the start point
st = start;
i = start;
it = null;
}
else
{
count = snapshot.getClasses().size();
step = 1;
st = 0;
it = snapshot.getClasses().iterator();
}
for (int j = st; j < count; j += step)
{
int objs[];
if (sequential)
{
int n = Math.min(step, count - j);
objs = new int[n];
for (int k = 0; k < n; ++k)
{
objs[k] = j + k;
}
}
else
{
objs = it.next().getObjectIds();
}
i = dumpObjects(os, os2, start, end, i, objs, check, listener);
if (end >= 0 && i >= end)
return i;
if (i < 0)
return -i;
if (listener.isCanceled())
throw new OperationCanceledException();
}
}
return -i;
}
/**
* Dump array of objects from start to end (exclusive)
* @param os the main out stream
* @param os2 a temporary length measuring stream
* @param start Start object (inclusive)
* @param end End object (exclusive)
* @param i current position
* @param objs an array of the objects
* @param check whether to check for segment overflow
* @param listener
* @return next start position, or -position for segment overflow
* @throws IOException
* @throws SnapshotException
*/
private int dumpObjects(DataOutputStream3 os, DataOutputStream3 os2, int start, int end, int i,
int[] objs, boolean check, IProgressListener listener) throws SnapshotException, IOException
{
int numberOfObjects = objs.length;
if (i + numberOfObjects <= start)
{
// Skipping class where none of the objects will be used
i += numberOfObjects;
// Try to keep the progress meter moving
if (i == start && numberOfObjects == 0 )
listener.worked(WORK_OBJECT);
}
else
{
int j = 0;
for (int o : objs)
{
if (i < start)
{
// skipping some initial objects
++i;
}
else
{
// Use these objects
// check for overflow if requested and this not the first object
if (dumpObject(os, os2, snapshot.getObject(o), check && i > start))
{
// Success, enough room
++totalObjects;
++i;
++j;
progress(numberOfObjects, j, listener);
if (end >= 0 && i >= end)
{
// Give up here if we have dumped all we should
return i;
}
}
else
{
// No room for this object, so return and say so
// Negative indicates return from caller too
return -i;
}
}
}
// Try to keep the progress meter moving
if (numberOfObjects == 0)
listener.worked(WORK_OBJECT);
}
return i;
}
private void progress(int numberOfObjects, int j, IProgressListener listener)
{
// 1 : 1,1,1
// 2 : 1,2,2
// 3 : 1,2,3
// 4 : 2,3,4
// 5 : 2,4,5
// More rapid progress indicator
for (int k = 1; k <= WORK_OBJECT; ++k)
{
if (j == (k * numberOfObjects + WORK_OBJECT - 1) / WORK_OBJECT)
listener.worked(1);
}
if (listener.isCanceled())
throw new OperationCanceledException();
}
/**
* Find the stack frame in which an object is referenced.
* Remove it from the list.
* @param id
* @param objid
* @return the frame number (0-based)
*/
public int findID(int id, int objid[][])
{
for (int i = 0; i < objid.length; ++i)
{
for (int j = 0; j < objid[i].length; ++j)
{
if (objid[i][j] == id)
{
// Remove from the list so the same object will be found
// in other frames
objid[i][j] = -1;
return i;
}
}
}
return -1;
}
/**
* Process local roots
* @param os
* @param g
* @param objs
* @throws IOException
*/
private void processRoot(DataOutput os, GCRootInfo g, int objs[][]) throws IOException
{
if (!includeObject(g.getObjectId()))
return;
switch (g.getType())
{
case GCRootInfo.Type.NATIVE_LOCAL:
Integer serial = threadToSerial.get(g.getContextId());
if (serial != null)
{
os.writeByte(Constants.DumpSegment.ROOT_JNI_LOCAL);
writeID(os, g.getObjectAddress());
os.writeInt(serial);
os.writeInt(findID(g.getObjectId(), objs)); // stack frame
// number
}
break;
case GCRootInfo.Type.JAVA_LOCAL:
serial = threadToSerial.get(g.getContextId());
if (serial != null)
{
os.writeByte(Constants.DumpSegment.ROOT_JAVA_FRAME);
writeID(os, g.getObjectAddress());
os.writeInt(serial);
os.writeInt(findID(g.getObjectId(), objs)); // stack frame
// number
}
break;
case GCRootInfo.Type.NATIVE_STACK:
serial = threadToSerial.get(g.getContextId());
if (serial != null)
{
os.writeByte(Constants.DumpSegment.ROOT_NATIVE_STACK);
// System.out.println("Thread found "+g.getContextId()+"
// "+serial+" "+g.getContextAddress());
writeID(os, g.getObjectAddress());
os.writeInt(serial);
}
break;
case GCRootInfo.Type.THREAD_BLOCK:
serial = threadToSerial.get(g.getContextId());
if (serial != null)
{
os.writeByte(Constants.DumpSegment.ROOT_THREAD_BLOCK);
writeID(os, g.getObjectAddress());
os.writeInt(serial);
}
break;
case GCRootInfo.Type.JAVA_STACK_FRAME:
serial = threadToSerial.get(g.getContextId());
if (serial != null)
{
os.writeByte(Constants.DumpSegment.ROOT_JAVA_FRAME);
writeID(os, g.getObjectAddress());
os.writeInt(serial);
// Probably won't have a frame number??
os.writeInt(findID(g.getObjectId(), objs)); // stack frame
// number
}
break;
default:
break;
}
}
public void dumpThreadRoots(DataOutput os, IClass cls) throws SnapshotException, IOException
{
for (int oid : cls.getObjectIds())
{
if (!includeObject(cls.getObjectId()))
{
// Skip the thread
continue;
}
GCRootInfo gp[] = snapshot.getGCRootInfo(oid);
if (gp != null)
{
for (GCRootInfo g : gp)
{
// System.out.println("Root
// "+GCRootInfo.getTypeAsString(g.getType())+"
// 0x"+Long.toHexString(g.getObjectAddress())+"
// 0x"+Long.toHexString(g.getContextAddress()));
switch (g.getType())
{
case GCRootInfo.Type.THREAD_OBJ:
IObject io = snapshot.getObject(oid);
IThreadStack its = snapshot.getThreadStack(oid);
int objs[][];
if (its != null)
{
IStackFrame fms[] = its.getStackFrames();
objs = new int[fms.length][];
for (int i = 0; i < fms.length; ++i)
{
objs[i] = fms[i].getLocalObjectsIds().clone();
}
} else {
objs = new int[0][0];
}
processThreadLocalRefs(os, io, objs);
break;
default:
processRoot(os, g, new int[0][0]);
break;
}
}
}
}
}
private void processThreadLocalRefs(DataOutput os, IObject io, int[][] objs) throws IOException, SnapshotException
{
Integer threadSerial = threadToSerial.get(io.getObjectId());
for (NamedReference nr : io.getOutboundReferences())
{
if (nr instanceof ThreadToLocalReference)
{
ThreadToLocalReference tlr = (ThreadToLocalReference) nr;
for (GCRootInfo g2 : tlr.getGcRootInfo())
{
processRoot(os, g2, objs);
if (g2.getType() == Type.JAVA_STACK_FRAME)
{
// Another layer: Thread -> JAVA_STACK_FRAME -> objects
// Fix up the thread serial number for the stack frame
if (threadSerial != null)
threadToSerial.put(tlr.getObjectId(), threadSerial);
processThreadLocalRefs(os, tlr.getObject(), objs);
}
}
}
}
}
/**
* Output all objects of a particular type.
* @param os the main out stream
* @param os2 a temporary length measuring stream
* @param cls the type of the object to dump
* @param io the object to dump
* @param check whether to check for segment overflow
* @return false if no room in segment
* @throws IOException
* @throws SnapshotException
*/
private boolean dumpObject(DataOutputStream3 os, DataOutputStream3 os2, IObject io, boolean check)
throws IOException, SnapshotException
{
if (io instanceof IInstance)
{
return dumpInstance(os, os2, (IInstance)io, check);
}
else if (io instanceof IPrimitiveArray)
{
return dumpPrimitiveArray(os, (IPrimitiveArray)io, check);
}
else if (io instanceof IObjectArray)
{
return dumpObjectArray(os, (IObjectArray)io, check);
} else if (io instanceof IClass){
// Classes are IObject but not necessarily IInstance
return dumpClassObject(os, (IClass)io, check);
}
return true;
}
/**
* Sometimes it might be desirable to dump an IClass also as an instance.
* This is likely to be incompatible with other HPROF tools, but might be necessary to indicate
* the type of an object is something other than java.lang.Class,
* or to output per instance fields declared in java.lang.Class for other classes.
* CLASS_DUMP only has constant pool, declared fields and static fields - but not fields
* declared by java.lang.Class.
* @param os where to output
* @param io what to output
* @param check whether to check if no room
* @return false if no room in segment
* @throws IOException
*/
private boolean dumpClassObject(DataOutputStream3 os, IClass io, boolean check) throws IOException
{
if (!classesAsInstances)
{
return true;
}
IClass cls = io.getClazz();
// Classes are IObject but not necessarily IInstance
int size = (int)cls.getHeapSizePerInstance();
int size2 = 0;
for (IClass cls2 = cls; cls2 != null; cls2 = cls2.getSuperClass())
{
for (FieldDescriptor fd : cls2.getFieldDescriptors()) {
int se;
switch (fd.getType())
{
case IObject.Type.OBJECT:
se = idsize;
break;
default:
se = IPrimitiveArray.ELEMENT_SIZE[fd.getType()];
break;
}
size2 += se;
}
}
size = size2;
if (check && os.size() + 1L + idsize + 4 + idsize + 4 + size > segsize)
{
// Overflow
return false;
}
os.writeByte(Constants.DumpSegment.INSTANCE_DUMP);
writeID(os, io.getObjectAddress());
os.writeInt(UNKNOWN_STACK_TRACE_SERIAL); // stack trace serial number
writeID(os, cls.getObjectAddress());
os.writeInt((int)size);
// Dump the actual fields
Field ss[] = cls.getStaticFields().toArray(new Field[0]);
for (IClass cls2 = cls; cls2 != null; cls2 = cls2.getSuperClass())
{
for (FieldDescriptor fd : cls2.getFieldDescriptors()) {
int fix = 0;
for (fix = 0; fix < ss.length; ++fix)
{
Field fs = ss[fix];
if (fs == null)
continue;
// match by type and pseudo-reference name
if (fs.getType() == fd.getType() && fs.getName().equals("<"+fd.getName()+">")) //$NON-NLS-1$ //$NON-NLS-2$
{
writeField(os, fs, false);
ss[fix] = null;
break;
}
}
if (fix >= ss.length)
{
// Not found
int se;
switch (fd.getType())
{
case IObject.Type.OBJECT:
se = idsize;
break;
default:
se = IPrimitiveArray.ELEMENT_SIZE[fd.getType()];
break;
}
os.write(new byte[se]);
}
}
}
//if (check && os.size() > MAX_SEGMENT)
// throw new IllegalStateException(""+os.size());
return true;
}
/**
* Dump an object array.
* @param os where to output
* @param ii the object array
* @param check whether to check if no room
* @return false if no room in segment
* @throws IOException
*/
private boolean dumpObjectArray(DataOutputStream3 os, IObjectArray ii, boolean check) throws IOException
{
if (check && os.size() + 1L + idsize + 4 + 4 + idsize + ii.getLength() * idsize > segsize)
{
// This object would overflow
return false;
}
os.writeByte(Constants.DumpSegment.OBJECT_ARRAY_DUMP);
writeID(os, ii.getObjectAddress());
os.writeInt(UNKNOWN_STACK_TRACE_SERIAL); // stack trace serial number
os.writeInt(ii.getLength());
writeID(os, ii.getClazz().getObjectAddress());
long l[] = skipData ? new long[ii.getLength()] : ii.getReferenceArray();
for (int i = 0; i < ii.getLength(); ++i)
{
writeID(os, l[i]);
}
//if (check && os.size() > MAX_SEGMENT)
// throw new IllegalStateException(""+os.size());
totalBytes += ii.getUsedHeapSize();
return true;
}
/**
* Dump a primitive array.
* @param os where to output
* @param ii the object array
* @param check whether to check if no room
* @return false if no room in segment
* @throws IOException
*/
private boolean dumpPrimitiveArray(DataOutputStream3 os, IPrimitiveArray ii, boolean check) throws IOException
{
if (check && os.size() + 1L + idsize + 4 + 4 + 1 + ii.getLength() * (1L << (ii.getType() & 3) ) > segsize)
{
return false;
}
os.writeByte(Constants.DumpSegment.PRIMITIVE_ARRAY_DUMP);
writeID(os, ii.getObjectAddress());
os.writeInt(UNKNOWN_STACK_TRACE_SERIAL); // stack trace serial number
os.writeInt(ii.getLength());
os.writeByte(ii.getType());
// For safety, don't even read value for full redaction
Object a = skipData || redact == RedactType.FULL ? null : ii.getValueArray();
if (ii.getType() == IObject.Type.BOOLEAN)
{
for (int i = 0; i < ii.getLength(); ++i)
{
int booleanValue = skipData || redact == RedactType.FULL ? 0 : ((boolean[]) a)[i] ? 1 : 0;
os.writeByte(booleanValue);
}
}
else if (ii.getType() == IObject.Type.BYTE)
{
if (!skipData && redact == RedactType.NAMES)
{
String s = new String((byte[])a, UTF8);
String newstr = remap.mapClass(s);
if (newstr == null)
{
newstr = remap.mapField(s);
}
if (newstr == null)
{
newstr = remap.mapSignature(s);
}
if (newstr != null)
{
byte b[] = newstr.getBytes(UTF8);
if (b.length == ii.getLength())
{
// Mapped with exact length
a = b;
}
else
{
// Length problem, shouldn't happen
byte b2[] = new byte[ii.getLength()];
System.arraycopy(b, 0, b2, 0, Math.min(b.length, ii.getLength()));
a = b2;
}
}
}
else if (skipData || redact != RedactType.NONE)
{
a = new byte[ii.getLength()];
}
os.write((byte[]) a);
}
else if (ii.getType() == IObject.Type.SHORT)
{
for (int i = 0; i < ii.getLength(); ++i)
{
short shortValue = skipData || redact == RedactType.FULL ? 0 : ((short[]) a)[i];
os.writeShort(shortValue);
}
}
else if (ii.getType() == IObject.Type.CHAR)
{
if (!skipData && redact == RedactType.NAMES)
{
String s = new String((char[])a);
String newstr = remap.mapClass(s);
if (newstr == null)
{
newstr = remap.mapField(s);
}
if (newstr == null)
{
newstr = remap.mapSignature(s);
}
if (newstr != null)
{
char b[] = newstr.toCharArray();
if (b.length == ii.getLength())
{
// Mapped with exact length
a = b;
}
else
{
// Length problem, shouldn't happen
char b2[] = new char[ii.getLength()];
System.arraycopy(b, 0, b2, 0, Math.min(b.length, ii.getLength()));
a = b2;
}
}
}
for (int i = 0; i < ii.getLength(); ++i)
{
char shortValue = skipData || redact != RedactType.NONE && redact != RedactType.NAMES ? 0 : ((char[]) a)[i];
os.writeChar(shortValue);
}
}
else if (ii.getType() == IObject.Type.INT)
{
for (int i = 0; i < ii.getLength(); ++i)
{
int intValue = skipData || redact != RedactType.NONE && redact != RedactType.NAMES ? 0 : ((int[]) a)[i];
os.writeInt(intValue);
}
}
else if (ii.getType() == IObject.Type.LONG)
{
for (int i = 0; i < ii.getLength(); ++i)
{
long longValue = skipData || redact == RedactType.FULL ? 0L : ((long[]) a)[i];
os.writeLong(longValue);
}
}
else if (ii.getType() == IObject.Type.FLOAT)
{
for (int i = 0; i < ii.getLength(); ++i)
{
float floatValue = skipData || redact == RedactType.FULL ? 0.0f : ((float[]) a)[i];
os.writeFloat(floatValue);
}
}
else if (ii.getType() == IObject.Type.DOUBLE)
{
for (int i = 0; i < ii.getLength(); ++i)
{
double doubleValue = skipData || redact == RedactType.FULL ? 0.0 : ((double[]) a)[i];
os.writeDouble(doubleValue);
}
}
//if (check && os.size() > MAX_SEGMENT)
// throw new IllegalStateException(""+os.size());
totalBytes += ii.getUsedHeapSize();
return true;
}
private boolean dumpInstance(DataOutputStream3 os, DataOutputStream3 os2, IInstance ii, boolean check)
throws IOException
{
if (ii.getObjectAddress() == 0)
{
// skip Bootstrap class loader as it has no fields
if (classloaders.contains(ii.getObjectId()))
++totalClassloaders;
return true;
}
IClass cls = ii.getClazz();
long mark1 = os2.size();
boolean saveSkip = skipData;
skipData = true;
dumpInstanceFields(os2, cls, ii);
skipData = saveSkip;
long mark2 = os2.size();
long size = mark2 - mark1;
if (check && os.size() + 1L + idsize + 4 + idsize + 4 + size > segsize)
{
// Overflow
return false;
}
os.writeByte(Constants.DumpSegment.INSTANCE_DUMP);
writeID(os, ii.getObjectAddress());
os.writeInt(UNKNOWN_STACK_TRACE_SERIAL); // stack trace serial number
writeID(os, cls.getObjectAddress());
os.writeInt((int)size);
dumpInstanceFields(os, cls, ii);
//if (check && os.size() > MAX_SEGMENT)
// throw new IllegalStateException(""+os.size());
if (classloaders.contains(ii.getObjectId()))
++totalClassloaders;
totalBytes += cls.getHeapSizePerInstance();
return true;
}
/**
* Output a single plain object.
* @param os where to output
* @param cls the type of the object
* @param ii the actual object
* @throws IOException
*/
private void dumpInstanceFields(DataOutputStream3 os, IClass cls, IInstance ii) throws IOException
{
List<Field> allf = new ArrayList<Field>(ii.getFields());
for (IClass cls1 = cls; cls1 != null; cls1 = cls1.getSuperClass())
{
// Index by each descriptor
for (FieldDescriptor f : cls1.getFieldDescriptors())
{
boolean found = false;
// Find each field matching the descriptor
for (ListIterator<Field> it = allf.listIterator(); it.hasNext();)
{
Field fld = it.next();
if (f.getName().equals(fld.getName()))
{
it.remove(); // In case fields are defined in superclass
// too
found = true;
writeField(os, fld, false);
break;
}
}
if (!found)
throw new IllegalStateException("missing field value " + f); //$NON-NLS-1$
}
}
}
/**
* Write an ID of an object as an appropriate size.
* @param os where to output
* @param addr the address of the object
* @throws IOException
*/
private void writeID(DataOutput os, long addr) throws IOException
{
if (idsize == 4)
{
os.writeInt((int)addr);
}
else
{
os.writeLong(addr);
}
}
/**
* Write a string to the output stream as an ID.
* Add ID to the map if new, otherwise use the
* existing ID.
* Something else will have to output the string definition.
* @param os
* @param s
* @throws IOException
*/
private void writeString(DataOutput os, String s) throws IOException
{
long id;
if (stringToID.containsKey(s))
{
id = stringToID.get(s);
}
else
{
id = nextStringID++;
stringToID.put(s, (int) id);
}
writeID(os, id);
}
/**
* Remaps class names.
* Separate class to isolate the generation of names from the actual
* contents of the snapshot.
*
*/
public static class Remap {
Pattern skipPattern, avoidPattern;
boolean undo;
boolean matchFields;
int remapFail = 0;
/**
* Random number generator.
* Doesn't need to be particular secure as it just generates replacement names.
* The replacement name can't be remapped to the original name from the random number generator.
*/
private Random rnd = new Random();
/**
* Create a remapper
* @param skipPattern Remap names unless they match this
* @param avoidPattern Avoid remapping to names which match this
* @param matchFields TODO
* @param undo Just use existing remappings
*/
public Remap(Pattern skipPattern, Pattern avoidPattern, boolean matchFields, boolean undo)
{
this.skipPattern = skipPattern;
this.avoidPattern = avoidPattern;
this.matchFields = matchFields;
this.undo = undo;
}
/*
* The following section is for remapping class names to hide potential sensitive names.
*/
/**
* Load the existing mapping table from a mapping properties file.
* Properties file format:
* original.package.Classname=new.package.Classname
* @param mapFile the Java format properties file
* @param undo whether to reverse the mappings contained in the file
* @throws IOException
*/
public void loadMapping(File mapFile, boolean undo) throws IOException
{
if (mapFile != null && mapFile.canRead())
{
Properties p = new Properties();
// Properties always written in ISO8859_1, so use stream
FileInputStream rdr = new FileInputStream(mapFile);
try {
p.load(rdr);
}
finally
{
rdr.close();
}
for (Map.Entry<Object,Object> e : p.entrySet())
{
String key = e.getKey().toString();
String value = e.getValue().toString();
if (undo)
{
// Reverse the mapping
obfuscated.put(value, key);
used.add(key);
String fieldOld = fieldName(key);
String fieldNew = fieldName(value);
usedField.put(fieldNew, fieldOld);
}
else
{
// Mapping file shows old to new
obfuscated.put(key, value);
used.add(value);
String fieldOld = fieldName(key);
String fieldNew = fieldName(value);
usedField.put(fieldOld, fieldNew);
}
}
}
}
private static class SortedProperties extends Properties
{
private static final long serialVersionUID = 1L;
@Override
public Enumeration<Object> keys()
{
List<Object> list = Collections.list(super.keys());
Collections.sort(list, new Comparator<Object>()
{
public int compare(Object o1, Object o2)
{
return o1.toString().compareTo(o2.toString());
}
});
// Sorted, not sure about locking, but okay for this
// purpose
return Collections.enumeration(list);
}
};
public void saveMapping(File mapFile, boolean undo, String comments) throws IOException
{
// Only save if we are not doing a reverse mapping, and it is okay to write to the file
if (!undo && mapFile != null && (mapFile.canWrite() || !mapFile.exists()))
{
// Sorted keys properties file
Properties p = new SortedProperties();
for (Map.Entry<String,String> e : obfuscated.entrySet())
{
p.setProperty(e.getKey(), e.getValue());
}
// Properties always written in ISO8859_1, so use stream
FileOutputStream wrt = new FileOutputStream(mapFile);
try {
p.store(wrt, comments);
}
finally
{
wrt.close();
}
}
}
/**
* Is the class name one which should have a new name invented?
* @param cn
* @return true if the class name is to be changed
*/
public boolean isRemapped(String cn)
{
return !undo && (skipPattern == null || !skipPattern.matcher(cn).matches());
}
/**
* Return the renamed version of a class
* @param cn
* @return null if not renamed
*/
public String mapClass(String cn)
{
return obfuscated.get(cn);
}
/**
* Return the renamed version of a simple field/method
* @param cn
* @return null if not renamed
*/
public String mapField(String cn)
{
return usedField.get(cn);
}
/**
* Return the renamed version of a method/type signature
* @param sig
* @return null if not renamed
*/
public String mapSignature(String sig)
{
// Quick test for signature with class name
if (!sig.matches("\\p{Print}*L\\p{Print}+;\\p{Print}*")) //$NON-NLS-1$
return null;
// Split up around possible class names
String words[] = sig.split("[;<\\(\\)\\[\\]+*]+"); //$NON-NLS-1$
String wordsReplace[] = new String[words.length];
int found = 0;
for (int i = 0; i < words.length; ++i)
{
String w = words[i];
// Remove any simple types in front of the name
String w2 = w.replaceFirst("[VZBCIJFD]*", ""); //$NON-NLS-1$ //$NON-NLS-2$
// True encoded class?
if (!w2.startsWith("L")) //$NON-NLS-1$
{
wordsReplace[i] = w;
continue;
}
w2 = w2.substring(1);
if (!obfuscated.containsKey(w2))
{
String w3 = w2.replace('/', '.');
if (!obfuscated.containsKey(w3))
{
wordsReplace[i] = w;
}
else
{
words[i] = "L" + w2; //$NON-NLS-1$
wordsReplace[i] = "L" + obfuscated.get(w3).replace('.', '/'); //$NON-NLS-1$
++found;
}
}
else
{
words[i] = "L" + w2; //$NON-NLS-1$
wordsReplace[i] = "L" + obfuscated.get(w2); //$NON-NLS-1$
++found;
}
}
if (found == 0)
{
// No changes
return null;
}
int j = 0;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < words.length; ++i)
{
int k = sig.indexOf(words[i], j);
if (k < 0)
return null;
sb.append(sig.substring(j, k));
sb.append(wordsReplace[i]);
j = k + words[i].length();
}
sb.append(sig.substring(j));
return sb.toString();
}
/**
* Whether to avoid generating this particular new name.
* @param cn
* @return
*/
private boolean isAvoid(String cn)
{
return avoidPattern != null && avoidPattern.matcher(cn).matches();
}
/**
* java.lang.String: java.=rurl. java.lang.rurl.morl.
* java.lang.String=rurl.morl.Glaeck java.lang.Integer=rurl.morl.Wrirurl
* java.lang.String.split(Ljava.lang.String;)Ljava.lang.String;
*/
private Map<String, String> obfuscated = new HashMap<String, String>();
/** Holds whether the new name has been used, to prevent conflicts */
private Set<String> used = new HashSet<String>();
/** Holds whether the field name has been mapped, so should be zeroed elsewhere */
private Map<String, String> usedField = new HashMap<String,String>();
/**
* Rename a file name based on a class name.
* @param classname
* @param filename
* @return the renamed file name
*/
public String renameFileName(String classname, String filename)
{
String clsnw = renameClassName(classname);
String old = baseName(classname);
String nw = baseName(clsnw);
return filename.replaceFirst(old, nw);
}
/**
* Extract the last part of a class name.
* Up to a $ for an inner class,
* up to dot for an ordinary class
* @param classname
* @return
*/
private String baseName(String classname)
{
int dot = classname.lastIndexOf('.');
int dol = classname.indexOf('$', dot);
if (dol < 0)
dol = classname.length();
String fn = classname.substring(dot + 1, dol);
return fn;
}
/**
* Rename a method signature.
* Extract the class names from the Lpack1.class1;II)VLpack2.class2;
* @param signature
* @return renamed signature
*/
public String renameSignature(String signature)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < signature.length(); ++i)
{
char ch = signature.charAt(i);
sb.append(ch);
if (ch == 'L')
{
int semi = sb.indexOf(";", i); //$NON-NLS-1$
if (semi >= 0)
{
String cn = signature.substring(i + 1, semi);
String newcn = renameClassName(cn);
sb.append(newcn);
sb.append(";"); //$NON-NLS-1$
i = semi;
}
else
{
sb.append(signature.substring(i + 1));
break;
}
}
}
return sb.toString();
}
// Avoid using = or : or # or ! as have to be escaped in properties files
private static final String methodSep = "@"; //$NON-NLS-1$
/**
* Generate a new method name.
* Remember it as package.class^method
*
* - ? Some unusual Javac generated methods:
* HistogramQuery$Grouping.values()
* HistogramQuery.$SWITCH_TABLE$org$eclipse$mat$inspections$HistogramQuery$Grouping()
*
* @param className
* @param method method name or field name
* @param upper static field in upper case, else all lower case.
* @return the renamed method or field name
*/
public String renameMethodName(String className, String method, boolean upper)
{
String mn = className + methodSep + method;
if (obfuscated.containsKey(mn))
{
String newmn = obfuscated.get(mn);
return newmn.substring(newmn.indexOf(methodSep) + 1);
}
if (!isRemapped(className))
return method;
String newcls = renameClassName(className);
String newmn = remap(mn, newcls + methodSep, method, "", true, upper); //$NON-NLS-1$
return newmn.substring(newmn.indexOf(methodSep) + 1);
}
private String fieldName(String classField)
{
String fns[] = classField.split(Pattern.quote(methodSep), 2);
if (fns.length >= 2)
{
return fns[1];
}
else
{
return ""; //$NON-NLS-1$
}
}
/**
* Renames a class.
* Break into component parts, reusing existing mapping for package if
* already used.
* Removes array suffixes and uses base class name.
* Translates inner classes with '$' piece by piece, reusing existing
* mapping of outer class.
*
* @param classname
* @return the renamed class name, or the original name if no renaming is to be done for this class
*/
public String renameClassName(String classname)
{
if (obfuscated.containsKey(classname))
return obfuscated.get(classname);
// Remap arrays preserving base class
if (classname.endsWith("[]")) //$NON-NLS-1$
{
String baseclassname = classname.replace("[]", ""); //$NON-NLS-1$//$NON-NLS-2$
return renameClassName(baseclassname) + classname.substring(baseclassname.length());
}
else if (classname.startsWith("[")) //$NON-NLS-1$
{
String baseclassname = classname.replace("[", ""); //$NON-NLS-1$//$NON-NLS-2$
return classname.substring(0, classname.length() - baseclassname.length()) + renameClassName(baseclassname);
}
// E.g. If only com.sun. is renamed, don't rename com.
if (!isRemapped(classname))
return classname;
String pack;
String newpack;
String cn;
String last = ""; //$NON-NLS-1$
if (classname.endsWith(".")) //$NON-NLS-1$
{
// Package name
last = "."; //$NON-NLS-1$
int i = classname.lastIndexOf('.', classname.length() - 2);
if (i >= 0)
{
pack = classname.substring(0, i + 1);
newpack = renameClassName(pack);
cn = classname.substring(i + 1, classname.length() - 1);
}
else
{
pack = ""; //$NON-NLS-1$
newpack = ""; //$NON-NLS-1$
cn = classname.substring(0, classname.length() - 1);
}
}
else
{
// Ordinary class name
int i = classname.lastIndexOf('.', classname.length() - 2);
int j = classname.lastIndexOf('$', classname.length() - 2);
if (j > i)
{
// Without $
pack = classname.substring(0, j);
newpack = renameClassName(pack) + "$"; //$NON-NLS-1$
cn = classname.substring(j + 1);
}
else if (i >= 0)
{
// With the dot
pack = classname.substring(0, i + 1);
newpack = renameClassName(pack);
cn = classname.substring(i + 1);
}
else
{
pack = ""; //$NON-NLS-1$
newpack = ""; //$NON-NLS-1$
cn = classname;
}
}
return remap(classname, newpack, cn, last, false, false);
}
/**
* Map a class name or package name to a new name
* @param classname the new fully qualified class name, include "." for just a package name
* @param newpack The new package name (already replaced), excluding cn
* @param cn The class name or last package component
* @param last The suffix to add onto the name ("." for package, "$" for inner class)
* @param field Is this a field name (do not title case)
* @param upper If a field name, then upper case
* @return
*/
private String remap(String classname, String newpack, String cn, String last, boolean field, boolean upper)
{
/*
* 0 => 0-9 unchanged
* 0 + fields => field map
* 1-49 => word
* 50-99 => random lower case string
* 100-149 => random mixed case string
* 150-199 => random mixed case string, don't use avoid pattern
*/
for (int k = 0; k < 200; ++k)
{
String np;
int ln = cn.length();
// Try not mapping numeric
if (k == 0 && cn.matches("[0-9]+")) //$NON-NLS-1$
{
np = cn;
}
else if (k == 0 && matchFields && field && usedField.containsKey(cn))
{
// Match field values across classes, keep the same names
np = usedField.get(cn);
}
else if (k < 50)
{
np = randomWordsLen(ln, field && upper);
}
else if (k < 100)
np = randomString(ln);
else
np = randomString2(ln);
if (field)
{
// otherwise lower/mixed
if (upper)
{
// static field in upper case
np = np.toUpperCase(Locale.ENGLISH);
}
}
else if (!last.equals(".")) //$NON-NLS-1$
{
// Class name in title case
np = titleCase(np);
}
else
{
// package in lower case
np = np.toLowerCase(Locale.ENGLISH);
}
String newcn = newpack + np + last;
// Check if already used, or could clash with the unmapped names
// Also avoid unwanted strings, unless we get desperate!
if (!used.contains(newcn) && isRemapped(newcn) && (k >= 150 || !isAvoid(newcn)))
{
// found new mapping
obfuscated.put(classname, newcn);
used.add(newcn);
if (field)
usedField.put(cn, np);
return newcn;
}
}
// Failed to find suitable mapping, record to avoid slow lookup later
// found new mapping
String newcn = newpack + cn + last;
obfuscated.put(classname, newcn);
used.add(newcn);
if (field)
usedField.put(cn, cn);
++remapFail;
return newcn;
}
/**
* Title case a string.
* @param np lower/mixed case version
* @return Upper case first letter
*/
private String titleCase(String np)
{
if (np.length() < 1)
return np;
np = Character.toTitleCase(np.charAt(0)) + np.substring(1);
return np;
}
/**
* Generate a random word of given length.
* @param length the length in chars
* @param unders whether to separate words with underscore
* @return
*/
private String randomWordsLen(int length, boolean unders)
{
final int minlen = 5;
// Capitals are hard to read, so shorten the words between underscores
final int maxlen = unders ? 11: 15;
if (length > maxlen)
{
// Split the word
int sp = minlen + rnd.nextInt(Math.min(maxlen, length - minlen - (unders ? 1 : 0)) + 1 - minlen);
String w = randomWordLen(sp);
if (unders)
{
w = w + "_" + randomWordsLen(length - sp - 1, unders); //$NON-NLS-1$
}
else
{
w = w + titleCase(randomWordsLen(length - sp, unders));
}
return w;
}
return randomWordLen(length);
}
/**
* Generate a random word
* @param length
* @return
*/
private String randomWordLen(int length)
{
if (length < 3)
return randomString(length);
// Minimum number of parts
int p1 = (length - 2) / 4;
// Maximum number of parts
int p2 = (length - 1) / 2;
String ret;
do
{
// Decide whether to start with a vowel
boolean vowel = rnd.nextFloat() < 0.15f;
do
{
// Choose the number of parts
int pt = p1 + rnd.nextInt(p2 - p1 + 1);
ret = randomWord(pt);
}
while (ret.length() != (vowel ? length - 1 : length));
ret = addVowel(ret, vowel);
}
while (tryAgain(ret));
return ret;
}
/**
* Check whether the word is unsuitable.
* @param tocheck
* @return
*/
private boolean tryAgain(String tocheck)
{
StringBuilder sb = new StringBuilder(tocheck);
String rev = sb.reverse().toString().toLowerCase(Locale.ENGLISH);
for (String ts : names4)
{
if (rev.indexOf(ts) >= 0)
return true;
}
return false;
}
/** Starting vowels */
@SuppressWarnings("nls")
private static final String names0[] = { "a", "e", "i", "o", "u" };
/** Starting / middle consonants */
@SuppressWarnings("nls")
private static final String names1[] = { "b", "bl", "br", "c", "cl", "cr", "d", "dr", "f", "fl", "fr", "g", "gl", "gr", "h", //$NON-NLS-1$
"j", "k", "kl", "kn", "kr", "kw", "l", "m", "n", "p", "pl", "pr", "qu", "r", "s", "sh", "st", "t", "th", //$NON-NLS-1$
"tr", "v", "w", "wr", "x", "y", "z" }; //$NON-NLS-1$
/** middle vowels */
@SuppressWarnings("nls")
private static final String names2[] = { "a", "ae", "ai", "e", "ea", "ee", "ei", "eo", "eu", "i", "ia", "ie", "io", "o", "oa", //$NON-NLS-1$
"oe", "oo", "ou", "u", "ua" }; //$NON-NLS-1$
/** endings */
@SuppressWarnings("nls")
private static final String names3[] = { "b", "ch", "ck", "d", "de", "ff", "g", "gh", "k", "l", "le", "ly", "m", "n", "nd", "ne", "ng", "nk", //$NON-NLS-1$
"p", "r", "rb", "rd", "re", "rf", "rk", "rl", "rm", "rn", "rp", "rt", "ry", "s", "sh", "st", "sy", //$NON-NLS-1$
"t", "te", "th", "ts", "ve", "w", "y", "z" }; //$NON-NLS-1$
/** excludes */
@SuppressWarnings("nls")
private static final String names4[] = {"tihs", "ssip", "kcuf", "tnuc", "kcoc", "stit", "knaw", "ggin"}; //$NON-NLS-1$
/**
* Perhaps add a vowel prefix to a string
* @param base
* @param add whether to add
* @return
*/
private String addVowel(String base, boolean add)
{
if (add)
{
base = names0[rnd.nextInt(names0.length)] + base;
}
return base;
}
/**
* length varies from 2*parts+1 to 4*part+2
*
* @param parts
* @return random word
*/
private String randomWord(int parts)
{
StringBuilder sb = new StringBuilder();
for (int j = 0; j < parts; ++j)
{
sb.append(names1[rnd.nextInt(names1.length)]);
sb.append(names2[rnd.nextInt(names2.length)]);
}
sb.append(names3[rnd.nextInt(names3.length)]);
return sb.toString();
}
/**
* Random lower case string
* @param length
* @return random lower case string
*/
private String randomString(int length)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; ++i)
{
sb.append((char) ('a' + rnd.nextInt(26)));
}
return sb.toString();
}
/**
* Random mixed case string
*
* @param length
* @return random upper and lower case string
*/
private String randomString2(int length)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; ++i)
{
sb.append((char) ('a' + rnd.nextInt(26) + (rnd.nextBoolean() ? 'A' - 'a' : 0)));
}
return sb.toString();
}
}
}