Ray Milkey

Refactor criterion codec

The previous implementation was a huge switch statement. These
changes attempt to use polymorphism to avoid all of the branching
inherent in a switch.

Change-Id: If997f028346de02b883356bcde07f62674e319be
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
15 */ 15 */
16 package org.onosproject.codec.impl; 16 package org.onosproject.codec.impl;
17 17
18 +import java.util.EnumMap;
19 +
18 import org.onosproject.codec.CodecContext; 20 import org.onosproject.codec.CodecContext;
19 import org.onosproject.codec.JsonCodec; 21 import org.onosproject.codec.JsonCodec;
20 import org.onosproject.net.flow.criteria.Criteria; 22 import org.onosproject.net.flow.criteria.Criteria;
...@@ -31,172 +33,295 @@ import static com.google.common.base.Preconditions.checkNotNull; ...@@ -31,172 +33,295 @@ import static com.google.common.base.Preconditions.checkNotNull;
31 */ 33 */
32 public final class CriterionCodec extends JsonCodec<Criterion> { 34 public final class CriterionCodec extends JsonCodec<Criterion> {
33 35
34 - protected static final Logger log = LoggerFactory.getLogger(CriterionCodec.class); 36 + protected static final Logger log =
35 - 37 + LoggerFactory.getLogger(CriterionCodec.class);
36 - @Override 38 +
37 - public ObjectNode encode(Criterion criterion, CodecContext context) { 39 + private final EnumMap<Criterion.Type, CriterionTypeFormatter> formatMap;
38 - checkNotNull(criterion, "Criterion cannot be null"); 40 +
41 + public CriterionCodec() {
42 + formatMap = new EnumMap<>(Criterion.Type.class);
43 +
44 + formatMap.put(Criterion.Type.IN_PORT, new FormatInPort());
45 + formatMap.put(Criterion.Type.IN_PHY_PORT, new FormatInPort());
46 + formatMap.put(Criterion.Type.ETH_DST, new FormatEth());
47 + formatMap.put(Criterion.Type.ETH_SRC, new FormatEth());
48 + formatMap.put(Criterion.Type.IP_PROTO, new FormatIpProto());
49 + formatMap.put(Criterion.Type.ETH_TYPE, new FormatEthType());
50 + formatMap.put(Criterion.Type.METADATA, new FormatMetadata());
51 + formatMap.put(Criterion.Type.VLAN_VID, new FormatVlanVid());
52 + formatMap.put(Criterion.Type.VLAN_PCP, new FormatVlanPcp());
53 + formatMap.put(Criterion.Type.IP_DSCP, new FormatIpDscp());
54 + formatMap.put(Criterion.Type.IP_ECN, new FormatIpEcn());
55 + formatMap.put(Criterion.Type.IPV4_SRC, new FormatIp());
56 + formatMap.put(Criterion.Type.IPV4_DST, new FormatIp());
57 + formatMap.put(Criterion.Type.IPV6_SRC, new FormatIp());
58 + formatMap.put(Criterion.Type.IPV6_DST, new FormatIp());
59 + formatMap.put(Criterion.Type.TCP_SRC, new FormatTcp());
60 + formatMap.put(Criterion.Type.TCP_DST, new FormatTcp());
61 + formatMap.put(Criterion.Type.UDP_SRC, new FormatUdp());
62 + formatMap.put(Criterion.Type.UDP_DST, new FormatUdp());
63 + formatMap.put(Criterion.Type.SCTP_SRC, new FormatSctp());
64 + formatMap.put(Criterion.Type.SCTP_DST, new FormatSctp());
65 + formatMap.put(Criterion.Type.ICMPV4_TYPE, new FormatIcmpV4Type());
66 + formatMap.put(Criterion.Type.ICMPV4_CODE, new FormatIcmpV4Code());
67 + formatMap.put(Criterion.Type.IPV6_FLABEL, new FormatIpV6FLabel());
68 + formatMap.put(Criterion.Type.ICMPV6_TYPE, new FormatIcmpV6Type());
69 + formatMap.put(Criterion.Type.ICMPV6_CODE, new FormatIcmpV6Code());
70 + formatMap.put(Criterion.Type.IPV6_ND_TARGET, new FormatV6NDTarget());
71 + formatMap.put(Criterion.Type.IPV6_ND_SLL, new FormatV6NDTll());
72 + formatMap.put(Criterion.Type.IPV6_ND_TLL, new FormatV6NDTll());
73 + formatMap.put(Criterion.Type.MPLS_LABEL, new FormatMplsLabel());
74 + formatMap.put(Criterion.Type.OCH_SIGID, new FormatOchSigId());
75 + formatMap.put(Criterion.Type.OCH_SIGTYPE, new FormatOchSigType());
76 + formatMap.put(Criterion.Type.MPLS_LABEL, new FormatMplsLabel());
77 +
78 + // Currently unimplemented
79 + formatMap.put(Criterion.Type.ARP_OP, new FormatUnknown());
80 + formatMap.put(Criterion.Type.ARP_SHA, new FormatUnknown());
81 + formatMap.put(Criterion.Type.ARP_SPA, new FormatUnknown());
82 + formatMap.put(Criterion.Type.ARP_THA, new FormatUnknown());
83 + formatMap.put(Criterion.Type.ARP_TPA, new FormatUnknown());
84 + formatMap.put(Criterion.Type.MPLS_TC, new FormatUnknown());
85 + formatMap.put(Criterion.Type.MPLS_BOS, new FormatUnknown());
86 + formatMap.put(Criterion.Type.PBB_ISID, new FormatUnknown());
87 + formatMap.put(Criterion.Type.PBB_UCA, new FormatUnknown());
88 + formatMap.put(Criterion.Type.TUNNEL_ID, new FormatUnknown());
89 + formatMap.put(Criterion.Type.IPV6_EXTHDR, new FormatUnknown());
90 + formatMap.put(Criterion.Type.UNASSIGNED_40, new FormatUnknown());
91 + formatMap.put(Criterion.Type.TCP_FLAGS, new FormatUnknown());
92 + formatMap.put(Criterion.Type.ACTSET_OUTPUT, new FormatUnknown());
93 + formatMap.put(Criterion.Type.PACKET_TYPE, new FormatUnknown());
94 + }
39 95
40 - final ObjectNode result = context.mapper().createObjectNode() 96 + private interface CriterionTypeFormatter {
41 - .put("type", criterion.type().toString()); 97 + ObjectNode formatCriterion(ObjectNode root, Criterion criterion);
98 + }
42 99
43 - switch (criterion.type()) { 100 + private static class FormatUnknown implements CriterionTypeFormatter {
101 + @Override
102 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
103 + return root;
104 + }
105 + }
44 106
45 - case IN_PORT: 107 + private static class FormatInPort implements CriterionTypeFormatter {
46 - case IN_PHY_PORT: 108 + @Override
109 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
47 final Criteria.PortCriterion portCriterion = (Criteria.PortCriterion) criterion; 110 final Criteria.PortCriterion portCriterion = (Criteria.PortCriterion) criterion;
48 - result.put("port", portCriterion.port().toLong()); 111 + return root.put("port", portCriterion.port().toLong());
49 - break; 112 + }
50 - 113 + }
51 - case METADATA:
52 - final Criteria.MetadataCriterion metadataCriterion =
53 - (Criteria.MetadataCriterion) criterion;
54 - result.put("metadata", metadataCriterion.metadata());
55 - break;
56 114
57 - case ETH_DST: 115 + private static class FormatEth implements CriterionTypeFormatter {
58 - case ETH_SRC: 116 + @Override
117 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
59 final Criteria.EthCriterion ethCriterion = (Criteria.EthCriterion) criterion; 118 final Criteria.EthCriterion ethCriterion = (Criteria.EthCriterion) criterion;
60 - result.put("mac", ethCriterion.mac().toString()); 119 + return root.put("mac", ethCriterion.mac().toString());
61 - break; 120 + }
121 + }
62 122
63 - case ETH_TYPE: 123 + private static class FormatIpProto implements CriterionTypeFormatter {
124 + @Override
125 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
126 + final Criteria.IPProtocolCriterion iPProtocolCriterion =
127 + (Criteria.IPProtocolCriterion) criterion;
128 + return root.put("protocol", iPProtocolCriterion.protocol());
129 + }
130 + }
131 +
132 + private static class FormatEthType implements CriterionTypeFormatter {
133 + @Override
134 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
64 final Criteria.EthTypeCriterion ethTypeCriterion = 135 final Criteria.EthTypeCriterion ethTypeCriterion =
65 (Criteria.EthTypeCriterion) criterion; 136 (Criteria.EthTypeCriterion) criterion;
66 - result.put("ethType", ethTypeCriterion.ethType()); 137 + return root.put("ethType", ethTypeCriterion.ethType());
67 - break; 138 + }
139 + }
68 140
69 - case VLAN_VID: 141 + private static class FormatMetadata implements CriterionTypeFormatter {
142 + @Override
143 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
144 + final Criteria.MetadataCriterion metadataCriterion =
145 + (Criteria.MetadataCriterion) criterion;
146 + return root.put("metadata", metadataCriterion.metadata());
147 + }
148 + }
149 +
150 + private static class FormatVlanVid implements CriterionTypeFormatter {
151 + @Override
152 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
70 final Criteria.VlanIdCriterion vlanIdCriterion = 153 final Criteria.VlanIdCriterion vlanIdCriterion =
71 (Criteria.VlanIdCriterion) criterion; 154 (Criteria.VlanIdCriterion) criterion;
72 - result.put("vlanId", vlanIdCriterion.vlanId().toShort()); 155 + return root.put("vlanId", vlanIdCriterion.vlanId().toShort());
73 - break; 156 + }
157 + }
74 158
75 - case VLAN_PCP: 159 + private static class FormatVlanPcp implements CriterionTypeFormatter {
160 + @Override
161 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
76 final Criteria.VlanPcpCriterion vlanPcpCriterion = 162 final Criteria.VlanPcpCriterion vlanPcpCriterion =
77 (Criteria.VlanPcpCriterion) criterion; 163 (Criteria.VlanPcpCriterion) criterion;
78 - result.put("priority", vlanPcpCriterion.priority()); 164 + return root.put("priority", vlanPcpCriterion.priority());
79 - break; 165 + }
166 + }
80 167
81 - case IP_DSCP: 168 + private static class FormatIpDscp implements CriterionTypeFormatter {
169 + @Override
170 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
82 final Criteria.IPDscpCriterion ipDscpCriterion = 171 final Criteria.IPDscpCriterion ipDscpCriterion =
83 (Criteria.IPDscpCriterion) criterion; 172 (Criteria.IPDscpCriterion) criterion;
84 - result.put("ipDscp", ipDscpCriterion.ipDscp()); 173 + return root.put("ipDscp", ipDscpCriterion.ipDscp());
85 - break; 174 + }
175 + }
86 176
87 - case IP_ECN: 177 + private static class FormatIpEcn implements CriterionTypeFormatter {
178 + @Override
179 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
88 final Criteria.IPEcnCriterion ipEcnCriterion = 180 final Criteria.IPEcnCriterion ipEcnCriterion =
89 (Criteria.IPEcnCriterion) criterion; 181 (Criteria.IPEcnCriterion) criterion;
90 - result.put("ipEcn", ipEcnCriterion.ipEcn()); 182 + return root.put("ipEcn", ipEcnCriterion.ipEcn());
91 - break; 183 + }
92 - 184 + }
93 - case IP_PROTO:
94 - final Criteria.IPProtocolCriterion iPProtocolCriterion =
95 - (Criteria.IPProtocolCriterion) criterion;
96 - result.put("protocol", iPProtocolCriterion.protocol());
97 - break;
98 185
99 - case IPV4_SRC: 186 + private static class FormatIp implements CriterionTypeFormatter {
100 - case IPV4_DST: 187 + @Override
101 - case IPV6_SRC: 188 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
102 - case IPV6_DST:
103 final Criteria.IPCriterion iPCriterion = (Criteria.IPCriterion) criterion; 189 final Criteria.IPCriterion iPCriterion = (Criteria.IPCriterion) criterion;
104 - result.put("ip", iPCriterion.ip().toString()); 190 + return root.put("ip", iPCriterion.ip().toString());
105 - break; 191 + }
192 + }
106 193
107 - case TCP_SRC: 194 + private static class FormatTcp implements CriterionTypeFormatter {
108 - case TCP_DST: 195 + @Override
196 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
109 final Criteria.TcpPortCriterion tcpPortCriterion = 197 final Criteria.TcpPortCriterion tcpPortCriterion =
110 (Criteria.TcpPortCriterion) criterion; 198 (Criteria.TcpPortCriterion) criterion;
111 - result.put("tcpPort", tcpPortCriterion.tcpPort().byteValue()); 199 + return root.put("tcpPort", tcpPortCriterion.tcpPort().byteValue());
112 - break; 200 + }
201 + }
113 202
114 - case UDP_SRC: 203 + private static class FormatUdp implements CriterionTypeFormatter {
115 - case UDP_DST: 204 + @Override
205 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
116 final Criteria.UdpPortCriterion udpPortCriterion = 206 final Criteria.UdpPortCriterion udpPortCriterion =
117 (Criteria.UdpPortCriterion) criterion; 207 (Criteria.UdpPortCriterion) criterion;
118 - result.put("udpPort", udpPortCriterion.udpPort().byteValue()); 208 + return root.put("udpPort", udpPortCriterion.udpPort().byteValue());
119 - break; 209 + }
210 + }
120 211
121 - case SCTP_SRC: 212 + private static class FormatSctp implements CriterionTypeFormatter {
122 - case SCTP_DST: 213 + @Override
214 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
123 final Criteria.SctpPortCriterion sctpPortCriterion = 215 final Criteria.SctpPortCriterion sctpPortCriterion =
124 (Criteria.SctpPortCriterion) criterion; 216 (Criteria.SctpPortCriterion) criterion;
125 - result.put("sctpPort", 217 + return root.put("sctpPort",
126 sctpPortCriterion.sctpPort().byteValue()); 218 sctpPortCriterion.sctpPort().byteValue());
127 - break; 219 + }
220 + }
128 221
129 - case ICMPV4_TYPE: 222 + private static class FormatIcmpV4Type implements CriterionTypeFormatter {
223 + @Override
224 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
130 final Criteria.IcmpTypeCriterion icmpTypeCriterion = 225 final Criteria.IcmpTypeCriterion icmpTypeCriterion =
131 (Criteria.IcmpTypeCriterion) criterion; 226 (Criteria.IcmpTypeCriterion) criterion;
132 - result.put("icmpType", icmpTypeCriterion.icmpType()); 227 + return root.put("icmpType", icmpTypeCriterion.icmpType());
133 - break; 228 + }
229 + }
134 230
135 - case ICMPV4_CODE: 231 + private static class FormatIcmpV4Code implements CriterionTypeFormatter {
232 + @Override
233 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
136 final Criteria.IcmpCodeCriterion icmpCodeCriterion = 234 final Criteria.IcmpCodeCriterion icmpCodeCriterion =
137 (Criteria.IcmpCodeCriterion) criterion; 235 (Criteria.IcmpCodeCriterion) criterion;
138 - result.put("icmpCode", icmpCodeCriterion.icmpCode()); 236 + return root.put("icmpCode", icmpCodeCriterion.icmpCode());
139 - break; 237 + }
238 + }
140 239
141 - case IPV6_FLABEL: 240 + private static class FormatIpV6FLabel implements CriterionTypeFormatter {
241 + @Override
242 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
142 final Criteria.IPv6FlowLabelCriterion ipv6FlowLabelCriterion = 243 final Criteria.IPv6FlowLabelCriterion ipv6FlowLabelCriterion =
143 (Criteria.IPv6FlowLabelCriterion) criterion; 244 (Criteria.IPv6FlowLabelCriterion) criterion;
144 - result.put("flowLabel", 245 + return root.put("flowLabel", ipv6FlowLabelCriterion.flowLabel());
145 - ipv6FlowLabelCriterion.flowLabel()); 246 + }
146 - break; 247 + }
147 248
148 - case ICMPV6_TYPE: 249 + private static class FormatIcmpV6Type implements CriterionTypeFormatter {
250 + @Override
251 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
149 final Criteria.Icmpv6TypeCriterion icmpv6TypeCriterion = 252 final Criteria.Icmpv6TypeCriterion icmpv6TypeCriterion =
150 (Criteria.Icmpv6TypeCriterion) criterion; 253 (Criteria.Icmpv6TypeCriterion) criterion;
151 - result.put("icmpv6Type", icmpv6TypeCriterion.icmpv6Type()); 254 + return root.put("icmpv6Type", icmpv6TypeCriterion.icmpv6Type());
152 - break; 255 + }
256 + }
153 257
154 - case ICMPV6_CODE: 258 + private static class FormatIcmpV6Code implements CriterionTypeFormatter {
259 + @Override
260 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
155 final Criteria.Icmpv6CodeCriterion icmpv6CodeCriterion = 261 final Criteria.Icmpv6CodeCriterion icmpv6CodeCriterion =
156 (Criteria.Icmpv6CodeCriterion) criterion; 262 (Criteria.Icmpv6CodeCriterion) criterion;
157 - result.put("icmpv6Code", icmpv6CodeCriterion.icmpv6Code()); 263 + return root.put("icmpv6Code", icmpv6CodeCriterion.icmpv6Code());
158 - break; 264 + }
265 + }
159 266
160 - case IPV6_ND_TARGET: 267 + private static class FormatV6NDTarget implements CriterionTypeFormatter {
268 + @Override
269 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
161 final Criteria.IPv6NDTargetAddressCriterion ipv6NDTargetAddressCriterion 270 final Criteria.IPv6NDTargetAddressCriterion ipv6NDTargetAddressCriterion
162 = (Criteria.IPv6NDTargetAddressCriterion) criterion; 271 = (Criteria.IPv6NDTargetAddressCriterion) criterion;
163 - result.put("targetAddress", 272 + return root.put("targetAddress", ipv6NDTargetAddressCriterion.targetAddress().toString());
164 - ipv6NDTargetAddressCriterion.targetAddress().toString()); 273 + }
165 - break; 274 + }
166 275
167 - case IPV6_ND_SLL: 276 + private static class FormatV6NDTll implements CriterionTypeFormatter {
168 - case IPV6_ND_TLL: 277 + @Override
278 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
169 final Criteria.IPv6NDLinkLayerAddressCriterion ipv6NDLinkLayerAddressCriterion 279 final Criteria.IPv6NDLinkLayerAddressCriterion ipv6NDLinkLayerAddressCriterion
170 = (Criteria.IPv6NDLinkLayerAddressCriterion) criterion; 280 = (Criteria.IPv6NDLinkLayerAddressCriterion) criterion;
171 - result.put("mac", 281 + return root.put("mac", ipv6NDLinkLayerAddressCriterion.mac().toString());
172 - ipv6NDLinkLayerAddressCriterion.mac().toString()); 282 + }
173 - break; 283 + }
174 284
175 - case MPLS_LABEL: 285 + private static class FormatMplsLabel implements CriterionTypeFormatter {
286 + @Override
287 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
176 final Criteria.MplsCriterion mplsCriterion = 288 final Criteria.MplsCriterion mplsCriterion =
177 (Criteria.MplsCriterion) criterion; 289 (Criteria.MplsCriterion) criterion;
178 - result.put("label", mplsCriterion.label()); 290 + return root.put("label", mplsCriterion.label());
179 - break; 291 + }
292 + }
180 293
181 - case OCH_SIGID: 294 + private static class FormatOchSigId implements CriterionTypeFormatter {
295 + @Override
296 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
182 final Criteria.LambdaCriterion lambdaCriterion = 297 final Criteria.LambdaCriterion lambdaCriterion =
183 (Criteria.LambdaCriterion) criterion; 298 (Criteria.LambdaCriterion) criterion;
184 - result.put("lambda", lambdaCriterion.lambda()); 299 + return root.put("lambda", lambdaCriterion.lambda());
185 - break; 300 + }
301 + }
186 302
187 - case OCH_SIGTYPE: 303 + private static class FormatOchSigType implements CriterionTypeFormatter {
304 + @Override
305 + public ObjectNode formatCriterion(ObjectNode root, Criterion criterion) {
188 final Criteria.OpticalSignalTypeCriterion opticalSignalTypeCriterion = 306 final Criteria.OpticalSignalTypeCriterion opticalSignalTypeCriterion =
189 (Criteria.OpticalSignalTypeCriterion) criterion; 307 (Criteria.OpticalSignalTypeCriterion) criterion;
190 - result.put("signalType", opticalSignalTypeCriterion.signalType()); 308 + return root.put("signalType", opticalSignalTypeCriterion.signalType());
191 - break;
192 -
193 - default:
194 - // Don't know how to format this type
195 - log.info("Cannot convert criterion of type {} to JSON",
196 - criterion.type());
197 - break;
198 } 309 }
310 + }
311 +
312 + @Override
313 + public ObjectNode encode(Criterion criterion, CodecContext context) {
314 + checkNotNull(criterion, "Criterion cannot be null");
315 +
316 + final ObjectNode result = context.mapper().createObjectNode()
317 + .put("type", criterion.type().toString());
318 +
319 + CriterionTypeFormatter formatter =
320 + checkNotNull(
321 + formatMap.get(criterion.type()),
322 + "No formatter found for criterion type "
323 + + criterion.type().toString());
199 324
200 - return result; 325 + return formatter.formatCriterion(result, criterion);
201 } 326 }
202 } 327 }
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onosproject.codec.impl;
17 +
18 +import java.util.EnumMap;
19 +
20 +import org.junit.Test;
21 +import org.onosproject.net.flow.criteria.Criterion;
22 +
23 +import static org.onlab.junit.TestUtils.getField;
24 +import static org.hamcrest.MatcherAssert.assertThat;
25 +import static org.hamcrest.Matchers.*;
26 +
27 +/**
28 + * Unit tests for criterion codec.
29 + */
30 +public class CriterionCodecTest {
31 +
32 + /**
33 + * Checks that all criterion types are covered by the codec.
34 + */
35 + @Test
36 + public void checkCriterionTypes() throws Exception {
37 + CriterionCodec codec = new CriterionCodec();
38 + EnumMap<Criterion.Type, Object> formatMap = getField(codec, "formatMap");
39 + assertThat(formatMap, notNullValue());
40 +
41 + for (Criterion.Type type : Criterion.Type.values()) {
42 + assertThat("Entry not found for " + type.toString(),
43 + formatMap.get(type), notNullValue());
44 + }
45 + }
46 +}