/** * erlyberly, erlang trace debugger * Copyright (C) 2016 Andy Till * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package erlyberly.node; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.ericsson.otp.erlang.OtpConn; import com.ericsson.otp.erlang.OtpErlangAtom; import com.ericsson.otp.erlang.OtpErlangBinary; import com.ericsson.otp.erlang.OtpErlangDecodeException; import com.ericsson.otp.erlang.OtpErlangExit; import com.ericsson.otp.erlang.OtpErlangExternalFun; import com.ericsson.otp.erlang.OtpErlangFun; import com.ericsson.otp.erlang.OtpErlangList; import com.ericsson.otp.erlang.OtpErlangLong; import com.ericsson.otp.erlang.OtpErlangMap; import com.ericsson.otp.erlang.OtpErlangObject; import com.ericsson.otp.erlang.OtpErlangString; import com.ericsson.otp.erlang.OtpErlangTuple; import com.ericsson.otp.erlang.OtpMbox; /** * Sin bin for utils dealing with jinterface. */ public class OtpUtil { private static final OtpErlangAtom ERLYBERLY_RECORD_ATOM = OtpUtil.atom("erlyberly_record"); private static final OtpErlangAtom ERLYBERLY_RECORD_FIELD_ATOM = OtpUtil.atom("erlyberly_record_field"); public static final Set<Class<?>> CONTAINER_TERM_TYPES = new HashSet<Class<?>>( Arrays.asList(OtpErlangTuple.class, OtpErlangMap.class, OtpErlangList.class) ); public static final Set<Class<?>> LARGE_TERM_TYPES = new HashSet<Class<?>>( Arrays.asList(OtpErlangFun.class, OtpErlangExternalFun.class) ); private static final OtpErlangAtom TRUE_ATOM = new OtpErlangAtom(true); private static final OtpErlangAtom FALSE_ATOM = new OtpErlangAtom(false); private static final OtpErlangAtom CALL_ATOM = new OtpErlangAtom("call"); private static final OtpErlangAtom USER_ATOM = new OtpErlangAtom("user"); private static final OtpErlangAtom ERROR_ATOM = atom("error"); public static final OtpErlangAtom OK_ATOM = atom("ok"); private OtpUtil() {} public static OtpErlangTuple tuple(Object... elements) { OtpErlangObject[] tuple = toOtpElementArray(elements); return new OtpErlangTuple(tuple); } public static OtpErlangList list(Object... elements) { OtpErlangObject[] tuple = toOtpElementArray(elements); return new OtpErlangList(tuple); } private static OtpErlangObject[] toOtpElementArray(Object... elements) { OtpErlangObject[] tuple = new OtpErlangObject[elements.length]; for(int i=0; i < elements.length; i++) { Object e = elements[i]; if(e instanceof Integer) { tuple[i] = new OtpErlangLong((Integer)e); } else if(e instanceof Long) { tuple[i] = new OtpErlangLong((Long)e); } else if(e instanceof OtpErlangObject) { tuple[i] = (OtpErlangObject) e; } else if(e instanceof String) { tuple[i] = new OtpErlangString((String)e); } else if(e instanceof Boolean) { if(Boolean.TRUE.equals(e)) tuple[i] = TRUE_ATOM; else tuple[i] = FALSE_ATOM; } else { throw new RuntimeException(e + " cannot be converted to an OtpErlangObject"); } } return tuple; } public static OtpErlangAtom atom(String name) { return new OtpErlangAtom(name); } /** * Take an {@link OtpErlangList} of erlang key value tuples and converts it to a map. */ public static Map<Object, Object> propsToMap(OtpErlangList pinfo) { HashMap<Object, Object> map = new HashMap<>(); for (OtpErlangObject otpErlangObject : pinfo) { if(otpErlangObject instanceof OtpErlangTuple && ((OtpErlangTuple) otpErlangObject).arity() == 2) { OtpErlangTuple tuple = ((OtpErlangTuple) otpErlangObject); map.put(tuple.elementAt(0), tuple.elementAt(1)); } } return map; } public static OtpErlangObject[] elementsForTerm(OtpErlangObject obj) { assert obj != null; if(obj instanceof OtpErlangTuple) return ((OtpErlangTuple) obj).elements(); else if(obj instanceof OtpErlangList) return ((OtpErlangList) obj).elements(); else throw new RuntimeException("No brackets for type " + obj.getClass()); } public static boolean isTupleTagged(OtpErlangObject tag, OtpErlangObject result) { return isTupleTagged(tag, 0, result); } public static boolean isTupleTagged(OtpErlangObject tag, int index, OtpErlangObject result) { boolean r = false; if(result instanceof OtpErlangTuple) { OtpErlangTuple resultTuple = (OtpErlangTuple) result; r = resultTuple.arity() > 0 && resultTuple.elementAt(0).equals(tag); } return r; } /** * Checks if a term is tuple tagged with the erlyberly_record atom, meaning * it has metadata tagged with record and field names around the field values. */ public static boolean isErlyberlyRecord(OtpErlangObject obj) { return isTupleTagged(ERLYBERLY_RECORD_ATOM, obj); } public static boolean isErlyberlyRecordField(OtpErlangObject obj) { return isTupleTagged(ERLYBERLY_RECORD_FIELD_ATOM, obj); } public static boolean isErrorReason(OtpErlangObject reason, OtpErlangObject error) { assert isTupleTagged(ERROR_ATOM, error) : "tuple " + error + "is not tagged with 'error'"; return isTupleTagged(reason, 1, error); } public static OtpErlangList toOtpList(OtpErlangObject obj) { if(obj instanceof OtpErlangList) { return (OtpErlangList) obj; } else if(obj instanceof OtpErlangString) { OtpErlangString s = (OtpErlangString) obj; return new OtpErlangList(s.stringValue()); } else { throw new ClassCastException("" + obj + " cannot be converted to an OtpErlangList"); } } public static void sendRPC(OtpConn conn, OtpMbox m, OtpErlangAtom mod, OtpErlangAtom fun, OtpErlangList args) throws IOException { OtpErlangTuple rpcMessage = tuple(m.self(), tuple(CALL_ATOM, mod, fun, args, USER_ATOM)); conn.send(m.self(), "rex", rpcMessage); } public static OtpErlangTuple receiveRPC(OtpMbox mbox) throws OtpErlangExit, OtpErlangDecodeException { return receiveRPC(mbox,5000); } public static OtpErlangTuple receiveRPC(OtpMbox mbox ,long timeout) throws OtpErlangExit, OtpErlangDecodeException { return (OtpErlangTuple) mbox.receive(timeout); } public static OtpErlangObject tupleElement(int i, OtpErlangObject obj) { return ((OtpErlangTuple)obj).elementAt(i); } public static OtpErlangObject[] iterableElements(OtpErlangObject obj) { if(obj instanceof OtpErlangTuple) return ((OtpErlangTuple) obj).elements(); else if(obj instanceof OtpErlangList) return ((OtpErlangList) obj).elements(); else if(obj instanceof OtpErlangString) { OtpErlangString s = (OtpErlangString) obj; return new OtpErlangList(s.stringValue()).elements(); } else { throw new RuntimeException("" + obj + " cannot return OtpErlangObject[]"); } } /** * A short term can be displayed on a single line and does not have to be * broken down further. */ public static boolean isLittleTerm(OtpErlangObject obj) { OtpErlangObject[] elements; if(obj instanceof OtpErlangList) { // if the list is empty consider it a little term return ((OtpErlangList)obj).arity() == 0; } else if(LARGE_TERM_TYPES.contains(obj.getClass())) { return false; } else if(CONTAINER_TERM_TYPES.contains(obj.getClass())) { elements = iterableElements(obj); // short lists and tuples which do not contain other short lists or // tuples are ok if(elements.length > 3) return false; } else { return true; } for (OtpErlangObject e : elements) { if(LARGE_TERM_TYPES.contains(e.getClass()) || CONTAINER_TERM_TYPES.contains(e.getClass())) { return false; } else if(e instanceof OtpErlangString) { int stringLength = ((OtpErlangString) e).stringValue().length(); if(stringLength > 50) return true; } else if(e instanceof OtpErlangBinary) { int binaryLength = ((OtpErlangBinary) e).size(); if(binaryLength > 50) return true; } } return true; } /** * jinterface interprets lists of integers OtpErlangString whatever * might be the intent, for example a list of function arguments `[10]`. * * This can cause ClassCastExceptions when something that is normally an * OtpErlangList comes back as an OtpErlangString, which does not inherit * from OtpErlangList! */ public static OtpErlangList toErlangList(OtpErlangObject obj) { if(obj instanceof OtpErlangString) { return new OtpErlangList(((OtpErlangString)obj).stringValue()); } else { // we have done our best to convert the nobbly string objects to // lists, if it fails just throw a ClassCastException return (OtpErlangList)obj; } } public static OtpErlangTuple toTuple(OtpErlangObject obj) { if(!(obj instanceof OtpErlangTuple)) { throw new ClassCastException(obj + " cannot be case to OtpErlangTuple."); } return (OtpErlangTuple) obj; } }