1/*
2 * Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package com.sun.hotspot.igv.data.serialization;
26
27import com.sun.hotspot.igv.data.*;
28import com.sun.hotspot.igv.data.Properties;
29import com.sun.hotspot.igv.data.services.GroupCallback;
30import java.io.EOFException;
31import java.io.IOException;
32import java.nio.ByteBuffer;
33import java.nio.channels.ReadableByteChannel;
34import java.nio.charset.Charset;
35import java.util.*;
36import java.util.regex.Matcher;
37import java.util.regex.Pattern;
38import javax.swing.SwingUtilities;
39import java.security.MessageDigest;
40import java.security.NoSuchAlgorithmException;
41
42public class BinaryParser implements GraphParser {
43    private static final int BEGIN_GROUP = 0x00;
44    private static final int BEGIN_GRAPH = 0x01;
45    private static final int CLOSE_GROUP = 0x02;
46
47    private static final int POOL_NEW = 0x00;
48    private static final int POOL_STRING = 0x01;
49    private static final int POOL_ENUM = 0x02;
50    private static final int POOL_CLASS = 0x03;
51    private static final int POOL_METHOD = 0x04;
52    private static final int POOL_NULL = 0x05;
53    private static final int POOL_NODE_CLASS = 0x06;
54    private static final int POOL_FIELD = 0x07;
55    private static final int POOL_SIGNATURE = 0x08;
56
57    private static final int KLASS = 0x00;
58    private static final int ENUM_KLASS = 0x01;
59
60    private static final int PROPERTY_POOL = 0x00;
61    private static final int PROPERTY_INT = 0x01;
62    private static final int PROPERTY_LONG = 0x02;
63    private static final int PROPERTY_DOUBLE = 0x03;
64    private static final int PROPERTY_FLOAT = 0x04;
65    private static final int PROPERTY_TRUE = 0x05;
66    private static final int PROPERTY_FALSE = 0x06;
67    private static final int PROPERTY_ARRAY = 0x07;
68    private static final int PROPERTY_SUBGRAPH = 0x08;
69
70    private static final String NO_BLOCK = "noBlock";
71
72    private static final Charset utf8 = Charset.forName("UTF-8");
73
74    private final GroupCallback callback;
75    private final List<Object> constantPool;
76    private final ByteBuffer buffer;
77    private final ReadableByteChannel channel;
78    private final GraphDocument rootDocument;
79    private final Deque<Folder> folderStack;
80    private final Deque<byte[]> hashStack;
81    private final ParseMonitor monitor;
82
83    private MessageDigest digest;
84
85    private enum Length {
86        S,
87        M,
88        L
89    }
90
91    private interface LengthToString {
92        String toString(Length l);
93    }
94
95    private static abstract class Member implements LengthToString {
96        public final Klass holder;
97        public final int accessFlags;
98        public final String name;
99        public Member(Klass holder, String name, int accessFlags) {
100            this.holder = holder;
101            this.accessFlags = accessFlags;
102            this.name = name;
103        }
104    }
105
106    private static class Method extends Member {
107        public final Signature signature;
108        public final byte[] code;
109        public Method(String name, Signature signature, byte[] code, Klass holder, int accessFlags) {
110            super(holder, name, accessFlags);
111            this.signature = signature;
112            this.code = code;
113        }
114        @Override
115        public String toString() {
116            StringBuilder sb = new StringBuilder();
117            sb.append(holder).append('.').append(name).append('(');
118            for (int i = 0; i < signature.argTypes.length; i++) {
119                if (i > 0) {
120                    sb.append(", ");
121                }
122                sb.append(signature.argTypes[i]);
123            }
124            sb.append(')');
125            return sb.toString();
126        }
127        @Override
128        public String toString(Length l) {
129            switch(l) {
130                case M:
131                    return holder.toString(Length.L) + "." + name;
132                case S:
133                    return holder.toString(Length.S) + "." + name;
134                default:
135                case L:
136                    return toString();
137            }
138        }
139    }
140
141    private static class Signature {
142        public final String returnType;
143        public final String[] argTypes;
144        public Signature(String returnType, String[] argTypes) {
145            this.returnType = returnType;
146            this.argTypes = argTypes;
147        }
148    }
149
150    private static class Field extends Member {
151        public final String type;
152        public Field(String type, Klass holder, String name, int accessFlags) {
153            super(holder, name, accessFlags);
154            this.type = type;
155        }
156        @Override
157        public String toString() {
158            return holder + "." + name;
159        }
160        @Override
161        public String toString(Length l) {
162            switch(l) {
163                case M:
164                    return holder.toString(Length.L) + "." + name;
165                case S:
166                    return holder.toString(Length.S) + "." + name;
167                default:
168                case L:
169                    return toString();
170            }
171        }
172    }
173
174    private static class Klass implements LengthToString {
175        public final String name;
176        public final String simpleName;
177        public Klass(String name) {
178            this.name = name;
179            String simple;
180            try {
181                simple = name.substring(name.lastIndexOf('.') + 1);
182            } catch (IndexOutOfBoundsException ioobe) {
183                simple = name;
184            }
185            this.simpleName = simple;
186        }
187        @Override
188        public String toString() {
189            return name;
190        }
191        @Override
192        public String toString(Length l) {
193            switch(l) {
194                case S:
195                    return simpleName;
196                default:
197                case L:
198                case M:
199                    return toString();
200            }
201        }
202    }
203
204    private static class EnumKlass extends Klass {
205        public final String[] values;
206        public EnumKlass(String name, String[] values) {
207            super(name);
208            this.values = values;
209        }
210    }
211
212    private static class Port {
213        public final boolean isList;
214        public final String name;
215        private Port(boolean isList, String name) {
216            this.isList = isList;
217            this.name = name;
218        }
219    }
220
221    private static class TypedPort extends Port {
222        public final EnumValue type;
223        private TypedPort(boolean isList, String name, EnumValue type) {
224            super(isList, name);
225            this.type = type;
226        }
227    }
228
229    private static class NodeClass {
230        public final String className;
231        public final String nameTemplate;
232        public final List<TypedPort> inputs;
233        public final List<Port> sux;
234        private NodeClass(String className, String nameTemplate, List<TypedPort> inputs, List<Port> sux) {
235            this.className = className;
236            this.nameTemplate = nameTemplate;
237            this.inputs = inputs;
238            this.sux = sux;
239        }
240        @Override
241        public String toString() {
242            return className;
243        }
244    }
245
246    private static class EnumValue implements LengthToString {
247        public EnumKlass enumKlass;
248        public int ordinal;
249        public EnumValue(EnumKlass enumKlass, int ordinal) {
250            this.enumKlass = enumKlass;
251            this.ordinal = ordinal;
252        }
253        @Override
254        public String toString() {
255            return enumKlass.simpleName + "." + enumKlass.values[ordinal];
256        }
257        @Override
258        public String toString(Length l) {
259            switch(l) {
260                case S:
261                    return enumKlass.values[ordinal];
262                default:
263                case M:
264                case L:
265                    return toString();
266            }
267        }
268    }
269
270    public BinaryParser(ReadableByteChannel channel, ParseMonitor monitor, GraphDocument rootDocument, GroupCallback callback) {
271        this.callback = callback;
272        constantPool = new ArrayList<>();
273        buffer = ByteBuffer.allocateDirect(256 * 1024);
274        buffer.flip();
275        this.channel = channel;
276        this.rootDocument = rootDocument;
277        folderStack = new LinkedList<>();
278        hashStack = new LinkedList<>();
279        this.monitor = monitor;
280        try {
281            this.digest = MessageDigest.getInstance("SHA-1");
282        } catch (NoSuchAlgorithmException e) {
283        }
284    }
285
286    private void fill() throws IOException {
287        // All the data between lastPosition and position has been
288        // used so add it to the digest.
289        int position = buffer.position();
290        buffer.position(lastPosition);
291        byte[] remaining = new byte[position - buffer.position()];
292        buffer.get(remaining);
293        digest.update(remaining);
294        assert position == buffer.position();
295
296        buffer.compact();
297        if (channel.read(buffer) < 0) {
298            throw new EOFException();
299        }
300        buffer.flip();
301        lastPosition = buffer.position();
302    }
303
304    private void ensureAvailable(int i) throws IOException {
305        if (i > buffer.capacity()) {
306            throw new IllegalArgumentException(String.format("Can not request %d bytes: buffer capacity is %d", i, buffer.capacity()));
307        }
308        while (buffer.remaining() < i) {
309            fill();
310        }
311    }
312
313    private int readByte() throws IOException {
314        ensureAvailable(1);
315        return ((int)buffer.get()) & 0xff;
316    }
317
318    private int readInt() throws IOException {
319        ensureAvailable(4);
320        return buffer.getInt();
321    }
322
323    private char readShort() throws IOException {
324        ensureAvailable(2);
325        return buffer.getChar();
326    }
327
328    private long readLong() throws IOException {
329        ensureAvailable(8);
330        return buffer.getLong();
331    }
332
333    private double readDouble() throws IOException {
334        ensureAvailable(8);
335        return buffer.getDouble();
336    }
337
338    private float readFloat() throws IOException {
339        ensureAvailable(4);
340        return buffer.getFloat();
341    }
342
343    private String readString() throws IOException {
344        return new String(readBytes(), utf8).intern();
345    }
346
347    private byte[] readBytes() throws IOException {
348        int len = readInt();
349        if (len < 0) {
350            return null;
351        }
352        byte[] b = new byte[len];
353        int bytesRead = 0;
354        while (bytesRead < b.length) {
355            int toRead = Math.min(b.length - bytesRead, buffer.capacity());
356            ensureAvailable(toRead);
357            buffer.get(b, bytesRead, toRead);
358            bytesRead += toRead;
359        }
360        return b;
361    }
362
363    private String readIntsToString() throws IOException {
364        int len = readInt();
365        if (len < 0) {
366            return "null";
367        }
368        ensureAvailable(len * 4);
369        StringBuilder sb = new StringBuilder().append('[');
370        for (int i = 0; i < len; i++) {
371            sb.append(buffer.getInt());
372            if (i < len - 1) {
373                sb.append(", ");
374            }
375        }
376        sb.append(']');
377        return sb.toString().intern();
378    }
379
380    private String readDoublesToString() throws IOException {
381        int len = readInt();
382        if (len < 0) {
383            return "null";
384        }
385        ensureAvailable(len * 8);
386        StringBuilder sb = new StringBuilder().append('[');
387        for (int i = 0; i < len; i++) {
388            sb.append(buffer.getDouble());
389            if (i < len - 1) {
390                sb.append(", ");
391            }
392        }
393        sb.append(']');
394        return sb.toString().intern();
395    }
396
397    private String readPoolObjectsToString() throws IOException {
398        int len = readInt();
399        if (len < 0) {
400            return "null";
401        }
402        StringBuilder sb = new StringBuilder().append('[');
403        for (int i = 0; i < len; i++) {
404            sb.append(readPoolObject(Object.class));
405            if (i < len - 1) {
406                sb.append(", ");
407            }
408        }
409        sb.append(']');
410        return sb.toString().intern();
411    }
412
413    private <T> T readPoolObject(Class<T> klass) throws IOException {
414        int type = readByte();
415        if (type == POOL_NULL) {
416            return null;
417        }
418        if (type == POOL_NEW) {
419            return (T) addPoolEntry(klass);
420        }
421        assert assertObjectType(klass, type);
422        char index = readShort();
423        if (index < 0 || index >= constantPool.size()) {
424            throw new IOException("Invalid constant pool index : " + index);
425        }
426        Object obj = constantPool.get(index);
427        return (T) obj;
428    }
429
430    private boolean assertObjectType(Class<?> klass, int type) {
431        switch(type) {
432            case POOL_CLASS:
433                return klass.isAssignableFrom(EnumKlass.class);
434            case POOL_ENUM:
435                return klass.isAssignableFrom(EnumValue.class);
436            case POOL_METHOD:
437                return klass.isAssignableFrom(Method.class);
438            case POOL_STRING:
439                return klass.isAssignableFrom(String.class);
440            case POOL_NODE_CLASS:
441                return klass.isAssignableFrom(NodeClass.class);
442            case POOL_FIELD:
443                return klass.isAssignableFrom(Field.class);
444            case POOL_SIGNATURE:
445                return klass.isAssignableFrom(Signature.class);
446            case POOL_NULL:
447                return true;
448            default:
449                return false;
450        }
451    }
452
453    private Object addPoolEntry(Class<?> klass) throws IOException {
454        char index = readShort();
455        int type = readByte();
456        assert assertObjectType(klass, type) : "Wrong object type : " + klass + " != " + type;
457        Object obj;
458        switch(type) {
459            case POOL_CLASS: {
460                String name = readString();
461                int klasstype = readByte();
462                if (klasstype == ENUM_KLASS) {
463                    int len = readInt();
464                    String[] values = new String[len];
465                    for (int i = 0; i < len; i++) {
466                        values[i] = readPoolObject(String.class);
467                    }
468                    obj = new EnumKlass(name, values);
469                } else if (klasstype == KLASS) {
470                    obj = new Klass(name);
471                } else {
472                    throw new IOException("unknown klass type : " + klasstype);
473                }
474                break;
475            }
476            case POOL_ENUM: {
477                EnumKlass enumClass = readPoolObject(EnumKlass.class);
478                int ordinal = readInt();
479                obj = new EnumValue(enumClass, ordinal);
480                break;
481            }
482            case POOL_NODE_CLASS: {
483                String className = readString();
484                String nameTemplate = readString();
485                int inputCount = readShort();
486                List<TypedPort> inputs = new ArrayList<>(inputCount);
487                for (int i = 0; i < inputCount; i++) {
488                    boolean isList = readByte() != 0;
489                    String name = readPoolObject(String.class);
490                    EnumValue inputType = readPoolObject(EnumValue.class);
491                    inputs.add(new TypedPort(isList, name, inputType));
492                }
493                int suxCount = readShort();
494                List<Port> sux = new ArrayList<>(suxCount);
495                for (int i = 0; i < suxCount; i++) {
496                    boolean isList = readByte() != 0;
497                    String name = readPoolObject(String.class);
498                    sux.add(new Port(isList, name));
499                }
500                obj = new NodeClass(className, nameTemplate, inputs, sux);
501                break;
502            }
503            case POOL_METHOD: {
504                Klass holder = readPoolObject(Klass.class);
505                String name = readPoolObject(String.class);
506                Signature sign = readPoolObject(Signature.class);
507                int flags = readInt();
508                byte[] code = readBytes();
509                obj = new Method(name, sign, code, holder, flags);
510                break;
511            }
512            case POOL_FIELD: {
513                Klass holder = readPoolObject(Klass.class);
514                String name = readPoolObject(String.class);
515                String fType = readPoolObject(String.class);
516                int flags = readInt();
517                obj = new Field(fType, holder, name, flags);
518                break;
519            }
520            case POOL_SIGNATURE: {
521                int argc = readShort();
522                String[] args = new String[argc];
523                for (int i = 0; i < argc; i++) {
524                    args[i] = readPoolObject(String.class);
525                }
526                String returnType = readPoolObject(String.class);
527                obj = new Signature(returnType, args);
528                break;
529            }
530            case POOL_STRING: {
531                obj = readString();
532                break;
533            }
534            default:
535                throw new IOException("unknown pool type");
536        }
537        while (constantPool.size() <= index) {
538            constantPool.add(null);
539        }
540        constantPool.set(index, obj);
541        return obj;
542    }
543
544    private Object readPropertyObject() throws IOException {
545        int type = readByte();
546        switch (type) {
547            case PROPERTY_INT:
548                return readInt();
549            case PROPERTY_LONG:
550                return readLong();
551            case PROPERTY_FLOAT:
552                return readFloat();
553            case PROPERTY_DOUBLE:
554                return readDouble();
555            case PROPERTY_TRUE:
556                return Boolean.TRUE;
557            case PROPERTY_FALSE:
558                return Boolean.FALSE;
559            case PROPERTY_POOL:
560                return readPoolObject(Object.class);
561            case PROPERTY_ARRAY:
562                int subType = readByte();
563                switch(subType) {
564                    case PROPERTY_INT:
565                        return readIntsToString();
566                    case PROPERTY_DOUBLE:
567                        return readDoublesToString();
568                    case PROPERTY_POOL:
569                        return readPoolObjectsToString();
570                    default:
571                        throw new IOException("Unknown type");
572                }
573            case PROPERTY_SUBGRAPH:
574                InputGraph graph = parseGraph("");
575                new Group(null).addElement(graph);
576                return graph;
577            default:
578                throw new IOException("Unknown type");
579        }
580    }
581
582    @Override
583    public GraphDocument parse() throws IOException {
584        folderStack.push(rootDocument);
585        hashStack.push(null);
586        if (monitor != null) {
587            monitor.setState("Starting parsing");
588        }
589        try {
590            while(true) {
591                parseRoot();
592            }
593        } catch (EOFException e) {
594
595        }
596        if (monitor != null) {
597            monitor.setState("Finished parsing");
598        }
599        return rootDocument;
600    }
601
602    private void parseRoot() throws IOException {
603        int type = readByte();
604        switch(type) {
605            case BEGIN_GRAPH: {
606                final Folder parent = folderStack.peek();
607                final InputGraph graph = parseGraph();
608                SwingUtilities.invokeLater(new Runnable(){
609                    @Override
610                    public void run() {
611                        parent.addElement(graph);
612                    }
613                });
614                break;
615            }
616            case BEGIN_GROUP: {
617                final Folder parent = folderStack.peek();
618                final Group group = parseGroup(parent);
619                if (callback == null || parent instanceof Group) {
620                    SwingUtilities.invokeLater(new Runnable(){
621                        @Override
622                        public void run() {
623                            parent.addElement(group);
624                        }
625                    });
626                }
627                folderStack.push(group);
628                hashStack.push(null);
629                if (callback != null && parent instanceof GraphDocument) {
630                    callback.started(group);
631                }
632                break;
633            }
634            case CLOSE_GROUP: {
635                if (folderStack.isEmpty()) {
636                    throw new IOException("Unbalanced groups");
637                }
638                folderStack.pop();
639                hashStack.pop();
640                break;
641            }
642            default:
643                throw new IOException("unknown root : " + type);
644        }
645    }
646
647    private Group parseGroup(Folder parent) throws IOException {
648        String name = readPoolObject(String.class);
649        String shortName = readPoolObject(String.class);
650        if (monitor != null) {
651            monitor.setState(shortName);
652        }
653        Method method = readPoolObject(Method.class);
654        int bci = readInt();
655        Group group = new Group(parent);
656        group.getProperties().setProperty("name", name);
657        parseProperties(group.getProperties());
658        if (method != null) {
659            InputMethod inMethod = new InputMethod(group, method.name, shortName, bci);
660            inMethod.setBytecodes("TODO");
661            group.setMethod(inMethod);
662        }
663        return group;
664    }
665
666    int lastPosition = 0;
667
668    private InputGraph parseGraph() throws IOException {
669        if (monitor != null) {
670            monitor.updateProgress();
671        }
672        String title = readPoolObject(String.class);
673        digest.reset();
674        lastPosition = buffer.position();
675        InputGraph graph = parseGraph(title);
676
677        int position = buffer.position();
678        buffer.position(lastPosition);
679        byte[] remaining = new byte[position - buffer.position()];
680        buffer.get(remaining);
681        digest.update(remaining);
682        assert position == buffer.position();
683        lastPosition = buffer.position();
684
685        byte[] d = digest.digest();
686        byte[] hash = hashStack.peek();
687        if (hash != null && Arrays.equals(hash, d)) {
688            graph.getProperties().setProperty("_isDuplicate", "true");
689        } else {
690            hashStack.pop();
691            hashStack.push(d);
692        }
693        return graph;
694    }
695
696    private void parseProperties(Properties properties) throws IOException {
697        int propCount = readShort();
698        for (int j = 0; j < propCount; j++) {
699            String key = readPoolObject(String.class);
700            Object value = readPropertyObject();
701            properties.setProperty(key, value != null ? value.toString() : "null");
702        }
703    }
704
705    private InputGraph parseGraph(String title) throws IOException {
706        InputGraph graph = new InputGraph(title);
707        parseProperties(graph.getProperties());
708        parseNodes(graph);
709        parseBlocks(graph);
710        graph.ensureNodesInBlocks();
711        for (InputNode node : graph.getNodes()) {
712            node.internProperties();
713        }
714        return graph;
715    }
716
717    private void parseBlocks(InputGraph graph) throws IOException {
718        int blockCount = readInt();
719        List<Edge> edges = new LinkedList<>();
720        for (int i = 0; i < blockCount; i++) {
721            int id = readInt();
722            String name = id >= 0 ? Integer.toString(id) : NO_BLOCK;
723            InputBlock block = graph.addBlock(name);
724            int nodeCount = readInt();
725            for (int j = 0; j < nodeCount; j++) {
726                int nodeId = readInt();
727                if (nodeId < 0) {
728                    continue;
729                }
730                final Properties properties = graph.getNode(nodeId).getProperties();
731                final String oldBlock = properties.get("block");
732                if(oldBlock != null) {
733                    properties.setProperty("block", oldBlock + ", " + name);
734                } else {
735                    block.addNode(nodeId);
736                    properties.setProperty("block", name);
737                }
738            }
739            int edgeCount = readInt();
740            for (int j = 0; j < edgeCount; j++) {
741                int to = readInt();
742                edges.add(new Edge(id, to));
743            }
744        }
745        for (Edge e : edges) {
746            String fromName = e.from >= 0 ? Integer.toString(e.from) : NO_BLOCK;
747            String toName = e.to >= 0 ? Integer.toString(e.to) : NO_BLOCK;
748            graph.addBlockEdge(graph.getBlock(fromName), graph.getBlock(toName));
749        }
750    }
751
752    private void parseNodes(InputGraph graph) throws IOException {
753        int count = readInt();
754        Map<String, Object> props = new HashMap<>();
755        List<Edge> inputEdges = new ArrayList<>(count);
756        List<Edge> succEdges = new ArrayList<>(count);
757        for (int i = 0; i < count; i++) {
758            int id = readInt();
759            InputNode node = new InputNode(id);
760            final Properties properties = node.getProperties();
761            NodeClass nodeClass = readPoolObject(NodeClass.class);
762            int preds = readByte();
763            if (preds > 0) {
764                properties.setProperty("hasPredecessor", "true");
765            }
766            properties.setProperty("idx", Integer.toString(id));
767            int propCount = readShort();
768            for (int j = 0; j < propCount; j++) {
769                String key = readPoolObject(String.class);
770                if (key.equals("hasPredecessor") || key.equals("name") || key.equals("class") || key.equals("id") || key.equals("idx")) {
771                    key = "!data." + key;
772                }
773                Object value = readPropertyObject();
774                if (value instanceof InputGraph) {
775                    InputGraph subgraph = (InputGraph) value;
776                    subgraph.getProperties().setProperty("name", node.getId() + ":" + key);
777                    node.addSubgraph((InputGraph) value);
778                } else {
779                    properties.setProperty(key, value != null ? value.toString() : "null");
780                    props.put(key, value);
781                }
782            }
783            ArrayList<Edge> currentEdges = new ArrayList<>();
784            int portNum = 0;
785            for (TypedPort p : nodeClass.inputs) {
786                if (p.isList) {
787                    int size = readShort();
788                    for (int j = 0; j < size; j++) {
789                        int in = readInt();
790                        if (in >= 0) {
791                            Edge e = new Edge(in, id, (char) (preds + portNum), p.name + "[" + j + "]", p.type.toString(Length.S), true);
792                            currentEdges.add(e);
793                            inputEdges.add(e);
794                            portNum++;
795                        }
796                    }
797                } else {
798                    int in = readInt();
799                    if (in >= 0) {
800                        Edge e = new Edge(in, id, (char) (preds + portNum), p.name, p.type.toString(Length.S), true);
801                        currentEdges.add(e);
802                        inputEdges.add(e);
803                        portNum++;
804                    }
805                }
806
807            }
808            portNum = 0;
809            for (Port p : nodeClass.sux) {
810                if (p.isList) {
811                    int size = readShort();
812                    for (int j = 0; j < size; j++) {
813                        int sux = readInt();
814                        if (sux >= 0) {
815                            Edge e = new Edge(id, sux, (char) portNum, p.name + "[" + j + "]", "Successor", false);
816                            currentEdges.add(e);
817                            succEdges.add(e);
818                            portNum++;
819                        }
820                    }
821                } else {
822                    int sux = readInt();
823                    if (sux >= 0) {
824                        Edge e = new Edge(id, sux, (char) portNum, p.name, "Successor", false);
825                        currentEdges.add(e);
826                        succEdges.add(e);
827                        portNum++;
828                    }
829                }
830            }
831            properties.setProperty("name", createName(currentEdges, props, nodeClass.nameTemplate));
832            properties.setProperty("class", nodeClass.className);
833            switch (nodeClass.className) {
834                case "BeginNode":
835                    properties.setProperty("shortName", "B");
836                    break;
837                case "EndNode":
838                    properties.setProperty("shortName", "E");
839                    break;
840            }
841            graph.addNode(node);
842            props.clear();
843        }
844
845        Set<InputNode> nodesWithSuccessor = new HashSet<>();
846
847        for (Edge e : succEdges) {
848            assert !e.input;
849            char fromIndex = e.num;
850            nodesWithSuccessor.add(graph.getNode(e.from));
851            char toIndex = 0;
852            graph.addEdge(InputEdge.createImmutable(fromIndex, toIndex, e.from, e.to, e.label, e.type));
853        }
854        for (Edge e : inputEdges) {
855            assert e.input;
856            char fromIndex = (char) (nodesWithSuccessor.contains(graph.getNode(e.from)) ? 1 : 0);
857            char toIndex = e.num;
858            graph.addEdge(InputEdge.createImmutable(fromIndex, toIndex, e.from, e.to, e.label, e.type));
859        }
860    }
861
862    static final Pattern templatePattern = Pattern.compile("\\{(p|i)#([a-zA-Z0-9$_]+)(/(l|m|s))?\\}");
863
864    private String createName(List<Edge> edges, Map<String, Object> properties, String template) {
865        Matcher m = templatePattern.matcher(template);
866        StringBuffer sb = new StringBuffer();
867        while (m.find()) {
868            String name = m.group(2);
869            String type = m.group(1);
870            String result;
871            switch (type) {
872                case "i":
873                    StringBuilder inputString = new StringBuilder();
874                    for(Edge edge : edges) {
875                        if (edge.label.startsWith(name) && (name.length() == edge.label.length() || edge.label.charAt(name.length()) == '[')) {
876                            if (inputString.length() > 0) {
877                                inputString.append(", ");
878                            }
879                            inputString.append(edge.from);
880                        }
881                    }
882                    result = inputString.toString();
883                    break;
884                case "p":
885                    Object prop = properties.get(name);
886                    String length = m.group(4);
887                    if (prop == null) {
888                        result = "?";
889                    } else if (length != null && prop instanceof LengthToString) {
890                        LengthToString lengthProp = (LengthToString) prop;
891                        switch(length) {
892                            default:
893                            case "l":
894                                result = lengthProp.toString(Length.L);
895                                break;
896                            case "m":
897                                result = lengthProp.toString(Length.M);
898                                break;
899                            case "s":
900                                result = lengthProp.toString(Length.S);
901                                break;
902                        }
903                    } else {
904                        result = prop.toString();
905                    }
906                    break;
907                default:
908                    result = "#?#";
909                    break;
910            }
911            result = result.replace("\\", "\\\\");
912            result = result.replace("$", "\\$");
913            m.appendReplacement(sb, result);
914        }
915        m.appendTail(sb);
916        return sb.toString().intern();
917    }
918
919    private static class Edge {
920        final int from;
921        final int to;
922        final char num;
923        final String label;
924        final String type;
925        final boolean input;
926        public Edge(int from, int to) {
927            this(from, to, (char) 0, null, null, false);
928        }
929        public Edge(int from, int to, char num, String label, String type, boolean input) {
930            this.from = from;
931            this.to = to;
932            this.label = label != null ? label.intern() : label;
933            this.type = type != null ? type.intern() : type;
934            this.num = num;
935            this.input = input;
936        }
937    }
938}
939