public static PiPipelineModel parse(URL p4InfoUrl) throws P4InfoParserException {

        final P4Info p4info;
        try {
            p4info = getP4InfoMessage(p4InfoUrl);
        } catch (IOException e) {
            throw new P4InfoParserException("Unable to parse protobuf " + p4InfoUrl.toString(), e);
        }

        // Generate fingerprint of the pipeline by hashing p4info file
        final int fingerprint;
        try {
            HashingInputStream hin = new HashingInputStream(Hashing.crc32(), p4InfoUrl.openStream());
            //noinspection StatementWithEmptyBody
            while (hin.read() != -1) {
                // Do nothing. Reading all input stream to update hash.
            }
            fingerprint = hin.hash().asInt();
        } catch (IOException e) {
            throw new P4InfoParserException("Unable to generate fingerprint " + p4InfoUrl.toString(), e);
        }

        // Start by parsing and mapping instances to to their integer P4Info IDs.
        // Convenient to build the table model at the end.

        final String architecture = parseArchitecture(p4info);

        // Counters.
        final Map<Integer, PiCounterModel> counterMap = Maps.newHashMap();
        counterMap.putAll(parseCounters(p4info));
        counterMap.putAll(parseDirectCounters(p4info));

        // Meters.
        final Map<Integer, PiMeterModel> meterMap = Maps.newHashMap();
        meterMap.putAll(parseMeters(p4info));
        meterMap.putAll(parseDirectMeters(p4info));

        // Registers.
        final Map<Integer, PiRegisterModel> registerMap = Maps.newHashMap();
        registerMap.putAll(parseRegisters(p4info));

        // Action profiles.
        final Map<Integer, PiActionProfileModel> actProfileMap = parseActionProfiles(p4info);

        // Actions.
        final Map<Integer, PiActionModel> actionMap = parseActions(p4info);

        // Controller packet metadatas.
        final Map<PiPacketOperationType, PiPacketOperationModel> pktOpMap = parseCtrlPktMetadatas(p4info);

        // Finally, parse tables.
        final ImmutableMap.Builder<PiTableId, PiTableModel> tableImmMapBuilder =
                ImmutableMap.builder();
        for (Table tableMsg : p4info.getTablesList()) {
            final PiTableId tableId = PiTableId.of(tableMsg.getPreamble().getName());
            // Parse match fields.
            final ImmutableMap.Builder<PiMatchFieldId, PiMatchFieldModel> tableFieldMapBuilder =
                    ImmutableMap.builder();
            for (MatchField fieldMsg : tableMsg.getMatchFieldsList()) {
                final PiMatchFieldId fieldId = PiMatchFieldId.of(fieldMsg.getName());
                tableFieldMapBuilder.put(
                        fieldId,
                        new P4MatchFieldModel(fieldId,
                                              isFieldString(p4info, fieldMsg.getTypeName().getName()) ?
                                                      P4MatchFieldModel.BIT_WIDTH_UNDEFINED :
                                                      fieldMsg.getBitwidth(),
                                              mapMatchFieldType(fieldMsg.getMatchType())));

            }
            // Retrieve action models by inter IDs.
            final ImmutableMap.Builder<PiActionId, PiActionModel> tableActionMapBuilder =
                    ImmutableMap.builder();
            tableMsg.getActionRefsList().stream()
                    .map(ActionRef::getId)
                    .map(actionMap::get)
                    .forEach(actionModel -> tableActionMapBuilder.put(actionModel.id(), actionModel));
            // Retrieve direct meters by integer IDs.
            final ImmutableMap.Builder<PiMeterId, PiMeterModel> tableMeterMapBuilder =
                    ImmutableMap.builder();
            tableMsg.getDirectResourceIdsList()
                    .stream()
                    .map(meterMap::get)
                    // Direct resource ID might be that of a counter.
                    // Filter out missed mapping.
                    .filter(Objects::nonNull)
                    .forEach(meterModel -> tableMeterMapBuilder.put(meterModel.id(), meterModel));
            // Retrieve direct counters by integer IDs.
            final ImmutableMap.Builder<PiCounterId, PiCounterModel> tableCounterMapBuilder =
                    ImmutableMap.builder();
            tableMsg.getDirectResourceIdsList()
                    .stream()
                    .map(counterMap::get)
                    // As before, resource ID might be that of a meter.
                    // Filter out missed mapping.
                    .filter(Objects::nonNull)
                    .forEach(counterModel -> tableCounterMapBuilder.put(counterModel.id(), counterModel));
            // Check if table supports one-shot only
            boolean oneShotOnly = isAnnotationPresent(ONE_SHOT_ONLY_ANNOTATION, tableMsg.getPreamble());
            tableImmMapBuilder.put(
                    tableId,
                    new P4TableModel(
                            PiTableId.of(tableMsg.getPreamble().getName()),
                            tableMsg.getImplementationId() == 0 ? PiTableType.DIRECT : PiTableType.INDIRECT,
                            actProfileMap.get(tableMsg.getImplementationId()),
                            tableMsg.getSize(),
                            tableCounterMapBuilder.build(),
                            tableMeterMapBuilder.build(),
                            !tableMsg.getIdleTimeoutBehavior()
                                    .equals(Table.IdleTimeoutBehavior.NO_TIMEOUT),
                            tableFieldMapBuilder.build(),
                            tableActionMapBuilder.build(),
                            actionMap.get(tableMsg.getConstDefaultActionId()),
                            tableMsg.getIsConstTable(), oneShotOnly));
        }

        // Get a map with proper PI IDs for some of those maps we created at the beginning.
        ImmutableMap<PiCounterId, PiCounterModel> counterImmMap = ImmutableMap.copyOf(
                counterMap.values().stream()
                        .collect(Collectors.toMap(PiCounterModel::id, c -> c)));
        ImmutableMap<PiMeterId, PiMeterModel> meterImmMap = ImmutableMap.copyOf(
                meterMap.values().stream()
                        .collect(Collectors.toMap(PiMeterModel::id, m -> m)));
        ImmutableMap<PiRegisterId, PiRegisterModel> registerImmMap = ImmutableMap.copyOf(
                registerMap.values().stream()
                        .collect(Collectors.toMap(PiRegisterModel::id, r -> r)));
        ImmutableMap<PiActionProfileId, PiActionProfileModel> actProfileImmMap = ImmutableMap.copyOf(
                actProfileMap.values().stream()
                        .collect(Collectors.toMap(PiActionProfileModel::id, a -> a)));

        return new P4PipelineModel(
                tableImmMapBuilder.build(),
                counterImmMap,
                meterImmMap,
                registerImmMap,
                actProfileImmMap,
                ImmutableMap.copyOf(pktOpMap),
                architecture,
                fingerprint);
    }
@Test
    public void testParse() throws Exception {
        // Generate two PiPipelineModels from the same p4Info file
        PiPipelineModel model = P4InfoParser.parse(p4InfoUrl);
        PiPipelineModel sameAsModel = P4InfoParser.parse(p4InfoUrl);

        // Check equality
        new EqualsTester().addEqualityGroup(model, sameAsModel).testEquals();

        // Generate a P4Info object from the file
        final P4Info p4info;
        try {
            p4info = getP4InfoMessage(p4InfoUrl);
        } catch (IOException e) {
            throw new P4InfoParserException("Unable to parse protobuf " + p4InfoUrl.toString(), e);
        }
        List<Table> tableMsgs =  p4info.getTablesList();
        PiTableId table0Id = PiTableId.of(tableMsgs.get(0).getPreamble().getName());
        PiTableId wcmpTableId = PiTableId.of(tableMsgs.get(1).getPreamble().getName());
        PiTableId wcmpTableOneShotId = PiTableId.of(tableMsgs.get(2).getPreamble().getName());
        //parse tables
        PiTableModel table0Model = model.table(table0Id).orElse(null);
        PiTableModel wcmpTableModel = model.table(wcmpTableId).orElse(null);
        PiTableModel wcmpTableOneShotModel = model.table(wcmpTableOneShotId).orElse(null);
        PiTableModel table0Model2 = sameAsModel.table(table0Id).orElse(null);
        PiTableModel wcmpTableModel2 = sameAsModel.table(wcmpTableId).orElse(null);

        new EqualsTester().addEqualityGroup(table0Model, table0Model2)
                .addEqualityGroup(wcmpTableModel, wcmpTableModel2).testEquals();
        // Check existence
        assertThat("model parsed value is null", table0Model, notNullValue());
        assertThat("model parsed value is null", wcmpTableModel, notNullValue());
        assertThat("model parsed value is null", wcmpTableOneShotModel, notNullValue());
        assertThat("Incorrect size for table0 size", table0Model.maxSize(), is(equalTo(DEFAULT_MAX_TABLE_SIZE)));
        assertThat("Incorrect size for wcmp_table size", wcmpTableModel.maxSize(), is(equalTo(DEFAULT_MAX_TABLE_SIZE)));
        assertThat("Incorrect size for wcmp_table_one_shot size", wcmpTableOneShotModel.maxSize(),
                   is(equalTo(DEFAULT_MAX_TABLE_SIZE)));
        // Check one-shot annotation
        assertThat("error parsing one-shot annotation", wcmpTableModel.oneShotOnly(), is(false));
        assertThat("error parsing one-shot annotation", wcmpTableOneShotModel.oneShotOnly(), is(true));

        // Check matchFields
        List<MatchField> matchFieldList = tableMsgs.get(0).getMatchFieldsList();
        List<PiMatchFieldModel> piMatchFieldList = new ArrayList<>();
        for (MatchField matchFieldIter : matchFieldList) {
            MatchField.MatchType matchType = matchFieldIter.getMatchType();
            PiMatchType piMatchType;
            switch (matchType) {
                case EXACT: piMatchType = PiMatchType.EXACT; break;
                case LPM: piMatchType = PiMatchType.LPM; break;
                case TERNARY: piMatchType = PiMatchType.TERNARY; break;
                case RANGE: piMatchType = PiMatchType.RANGE; break;
                default: Assert.fail(); return;
            }
            piMatchFieldList.add(new P4MatchFieldModel(PiMatchFieldId.of(matchFieldIter.getName()),
                                                       matchFieldIter.getBitwidth(), piMatchType));
        }
        // Check MatchFields size
        assertThat("Incorrect size for matchFields", table0Model.matchFields().size(), is(equalTo(9)));
        // Check if matchFields are in order
        assertThat("Incorrect order for matchFields", table0Model.matchFields(), IsIterableContainingInOrder.contains(
                piMatchFieldList.get(0), piMatchFieldList.get(1),
                piMatchFieldList.get(2), piMatchFieldList.get(3),
                piMatchFieldList.get(4), piMatchFieldList.get(5),
                piMatchFieldList.get(6), piMatchFieldList.get(7),
                piMatchFieldList.get(8)));
        assertThat("Incorrect size for matchFields", wcmpTableModel.matchFields().size(), is(equalTo(1)));
        // check if matchFields are in order
        matchFieldList = tableMsgs.get(1).getMatchFieldsList();
        assertThat("Incorrect order for matchFields",
                   wcmpTableModel.matchFields(), IsIterableContainingInOrder.contains(
                        new P4MatchFieldModel(PiMatchFieldId.of(matchFieldList.get(0).getName()),
                                              matchFieldList.get(0).getBitwidth(), PiMatchType.EXACT)));
        //check table0 actionsRefs
        List<ActionRef> actionRefList = tableMsgs.get(0).getActionRefsList();
        assertThat("Incorrect size for actionRefs", actionRefList.size(), is(equalTo(4)));
        //create action instances
        PiActionId actionId = PiActionId.of("set_egress_port");
        PiActionParamId piActionParamId = PiActionParamId.of("port");
        int bitWitdth = 9;
        PiActionParamModel actionParamModel = new P4ActionParamModel(piActionParamId, bitWitdth);
        ImmutableMap<PiActionParamId, PiActionParamModel> params = new
                ImmutableMap.Builder<PiActionParamId, PiActionParamModel>()
                .put(piActionParamId, actionParamModel).build();

        PiActionModel setEgressPortAction = new P4ActionModel(actionId, params);

        actionId = PiActionId.of("send_to_cpu");
        PiActionModel sendToCpuAction =
                new P4ActionModel(actionId, new ImmutableMap.Builder<PiActionParamId, PiActionParamModel>().build());
        actionId = PiActionId.of("_drop");
        PiActionModel dropAction =
                new P4ActionModel(actionId, new ImmutableMap.Builder<PiActionParamId, PiActionParamModel>().build());
        actionId = PiActionId.of("NoAction");
        PiActionModel noAction =
                new P4ActionModel(actionId, new ImmutableMap.Builder<PiActionParamId, PiActionParamModel>().build());
        actionId = PiActionId.of("table0_control.set_next_hop_id");
        piActionParamId = PiActionParamId.of("next_hop_id");
        bitWitdth = 16;
        actionParamModel = new P4ActionParamModel(piActionParamId, bitWitdth);
        params = new ImmutableMap.Builder<PiActionParamId, PiActionParamModel>()
                .put(piActionParamId, actionParamModel).build();

        PiActionModel setNextHopIdAction = new P4ActionModel(actionId, params);

        //check table0 actions
        assertThat("action dose not match",
                   table0Model.actions(), IsIterableContainingInAnyOrder.containsInAnyOrder(
                        setEgressPortAction, sendToCpuAction, setNextHopIdAction, dropAction));
        //check wcmp_table actions
        assertThat("actions dose not match",
                   wcmpTableModel.actions(), IsIterableContainingInAnyOrder.containsInAnyOrder(
                        setEgressPortAction, noAction));

        PiActionModel table0DefaultAction = table0Model.constDefaultAction().orElse(null);

        new EqualsTester().addEqualityGroup(table0DefaultAction, dropAction).testEquals();

        // Check existence
        assertThat("model parsed value is null", table0DefaultAction, notNullValue());

        //parse action profiles
        PiTableId tableId = PiTableId.of("wcmp_control.wcmp_table");
        ImmutableSet<PiTableId> tableIds = new ImmutableSet.Builder<PiTableId>().add(tableId).build();
        PiActionProfileId actionProfileId = PiActionProfileId.of("wcmp_control.wcmp_selector");
        PiActionProfileModel wcmpSelector3 = new P4ActionProfileModel(actionProfileId, tableIds,
                                                                      true, DEFAULT_MAX_ACTION_PROFILE_SIZE,
                                                                      DEFAULT_MAX_GROUP_SIZE);
        PiActionProfileModel wcmpSelector = model.actionProfiles(actionProfileId).orElse(null);
        PiActionProfileModel wcmpSelector2 = sameAsModel.actionProfiles(actionProfileId).orElse(null);

        new EqualsTester().addEqualityGroup(wcmpSelector, wcmpSelector2, wcmpSelector3).testEquals();

        // Check existence
        assertThat("model parsed value is null", wcmpSelector, notNullValue());
        assertThat("Incorrect value for actions profiles", model.actionProfiles(), containsInAnyOrder(wcmpSelector));
        // ActionProfiles size
        assertThat("Incorrect size for action profiles", model.actionProfiles().size(), is(equalTo(1)));

        //parse counters
        PiCounterModel ingressPortCounterModel =
                model.counter(PiCounterId.of("port_counters_ingress.ingress_port_counter")).orElse(null);
        PiCounterModel egressPortCounterModel =
                model.counter(PiCounterId.of("port_counters_egress.egress_port_counter")).orElse(null);
        PiCounterModel table0CounterModel =
                model.counter(PiCounterId.of("table0_control.table0_counter")).orElse(null);
        PiCounterModel wcmpTableCounterModel =
                model.counter(PiCounterId.of("wcmp_control.wcmp_table_counter")).orElse(null);

        PiCounterModel ingressPortCounterModel2 =
                sameAsModel.counter(PiCounterId.of("port_counters_ingress.ingress_port_counter")).orElse(null);
        PiCounterModel egressPortCounterModel2 =
                sameAsModel.counter(PiCounterId.of("port_counters_egress.egress_port_counter")).orElse(null);
        PiCounterModel table0CounterModel2 =
                sameAsModel.counter(PiCounterId.of("table0_control.table0_counter")).orElse(null);
        PiCounterModel wcmpTableCounterModel2 =
                sameAsModel.counter(PiCounterId.of("wcmp_control.wcmp_table_counter")).orElse(null);

        new EqualsTester()
                .addEqualityGroup(ingressPortCounterModel, ingressPortCounterModel2)
                .addEqualityGroup(egressPortCounterModel, egressPortCounterModel2)
                .addEqualityGroup(table0CounterModel, table0CounterModel2)
                .addEqualityGroup(wcmpTableCounterModel, wcmpTableCounterModel2)
                .testEquals();

        assertThat("model parsed value is null", ingressPortCounterModel, notNullValue());
        assertThat("model parsed value is null", egressPortCounterModel, notNullValue());
        assertThat("model parsed value is null", table0CounterModel, notNullValue());
        assertThat("model parsed value is null", wcmpTableCounterModel, notNullValue());

        //Parse meters
        Collection<PiMeterModel> meterModel = model.meters();
        Collection<PiMeterModel> meterModel2 = sameAsModel.meters();

        assertThat("model parsed meter collection should be empty", meterModel.isEmpty(), is(true));
        assertThat("model parsed meter collection should be empty", meterModel2.isEmpty(), is(true));

        //parse packet operations
        PiPacketOperationModel packetInOperationalModel =
                model.packetOperationModel(PiPacketOperationType.PACKET_IN).orElse(null);
        PiPacketOperationModel packetOutOperationalModel =
                model.packetOperationModel(PiPacketOperationType.PACKET_OUT).orElse(null);

        PiPacketOperationModel packetInOperationalModel2 =
                sameAsModel.packetOperationModel(PiPacketOperationType.PACKET_IN).orElse(null);
        PiPacketOperationModel packetOutOperationalModel2 =
                sameAsModel.packetOperationModel(PiPacketOperationType.PACKET_OUT).orElse(null);

        new EqualsTester()
                .addEqualityGroup(packetInOperationalModel, packetInOperationalModel2)
                .addEqualityGroup(packetOutOperationalModel, packetOutOperationalModel2)
                .testEquals();

        // Check existence
        assertThat("model parsed value is null", packetInOperationalModel, notNullValue());
        assertThat("model parsed value is null", packetOutOperationalModel, notNullValue());
    }