| /* |
| * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code 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 |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package com.sun.hotspot.igv.data.serialization; |
| |
| import com.sun.hotspot.igv.data.*; |
| import com.sun.hotspot.igv.data.Properties; |
| import com.sun.hotspot.igv.data.services.GroupCallback; |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.ReadableByteChannel; |
| import java.nio.charset.Charset; |
| import java.util.*; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import javax.swing.SwingUtilities; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| |
| public class BinaryParser implements GraphParser { |
| private static final int BEGIN_GROUP = 0x00; |
| private static final int BEGIN_GRAPH = 0x01; |
| private static final int CLOSE_GROUP = 0x02; |
| |
| private static final int POOL_NEW = 0x00; |
| private static final int POOL_STRING = 0x01; |
| private static final int POOL_ENUM = 0x02; |
| private static final int POOL_CLASS = 0x03; |
| private static final int POOL_METHOD = 0x04; |
| private static final int POOL_NULL = 0x05; |
| private static final int POOL_NODE_CLASS = 0x06; |
| private static final int POOL_FIELD = 0x07; |
| private static final int POOL_SIGNATURE = 0x08; |
| |
| private static final int KLASS = 0x00; |
| private static final int ENUM_KLASS = 0x01; |
| |
| private static final int PROPERTY_POOL = 0x00; |
| private static final int PROPERTY_INT = 0x01; |
| private static final int PROPERTY_LONG = 0x02; |
| private static final int PROPERTY_DOUBLE = 0x03; |
| private static final int PROPERTY_FLOAT = 0x04; |
| private static final int PROPERTY_TRUE = 0x05; |
| private static final int PROPERTY_FALSE = 0x06; |
| private static final int PROPERTY_ARRAY = 0x07; |
| private static final int PROPERTY_SUBGRAPH = 0x08; |
| |
| private static final String NO_BLOCK = "noBlock"; |
| |
| private static final Charset utf8 = Charset.forName("UTF-8"); |
| |
| private final GroupCallback callback; |
| private final List<Object> constantPool; |
| private final ByteBuffer buffer; |
| private final ReadableByteChannel channel; |
| private final GraphDocument rootDocument; |
| private final Deque<Folder> folderStack; |
| private final Deque<byte[]> hashStack; |
| private final ParseMonitor monitor; |
| |
| private MessageDigest digest; |
| |
| private enum Length { |
| S, |
| M, |
| L |
| } |
| |
| private interface LengthToString { |
| String toString(Length l); |
| } |
| |
| private static abstract class Member implements LengthToString { |
| public final Klass holder; |
| public final int accessFlags; |
| public final String name; |
| public Member(Klass holder, String name, int accessFlags) { |
| this.holder = holder; |
| this.accessFlags = accessFlags; |
| this.name = name; |
| } |
| } |
| |
| private static class Method extends Member { |
| public final Signature signature; |
| public final byte[] code; |
| public Method(String name, Signature signature, byte[] code, Klass holder, int accessFlags) { |
| super(holder, name, accessFlags); |
| this.signature = signature; |
| this.code = code; |
| } |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(holder).append('.').append(name).append('('); |
| for (int i = 0; i < signature.argTypes.length; i++) { |
| if (i > 0) { |
| sb.append(", "); |
| } |
| sb.append(signature.argTypes[i]); |
| } |
| sb.append(')'); |
| return sb.toString(); |
| } |
| @Override |
| public String toString(Length l) { |
| switch(l) { |
| case M: |
| return holder.toString(Length.L) + "." + name; |
| case S: |
| return holder.toString(Length.S) + "." + name; |
| default: |
| case L: |
| return toString(); |
| } |
| } |
| } |
| |
| private static class Signature { |
| public final String returnType; |
| public final String[] argTypes; |
| public Signature(String returnType, String[] argTypes) { |
| this.returnType = returnType; |
| this.argTypes = argTypes; |
| } |
| } |
| |
| private static class Field extends Member { |
| public final String type; |
| public Field(String type, Klass holder, String name, int accessFlags) { |
| super(holder, name, accessFlags); |
| this.type = type; |
| } |
| @Override |
| public String toString() { |
| return holder + "." + name; |
| } |
| @Override |
| public String toString(Length l) { |
| switch(l) { |
| case M: |
| return holder.toString(Length.L) + "." + name; |
| case S: |
| return holder.toString(Length.S) + "." + name; |
| default: |
| case L: |
| return toString(); |
| } |
| } |
| } |
| |
| private static class Klass implements LengthToString { |
| public final String name; |
| public final String simpleName; |
| public Klass(String name) { |
| this.name = name; |
| String simple; |
| try { |
| simple = name.substring(name.lastIndexOf('.') + 1); |
| } catch (IndexOutOfBoundsException ioobe) { |
| simple = name; |
| } |
| this.simpleName = simple; |
| } |
| @Override |
| public String toString() { |
| return name; |
| } |
| @Override |
| public String toString(Length l) { |
| switch(l) { |
| case S: |
| return simpleName; |
| default: |
| case L: |
| case M: |
| return toString(); |
| } |
| } |
| } |
| |
| private static class EnumKlass extends Klass { |
| public final String[] values; |
| public EnumKlass(String name, String[] values) { |
| super(name); |
| this.values = values; |
| } |
| } |
| |
| private static class Port { |
| public final boolean isList; |
| public final String name; |
| private Port(boolean isList, String name) { |
| this.isList = isList; |
| this.name = name; |
| } |
| } |
| |
| private static class TypedPort extends Port { |
| public final EnumValue type; |
| private TypedPort(boolean isList, String name, EnumValue type) { |
| super(isList, name); |
| this.type = type; |
| } |
| } |
| |
| private static class NodeClass { |
| public final String className; |
| public final String nameTemplate; |
| public final List<TypedPort> inputs; |
| public final List<Port> sux; |
| private NodeClass(String className, String nameTemplate, List<TypedPort> inputs, List<Port> sux) { |
| this.className = className; |
| this.nameTemplate = nameTemplate; |
| this.inputs = inputs; |
| this.sux = sux; |
| } |
| @Override |
| public String toString() { |
| return className; |
| } |
| } |
| |
| private static class EnumValue implements LengthToString { |
| public EnumKlass enumKlass; |
| public int ordinal; |
| public EnumValue(EnumKlass enumKlass, int ordinal) { |
| this.enumKlass = enumKlass; |
| this.ordinal = ordinal; |
| } |
| @Override |
| public String toString() { |
| return enumKlass.simpleName + "." + enumKlass.values[ordinal]; |
| } |
| @Override |
| public String toString(Length l) { |
| switch(l) { |
| case S: |
| return enumKlass.values[ordinal]; |
| default: |
| case M: |
| case L: |
| return toString(); |
| } |
| } |
| } |
| |
| public BinaryParser(ReadableByteChannel channel, ParseMonitor monitor, GraphDocument rootDocument, GroupCallback callback) { |
| this.callback = callback; |
| constantPool = new ArrayList<>(); |
| buffer = ByteBuffer.allocateDirect(256 * 1024); |
| buffer.flip(); |
| this.channel = channel; |
| this.rootDocument = rootDocument; |
| folderStack = new LinkedList<>(); |
| hashStack = new LinkedList<>(); |
| this.monitor = monitor; |
| try { |
| this.digest = MessageDigest.getInstance("SHA-1"); |
| } catch (NoSuchAlgorithmException e) { |
| } |
| } |
| |
| private void fill() throws IOException { |
| // All the data between lastPosition and position has been |
| // used so add it to the digest. |
| int position = buffer.position(); |
| buffer.position(lastPosition); |
| byte[] remaining = new byte[position - buffer.position()]; |
| buffer.get(remaining); |
| digest.update(remaining); |
| assert position == buffer.position(); |
| |
| buffer.compact(); |
| if (channel.read(buffer) < 0) { |
| throw new EOFException(); |
| } |
| buffer.flip(); |
| lastPosition = buffer.position(); |
| } |
| |
| private void ensureAvailable(int i) throws IOException { |
| if (i > buffer.capacity()) { |
| throw new IllegalArgumentException(String.format("Can not request %d bytes: buffer capacity is %d", i, buffer.capacity())); |
| } |
| while (buffer.remaining() < i) { |
| fill(); |
| } |
| } |
| |
| private int readByte() throws IOException { |
| ensureAvailable(1); |
| return ((int)buffer.get()) & 0xff; |
| } |
| |
| private int readInt() throws IOException { |
| ensureAvailable(4); |
| return buffer.getInt(); |
| } |
| |
| private char readShort() throws IOException { |
| ensureAvailable(2); |
| return buffer.getChar(); |
| } |
| |
| private long readLong() throws IOException { |
| ensureAvailable(8); |
| return buffer.getLong(); |
| } |
| |
| private double readDouble() throws IOException { |
| ensureAvailable(8); |
| return buffer.getDouble(); |
| } |
| |
| private float readFloat() throws IOException { |
| ensureAvailable(4); |
| return buffer.getFloat(); |
| } |
| |
| private String readString() throws IOException { |
| return new String(readBytes(), utf8).intern(); |
| } |
| |
| private byte[] readBytes() throws IOException { |
| int len = readInt(); |
| if (len < 0) { |
| return null; |
| } |
| byte[] b = new byte[len]; |
| int bytesRead = 0; |
| while (bytesRead < b.length) { |
| int toRead = Math.min(b.length - bytesRead, buffer.capacity()); |
| ensureAvailable(toRead); |
| buffer.get(b, bytesRead, toRead); |
| bytesRead += toRead; |
| } |
| return b; |
| } |
| |
| private String readIntsToString() throws IOException { |
| int len = readInt(); |
| if (len < 0) { |
| return "null"; |
| } |
| ensureAvailable(len * 4); |
| StringBuilder sb = new StringBuilder().append('['); |
| for (int i = 0; i < len; i++) { |
| sb.append(buffer.getInt()); |
| if (i < len - 1) { |
| sb.append(", "); |
| } |
| } |
| sb.append(']'); |
| return sb.toString().intern(); |
| } |
| |
| private String readDoublesToString() throws IOException { |
| int len = readInt(); |
| if (len < 0) { |
| return "null"; |
| } |
| ensureAvailable(len * 8); |
| StringBuilder sb = new StringBuilder().append('['); |
| for (int i = 0; i < len; i++) { |
| sb.append(buffer.getDouble()); |
| if (i < len - 1) { |
| sb.append(", "); |
| } |
| } |
| sb.append(']'); |
| return sb.toString().intern(); |
| } |
| |
| private String readPoolObjectsToString() throws IOException { |
| int len = readInt(); |
| if (len < 0) { |
| return "null"; |
| } |
| StringBuilder sb = new StringBuilder().append('['); |
| for (int i = 0; i < len; i++) { |
| sb.append(readPoolObject(Object.class)); |
| if (i < len - 1) { |
| sb.append(", "); |
| } |
| } |
| sb.append(']'); |
| return sb.toString().intern(); |
| } |
| |
| private <T> T readPoolObject(Class<T> klass) throws IOException { |
| int type = readByte(); |
| if (type == POOL_NULL) { |
| return null; |
| } |
| if (type == POOL_NEW) { |
| return (T) addPoolEntry(klass); |
| } |
| assert assertObjectType(klass, type); |
| char index = readShort(); |
| if (index < 0 || index >= constantPool.size()) { |
| throw new IOException("Invalid constant pool index : " + index); |
| } |
| Object obj = constantPool.get(index); |
| return (T) obj; |
| } |
| |
| private boolean assertObjectType(Class<?> klass, int type) { |
| switch(type) { |
| case POOL_CLASS: |
| return klass.isAssignableFrom(EnumKlass.class); |
| case POOL_ENUM: |
| return klass.isAssignableFrom(EnumValue.class); |
| case POOL_METHOD: |
| return klass.isAssignableFrom(Method.class); |
| case POOL_STRING: |
| return klass.isAssignableFrom(String.class); |
| case POOL_NODE_CLASS: |
| return klass.isAssignableFrom(NodeClass.class); |
| case POOL_FIELD: |
| return klass.isAssignableFrom(Field.class); |
| case POOL_SIGNATURE: |
| return klass.isAssignableFrom(Signature.class); |
| case POOL_NULL: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| private Object addPoolEntry(Class<?> klass) throws IOException { |
| char index = readShort(); |
| int type = readByte(); |
| assert assertObjectType(klass, type) : "Wrong object type : " + klass + " != " + type; |
| Object obj; |
| switch(type) { |
| case POOL_CLASS: { |
| String name = readString(); |
| int klasstype = readByte(); |
| if (klasstype == ENUM_KLASS) { |
| int len = readInt(); |
| String[] values = new String[len]; |
| for (int i = 0; i < len; i++) { |
| values[i] = readPoolObject(String.class); |
| } |
| obj = new EnumKlass(name, values); |
| } else if (klasstype == KLASS) { |
| obj = new Klass(name); |
| } else { |
| throw new IOException("unknown klass type : " + klasstype); |
| } |
| break; |
| } |
| case POOL_ENUM: { |
| EnumKlass enumClass = readPoolObject(EnumKlass.class); |
| int ordinal = readInt(); |
| obj = new EnumValue(enumClass, ordinal); |
| break; |
| } |
| case POOL_NODE_CLASS: { |
| String className = readString(); |
| String nameTemplate = readString(); |
| int inputCount = readShort(); |
| List<TypedPort> inputs = new ArrayList<>(inputCount); |
| for (int i = 0; i < inputCount; i++) { |
| boolean isList = readByte() != 0; |
| String name = readPoolObject(String.class); |
| EnumValue inputType = readPoolObject(EnumValue.class); |
| inputs.add(new TypedPort(isList, name, inputType)); |
| } |
| int suxCount = readShort(); |
| List<Port> sux = new ArrayList<>(suxCount); |
| for (int i = 0; i < suxCount; i++) { |
| boolean isList = readByte() != 0; |
| String name = readPoolObject(String.class); |
| sux.add(new Port(isList, name)); |
| } |
| obj = new NodeClass(className, nameTemplate, inputs, sux); |
| break; |
| } |
| case POOL_METHOD: { |
| Klass holder = readPoolObject(Klass.class); |
| String name = readPoolObject(String.class); |
| Signature sign = readPoolObject(Signature.class); |
| int flags = readInt(); |
| byte[] code = readBytes(); |
| obj = new Method(name, sign, code, holder, flags); |
| break; |
| } |
| case POOL_FIELD: { |
| Klass holder = readPoolObject(Klass.class); |
| String name = readPoolObject(String.class); |
| String fType = readPoolObject(String.class); |
| int flags = readInt(); |
| obj = new Field(fType, holder, name, flags); |
| break; |
| } |
| case POOL_SIGNATURE: { |
| int argc = readShort(); |
| String[] args = new String[argc]; |
| for (int i = 0; i < argc; i++) { |
| args[i] = readPoolObject(String.class); |
| } |
| String returnType = readPoolObject(String.class); |
| obj = new Signature(returnType, args); |
| break; |
| } |
| case POOL_STRING: { |
| obj = readString(); |
| break; |
| } |
| default: |
| throw new IOException("unknown pool type"); |
| } |
| while (constantPool.size() <= index) { |
| constantPool.add(null); |
| } |
| constantPool.set(index, obj); |
| return obj; |
| } |
| |
| private Object readPropertyObject() throws IOException { |
| int type = readByte(); |
| switch (type) { |
| case PROPERTY_INT: |
| return readInt(); |
| case PROPERTY_LONG: |
| return readLong(); |
| case PROPERTY_FLOAT: |
| return readFloat(); |
| case PROPERTY_DOUBLE: |
| return readDouble(); |
| case PROPERTY_TRUE: |
| return Boolean.TRUE; |
| case PROPERTY_FALSE: |
| return Boolean.FALSE; |
| case PROPERTY_POOL: |
| return readPoolObject(Object.class); |
| case PROPERTY_ARRAY: |
| int subType = readByte(); |
| switch(subType) { |
| case PROPERTY_INT: |
| return readIntsToString(); |
| case PROPERTY_DOUBLE: |
| return readDoublesToString(); |
| case PROPERTY_POOL: |
| return readPoolObjectsToString(); |
| default: |
| throw new IOException("Unknown type"); |
| } |
| case PROPERTY_SUBGRAPH: |
| InputGraph graph = parseGraph(""); |
| new Group(null).addElement(graph); |
| return graph; |
| default: |
| throw new IOException("Unknown type"); |
| } |
| } |
| |
| @Override |
| public GraphDocument parse() throws IOException { |
| folderStack.push(rootDocument); |
| hashStack.push(null); |
| if (monitor != null) { |
| monitor.setState("Starting parsing"); |
| } |
| try { |
| while(true) { |
| parseRoot(); |
| } |
| } catch (EOFException e) { |
| |
| } |
| if (monitor != null) { |
| monitor.setState("Finished parsing"); |
| } |
| return rootDocument; |
| } |
| |
| private void parseRoot() throws IOException { |
| int type = readByte(); |
| switch(type) { |
| case BEGIN_GRAPH: { |
| final Folder parent = folderStack.peek(); |
| final InputGraph graph = parseGraph(); |
| SwingUtilities.invokeLater(new Runnable(){ |
| @Override |
| public void run() { |
| parent.addElement(graph); |
| } |
| }); |
| break; |
| } |
| case BEGIN_GROUP: { |
| final Folder parent = folderStack.peek(); |
| final Group group = parseGroup(parent); |
| if (callback == null || parent instanceof Group) { |
| SwingUtilities.invokeLater(new Runnable(){ |
| @Override |
| public void run() { |
| parent.addElement(group); |
| } |
| }); |
| } |
| folderStack.push(group); |
| hashStack.push(null); |
| if (callback != null && parent instanceof GraphDocument) { |
| callback.started(group); |
| } |
| break; |
| } |
| case CLOSE_GROUP: { |
| if (folderStack.isEmpty()) { |
| throw new IOException("Unbalanced groups"); |
| } |
| folderStack.pop(); |
| hashStack.pop(); |
| break; |
| } |
| default: |
| throw new IOException("unknown root : " + type); |
| } |
| } |
| |
| private Group parseGroup(Folder parent) throws IOException { |
| String name = readPoolObject(String.class); |
| String shortName = readPoolObject(String.class); |
| if (monitor != null) { |
| monitor.setState(shortName); |
| } |
| Method method = readPoolObject(Method.class); |
| int bci = readInt(); |
| Group group = new Group(parent); |
| group.getProperties().setProperty("name", name); |
| parseProperties(group.getProperties()); |
| if (method != null) { |
| InputMethod inMethod = new InputMethod(group, method.name, shortName, bci); |
| inMethod.setBytecodes("TODO"); |
| group.setMethod(inMethod); |
| } |
| return group; |
| } |
| |
| int lastPosition = 0; |
| |
| private InputGraph parseGraph() throws IOException { |
| if (monitor != null) { |
| monitor.updateProgress(); |
| } |
| String title = readPoolObject(String.class); |
| digest.reset(); |
| lastPosition = buffer.position(); |
| InputGraph graph = parseGraph(title); |
| |
| int position = buffer.position(); |
| buffer.position(lastPosition); |
| byte[] remaining = new byte[position - buffer.position()]; |
| buffer.get(remaining); |
| digest.update(remaining); |
| assert position == buffer.position(); |
| lastPosition = buffer.position(); |
| |
| byte[] d = digest.digest(); |
| byte[] hash = hashStack.peek(); |
| if (hash != null && Arrays.equals(hash, d)) { |
| graph.getProperties().setProperty("_isDuplicate", "true"); |
| } else { |
| hashStack.pop(); |
| hashStack.push(d); |
| } |
| return graph; |
| } |
| |
| private void parseProperties(Properties properties) throws IOException { |
| int propCount = readShort(); |
| for (int j = 0; j < propCount; j++) { |
| String key = readPoolObject(String.class); |
| Object value = readPropertyObject(); |
| properties.setProperty(key, value != null ? value.toString() : "null"); |
| } |
| } |
| |
| private InputGraph parseGraph(String title) throws IOException { |
| InputGraph graph = new InputGraph(title); |
| parseProperties(graph.getProperties()); |
| parseNodes(graph); |
| parseBlocks(graph); |
| graph.ensureNodesInBlocks(); |
| for (InputNode node : graph.getNodes()) { |
| node.internProperties(); |
| } |
| return graph; |
| } |
| |
| private void parseBlocks(InputGraph graph) throws IOException { |
| int blockCount = readInt(); |
| List<Edge> edges = new LinkedList<>(); |
| for (int i = 0; i < blockCount; i++) { |
| int id = readInt(); |
| String name = id >= 0 ? Integer.toString(id) : NO_BLOCK; |
| InputBlock block = graph.addBlock(name); |
| int nodeCount = readInt(); |
| for (int j = 0; j < nodeCount; j++) { |
| int nodeId = readInt(); |
| if (nodeId < 0) { |
| continue; |
| } |
| final Properties properties = graph.getNode(nodeId).getProperties(); |
| final String oldBlock = properties.get("block"); |
| if(oldBlock != null) { |
| properties.setProperty("block", oldBlock + ", " + name); |
| } else { |
| block.addNode(nodeId); |
| properties.setProperty("block", name); |
| } |
| } |
| int edgeCount = readInt(); |
| for (int j = 0; j < edgeCount; j++) { |
| int to = readInt(); |
| edges.add(new Edge(id, to)); |
| } |
| } |
| for (Edge e : edges) { |
| String fromName = e.from >= 0 ? Integer.toString(e.from) : NO_BLOCK; |
| String toName = e.to >= 0 ? Integer.toString(e.to) : NO_BLOCK; |
| graph.addBlockEdge(graph.getBlock(fromName), graph.getBlock(toName)); |
| } |
| } |
| |
| private void parseNodes(InputGraph graph) throws IOException { |
| int count = readInt(); |
| Map<String, Object> props = new HashMap<>(); |
| List<Edge> inputEdges = new ArrayList<>(count); |
| List<Edge> succEdges = new ArrayList<>(count); |
| for (int i = 0; i < count; i++) { |
| int id = readInt(); |
| InputNode node = new InputNode(id); |
| final Properties properties = node.getProperties(); |
| NodeClass nodeClass = readPoolObject(NodeClass.class); |
| int preds = readByte(); |
| if (preds > 0) { |
| properties.setProperty("hasPredecessor", "true"); |
| } |
| properties.setProperty("idx", Integer.toString(id)); |
| int propCount = readShort(); |
| for (int j = 0; j < propCount; j++) { |
| String key = readPoolObject(String.class); |
| if (key.equals("hasPredecessor") || key.equals("name") || key.equals("class") || key.equals("id") || key.equals("idx")) { |
| key = "!data." + key; |
| } |
| Object value = readPropertyObject(); |
| if (value instanceof InputGraph) { |
| InputGraph subgraph = (InputGraph) value; |
| subgraph.getProperties().setProperty("name", node.getId() + ":" + key); |
| node.addSubgraph((InputGraph) value); |
| } else { |
| properties.setProperty(key, value != null ? value.toString() : "null"); |
| props.put(key, value); |
| } |
| } |
| ArrayList<Edge> currentEdges = new ArrayList<>(); |
| int portNum = 0; |
| for (TypedPort p : nodeClass.inputs) { |
| if (p.isList) { |
| int size = readShort(); |
| for (int j = 0; j < size; j++) { |
| int in = readInt(); |
| if (in >= 0) { |
| Edge e = new Edge(in, id, (char) (preds + portNum), p.name + "[" + j + "]", p.type.toString(Length.S), true); |
| currentEdges.add(e); |
| inputEdges.add(e); |
| portNum++; |
| } |
| } |
| } else { |
| int in = readInt(); |
| if (in >= 0) { |
| Edge e = new Edge(in, id, (char) (preds + portNum), p.name, p.type.toString(Length.S), true); |
| currentEdges.add(e); |
| inputEdges.add(e); |
| portNum++; |
| } |
| } |
| |
| } |
| portNum = 0; |
| for (Port p : nodeClass.sux) { |
| if (p.isList) { |
| int size = readShort(); |
| for (int j = 0; j < size; j++) { |
| int sux = readInt(); |
| if (sux >= 0) { |
| Edge e = new Edge(id, sux, (char) portNum, p.name + "[" + j + "]", "Successor", false); |
| currentEdges.add(e); |
| succEdges.add(e); |
| portNum++; |
| } |
| } |
| } else { |
| int sux = readInt(); |
| if (sux >= 0) { |
| Edge e = new Edge(id, sux, (char) portNum, p.name, "Successor", false); |
| currentEdges.add(e); |
| succEdges.add(e); |
| portNum++; |
| } |
| } |
| } |
| properties.setProperty("name", createName(currentEdges, props, nodeClass.nameTemplate)); |
| properties.setProperty("class", nodeClass.className); |
| switch (nodeClass.className) { |
| case "BeginNode": |
| properties.setProperty("shortName", "B"); |
| break; |
| case "EndNode": |
| properties.setProperty("shortName", "E"); |
| break; |
| } |
| graph.addNode(node); |
| props.clear(); |
| } |
| |
| Set<InputNode> nodesWithSuccessor = new HashSet<>(); |
| |
| for (Edge e : succEdges) { |
| assert !e.input; |
| char fromIndex = e.num; |
| nodesWithSuccessor.add(graph.getNode(e.from)); |
| char toIndex = 0; |
| graph.addEdge(InputEdge.createImmutable(fromIndex, toIndex, e.from, e.to, e.label, e.type)); |
| } |
| for (Edge e : inputEdges) { |
| assert e.input; |
| char fromIndex = (char) (nodesWithSuccessor.contains(graph.getNode(e.from)) ? 1 : 0); |
| char toIndex = e.num; |
| graph.addEdge(InputEdge.createImmutable(fromIndex, toIndex, e.from, e.to, e.label, e.type)); |
| } |
| } |
| |
| static final Pattern templatePattern = Pattern.compile("\\{(p|i)#([a-zA-Z0-9$_]+)(/(l|m|s))?\\}"); |
| |
| private String createName(List<Edge> edges, Map<String, Object> properties, String template) { |
| Matcher m = templatePattern.matcher(template); |
| StringBuffer sb = new StringBuffer(); |
| while (m.find()) { |
| String name = m.group(2); |
| String type = m.group(1); |
| String result; |
| switch (type) { |
| case "i": |
| StringBuilder inputString = new StringBuilder(); |
| for(Edge edge : edges) { |
| if (edge.label.startsWith(name) && (name.length() == edge.label.length() || edge.label.charAt(name.length()) == '[')) { |
| if (inputString.length() > 0) { |
| inputString.append(", "); |
| } |
| inputString.append(edge.from); |
| } |
| } |
| result = inputString.toString(); |
| break; |
| case "p": |
| Object prop = properties.get(name); |
| String length = m.group(4); |
| if (prop == null) { |
| result = "?"; |
| } else if (length != null && prop instanceof LengthToString) { |
| LengthToString lengthProp = (LengthToString) prop; |
| switch(length) { |
| default: |
| case "l": |
| result = lengthProp.toString(Length.L); |
| break; |
| case "m": |
| result = lengthProp.toString(Length.M); |
| break; |
| case "s": |
| result = lengthProp.toString(Length.S); |
| break; |
| } |
| } else { |
| result = prop.toString(); |
| } |
| break; |
| default: |
| result = "#?#"; |
| break; |
| } |
| result = result.replace("\\", "\\\\"); |
| result = result.replace("$", "\\$"); |
| m.appendReplacement(sb, result); |
| } |
| m.appendTail(sb); |
| return sb.toString().intern(); |
| } |
| |
| private static class Edge { |
| final int from; |
| final int to; |
| final char num; |
| final String label; |
| final String type; |
| final boolean input; |
| public Edge(int from, int to) { |
| this(from, to, (char) 0, null, null, false); |
| } |
| public Edge(int from, int to, char num, String label, String type, boolean input) { |
| this.from = from; |
| this.to = to; |
| this.label = label != null ? label.intern() : label; |
| this.type = type != null ? type.intern() : type; |
| this.num = num; |
| this.input = input; |
| } |
| } |
| } |