package org.erlide.backend.debug.model;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.util.Collection;
import java.util.List;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IIndexedValue;
import org.eclipse.debug.core.model.IVariable;
import org.erlide.engine.ErlangEngine;
import org.erlide.engine.model.ErlElementKind;
import org.erlide.engine.model.ErlModelException;
import org.erlide.engine.model.IErlElement;
import org.erlide.engine.model.erlang.IErlPreprocessorDef;
import org.erlide.engine.model.erlang.IErlRecordDef;
import org.erlide.engine.model.erlang.IErlRecordField;
import org.erlide.engine.model.root.IErlModel;
import org.erlide.engine.model.root.IErlProject;
import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangBinary;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangLong;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
public class IndexedErlangValue extends ErlangValue implements IIndexedValue {
private static final List<IErlElement> EMPTY_LIST = Lists.newArrayList();
// FIXME JC Maybe we should use polymorphism for records?
protected IErlRecordDef record; // set if this value is a record
protected OtpErlangList list; // set if this value is a string-coded list
// TODO not for regular lists too?
public IndexedErlangValue(final IDebugTarget target, final String varName,
final OtpErlangObject value, final ErlangProcess process,
final String moduleName) {
super(target, varName, value, process, moduleName);
record = checkRecord(value);
list = checkList(value);
}
private OtpErlangList checkList(final OtpErlangObject theValue) {
if (theValue instanceof OtpErlangString) {
final OtpErlangString os = (OtpErlangString) theValue;
final String s = os.stringValue();
// TODO real encoding?
final byte[] b = s.getBytes(Charsets.ISO_8859_1);
if (!looksLikeAscii(b)) {
return new OtpErlangList(s);
}
}
return null;
}
@Override
public int getInitialOffset() {
return 0;
}
@Override
public int getSize() throws DebugException {
int arity = getArity();
if (record != null) {
--arity;
}
return arity;
}
@Override
public IVariable getVariable(final int offset) throws DebugException {
String name;
if (record != null) {
try {
final IErlRecordField recordField = (IErlRecordField) record.getChildren()
.get(offset);
name = recordField.getFieldName();
} catch (final ErlModelException e) {
name = varName + ":" + offset;
}
} else {
name = varName + ":" + offset;
}
return new ErlangVariable(getDebugTarget(), name, true, getElementAt(offset),
process, moduleName, -1);
}
@Override
public IVariable[] getVariables(final int offset, final int length)
throws DebugException {
final IVariable[] result = new IVariable[length];
for (int i = 0; i < length; ++i) {
result[i] = getVariable(i + offset);
}
return result;
}
private IErlRecordDef checkRecord(final OtpErlangObject o) {
if (o instanceof OtpErlangTuple) {
final OtpErlangTuple t = (OtpErlangTuple) o;
final OtpErlangObject h = t.elementAt(0);
if (h instanceof OtpErlangAtom) {
final OtpErlangAtom a = (OtpErlangAtom) h;
final ErlangDebugTarget target = getErlangDebugTarget();
IErlPreprocessorDef pd;
try {
pd = ErlangEngine.getInstance().getModelFindService()
.findPreprocessorDef(getErlProjects(target.getProjects()),
moduleName, a.atomValue(), ErlElementKind.RECORD_DEF);
if (pd instanceof IErlRecordDef) {
final IErlRecordDef r = (IErlRecordDef) pd;
if (r.hasChildren() && r.getChildCount() + 1 == t.arity()) {
return r;
}
}
} catch (final CoreException e) {
}
}
}
return null;
}
private Collection<IErlProject> getErlProjects(final Collection<IProject> projects) {
final List<IErlProject> result = Lists.newArrayListWithCapacity(projects.size());
final IErlModel model = ErlangEngine.getInstance().getModel();
for (final IProject project : projects) {
final IErlElement element = model.getChildWithResource(project);
if (element instanceof IErlProject) {
final IErlProject erlProject = (IErlProject) element;
result.add(erlProject);
}
}
return result;
}
@Override
public String getReferenceTypeName() throws DebugException {
if (record != null) {
return "record";
}
return super.getReferenceTypeName();
}
@Override
public String getValueString() throws DebugException {
if (record != null) {
return getRecordValueString(record, value);
} else if (list != null) {
return getListValueString(list);
} else {
return getValueString(value, false);
}
}
private String getValueString(final OtpErlangObject o, final boolean recordCheck)
throws DebugException {
if (o instanceof OtpErlangBinary) {
final OtpErlangBinary b = (OtpErlangBinary) o;
return getBinaryValueString(b);
} else if (o instanceof OtpErlangTuple) {
if (recordCheck) {
final IErlRecordDef r = checkRecord(o);
if (r != null) {
return getRecordValueString(r, o);
}
}
final OtpErlangTuple t = (OtpErlangTuple) o;
return getTupleValueString(t);
} else if (o instanceof OtpErlangList) {
final OtpErlangList l = (OtpErlangList) o;
return getListValueString(l);
} else {
return o.toString();
}
}
private String getRecordValueString(final IErlRecordDef r, final OtpErlangObject o) {
final StringBuilder b = new StringBuilder();
List<IErlElement> children;
try {
children = r.getChildren();
} catch (final ErlModelException e) {
children = EMPTY_LIST;
}
final OtpErlangTuple t = (OtpErlangTuple) o;
b.append(t.elementAt(0)).append("#{");
final int n = children.size();
if (n > 0) {
for (int i = 0; i < n; i++) {
final IErlRecordField field = (IErlRecordField) children.get(i);
b.append(field.getFieldName()).append('=')
.append(t.elementAt(i + 1).toString()).append(", ");
}
b.setLength(b.length() - 2);
}
b.append('}');
return b.toString();
}
private String getListValueString(final OtpErlangList l) throws DebugException {
final StringBuilder b = new StringBuilder("[");
if (l.arity() > 0) {
for (final OtpErlangObject o : l) {
b.append(getValueString(o, true)).append(", ");
}
b.setLength(b.length() - 2);
}
b.append(']');
return b.toString();
}
private String getTupleValueString(final OtpErlangTuple t) throws DebugException {
final StringBuilder b = new StringBuilder("{");
if (t.arity() > 0) {
for (final OtpErlangObject o : t.elements()) {
b.append(getValueString(o, true)).append(", ");
}
b.setLength(b.length() - 2);
}
b.append('}');
return b.toString();
}
private static String getBinaryValueString(final OtpErlangBinary b) {
final StringBuilder sb = new StringBuilder("<<");
if (b.size() > 0) {
final byte[] bytes = b.binaryValue();
CharBuffer cb = null;
if (looksLikeAscii(bytes)) {
final Charset[] css = { Charsets.UTF_8, Charsets.ISO_8859_1 };
final Charset[] tryCharsets = css;
for (final Charset cset : tryCharsets) {
final CharsetDecoder cd = cset.newDecoder();
cd.onMalformedInput(CodingErrorAction.REPORT);
cd.onUnmappableCharacter(CodingErrorAction.REPORT);
try {
cb = cd.decode(ByteBuffer.wrap(bytes));
break;
} catch (final CharacterCodingException e) {
}
}
}
if (cb != null && cb.length() > 0) {
sb.append('"').append(cb).append('"');
} else {
for (int i = 0, n = bytes.length; i < n; ++i) {
int j = bytes[i];
if (j < 0) {
j += 256;
}
sb.append(j);
if (i < n - 1) {
sb.append(',');
}
}
}
}
sb.append(">>");
return sb.toString();
}
private static boolean looksLikeAscii(final byte[] bytes) {
for (final byte b : bytes) {
if (b < 32) {
return false;
}
}
return true;
}
@Override
public boolean hasVariables() throws DebugException {
return getArity() != -1;
}
protected OtpErlangObject getElementAt(final int index) {
if (value instanceof OtpErlangTuple) {
final OtpErlangTuple t = (OtpErlangTuple) value;
final int ofs = record != null ? 1 : 0;
return t.elementAt(index + ofs);
} else if (value instanceof OtpErlangList) {
final OtpErlangList l = (OtpErlangList) value;
return l.elementAt(index);
} else if (value instanceof OtpErlangBinary) {
final OtpErlangBinary bs = (OtpErlangBinary) value;
int j = bs.binaryValue()[index];
if (j < 0) {
j += 256;
}
return new OtpErlangLong(j);
} else if (list != null) {
return list.elementAt(index);
}
return null;
}
protected int getArity() {
if (value instanceof OtpErlangTuple) {
final OtpErlangTuple t = (OtpErlangTuple) value;
return t.arity();
} else if (value instanceof OtpErlangList) {
final OtpErlangList l = (OtpErlangList) value;
return l.arity();
} else if (value instanceof OtpErlangBinary) {
final OtpErlangBinary bs = (OtpErlangBinary) value;
return bs.size();
} else if (list != null) {
return list.arity();
} else {
return -1;
}
}
@Override
public IVariable[] getVariables() throws DebugException {
final int arity = getArity();
if (arity != -1) {
return getVariables(0, getSize());
}
return null;
}
}