/*
* Copyright 2012-2014 Sergey Ignatov
* Copyright 2017 Jake Becker
* Copyright 2017 Luke Imhoff
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.elixir_lang.debugger;
import com.ericsson.otp.erlang.*;
import com.intellij.xdebugger.frame.presentation.XValuePresentation;
import org.elixir_lang.utils.ElixirModulesUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.Map;
public class ElixirXValuePresentation extends XValuePresentation {
private final OtpErlangObject myValue;
public ElixirXValuePresentation(OtpErlangObject value) {
myValue = value;
}
private static void renderObject(OtpErlangObject o, XValueTextRenderer renderer) {
if (o instanceof OtpErlangMap) {
renderMap((OtpErlangMap) o, renderer);
} else if (o instanceof OtpErlangAtom) {
renderAtom((OtpErlangAtom) o, renderer);
} else if (o instanceof OtpErlangTuple) {
renderTuple((OtpErlangTuple) o, renderer);
} else if (o instanceof OtpErlangList) {
renderList((OtpErlangList) o, renderer);
} else if (o instanceof OtpErlangBitstr) {
renderBitstr((OtpErlangBitstr) o, renderer);
} else if (o instanceof OtpErlangString) {
renderErlangString((OtpErlangString) o, renderer);
} else if (o instanceof OtpErlangDouble || o instanceof OtpErlangLong) {
renderer.renderNumericValue(o.toString());
} else {
renderer.renderValue(o.toString());
}
}
private static void renderMap(OtpErlangMap map, XValueTextRenderer renderer) {
renderer.renderSpecialSymbol("%");
if (isStruct(map)) {
String structType = structType(map);
assert structType != null;
renderer.renderKeywordValue(structType);
}
renderer.renderSpecialSymbol("{");
boolean first = true;
boolean symbolKeys = hasSymbolKeys(map);
for (final Map.Entry<OtpErlangObject, OtpErlangObject> e : map.entrySet()) {
OtpErlangObject key = e.getKey();
if (!(isStruct(map) && key instanceof OtpErlangAtom && ((OtpErlangAtom) key).atomValue().equals("__struct__"))) {
if (first) {
first = false;
} else {
renderer.renderSpecialSymbol(", ");
}
if (symbolKeys) {
assert key instanceof OtpErlangAtom;
renderer.renderKeywordValue(((OtpErlangAtom) key).atomValue());
renderer.renderKeywordValue(": ");
} else {
renderObject(key, renderer);
renderer.renderSpecialSymbol(" => ");
}
renderObject(e.getValue(), renderer);
}
}
renderer.renderSpecialSymbol("}");
}
private static void renderAtom(OtpErlangAtom atom, XValueTextRenderer renderer) {
renderer.renderKeywordValue(ElixirModulesUtil.erlangModuleNameToElixir(atom.atomValue()));
}
private static void renderTuple(OtpErlangTuple tuple, XValueTextRenderer renderer) {
int i;
renderer.renderSpecialSymbol("{");
for (i = 0; i < tuple.arity(); i++) {
if (i > 0) {
renderer.renderSpecialSymbol(", ");
}
renderObject(tuple.elementAt(i), renderer);
}
renderer.renderSpecialSymbol("}");
}
private static void renderList(OtpErlangList list, XValueTextRenderer renderer) {
int i;
renderer.renderSpecialSymbol("[");
for (i = 0; i < list.arity(); i++) {
if (i > 0) {
renderer.renderSpecialSymbol(", ");
}
renderObject(list.elementAt(i), renderer);
}
renderer.renderSpecialSymbol("]");
}
private static void renderBitstr(OtpErlangBitstr bitstr, XValueTextRenderer renderer) {
String utf8String = toUtf8String(bitstr);
if (utf8String != null) {
renderer.renderStringValue(utf8String);
} else {
renderer.renderSpecialSymbol("<<");
boolean first = true;
for (byte b : bitstr.binaryValue()) {
if (!first) renderer.renderSpecialSymbol(", ");
renderer.renderValue("" + ((int) b & 0xFF));
first = false;
}
if (bitstr.pad_bits() > 0) {
renderer.renderSpecialSymbol("::size(" + (8 - bitstr.pad_bits()) + ")");
}
renderer.renderSpecialSymbol(">>");
}
}
public static boolean isPrintable(OtpErlangString s) {
String str = s.toString();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (!isPrintable(c)) return false;
}
return true;
}
private static boolean isPrintable(char c) {
return (32 <= c && c <= 126)
|| c == '\n' || c == '\r' || c == '\t' || c == 11 /* vertical tab */ || c == '\b' || c == '\f'
|| c == 27 /* esc */ || c == 7; /* bell */
}
private static void renderErlangString(OtpErlangString str, XValueTextRenderer renderer) {
if (isPrintable(str)) {
renderer.renderSpecialSymbol("'");
renderer.renderValue(str.stringValue());
renderer.renderSpecialSymbol("'");
} else {
renderObject(new OtpErlangList(str.stringValue()), renderer);
}
}
@Nullable
public static String toUtf8String(OtpErlangBitstr bitstr) {
if (bitstr.pad_bits() > 0) return null;
try {
return Charset.availableCharsets().get("UTF-8").newDecoder().decode(ByteBuffer.wrap(bitstr.binaryValue())).toString();
} catch (CharacterCodingException e) {
return null;
}
}
@Nullable
private static String structType(OtpErlangMap map) {
OtpErlangObject structValue = map.get(new OtpErlangAtom("__struct__"));
if (structValue instanceof OtpErlangAtom) {
return ElixirModulesUtil.erlangModuleNameToElixir(((OtpErlangAtom) structValue).atomValue());
} else {
return null;
}
}
private static boolean isStruct(OtpErlangMap map) {
return structType(map) != null;
}
public static boolean hasSymbolKeys(OtpErlangMap map) {
for (OtpErlangObject key : map.keys()) {
if (!(key instanceof OtpErlangAtom) || ((OtpErlangAtom) key).atomValue().startsWith("Elixir.")) return false;
}
return true;
}
@Override
public void renderValue(@NotNull XValueTextRenderer renderer) {
ElixirXValuePresentation.renderObject(myValue, renderer);
}
}