pax_global_header00006660000000000000000000000064144500044060014507gustar00rootroot0000000000000052 comment=650cd3e6bc1ea89f781b5b3d87dc0341d471922d java-binary-block-parser-2.0.6/000077500000000000000000000000001445000440600163015ustar00rootroot00000000000000java-binary-block-parser-2.0.6/.gitignore000066400000000000000000000020151445000440600202670ustar00rootroot00000000000000/**/target/ /target/ /nbproject/ /**/.idea/ *.iml /jbbp-plugins/jbbp-gradle/.gradle/ /jbbp-plugins/jbbp-gradle/build/ /jbbp/BenchmarkList /jbbp/CompilerHints /jbbp/nb-configuration.xml /jbbp-plugins/**/target/ /jbbp/jmh-out.txt /jbbp/jmh-result.json /jbbp-plugins/jbbp-gradle/.settings/ /jbbp-plugins/jbbp-gradle/bin/ /jbbp-plugins/jbbp-gradle/.classpath /jbbp-plugins/jbbp-gradle/.project /**/.vscode/ /jbbp-plugins/jbbp-gradle/gradle/wrapper/ /jbbp-plugins/jbbp-gradle/gradlew /jbbp-plugins/jbbp-gradle/gradlew.bat /jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/build/ /jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/.gradle/ /jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/.gradle/ /jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/build/ /jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/.gradle/ /jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/build/ /jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/.gradle/ /jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/build/ java-binary-block-parser-2.0.6/.projectKnowledge/000077500000000000000000000000001445000440600216655ustar00rootroot00000000000000java-binary-block-parser-2.0.6/.projectKnowledge/JBBP.mmd000066400000000000000000000062451445000440600231100ustar00rootroot00000000000000[Scia Reto](https://sciareto.org) mind map > __version__=`1.1`,showJumps=`true` --- # Java Binary
Block Parser
v 2\.0\.6 ## License > fillColor=`#33CC00`,leftSide=`true` ### Apache License 2\.0 > fillColor=`#99FF00` ## Compatibility > fillColor=`#CCCCFF`,leftSide=`true` ### Java 1\.8\+ > fillColor=`#3399FF`,textColor=`#FFFFCC` ### Android 3\.0\+ > fillColor=`#3399FF`,textColor=`#FFFFCC` ## does\.\.\. ### have small size
and no 3th part dependencies > fillColor=`#33FF66` ### have strong
embedded DSL with\.\.\. > fillColor=`#CCCCFF`,topicLinkUID=`15292664291A` - TOPIC
1528DC7A000A
#### signed and
unsigned data types > fillColor=`#CCCCFF` #### custom processing of
non\-standard data types > fillColor=`#CCCCFF` #### arrays and
structures > fillColor=`#CCCCFF` #### expressions and another field value
as data length > fillColor=`#CCCCFF` #### aligning and skipping data > fillColor=`#CCCCFF` ### have support for \.\.\. > fillColor=`#FFCCCC` #### all bit orders > fillColor=`#FFCCCC` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitOrder.java
#### all byte orders > fillColor=`#FFCCCC` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPByteOrder.java
### have annotation to\.\. > fillColor=`#CCFFCC` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/Bin.java
#### map parsed data to class fields > fillColor=`#CCFFCC` #### serialize field data into stream > fillColor=`#CCFFCC` ### can make text representation
of parsed data > fillColor=`#FFFFCC` - TOPIC
152926734C4A
### conversion of script into
Java classes > fillColor=`#AAFF82`,topicLinkUID=`15E33F334A5A` - TOPIC
15E33F384B4A
## Main parts > fillColor=`#00FFFF`,leftSide=`true` ### Java source generator > fillColor=`#D1FF9C`,topicLinkUID=`15E33F384B4A` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/conversion/JBBPToJavaConverter.java
#### Maven plugin > fillColor=`#D1FF9C` - FILE
jbbp-plugins/jbbp-maven
#### Gradle plugin > fillColor=`#D1FF9C`,leftSide=`true` - FILE
jbbp-plugins/jbbp-gradle
### Streams > fillColor=`#FF99FF` #### JBBPBitInputStream > fillColor=`#FF99FF` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitInputStream.java
#### JBBPBitOutputStream > fillColor=`#FF99FF`,leftSide=`true` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitOutputStream.java
### DSL > fillColor=`#66FF00`,leftSide=`true`,topicLinkUID=`1528DC7A000A` #### JBBPParser > fillColor=`#66FF00` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/JBBPParser.java
#### JBBPDslBuilder > fillColor=`#66FF00`,leftSide=`true` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPDslBuilder.java
### Write binary
data > fillColor=`#00CCCC`,leftSide=`true` #### JBBPOut > fillColor=`#00CCCC` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPOut.java
### Convert To Text > fillColor=`#FFFFCC`,leftSide=`true`,topicLinkUID=`152926734C4A` #### JBBPTextWriter > fillColor=`#FFFFCC` - FILE
jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPTextWriter.java
java-binary-block-parser-2.0.6/.projectKnowledge/JBBP_Types.mmd000066400000000000000000000127671445000440600243020ustar00rootroot00000000000000Mind Map generated by NB MindMap plugin > __version__=`1.1`,showJumps=`true` --- # Fields ## Primitive
types ### allowed types > fillColor=`#FF9999` #### bit\[:1\.\.7\] > fillColor=`#9999FF` ##### 1 bit by default > fillColor=`#FF66FF` #### bool > fillColor=`#9999FF` ##### 8 bit field > fillColor=`#99FFFF` ###### FALSE \(==0\) > fillColor=`#CCFFCC` ####### 0 in write operations > fillColor=`#CCFFCC` ###### TRUE \(\!=0\) > fillColor=`#CCFFCC` ####### 1 in write operations > fillColor=`#CCFFCC` #### byte > fillColor=`#9999FF` ##### 8 bit signed field > fillColor=`#99FFFF` #### ubyte > fillColor=`#9999FF` ##### 8 bit unsigned field > fillColor=`#99FFFF` #### short > fillColor=`#9999FF` ##### 16 bit signed field > fillColor=`#99FFFF` #### ushort > fillColor=`#9999FF` ##### 16 bit unsigned field > fillColor=`#99FFFF` #### int > fillColor=`#9999FF` ##### 32 bit signed field > fillColor=`#99FFFF` #### uint > fillColor=`#9999FF` ##### 32 bit unsigned field > fillColor=`#99FFFF` ###### requires long representation > fillColor=`#FF6666`,mmd.emoticon=`error` ###### can throw error in expression if value can\\t be represented as zero or positive int > fillColor=`#FF6666`,mmd.emoticon=`error` #### long > fillColor=`#9999FF` ##### 64 bit signed field > fillColor=`#99FFFF` #### floatj > fillColor=`#FFFE9F` ##### 32 bit signed field
\(Java float format\) > fillColor=`#99FFFF` #### doublej > fillColor=`#FFFE9F` ##### 64 bit signed field
\(Java double format\) > fillColor=`#99FFFF` #### stringj > fillColor=`#FFFE9F` ##### UTF\-8 string with length info, NULL value is allowed > fillColor=`#99FFFF` #### var > fillColor=`#00FAFF` ##### custom field to be processed by specific code > fillColor=`#00FAFF` #### val > fillColor=`#71B9FF` - TOPIC
164BC0A19BEA
##### Virtual int field which value is calculated by expression > fillColor=`#71B9FF` #### CUSTOM TYPES > fillColor=`#FFF56B` ##### list of types must be provided by external processor
and the processor must provide read operations > fillColor=`#FFF56B` ### format > fillColor=`#FF9999` #### \ \[\\]; > fillColor=`#FFCC00` ##### int; > fillColor=`#99FF99` ##### int data; > fillColor=`#99FF99` ##### bit:3 bits; > fillColor=`#99FF99` ##### bit:\(a\+1\) b; > fillColor=`#99FF99` ## Complex types ### Arrays > fillColor=`#9999FF` #### \ '\['\|\|\_'\]' \[\\]; > fillColor=`#FFCC00`,topicLinkUID=`152971A8B02A` ##### \ is hardcoded
number of items > fillColor=`#6666FF`,textColor=`#CCFFCC` ###### bit:3 \[1244\]; > fillColor=`#99FF99` ##### \ is some expression
provides length of the array > fillColor=`#6666FF`,textColor=`#CCFFCC` ###### int length; byte \[length\*2\] data; > fillColor=`#99FF99` ##### '\_' means read whole stream
untill the stream end > fillColor=`#6666FF`,textColor=`#CCFFCC` ###### it is impossible to have fields after > fillColor=`#FF3300`,textColor=`#FFFFFF` ###### long header; byte \[\_\] allData; > fillColor=`#99FF99` ### Structures > fillColor=`#9999FF` #### \[\\]\['\['array\_length|expression|\_'\]'\]\{ \\.\.\. \} > fillColor=`#FFCC00` - TOPIC
152971A8B02A
##### \{bit;\} > fillColor=`#99FF99` ##### header \{ubyte sections; ubyte datalen;\}
sections \[header\.sections\]\{byte \[header\.datalen\] data;\} > fillColor=`#99FF99` ## Special > leftSide=`true` ### Align stream > fillColor=`#9999FF` #### align\[:number\_of\_bytes|:\(expression\_in\_parentheses\)\] > fillColor=`#FFCC00` ##### align; byte \[123\]; > fillColor=`#99FF99`,leftSide=`true` ##### align:4; byte\[123\]; > fillColor=`#99FF99`,leftSide=`true` ##### align:\($$\+45\); int data; > fillColor=`#99FF99`,leftSide=`true` #### by default
align to 1 byte > fillColor=`#FF66FF` #### Skips bytes to align the byte
counter to needed value > fillColor=`#FFFFCC` - TOPIC
1529753E90BA
### Byte counter > fillColor=`#FF9999`,topicLinkUID=`1529753E90BA` #### $$ in expressions > fillColor=`#FF9999` #### can be reset by special command > fillColor=`#FFCB2F`,leftSide=`true` ##### reset$$; > fillColor=`#FFCB2F` ### Skip bytes > fillColor=`#9999FF` #### Skips needed number of bytes > fillColor=`#FFFFCC` - TOPIC
1529753E90BA
#### by default
skip 1 byte > fillColor=`#FF66FF` #### skip\[:number\_of\_bytes|\(expression\_in\_parentheses\)\] > fillColor=`#FFCC00` ##### skip; int; > fillColor=`#99FF99` ##### skip:34; byte; > fillColor=`#99FF99`,leftSide=`true` ##### skip:\($$\*2\); int data; > fillColor=`#99FF99`,leftSide=`true` ### Virtual field > fillColor=`#71B9FF`,leftSide=`true`,topicLinkUID=`164BC0A19BEA` #### val:\(\\) \ > fillColor=`#FFC522` ##### val:45 a; > fillColor=`#63FF8F` ##### val:\(a\+b\) c; > fillColor=`#63FF8F`,leftSide=`true` #### must be named > fillColor=`#FFFF00` #### allows to set a value
calculated by expression > fillColor=`#FFFF00`,leftSide=`true` #### doesn't take part in read\-write > fillColor=`#FFFF00`,leftSide=`true` ## Byte order > leftSide=`true` ### \[\<|\>\]\\.\.\. > fillColor=`#FFCC33` #### \> > fillColor=`#FF99FF` ##### Big\-Endinan ###### \>int \[26\] data; > fillColor=`#99FF99` ##### It is the default value and
can be omitted > fillColor=`#FF6666` #### \< > fillColor=`#FF99FF` ##### Little\-Endian > leftSide=`true` ###### \ fillColor=`#99FF99` java-binary-block-parser-2.0.6/LICENSE000066400000000000000000000261351445000440600173150ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. java-binary-block-parser-2.0.6/README.md000066400000000000000000000434111445000440600175630ustar00rootroot00000000000000![JBBP Logo](https://github.com/raydac/java-binary-block-parser/blob/master/logo.png) [![License Apache 2.0](https://img.shields.io/badge/license-Apache%20License%202.0-green.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![Maven central](https://maven-badges.herokuapp.com/maven-central/com.igormaznitsa/jbbp/badge.svg)](http://search.maven.org/#artifactdetails|com.igormaznitsa|jbbp|2.0.6|jar) [![Java 1.8+](https://img.shields.io/badge/java-1.8%2b-green.svg)](http://www.oracle.com/technetwork/java/javase/downloads/index.html) [![Android 3.0+](https://img.shields.io/badge/android-3.0%2b-green.svg)](http://developer.android.com/sdk/index.html) [![PayPal donation](https://img.shields.io/badge/donation-PayPal-cyan.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=AHWJHJFBAWGL2) [![YooMoney donation](https://img.shields.io/badge/donation-Yoo.money-blue.svg)](https://yoomoney.ru/to/41001158080699) # Introduction Java has some embedded features to parse binary data (for instance ByteBuffer), but sometime it is needed to work on bit level and describe binary structures through some DSL(domain specific language). I was impressed by the [the Python Struct package](https://docs.python.org/2/library/struct.html) package and wanted to get something like that for Java. So I developed the JBBP library.
![Use cases](https://github.com/raydac/java-binary-block-parser/blob/master/docs/jbbp_mm.png) # Change log - __2.0.6 (01-jul-2023)__ - [#42](https://github.com/raydac/java-binary-block-parser/issues/42) unexpected exception during JBBPDslBuilder.AnnotatedClass method call - improved tests - __2.0.5 (17-jun-2023)__ - [#41](https://github.com/raydac/java-binary-block-parser/issues/41) fix for Gradle plug-in - improved tests [Full changelog](https://github.com/raydac/java-binary-block-parser/blob/master/changelog.txt) # Maven dependency The Framework has been published in the Maven Central and can be easily added as a dependency ``` com.igormaznitsa jbbp 2.0.6 ``` the precompiled library jar, javadoc and sources also can be downloaded directly from [the Maven central.](https://search.maven.org/artifact/com.igormaznitsa/jbbp/2.0.6/jar) # Hello world The library is very easy in use because in many cases only two its classes are needed - com.igormaznitsa.jbbp.JBBPParser (for data parsing) and com.igormaznitsa.jbbp.io.JBBPOut (for binary block writing). Both these classes work over low-level IO classes - com.igormaznitsa.jbbp.io.JBBPBitInputStream and com.igormaznitsa.jbbp.io.JBBPBitOutputStream, those bit stream classes are the core of the library. The easiet use case shows parsing of whole byte array to bits. ```Java byte[]parsedBits=JBBPParser.prepare("bit:1 [_];").parse(new byte[]{1,2,3,4,5}). findFieldForType(JBBPFieldArrayBit.class).getArray(); ``` On start it was the only functionality but then I found that it is no so comfort way to get result, so that added some mapping of parsed result to pre-instantiated object. It works slower, because uses a lot of Java reflection but much easy in some cases. ```Java class Parsed { @Bin(type = BinType.BIT_ARRAY) byte[] parsed; } Parsed parsedBits = JBBPParser.prepare("bit:1 [_] parsed;").parse(new byte[] {1, 2, 3, 4, 5}).mapTo(new Parsed()); ``` # Relative speed of different approaches in parsing Mainly I developed the library to help in my development of ZX-Spectrum emulator where I needed to work with data snapshots containing data on bit level. It didn't need much productivity in work. But since 1.3.0 version I added way to generate Java classes from JBBP scripts, such classes work in about five times faster than dynamic parsing and mapping approaches. ![JMH results](https://github.com/raydac/java-binary-block-parser/blob/master/docs/jmh_results.png) Chart below compares speed of three provided ways to parse data with JBBP: * __Dynamic__ - the basic parsing through interpretation of prepared JBBP DSL script. It is no so fast, but provide way to generate parsers on fly from text description. * __Dynamic + map to class__ - parsing through interpretation of parsed JBBP script and mapping of parsed data to pre-instantiated class instance. It provides compfortable way to work with data and get result but uses a lot of Java reflection features and so fast. * __Static class__ - the fastest way of JBBP use, some JBBP script is translated into Java class. There is no any interpretation or reflection operators so that it is very fast. [You can take a look at auxiliary class which I use in tests](https://github.com/raydac/java-binary-block-parser/blob/master/jbbp/src/test/java/com/igormaznitsa/jbbp/testaux/AbstractJBBPToJavaConverterTest.java) . # Generate sources from JBBP scripts Since 1.3.0 version, the library provides Java source generator for JBBP scripts, __(keep in mind that generated sources anyway depends on JBBP library and it is needed for their work)__. For instance such snippet can be used to generate Java classes from a JBBP script. It also can generate multiple classes. ```Java JBBPParser parser=JBBPParser.prepare("byte a; byte b; byte c;"); List generated=parser.convertToSrc(TargetSources.JAVA,"com.test.jbbp.gen.SomeClazz"); for(ResultSrcItem i:generated){ for(Map.Entry j:i.getResult().entrySet()) { System.out.println("Class file name "+j.getKey()); System.out.println("Class file content "+j.getValue()); } } ``` also there are developed plug-ins for both Maven and Gradle to generate sources from JBBP scripts during source generate phase. in Maven it can be used through snippet: ```xml com.igormaznitsa jbbp-maven-plugin 2.0.6 gen-jbbp-src generate ``` By default the maven plug-in looks for files with `jbbp` extension in `src/jbbp` folder of the project (it can be changed through plug-in configuration) and produces resulting java classes into `target/generated-sources/jbbp` folder. [For instance, I use such approach in my ZX-Poly emulator](https://github.com/raydac/zxpoly/tree/master/zxpoly-emul/src/jbbp) . # More complex example with features added as of 1.1.0 Example below shows how to parse a byte stream written in non-standard MSB0 order (Java has LSB0 bit order) into bit fields, then print its values and pack fields back: ```Java class Flags { @Bin(order = 1, name = "f1", type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_1, comment = "It's flag one") byte flag1; @Bin(order = 2, name = "f2", type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_2, comment = "It's second flag") byte flag2; @Bin(order = 3, name = "f3", type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_1, comment = "It's 3th flag") byte flag3; @Bin(order = 4, name = "f4", type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_4, comment = "It's 4th flag") byte flag4; } final int data = 0b10101010; Flags parsed = JBBPParser.prepare("bit:1 f1; bit:2 f2; bit:1 f3; bit:4 f4;", JBBPBitOrder.MSB0).parse(new byte[]{(byte)data}).mapTo(new Flags()); assertEquals(1,parsed.flag1); assertEquals(2,parsed.flag2); assertEquals(0,parsed.flag3); assertEquals(5,parsed.flag4); System.out.println(new JBBPTextWriter().Bin(parsed).Close().toString()); assertEquals(data, JBBPOut.BeginBin(JBBPBitOrder.MSB0).Bin(parsed).End().toByteArray()[0] & 0xFF); ``` The Example will print in console the text below ``` ;-------------------------------------------------------------------------------- ; START : Flags ;-------------------------------------------------------------------------------- 01; f1, It's flag one 02; f2, It's second flag 00; f3, It's 3th flag 05; f4, It's 4th flag ;-------------------------------------------------------------------------------- ; END : Flags ;-------------------------------------------------------------------------------- ``` # Fields Each field can have case insensitive name which must not contain '.' (because dot is reserved for links to structure field values) and '#'(because it is also reserved for internal library use). A field name must not be started with either number or chars '$' and '_'. *Keep in mind that field names are case insensitive!* ``` int someNamedField; byte field1; byte field2; byte field3; ``` ![JBBP field format, types and examples](https://github.com/raydac/java-binary-block-parser/blob/master/docs/jbbp_fields.png) ## Primitive types JBBP supports full set of Java numeric primitives with some extra types like ubyte and bit. ![JBBP field format, types and examples](https://github.com/raydac/java-binary-block-parser/blob/master/docs/jbbp_primitives.png) ## Complex types JBBP provides support both arrays and structures. __In expressions you can use links only to field values which already read!__ ![JBBP field format, types and examples](https://github.com/raydac/java-binary-block-parser/blob/master/docs/jbbp_complex_types.png) ## Custom types It is possible to define processors for custom data types. For instance you can take a look at [case processing three byte unsigned integer types](https://github.com/raydac/java-binary-block-parser/blob/master/jbbp/src/test/java/com/igormaznitsa/jbbp/it/CustomThreeByteIntegerTypeTest.java) . ### Support of float, double and String types Since 1.4.0 in JBBP was added support of Java float, double and String values. Because they have specific format, they are named as `doublej`, `floatj` and `stringj`. ## Variable fields If you have some data which internal structure is undefined and variable then you can use the `var` type to mark such field and provide custom processor to read data of such value. Processor should implement interface [JBBPVarFieldProcessor](https://github.com/raydac/java-binary-block-parser/blob/master/src/main/java/com/igormaznitsa/jbbp/JBBPVarFieldProcessor.java) instance. ``` final JBBPParser parser = JBBPParser.prepare("short k; var; int;"); final JBBPIntCounter counter = new JBBPIntCounter(); final JBBPFieldStruct struct = parser.parse(new byte[]{9, 8, 33, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { public JBBPAbstractArrayField readVarArray(JBBPBitInputStream inStream, int arraySize, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } public JBBPAbstractField readVarField(JBBPBitInputStream inStream, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { final int value = inStream.readByte(); return new JBBPFieldByte(fieldName, (byte) value); } }, null); ``` *NB! Some programmers trying to use only parser for complex data, it is a mistake. In the case it is much better to have several easy parsers working with the same [JBBPBitInputStream](https://github.com/raydac/java-binary-block-parser/blob/master/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitInputStream.java) instance, it allows to keep decision points on Java level and make solution easier.* ## Special types Special types makes some actions to skip data in input stream ![JBBP field format, types and examples](https://github.com/raydac/java-binary-block-parser/blob/master/docs/jbbp_special_fields.png) ## Byte order Multi-byte types can be read with different byte order. ![JBBP field format, types and examples](https://github.com/raydac/java-binary-block-parser/blob/master/docs/jbbp_byteorder.png) # Expressions Expressions are used for calculation of length of arrays and allow brackets and integer operators which work similar to Java operators: - Arithmetic operators: +,-,%,*,/,% - Bit operators: &,|,^,~ - Shift operators: <<,>>,>>> - Brackets: (, ) Inside expression you can use integer numbers and named field values through their names (if you use fields from the same structure) or paths. Keep in your mind that you can't use array fields or fields placed inside structure arrays. ``` int field1; struct1 { int field2; } byte [field1+struct1.field2] data; ``` # Commentaries You can use commentaries inside a parser script, the parser supports the only comment format and recognizes as commentaries all text after '//' till the end of line. ``` int; // hello commentaries byte field; ``` # Expression macroses Inside expression you can use field names and field paths, also you can use the special macros '$$' which represents the current input stream byte counter, all fields started with '$' will be recognized by the parser as special user defined variables and it will be requesting them from special user defined provider. If the array size contains the only '_' symbol then the field or structure will not have defined size and whole stream will be read. # How to get result of parsing The Result of parsing is an instance of com.igormaznitsa.jbbp.model.JBBPFieldStruct class which represents the root invisible structure for the parsed data and you can use its inside methods to find desired fields for their names, paths or classes. All Fields are successors of com.igormaznitsa.jbbp.model.JBBPAbstractField class. To increase comfort, it is easier to use mapping to classes when the mapper automatically places values to fields of a Java class. # Example Example below shows how to parse a PNG file through JBBP parser: ```Java final InputStream pngStream = getResourceAsInputStream("picture.png"); try { final JBBPParser pngParser = JBBPParser.prepare( "long header;" + "// chunks\n" + "chunk [_]{" + " int length; " + " int type; " + " byte[length] data; " + " int crc;" + "}" ); JBBPFieldStruct result = pngParser.parse(pngStream); assertEquals(0x89504E470D0A1A0AL,result.findFieldForNameAndType("header",JBBPFieldLong.class).getAsLong()); JBBPFieldArrayStruct chunks = result.findFieldForNameAndType("chunk", JBBPFieldArrayStruct.class); String [] chunkNames = new String[]{"IHDR","gAMA","bKGD","pHYs","tIME","tEXt","IDAT","IEND"}; int [] chunkSizes = new int[]{0x0D, 0x04, 0x06, 0x09, 0x07, 0x19, 0x0E5F, 0x00}; assertEquals(chunkNames.length,chunks.size()); for(int i=0;i klazz){ return klazz == Chunk.class ? new Chunk() : null; } } final Png png = pngParser.parse(pngStream).mapTo(new Png()); ``` Example shows how to parse TCP frame: ```Java final JBBPParser tcpParser = JBBPParser.prepare( "skip:34; // skip bytes till the frame\n" + "ushort SourcePort;" + "ushort DestinationPort;" + "int SequenceNumber;" + "int AcknowledgementNumber;" + "bit:1 NONCE;" + "bit:3 RESERVED;" + "bit:4 HLEN;" + "bit:1 FIN;" + "bit:1 SYN;" + "bit:1 RST;" + "bit:1 PSH;" + "bit:1 ACK;" + "bit:1 URG;" + "bit:1 ECNECHO;" + "bit:1 CWR;" + "ushort WindowSize;" + "ushort TCPCheckSum;" + "ushort UrgentPointer;" + "byte [$$-34-HLEN*4] Option;" + "byte [_] Data;" ); final JBBPFieldStruct result = pngParser.parse(tcpFrameStream); ``` # F.A.Q. ## Is it possible to use `@Bin` annotations for parsing and not only mapping? `@Bin` annotations is used only for mapping and data writing, but there is special class [JBBPDslBuilder](/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPDslBuilder.java) which can convert `@Bin` marked class into JBBP script, for instance: ```java JBBPDslBuilder.Begin().AnnotatedClass(SomeBinAnnotatetClass.class).End(true); ``` ## My Binary data format is too complex one to be decoded by a JBBP script No problems! JBBP parser works over [com.igormaznitsa.jbbp.io.JBBPBitInputStream](/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitInputStream.java) class which can be used directly and allows read bits, bytes, count bytes and align data. For writing there is similar class [JBBPBitOutputStream](https://github.com/raydac/java-binary-block-parser/blob/master/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitOutputStream.java) . ## I want to make a binary data block instead of parsing! Library provides special helper [JBBPOut](/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPOut.java). The helper allows to generate binary blocks and provides some kind of DSL ```Java import static com.igormaznitsa.jbbp.io.JBBPOut.*; ... final byte [] array = BeginBin(). Bit(1, 2, 3, 0). Bit(true, false, true). Align(). Byte(5). Short(1, 2, 3, 4, 5). Bool(true, false, true, true). Int(0xABCDEF23, 0xCAFEBABE). Long(0x123456789ABCDEF1L, 0x212356239091AB32L). End().toByteArray(); ``` java-binary-block-parser-2.0.6/changelog.txt000066400000000000000000000127471445000440600210040ustar00rootroot000000000000002.0.6 (01-jul-2023) - [#42](https://github.com/raydac/java-binary-block-parser/issues/42) unexpected exception during JBBPDslBuilder.AnnotatedClass method call - improved tests 2.0.5 (17-jun-2023) - [#41](https://github.com/raydac/java-binary-block-parser/issues/41) fix for Gradle plug-in - improved tests 2.0.4 (01-aug-2022) - added unsigned 32 bit integer type __uint__ [#37](https://github.com/raydac/java-binary-block-parser/issues/37) - provided way to filter fields for mapping operations and write object operations [#36](https://github.com/raydac/java-binary-block-parser/issues/36) 2.0.3 (03-jan-2021) - added service methods `JBBPUtils.traceData` to print dump of an input stream into a PrintStream - improved `JBBPTokenizerException` to show marked error position [#30](https://github.com/raydac/java-binary-block-parser/issues/30) 2.0.2 (22-aug-2020) - added `JBBPOut#Bin` variant to override `@Bin` annotation fields in written objects. - [#28](https://github.com/raydac/java-binary-block-parser/issues/28) added `JBBPOut#BinForceByteOrder` to override byte order defined in `@Bin` annotations of written object. 2.0.1 - [#26](https://github.com/raydac/java-binary-block-parser/issues/26) fixed bug in array write with MSB0 2.0.0 - __removed DslBinCustom annotation, use @Bin annotation instead__ - __renamed attributes of @Bin annotation to their correct form__ - __reworked object mapping system, removed hacks to instantiate classes, now only mapping to objects allowed, support of private fields mapping is removed__ - __minimal JDK version now 1.8+__ - __minimal Android API now 3.0+__ - added support of getters and setters into mapping - added `Object newInstance(Class)` method support of mapped classes to generate local class member instances - added generating of `makeFIELD()` method for structure types in Java class converter - refactoring 1.4.1 - fixed incompatibility in tokenizer regex syntax for Android SDK [#23](https://github.com/raydac/java-binary-block-parser/issues/23) - added DslBinCustom annotation to provide way to mark custom type fields for JBBPDslBuilder - fixed NPE in JBBPDslBuilder for not-provided outBitNumber attribute in annotated field marked as type BIT or BIT_ARRAY [#20](https://github.com/raydac/java-binary-block-parser/issues/20) - naming of fields has been made more tolerant, now it is allowed to have field names with names similar to data types - improved check of field names in JBBPDslBuilder [#21](https://github.com/raydac/java-binary-block-parser/issues/21) 1.4.0 - added type `val` which allows to create virtual field with calculated value, can play role of variable in scripts - `val` and `var` have been added into reserved words and can't be used as field names - added field `outByteOrder` attribute to `Bin` annotation, it affects logic of `JBBPOut#Bin` for output of annotated objects which fields should be saved with different byte order - removed deprecated method `JBBPFinderException#getNameOrPath` - added [auxiliary class to build JBBP script](https://github.com/raydac/java-binary-block-parser/blob/master/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPDslBuilder.java) - added flag `JBBPParser#FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO` to recognize negative expression result as zero - improved Java 6 class source generator to process FLAG_SKIP_REMAINING_FIELDS_IF_EOF for structure fields - added stable automatic module name `igormaznitsa.jbbp` into manifest file - added support of float, double and string java types, as `floatj`,`doublej` and `stringj` 1.3.0 - fixed NPE when referencing a JBBPCustomFieldTypeProcessor parsed field - added Maven plugin to generate sources from JBBP scripts - added Gradle plugin to generate sources from JBBP scripts - added extra byte array reading writing methods with byte order support into JBBPBitInputStream and JBBPBitOutputStream - added converter of compiled parser data into Java class sources (1.6+) - added method to read unsigned short values as char [] into JBBPBitInputStream - Class version target has been changed to Java 1.6 - fixed compatibility of tests with Java 1.6 - Minor refactoring 1.2.0 - Refactoring - Improved tree of JBBP exceptions - Fixed NPE in JBBPTextWriter for String field mapped to byte array - Added support of custom field types through JBBPCustomFieldTypeProcessor - Added JBBPCustomFieldTypeProcessorAggregator, auxiliary class to join several JBBPCustomFieldTypeProcessors - Fixed JBBPTextWriter, added support of logging for JBBPAbstractField objects - Added support of payload objects in JBBPAbstractField - Improved inside script compiler and interpreter to support future extensions. - Fixed expression evaluator to support single char field names in expressions. - Added support of expressions in extra field numeric data part (example bit:(field*2)) 1.1.0 - Added support to write mapped classes into JBBPOut - Added JBBPTextWriter to log binary data as text with commentaries,tabs and separators - Fixed read byte counter, now it counts only fully processed bytes, if only several bits have been read from byte then the byte will not be counted until whole read - Fixed static fields including in mapping processes if class has marked by default Bin annotation - Added flag JBBPParser#FLAG_SKIP_REMAINING_FIELDS_IF_EOF to ignore remaining fields during parsing if EOF without exception - Added flag JBBPMapper#FLAG_IGNORE_MISSING_VALUES to ignore mapping for values which are not found in parsed source - Added new auxiliary methods in JBBPUtils 1.0 - The Initial version java-binary-block-parser-2.0.6/docs/000077500000000000000000000000001445000440600172315ustar00rootroot00000000000000java-binary-block-parser-2.0.6/docs/jbbp_byteorder.png000066400000000000000000000447371445000440600227520ustar00rootroot00000000000000PNG  IHDRcǞIIDATx xT!ADDD9HC!! H@Q vTV Z-X[ G)x@}|x(Z}C"X~Zӕ֜YYkfd&}0 ^x/TE>P>4XW!>>@@@x:͜9sԩS?y]yޓyyy54+Q)++dEEڱc:qݻUyyW^kdzVcMNyyɓ'P:J/hÇڭ\wrf7բeKJ;8b@cx~9m͛7=4߾:nTVVn9 ')--}1u[O&L*~B> H߰#Fmr+ao+JEw{`zNKףK*{\a dj^_y>.~sΨ‡m۶HiбFa*̓FVp\m\4ķ!6; M{mw4B/ .^"w@ :igf}d.0%~cփfUuY%fMYfu HJ1|.ڞe +m9NU!#av #OmJDת~CGGvZaVRO@hf:9nݺ79$86ݐ`Ȳr2^bprÏ=Vߨr]\gg 4{vʛ0%h f'.!]Hha}c %FM^XFQӛBzQLB g…OEŌ3vEљzKAQ2zkfέ5$p,)'A6%L +4NIy:Kmva!ZjS z!yf:41·VX1 **bY_5C;ŻWSQQOi|d*a&FX3 +6 @fϞ]Y\\|Upȑo^ggfR{ ͚k#ެ Fp7}G&McrرG->/U5sHϨ ђw *EEEe+**~I(U0]v}7o޼7ꐠ 6 dg^r!Q9 n?~^L8y¬c‡>84QG!f]/ ?|e5sC/3@@wb=9;fum | |7 ^, | |HJ:fVoC"L( QbG1 | |:`V{‡DxzlykY{Ǘ@@5_b z$*jLdɏ̪2|@@ ӌ%Ϭ/j@@?7jz?S:U5F:j-@l0ڨY wTD&7k/OHz!TD(1j‡TD0j‡< Q> !~c~SaQ>TDШ VTD6kOHf3%OERQT 6X y*+|x፿P .˭X6O Y [y*0j- | |$B[SADÇ~6Fr{y*h0į m~@@@`;$&֕Z y*Pi5²y*0jMhۉ>@(a?HnբeKգg/5Ʋ.4=FX%OE߾eWm;) F<>H{znu+^ ժukqF ?_PݺPiiTj=ԔW;?P=3a!!_/ZA~\۶ujKK}EK>47E)^Th*Zqviwa:p3\b|O/xζ2ƅ믥s|_߽Qռys5xp?s)R3Ͱ dN:Ex>PO_ ᑧ6):ۤ$ %|x7uH"䦭z+ץw[mv^[wm2iH!؜F܄nQN8=P_[uWv{ ( euԬ7xR7|p697j>\cO뫯v{!ۤF'3gNײ'O^Qcǎ0a‡濿4w%JD#v4n~ >HI`"nYT3'/Mv2Ò%KN0NJ cu %ݻw+sw999x< x>pI!ݯh‡|‡x>ޱ.D7m1=7?^BB7HX1pq /^ɓU(Rz|hȱS4O<>‡x Eڻ}{I:bO>鬕6<٪mvBV߸bn-p!|2|(--=sΨ‡?K E{ OE3>V4|3dK*書̙3WN:㜜ʛtm .wXfT FS6V*zzޤ5G1u]> d[ /o 7[=4~_x>1|(++du˿zfMTC8) |H0Ĭ V5?$<8`GXUm֧~;pB%Gp1|W=?z>4!Eg~thZP]{wU+ǫի+TvYЭ޷js DslC6˷]_7//h ^"nەzf̘>{ |f2K` C|;j,;FH(Qi\+`ChV܄av·^{m's>hPE}z_QuIw!s?ι8T.5d$CŋWGquZ=CzWBIFIЈV.XB$nL-‡ JJJ6E5\gVHܡOV'}M7FZnڦt?pq.ֵYWS0`3U.1,5-۪+nBp\:6^EƵkצO8T~fff~X_7ao[Zn֭[f?{7#Gi۶RO<\]wsVhBuQ͝;W}WQq&W.P*65*rJD_߶ZGus˭T=Ԃs_\ D2?hRO-Z>=EڶkiwZ{{z^ÇΝ;_^XX{Q;l1 :!hA./RY0m4ت }L=r_:}Xz\{>_\'asx!2jQ߼>PRuc'- ^d|9XMtPYY9TJii;1vC{A۲e./B7njAqs6lo_~Yo!%_ ݾԽ {9Gu[>ĉ|6xԬ/sA&~Yz{>{>Hx F\>+gXVa?~j?U_wGUa6i)R7$Ԑmrg^}SVTNN]y?gUmOAr۞۔wWU_j3ş,|7E}"Ça2g./e~HӐeU+Sq.ٷuO""c>oC e۶msyN;MAL={בF9Vsq'6|wοCq- ޟ kjѩ13|{TNgOo~f:DyC?Gm\4ķMݦ moݥ˷Mn[j :®W^ye H!PѹgCn{H+|5{/([j}OB޽GKBW4'+K.u\rl/jF nţɓ'k^/p. ~n{.ݾL'[χ7h k#Z>Xe "p c$ &eے{V>/BŖ-4|@<R3IW~2 '׾k:@ 7vsT|s>Hs><N8)ϏE9VV7tkBp\9h9f͚us>lݺuo,s>8{/,^Xo+..=ɣ o:d '=K+^СC_|Q}>7m C-ZH0S2|8c'9˞!DDb%prÏ=Vߨ,^طBV ."|@წ !Km:GfpPY:҈aM#Dn}|N=v!'Ȑ@CKm Cg}}JOkW_w}_p .\T]̘1cW,]8K6> 6޽{ ].'X4"pM^;й#|8pz!W^&|?NZj׊JT[V%- = dBH{ >P B Ԧ̓єc$e5hףrȐWa4*H!/ҳY~?LY1E%M"|UEEEko}ݨ֡Cj~[>PSY.SV!-ϣAVe5;%!X\{DoUxM8՛xٕŧܮ{ȑo^g~Kxԩy>W^>F4G4eHj!? @%/OȤI~ _;Huq?A >\OL)+cdeem>!.C$dNWL |_\E rssWTT࿾]7oςKvu>(.\>"++ǿz[00x'|i}bVY:"| |BPoO4AFo־=62amS (.h]4СNu=e+H |B@>dZJݛv`U7 xj}Sj|FcA@>>p!| |_U[ (.)fu%|>>P>$*|??Ռ"| |B@@%cLk@} Յ"| |B@@%e`q >P\(C"ÇCr0ՠդ^G> RQnAPTI%uDCc P >+>u\O@>7E@>$.|?E@‡PhoQ@&I>" nC"|݂NQ!'qsyL{ԉ~V;(q3W>LI˖ٯ9g_RN?=znl ‡KK@@h኉ԏ?R18[SH츂iW+t_9z.ƶ{  UÇH|@mpom~go۠u`CjH] <O'njfff7ʬcf5Ҭ>D FB`P@烐cdHNJ~J%|>x<^پ}'Njnݺwԩ}Z@ |6DH9,'s>< V!!Ha„ }K:" &E޵7EtaΒ$ ߗ )Ç'NxJ駟_u*!5`M&ufFu{퍝jB=xC/|9 xo>Y u }z9LG0-LJu/ /~r۸E/)sHȿ2 9@Rs}BEcʔ)1u@0ܡ&|$}?@BÇӧWܹ3a˖->4|ݵ!GyzԚikԱU1U-"udH=Q UF 'E*\^]M | |)lYk=OPA@F> "zv?XÇ;$DK/sX<@EA烳*Ke3|J R3z‡Z=JKK7<9z7bЉj 1Mχ7"|@@QTb‡g~#.nѬva ?SV}p(ECe˖ &N{/?gVVVi0`{4R@@> |(>rʗ'O\z<q@@Q>+׍e˖-DR&L8ug=(ч15MZdI(U0ݻwKV#++kQ=!GE[ ~ߟWYY?񨜜?4i9yͻ x0ٙW(!=a&E5jku+:t'|2W >PEB0JJ7B%y|`=-k~f>t9o\Y >PE@@QCe͚m־MlFnY[w> |((‡0FO5dFxx5H4i>nl'7zoZ4ko%!E>P1!,dužUY?q;4i4I+^ `Cv^έ!'JxWT!_'E|3‡[sZBC=5R5|!T r7 =C4ȇYGͺ<‡ 8ݞIhh`Իf]:= UߚYq !ԱF\@_?>knTz̆f,=5רSƧܗv1+Qi`~~uzl5g4(C\߸sՍX)"`g^Gn#!ٚ/ed.e|?su{"9p?w"|TY6{Yۓ~&Ey|Z?(*Cl4*-.밋'Ic˷M IQ+tG}vg9ErnCGz8KODsz+fΌ\͹½f ("|H #Ҭcf4I?x_RMx\‡ %YN_vEȴiq4zjg}:oilOO e{@ϽOY}W\z.uFs9` ("|hpP:f$|(‡2CI߾[۶C> I w FH#yshAC2߂g2K}]Gzќ{,?hΫIT1?̙3WN:㜜ו=WJ>P>2Chc *Hs`\ (𡞖ڔ/YuǏgiueܸnGr}W0?0eJ\)\Q*3S;F{9`鰋@e OVTT;v'N(!޽[e66N b C84c>>TT|x>~*C#> Mk`YYqYȹ_h%`~/P5idӳ^9A;\E!PO ɷ&L+H#dAi\K#턓ߔ+tCK #Ϗ=ɣs?{5#G2aHgfO(o^\=X~VќWn> -'OVW:tH>$S;4} z >T$>|(]-]즲rǫǟ{5IpZMwΐJV[?J+4AFKmʒל>^ Qa!}F#B&ܻnX˲t._֥Q,c@JC[ ҫ}{zԨ:}l6%!6dd%sIÚg|2scYEs^>_x`Rn~ƍ/M>|ps7|(^ߣQ3px wY=>ȶ)9khܓ>P,|(*>E[v' Z| 'e=~ޖ#:{󜂝`=\Oe^Am\loewfTVT6;2\w9Z85KhW+3}9ȹ9ߗ)YÇxbm۽ ->|6.{p4ժuk}S,sur?\4D+%_˶@{~sXp۝[*-:t5Qgj5s!,5frFYR+| ;E>>PÇIRa26=8ǵlPt7BsZ-&mmrNўamf?]}AzjΕ$way5CFvT}.:֞ȶc:>漢!]8?QjGx9%kQ,=_ t.G\>ֱW^Uҍe!W>7oGOUNz_ Bߝ=\`l틯 m=qajӛkVֺ )6HCUB. a#8.vd׷%1yVm}xKAr.m;⚮pg{ÇS{ngԏ'Δyk6jHO?d >vڴiV=/ -}Ǟ٭nKC=T tپw/=u֣g'ײMz@?G6 u_I"HEL~W[5jVuxìwR47kuuV"A.E>>P* Po>hA"7FӱxYYgF[۔>N%\`J~2!40C5J5]Bce DsZQ,=9WoQS^oqkK  -<%u~[;AZ viivqfƯ^y|䨣FlK+C vYj+zІ:{~6Rw. DC$Çli ɶ`hxڵ5ˮO !is >ء@uۉ >|ׂ\7Cϩ‡+X|¿q/ .Ԓߛ7WmۍH?g<>DZprfǟ[gX=!.zF,']~G-aM>u9sʩS~O? 4+JTÒд8|&{VχXb]<{>HW{Pc؅kwrۑ ǹ dbFʪ>!D ˞A& %DǾ႐.>HNnxa ѿ>%|si6Ɲۥ'l-z"F K( e{VE!2rW.4y9| Gu#qRii rA˭`mtPVVv_aaIhǎτݻUyyW^k@@*`YJø% f:ڱ[n9'y\t_cVJw[Rr܆L넓ќA/=zcM :>':p4<焏njmhu {iyg|9rI'ҟS=>(a<W'xj}v{?3'jzLx⥾I-Ct!9K}N!H\5,Np<>N#:BeH?LDhۮ$]T#Gg61,X,nCɑA@ꏉ!‡ >,3fؕ.uUKmV+$bHRқ`rvgP wQ;_ge Y%BzH^s)Mtl"12!qP o99@Kmy<=|(ҐUܴ2񢽟/%+B;}sn$36_c84pǃs 7eŎgp^#$|LBQ{5f=k\K O #7=Aa!CϟUUUƍ= Z>}T4ּ33ٵyj&kLulJp3fin;V<,պE }9ëvp~Q:uft蠞3ξo=m˹ڵι ԨRl t.kG 2sgʸqUmݺ-P?ЇP#}l&cW\A჌+**ދdH~gc~\R$ {?ϩR>(Q:eYMgt1Q31z j,]*Ǭ>\/Vx=$$4ERl Xֽ{eà7#Xk}9}x Å\[9I0)-Y7BͲvqr]݆Aۖy}D<) $D{HH,=܄~weff8qp=}ߚ~? \ynQ3i5רYoyW P-'JK}0&|w:DKs ?$Td?iuCm=s޷} 'W]}l8A`ი={veqq)#Gz :A`S Q2A 7 |IGh,ː *? XKg)?;zF%!^Q3Ahu2D}Oa {Nײك~^, r6m8oVUAÇW>'ѕ{dܾ}ݓ~_;(c"`qZlF<)9W<:'w[/ȶw~Ac6~|čA)ߨv-&|p+_6`oXIql6 OBvuH| mR򘜏+c 8> l OȤI~ ۃ~9v#i"=pۣCH>Oo'lsO>wCY"_w8]M20j0I+7YC~)TYdQxԨYDj+ Gwl4VVq&%@Kߺ}4߽&aleG'=/d_%e{4+ Hlm?[wr>&~܄E={w) >l۶oy@򘤑liOOa48uʰP` 8cGsq-c^hQTTT{O9]7o~ݭPM<|=RFwg/`Zjj9M5j/jo$*|e\7ی s+}3ܐy, kH|'{Ricdbp5wb37Vmp R‡c߼vHoptޭz_wA3Q`0do~H s>"]ۚA]7^}uB‡wCU&qGCn.!4^)‡G4 Dް.p3=Ti$r]& u{ڷ z%Tc r{2\"18K慐s˖1W =CB'}龆kU7 gA&y22>rɷ&L:S'UWmSQn`!H;|ڦ.G: csN8)8@@QvnMi oO\7!퇚prÏ^69Ku`bO(Mq +_ÚS9o=C-&e"@1crvbBܖ`w̙3[n˜ ]jSVr%4Ғ%1,C/d^۫գF%$|6-=-$ Ԧ${O1ҘsYTjMi\" QO7#yl#A/N"|@\B&e(%˙ mi _esx2lAr>Rҳ@_I` +kKPf+9c6d y^31y R\I^+0wѮv1cƌ]vA@QMH@QR}r-j$];w:LxP | |dHj!=S6|*..^m֩*WÑ#GzRrE@"|@%C9R|eo ScU\\I~ _;-(E@>"UTTT{^Cݵkw۟)=nF@Q|$|(.)++ǿz˄'z[z;0EQE@>/)~w>PE@@Q4"{w(!n(T"|E>>DfE5jh x(FE>P |(>PE]E>>PJGNE@tj:(>P |@@Q |"|3sczSzgf7&a(&"|?(>37?E E /R@33__@@SNQ. J @%H$PA | Tb>*1@ J @%Exm5@pKJ_x)V2̗x@@@@>>\( E@>p >A_[I>y‡F> +*^C(|p<>@"| |>>>>>@%i̙+NqNN?uY] E>ibCɅeeeP;vP'NPBݽ{*//l,~m6$:| u|61$k | |HklmFbD} ՘1cT۶mUZZ:tz~7nT#GT[й,ٷձ6D rlΝ;j Ľ>\q֧O`_߷oo4 mu{wAߡCϡ>‡xի3sQNTi-:Z]ܶa@Ok?wYoG4XOUko!E@!>ͱq* W"eWm6B ,F0hEB'A94bO"^[nZϷg:pxxyx1|F>de7Ayu">ͱ$32xu~-X7/"L 0cР-A6|CYvY"‡H6|a2 1 Żp5"aTu؅`RC,F>¯fRL4)׷lْ(ܞK.>Ç}mE2b TD D>O89}Z<YBp_94RnC' '<k4f:q[n[_s>tU_"4k+ 'N_2gF@<#FԦ:a3@z<8W$|KK*r[g8[/iMLj@:'ǵ e4#=/i ha…OE"3vŲڅR2@t=PcwQz]wz7rΡR{ֽ$nOBz!=K=I>됉#ϥ6$*|H˯>n(ﬤ?)+vD"|0x~x"j0ԬTjU25oC=v1ïAz`C{uύ8M8Uhٕŧ\59l.ph>$E>4nd1 {޶ ]bmM>I&rرG] "|4AA& W*wݼygff_o N@Bgee>~w^q2'z["O@Po-K# by P'|Hk |B@@@"| |>P"| |>P |B@>>\(ևj*A&"|lkոH"|@Ñ"!@ÑH"|t*kq?@ȼ(IENDB`java-binary-block-parser-2.0.6/docs/jbbp_complex_types.png000066400000000000000000002073031445000440600236340ustar00rootroot00000000000000PNG  IHDRu"nIDATxT?QhFFcc4I1~QkbTXc X +*Vc (F9{a3.޹|T c Љ['NVRBEuPP EuPP EuPP EuPP EuPP Eu$PBV۵s uu5;8pq' ŵˆUV]-|[O=C:.xPn,vnJ $xŽÎ*qW{K:u~vaV/ҥKy!NP (6oz#%#μ(|;nݺݠM(O9環 /|HgŸ:GϞkBa] cnPP"+nkq^Xkk^Ck s/kŕ?kPyWXX : H[}$>MYokor;q{?Zx"b̄sFhN#V#G(^J_ˍ~vl2q%׆AGWX|n^0]_q(g[aۯ~~UXGꈣ\xa_ۛn.hhc'mn]ﯭYKṷxϾq(1PoZ6Ա? xwo~%&CgZǭ CO8UC1ѣg(q_8s.YXǞ4Z(?<Á>2څWt f4rB(Q|50qV uڮNZS1FC=Whũi:C7{`4h3.Ga}m{kDcŕn`Q=_9g~r_^QKo3\xg_KX{ Z41yo=΁~ؿl6N_ޯ>Ò{@T:bU}}z8FsVoxuǭraWt:B^y啷o'&Mf̘gN_㭷z7hP9]6{e:ZSƍGlphp+ vVYuv38u@93uwӦM Mӟg(@RV@J:ƌs[ t̞=;4Gnv`N2eʦ_u sflB(qYgkፆ7kF_4hl6;GN4hǓ&Mj4f}m„ &A(:7֬Ye:fΜі[nB(Աۆ9sԛr%9VݩX>N#mwR?L7II]Ĥ^L utPǎ;8ܑ:}iF-M;)4^OꁤK”PG uᆵO4PM7>NPNӔ2k?Pn}vۻ)Y:6G^;鬋BF'ZO VO:8ɩW$Ug\B"NsvW[n”*L{RiOC(Cp"_S4t 1C 7-<ڹ_g.Z#\ 8dh8sӤ6/덈v]q#& 7 >[n>un;ÅWqF۠}<>#nc Xo u@;-R :'&v~K$P5V\k;mwƅc u|}uBn׻"q>cvuomzPS&u_R:&.Hˮύ|ѽG&C`6k^:~v&K߽GBFᨻ Mml׮aUW˝s] #μ6OsAx]t +}myv tu@ӪԜ@׽DͩF `4'IFbŤ>KovRGuwλƐ^3ZX~Ø[%!R ]NBH#_f!Jjrj?ЮC,%ѻ20v#BBH]gRڵRԂtNIKNp/BB)I=TNp?BB)ܔܤIG(B:R :OD$!Z^OjZR=:} Eu@vRRڢݗPPtXI}ބ":ú$ϓZޛPPtH$5': Eu@taj+4KLR_$uxᗙLJ. @{2wp:Nګ= Imyo1,S]VZ!a%h&$|Rt ]2po:&p tk&5/:MtU8& z@GpljA3Teeز::WrLx%gMtD&)f\?YeeX#:8ܤ*:MTTҙpW:H`rR;Í3alR'{@GNj~RC:ŧLt6&ڨ]xuuX5 $5c:z^|eU9 kz@gLR#\kU*3t:&S.:Hg´tU8ɣ:CTE{L&|cUJ+* ݒԳ⪫L2ɻI]؞/2:<*`inR!ߵ ' VUM<`iWjA݌QQIg§XZ{I-$/bB: kHZ 4=K2t&|{ Z6O$/bBL&\N<TIvk˓f2gR'V# Ա~[4ۧ3!$wRR&եOԧO^s&~v[R[$L6 3鰙&XOɄ5ә0:P4m%uDy\&Je43@i6M*$շɄALUag PR B78,N=P*@LRUVӬRiLRZ@L6 as PKoRK}:$&uG_Dʰe8&GgRJ u@IrvLgId4#@t*Pd'uX9;WWL8<US4IgRJ u@I6Ss6d6pMvEDgRJ u@I|ns6dt&H(\Pz6nе:J3W):$''Ic/3ᢤfUTޚ `UJ)(ɘ6beuȦˆ>}BwMXt*PIPQ~ɄUJJb,C&KgʰhQ:sR:.XZaI uTjQ**BPYZ\u4 J+x=cYЪtr[arrݣ/rsk-\_<ޮ3> 3=W.yOCCbZ{2LN[/tKVQcmZu9k U:uЗqӟm6{_/k]π!g׏=  |yj߯宥c7[}ȿ|nz܏/ 9&κzFx>}"}d3:l$qxx>8pդWakrzX:^% Uf¥lpRRj'6Qv0\+|Gk촿juǩ1 Z|6{R;hJS<=ǍZoA ^kO!G}qb|lKlsN~?/r>(1v9b kmpW̅bm v8wK5̫fϟڥ۔z'_~aeZ8y7"ɯq¸꿯Ǒ8kxx̬8ą# c'Whŝ>\{.rDžO RZb!>xvZ ]G+&;eFAq{">8KlQ O~(29vs5qd6g+_"3= ?~cZkk)ln}nix>0H9S{suX,BbTTo}7]oIQ&oN8¡C\0;u'LwӵF(<wvǑmJ=I]XGȯ{ FL44Iל__2ȿ;'v,AS.|0EqyQ9C/_=mϘ8zXP Jk<3j{SK#;0@e~ncK>8QU0N=S:w>,>~l-θrfYS{suX,Bb'tOg;IMN7,ӌ]&%uh1Q,h.&KῨNƶ)qJ1dqTB[d8f.qԄ)Ċ-Qw:ځ8GK:(ůOᒿEU܏OqRha1Zc (1Qʱ{->?ҟamxN ch8L8:O9S{suX,BbJCfnBRWh568@!? B~Zkz9H2oi%xĩL8o9sq.| ,-x'8Ǣ=2(sPGs07G10)PG @;>vcX{]bZYuu葮 gVVebVR$@(3vTtVnāNyOBOkP?|ֱǿlnԌ8MKm 1uǿօV57MYA3Z ~抟a W|rogi&4m;H|Nbf)C}=9Gk:&OѣG9p?7͆v˽}gXs; !a6o}z?-u6ZktUUX-s>a2J͗*@hfad`qrkϵZS>O%?/B&'|1`R73qo=12ًw=%9Gk:B^y啷o'&Mf̘gN_㭷zxI:F927W;ر7F-"5ÊW .lXrau?GǏ-+mnͩ/檵~`<aí7 zXa˅oToF=juН:WfL󍭾x=)TTL]u[bw- &ZSq7S0\6~z.H;co$КR[\ŝlSMđNt |Dnډ8ZBgG|X=<<}3Lj/GTAg_nV#`Rsrطsx{*\+Yu=%=GK:bsϝiӦ[o}}ܚCuhvȶ?~_XGU7@ќhptTQ8cȿ~ nӵ[X#oS׷RJaK#3fV\PG ;Ph]UL&/kU6*P3GC!8-H :%FPW{̘1@ٳCsvaj] u4,n# Ku ߴB] ݴߦ-S? {^gJf:~}7 a@?^?l q4(uE^N3V85_.~ڄ:8jAJ&?_=>lN^ 7bcԚ cyJ:Li9BG]/œmfF6U:|tnzʈAܴ* u'Ne. oll oF-(^(E:H"gwFVnX^o_둛%ՌF94(݅:kʚV.l\ZM-cF|Z;䗋xmtpO#r]qJ ^:_Ç4hl6;ܿ u\^ cWfhPٟLRkXRƖ¾xzɡJs~o/"#5 ĩ?g2BrQڄBΟS ඓ{W > x</pСsX.7Eb@8IN($c~Ο{~ uaʅ}w?oZ_0Avm낶ZT@}:Ʉ әN*R墇(IeK,J)UׯYfxw>b-fwqrB+b8s ٯ_k2=>BV$b54Ic.y&xoB'.*Q> u,݅:opc:kԗ,T7V:sRjacm s̩kB6Wq}ٳg-"wqPǷ~Z#kOiPǢfNzBа뙻_Q8Ov>mx~>/jNF12t5,2aM4٧XRBلe0x5’ ʒM-2RRJq;Rǫ:Gh/K$ܮwm,2qHXa L]$N}:UV3a^*NYRʒM-ɖܥο0fї:=tpęOG>G=ԵjPǾI utMf;Wz/+am6ʅItݟ~;|t$>8s垅y/ O{{mZNީVr.up*Wʭ;ާ]"t, TVl;qI%PKIc=~'Qu{njpx{f)ud^}tڴi%%:~ɾo;PGz PkV g:cT|+BkmV#St_{8#Z,GH-b4:}"oδ3q3ͺXj=^Fw:l@Rwx*eɦ:T; u,*lQj 2{|*۷;gnVc̙dٗj]B11aaˇ_!b#ܧ8Gm[n%:r kFbvq6_+?L?%Q;Q<˦6-+!T&vK /餮ʒM u,İ> =BxÃ?nxp_g^+sŽzm;^qq*7pKSs#TLt8wsq^̅&}D桗Amc֩/sSԽׁ N땩{{xca»OO6grs2n ~ĩOzusMTj+aϛv% %[/.G hwyƿl=X'Ý/O W0|6[0m-jĎGyHϋwxٝe)uWgߵv{-u?uA{ZW)e:t&ʪ0^ޫIS(K6%桎>x2<: uZӍK~#LdJ1X5c`@}bCb`5vO!G}9b!?FӡعbxbyhhncHgeCі[n6l3s/M83fokcǎ+q;|u:zT2u[_^7emQ}XI u@G@LL,ٔPG:ဣi=ffQ'~`=μjFn1Gbk:Fy3ν ]5RXG8OW8BIc爣l=مQ)yMɵel[pZϟȅN1Q|Fr'|Vr#s3Hz0B/K,)h*+PuuYR'yjeɦ:<qJkqzk\~áKui9q} 0w3`@Mɑ_5ϻZ#BcbȠxԉl{ovxT_xθrfn}n RɯABxRۣ_\Y%ȿ|0 BmH"Kt&d¶R$5?c<5dSBm#^N13 u\zO顎[]x8Hq(xʔPIk~Z?nǵuԒsvhn{=W8J@L[:i|~c{yX9׽ ~l uX,BATVɄ{|8(K6%桎`@Bcӯ7(>O%sXx'8ʢڥ:-h g]n-G./mME:t:΄PQ~.ş0UIK:eu0lؓ?)=Zܴ\&^S<ߡC ܺ^zECbv*|7]Ƥ6 u[V]kԉ2qSD10%8i}B^2|s)5_,V(K6%ԡR6ԡT+RUt&([6%ԡRBʰqeuHlPlJC):(_*̄kD=&f([6%ԡRB/ 3ϞZz]3-PJ)*&78,UtyR e˦:RJfoeҙ02^LhI=ʖM u(P͗Ʉ*ϴ"ݘPlPRJP!tdŸ+mQےzT3-*=Kt+01(IM e˦:`W+VT3z򿝻hMHjf([6% d2g:^̄۴b/{4@ٲ)X(G'Xl&uf([6%+,SYf2a[mMVPlJ՚kUҙv&5Zܔn e˦:X^+see8YXN˴gV3-`iZ!΄iL8^kiI e˦:XzeUt:-ZI]ʖM uЙahs'uf([6%@gUU6HgœLTk:_3-s ] =K{7s5@ٲ):^:d¯^RgjeSBtVJgUZc04@ٲ):t:lߧOpMXV $5R3- [3!$5ڝ/ʖMJ):Dtlkz5gt::kZݙԉlT J)q:2d?뮯3a|zZݛ0PlJC):ړʪt&̯Ve**·e2aw-ծuRz([6%ԡRBE&vHgœ訩3koӧOXYK{|)=JS-PJ)2l΄ϋ +k[͗ҡlٔPRJ u,iL4 t$GJJϚ/k eSBJ)%$UWәncz~B7aXPMPlJC):P΄Wȏ-a\t([6%ԡRBKBuuX5]n"1' ׵Xj͗AlٔPRJ uL&Lt{5*ZZKlٔPRJ uR :rL(12. } 5Pл/5@ٲ)hyBbْc ]*3tUx) aNt:59)ʖM u(PGjX,Baa ICdj5@ٲ)NYV\\KG콘X,Z['NVK;g{MPlJCdzQJbX,NKBYNS-uKQ~Zދ)bY<}4@ٲv ]pz<q.T w45 ~S( ?p}xڇ_Jϰ> O[ϸϖ|voo>;N?|2p_ m""v?p׫υxӉ3΅v={S/[%aX,ABY)ʖMA~M[䈿568B ~䷍;E꿯j/Fp˳ Q& o4$F:H ~z?gF\(c!ߑ;F]:+\?%lTu)aŭ-l8J@ý=cs;:1#w)኉NdBICU1JmPG~L٢x} uDlSh|:gKbX,BK9 57lT+: [7󺸳nőo)PţNi?#e}6:ZT- WwTR;3N`E#yx@,Jٶx}# c_{q!Wά?GSr_SU_;|/g<|ZxBSSj48zL (C8G\kG=|-gg:,"ԱY6yfi eS1QqXL%&|ϪYGh7=bw -qݱSkqZ~9wkumCu/FP|q( ;NRxyOmR_)M+u.{OERܡcq>>[KgKbX:O6GNBf٨)ʖMb#v) ~^u6u;HF+NPo ŝxŹr#XN/Znux:??x懹i2;}nx(M 4(d)>Vq(3y?[BbXB Xu,<5@ٲV uO?0Y~x:& v76B ⟏9婵_Cu'S u_kl:˽Z ι~a/M-kLQ cZS~0r}tf_(Z*-X,Pǒ'`!A|e˦ QwJY5(ԎC]#ڸFzG|\10v<7;>#/~/:uazюI@<‘E.zIR;΍`9t[WmcGNɯ'W]Wxt t<ǰFF|%QƬ)C T'&OK )ԑ&ddoڴVZiԨUv:֯_[8ak]`FMaeHv>$779Ǣghk yʢ+Jؕr~K~ĸ[Ros3%@ >=csi .Wm_Y][uiǥp}Xc668#}%04u[{+0-6t̚5ɓ'e˖"""{Ew (j@.˺xoUSCϹ&S7SG!1V\LS/OnѼL~G7kEY9 wu7׽LI9a3ݺu}Nǝ;w>۷)Ű6ȥ%#E.zɺ r5k0EH! 2AQ*tqU׬Yz%aꀩayu(.8zK>cz*RbȔ=R:/LR:Ղ%..naLL#ON'N\_aV~ ԅ|2eLg\PRe(A4 zk1u%޾.n 930ԗF SG3t͛7`xر??TNG&WT.n,YqԥG?)sG݈D<)ԑ2~`YtQ)A!  ( !ѳgv2eضmiŲ5%:emӈ#Nd ~:gW0nϖ7TڂT zձ-B)ff =vmz3&ZOim2S1upy}6tӁ}܍Uw7g࠮3(i۳(]t|~_^ޑJ7 F9Wu[xuΜyQgX,W3٘!Q ?e~yyVtf/Mim๗ N˸>KvF#<'u .2\M}וe7<ܛjV,N.@Oz. m>c=SIbcc/s:Z,e7nܧr:6!ԏ=+<[U2B|}sy*J?߮l_Ӛ)b/@Votg4 #u %\~4Mk]:   :LW0QS0uDEEу^D渕_Pz*խ[t0_:Az)eg*cOto܆8SV"aq\ jE3c>437TXzOρ|$gv`SgP~?̵#-]&Lmm\߈c쉱[M<^rQ }jB[-Zxqҥaaag:N48FUW%;StsuxBg}HO/̏n^&^ce>gaHBq:5j`u52gɪa#PqFزx'[{Ԋ<懫Jdd`zNʐ!-O~3814LDh)G"Ũ`ha (:;('Fw&L:`ꀩ  gF>k#`[OV]6NWJ?5^_W߇x_ϴ ::wLXvL{KSLjXuΔj[.aݜ@}+*s`Zls5U(,S裣_ɯ OS.M瞷x^g߳|\+_I+cEW(\Vw.!#vM&UZTqiԗfi枃$^fh&SHM[w AAvƎc'4?Ԩ]n?C4f,\;Ui潿sSvtxkaDErAQ;TZ"jQ&-jW(ѱZ6Dwnj%^ L0uAASL0uUi;"ωadNBBcG-[dXzcvnTl;ju/\0G }}G*RWڪdEIU:P\94wBb)5zJk)mNbp=ÝsF/Miux:`wl63Nm#=_vVubՋN33F6jR+ riԗfi枃$ݻwԱiӦ*)CZ W}I*I*՟nMYeG|aXaKgsRu5P^yZWUz=фJ*ZjC#f,**w\bGW`ꀩ  :YӮӇ_4y?0uY#R݃)bc~P99W)C( JSbޤI['O4׉۞S 8^fӮLb;y'q]8@s5Ӗyc̅}ñ/u8#=%16Uƨ#de\"kRW}S} m*ZS_ݧ{$$$ϱ䊚.]r:6!pv)s\VV7hʥ!{qC^˯hq)c{wZW]ŕWUp:l 5S+F6oLN4l?j<~Qm:   :`<ѓS0u^SG2tiԡORI&[n}ի /_kXoood"Q|b^p6ϗ,Od<,U+F#i7n_ҳMڝ9GfԤ4RO*ۼ Cj-UZV1u Be,e(c֌Rq3Vhm(o_b!\[#צ6VR'6DTl\Q:OVy16upٜ~HnɌTRYj:<}`0r NǏ(W9uG[kP*B R=qTNݍ{hVS2ۛ2f2GXdCe~iY'i4r4jԬ x~Dhe}6yzgtFqݚ1ݮz 2~piiaDH]QER>Q7UuGԷY=2s':`  `ꀩU:a5'hϽ!?=:m8if˙cvb.if r:ECFQ[匲ߝvjK?+Ǿ{c}[whOI!hnMo=k.ЦSHp[]m=C?[GG1><@_6 -O3W#mqu->?.N}9gȻ>Zs/#QK>sGiR9>.=oz{`Գ~/e|nT|䄛c`ׯ?y=ű9:D 7L&~[U~vp!|ŔSՆRI9Mja$TV kWL:5j[111:իWffiѰÝJ-OkPXIʔ)6 в۔m8>O)U溯tьQ7mK.VM(')!jvS:`  `ꀩC3Ƅ};.lUoAj65okP s{쫊 Co>V5ꝛv\9"ݙ"+x|n{ĥW߇m A|wf`a-|hl`Ȍld0bPIJS2zĥܵ?.3cԳ;ܿoNC&,,Mdd>}cǎ.]Uϟsݺu_jj \|&CSť }7^"IҎrQ_UkLx4fޘIS2r2g& aQ+I3[ک﫵j}_s0u_~Kcbbyr:8qVÆ /9kBX+3td(V=+7KfN>Cˮ+ôK%sDYx_XlM]zTK>=ΓW2o4kӑƽn}6t5Uxeϡ_fm?cG3}iڦ_@*RT8j]p.K2O߉vYTQR9N<(ic //mlK4aUEk}vK/ӸY'm.R"w0sMqBnep4u2*p66>,yZ/x]X >o6pp&oߢ^̚:յ}q[acmm%jeⵛGCoԏ. [n(Kv8 3V^Oٛ%z#gШ;ҘQ#~5mvc^G-6M _~UުM==z>%y>?7מHSÙEŅm{F,SGO, \B"5}ÝγF +`HzGGG;v,ڵd#Wt4eH+_RR@;+jܩ:`  `ꀩ1΄@: m8ccgSzuM[rYGClR@A{nM\B}I .!hX9Ն 6pp_^Ay.S!gsLoxU3mqʴ" eeqFsG,+f=.1^F$QA6dx?ut'.%gQ:ӊԻ㎟c3լ;zR/x\)LI&CSԀ(*Ve˛S朙XbdtveZ;_Dyhp.F &:v2mvX֭6n;4hOTT0uOZ X˳2ϥL:6m:UVG5jt?'կ_JDDķ~#?CAAiav5u7e]@(9sp'g,Podž r.UdzF e-8ͥ8,ٱMA}\ %# mu7qfwqК3]p1ƈL[QY|meñT>2c~ ZNhI*S2s4Y%ԻK.,6~-:` &:SGPIs/*8G#pLݺu};>>cF;w>۷煮ݚT?_!PJ(Czgte ogfSmE&q}^JTy6ĘNVj0<ǰSS81z S]+aOSۇ4)l \~C5B|z'=mqw-,󃞶CyԫC)\E>/.ehԳJ]wޮT-g\dL0a:`'EJ<Eó Gѣġ0&&ɓ''N^~saaaA8q覎ıUz +?lɖ*OAi#J˦gdxu4sFqVOjtŬҸ(qKr,@%JԢ7i^:%$$:wbLNSǭ-[Jw?."+`S*P =UD/kj@}&Pp~_oQYbZ!vs{?'P{>Y5@s ԡdc{- R >|Kkxc\nr%ʓӺ))f@/VecfƆo%>_NO:'?EֹCrt@_U[fƥkf.ȒIkmFtzgqmtp?*PRy#P zm̬nls|ueOl_^jvr@2ug/._>΅ggH+j5z~j{v!PF=?O3up>ǿ fM>jM,k{v>$w9ÀѠ\bR %5}ʥQջZY/ܵa;ߤc4OѰeIP¥S!Y r]Y39(3^wm1k4ꎔD;hOI_e_kYJx:oL:/#]l"\2Uj0z\W CL0aSIQDSk+ھ,;CFjmܚ:J2K.TutY68͘qMZ7>e~OҜ9 SYy~ժ}^:bcc/ڵ7ngX&^Fþ@*Oko_@7x3k`q{>e`#'s tm{8p ((yg~L)dorm@Oo^p1)PZ::s^&Yҿޘ:BYY˥>3c౒#x;ፌK׀~Ɂd9AYk١g\m7]W6YWkls}d(iލOFzfMx&&oFKmݩA|'کHXȼbc]"a<z:0a„ SL'mxqIax睿(((8G4ue73j:XyPk]1RƎOӤIhTKvmNW[l2?6e~DD_e~˖yl+obcij1;w^"UF>ꕩE\bK. ;T4+ 4s,~Üjkl@#ֽ6&g6xl|:*^޾]FLF1d#jdml'v}Ytׇ-hߧ~on]4qh#mJMކ '~ zR[>1v g==ѱ፩Û{Iz~FYop{aU*@ RgPgz!ǶpV6xƸ qipvzٖ|]3fzƵvsWgΔ d@m"Vnlo_0L*ff13=JW}Vlwm%cU=_]*'9>O$Ga^q 3+'ȐI{ ~R;0uԁ &L0u:`Hn `DY ʐAyq$+6V*U[f -SG%ClZzLkFŋפ',EٳSnilo~?@'ODSJ:b-H*"rnVI>fϾgE<SNdXܻw~XX}bu߆fSrznYf@4t9<_ʝqe/5o1u%m95`Eڶrs veTo'Wⷪ, M |xcq .Pk CEkRu0k^@9㗷eq}|3=-:?gpl/ƸV4{r9Ӊ-gqmݏZJZVMf@s{5oz>8ƶ?S :Lldic|6Ъ| ~6+% wcx ?۽3uxYΝ;|a0u@*uFljKpW\qـ;:0a„ SL)t%!]Ƈ>)$*8m!}//3A@2v]Po?h*^SӰS/ܯu4y㹔+?wCʝHRCn/QX5)?+S7ژ4:8kSO+ÆL!u؜ܟNQ/W9o5u헩cqL?nm[u u`'Lތy ~UsS`Nk5:6̚:MNJc#QSIUIn/ƸV>6)j5Ѹ'==Lp⸌ᜱ C޿umn?hnls<dzǛvMG?p6 yg<篙sʛ,:@Շ_MӮ%wI,}2u{v SL0aS Yh't!)]hHKbp?q A.GoLC1]6ԙ4L֙>TE A3gޔl2Khez,"`IeʄQ֬ΩE ^:w~l5k֜S~g `L=ۛ,cCk{}ex"NJָc MzQ~h,kz9w-3#$x>+x˵9@귟u$+magFMz17;.O9xޖKzKCϸ6nr6L&]Od3wdwLզ1@eUg +>O$Û}ѥ5j%K  L0aSG Q56t+ q B d MC_,u\eRC4 t:9] u֌*UZh:LDg[+a ,f㱴 oۦTe%K>畩#!!a(S?4wG*?!a[ZT}]~3h n?,um9-zZBbYdD΁09r9z+zI~#y\b[ڧtTuZ5pp`YSvh79)tӻ*b^r7X9޶><3= 7cS4{]b{~Vgqmݎc|j?>hn]=W%,2_:ԁgƛvԑ\'܍BbXGW{ggmSߧ0   (`ꀩ#%%A ᅂ=-v<<CYP,O7:vk'\R_:OCg?j54MZ:9} ^rR֏q~ʵ4m:VUVW'O4lxxyŒ=Lfi} N}o+F[ Fe<|8*Ъ x*o.췟=D޷kw\S#/=ۺ"kZ.}vkD}Nosy_5uo|neqq_D35:6 ^rnmi?oݯ}2oǸ kp]|N*lf8^a׼vFݶͭm0ZBBbvDJO)ԁgMf+SGr}p7!6xcgm: `L0utI%W`0ɧo='CԡV׮+\3g2HI.-{L5ڻt-ZխcȐ>|zٲue7fԨQoT}{9J3$_~S^@?LzǛ:ȫVղ 9ѱMW7_6xί}y̴1n6n\jy$eַyvFj NFj+gV(WB{uaO)ԁgMfKSGr|Ïٛ:yGa FAHIc JR o$ؑywf/_ɜ+S̙7)2]D*RԵJjv[SXj: UtNVbƔ7o1ʔ)T%{|>u[eh`4&&摧O8qaƆy fMrYN|%kwN_0fp0 ejUy7͘:88mtg`U)} $q&[sߚuH?qYӺx:T5lk9@4z\c%w%*~TuCQM^SgPsicAxqi,c;/6ĨKpAUJzƵѱk/1Yb A0u+3>SGj4Ҧ$=gj\\\;cǎ]vѥK0Ldd䥰6>:@:P\@YLmVg>)Pjxv&c<ժ\LN4䌐X6W3uxY :L0u`H*͛fμAPeg[ in.Œm۶Zn}qS ^ĺ>ۈorfNM_hbS{`,7a|ljfa96δe.en:|9Vu~ij1Uz gm: `0u#4|>5HT\w_8:uf!cc×cҟRgp?:`{~&C>/# SAL:0u$#(()J4wC: u*ZPXE`266|9&^^O>@9 T@ʶ6 T@߽S(4ɁL` `LLAASLA0u:`SLAASASLLSLAASߧ H &&U$~t\٦K0u`ꀩ  7uhJqÛC?@ a`ꀩ5AAL~>+:yK$sLE?CAA0u̚:&Zd&2.0utb`ꀩ5AAL~>+l%j6cN}7۩hTB|D~:.RSi `%o#AAᳶ_O MJrSohu>7up6>#'ҷkם τE@`((X4:IDATʣ'ߧĦ+\նlp3W\`ŗhʺ{E@`( SHbSk/G{HF7f9W'umߐJxt_J᭺OEK+[6m)2u ;]ZM#Ç,۾QaSE@"4&':8[G31md` `#$(J3eSc>U`ʀE@"$Lu V)2?B)Hmupaʂ|` RiCL0u!#$R(J;:8[ U R$H`ʀ!BH(}hupaʃ|`@!`u0e~ď@J!$60up)@ @jPSLGH BCi+S;Ⲣ!)A @jPSLGH BBiu,FO A0uRÇ:2`?BGH J4LCB'%)@ @jPSLGH BBhBH!ZB}BC';>Bԁ?7N c7Oȇx$!=Ԭpa*|5Bԁ?wҸIQOØ)x$!{ ,T=>0Bԁ?05@~ewP0gxyO]9D#$|OH(-j3z} a  ajIkBp: W 3Y:~bW# JBBz} a  ajRÍ6veOR)0l?O_BPm q~|@ 1J-7N\E_ʒ.s)8m!}/d?BOHI [>ML}i~KM}Ǎ%DN<=mƼ 7z*@[)!#$ :z} !6%?ƪaꀩmfa؋:SRG:!L n!XIQ[͙D1#$~R>!Yevz} n ߌ Z:Ūm_n z޾i,O_ȟN ꟔hfaS`{wl79ы9^{&M))#LI7+X1#$~R>t=>pRf:CQ3O=aJ2uxꧤ~f:(8%D]&>]<} R)j$l.['+c3ѝ[Ds=D!W|`K&wCng ۺ^2DAtD_.% =[|_M˭\ig y;M"65F[~S5]D$:WGL&z)mݏ&v϶Ft;9zcSo'DuITlkW2#$~|TH4up/LUYY5KW=~ Eض`.W{w55Fc5霴&s/&|c0گz 9e56r nNAX mQ SkY)4xLx>2uxg6vcSFo6rFs&pwm}]UFg~lXjzDYؗQk?;_lϾ]W#S?d:eA-mك`C4x<-긨ۢ|,Qxy箙! Ҙړ/@o@)z J3XF䝩ŁVWr@omkn\Fh*\ށsV͐6Ճ퐉 w} 26\>gbv7yG&0ozLlIk_r2m+`=eKƘ~;nX2q|;\DOX˨qAUu;;ϯNnи Gjo< 3wYш4,d-hճOƣ}zʼSƷ%{ӦMjHF_V8^ (SNTj+Sя3<ߧ L]`[6UrL#G_:ߗofMFL5}-ytI&'N\_afGw̘1#/B"it-Zb^}CR=`ꀩr%~8 6<&&իWueG׻wxxx[3AP-SZAAA4^[/s,0ua<^^YsATQMߢb``@'$&z\Go@cԑ] MAEk>j}]4-IԨQuMy)0պ| kWf 6WLƾn-{mk/FȺzLRM֒3X:'}m\f'z>_4D|-1+SGxnqV1q\,ьge oZg.g㩟G_L:th͛?ÑcǎiXΉn4h9n8z ~rɫ/]|4u}0uiG*3uL4QE=N<ӉM#?BuK&!uW~Y)PB '> /A0u$s'Y5Npqm!ScDwn9i{#gݻKԯ0Sx:'}:vK4ƌL5poy͚:_k?-J.™T8+S/F~3}g0u'>>~YBBfwIsYΝX,FOVE7_~e~tgI'YL*<6ȻPֽd"ɒ5+OB  ^'.2gD4YWۭ۱˛J[^G]@+UL2SRefj+牎'Z6[gW_:˧]j0Ѱ}ݛ:"ٗ8v|яfMFYQ:F:vm2uY`1u3:k.Mb\q}FZkPg*)ժcgn?+7}u* +!kXv*Yi/@9Y 2҈q\)p.}zJ>%3g=GN}ʝGja|#LD&ޢZ-XK\p蓇~XTQEehZBBBZ1uh+WL:.]t>,, Õ )Fs~jݳUk*ˇoں2Ҽ ?F)3fDf~H.-^]0"6UWR9Սog:0B*+Sz-e>g`Ӆ`(u;Dz y<OO=]n[iGVSdMD*5Qev+ꔨ鮨5j.-Sp`5̤``@@P0 !yPN$+fO! ALIE`HN%W(R,vwwA:J.KS-nXey՚O/( k6+y{y̙,¥Iuf~JYƙ@"/[s+p1heٲP8Lmp]v0meهlt9:̜v ..Ri`5^#X 44t)qcM5B@ ) MFf!!1$`Ajd)y!#AL:."SǰiqAKeu*eGi/Lr-zL3m"i~dBjʲfm::)W9KU_-R)xjڴ`o` B1P2X?q)ʎ|?5CTT~0 @0u (^r.L> /A0u$sP: # ޽]v2ulڴ逸| -e7B1`8m;u,9Z{eI3ٲkˑ%/=\)<VceQF``SBSG?w # IHHX!JӸXrEM.]~X,=A:=n%R e RP%ڭ?ح;m2e?\zSq^.]|˯ 1m#+zS;`. k}K{*sY@%_?'1u>|ʌ3ٺuGllٲ_~{>2 (i?qJsgc{ #E2~\ɓdǏ ? 6u7FOIFO*՟[0mFNDho['0}dNXdCeyiYibR5kC%*FW+w9hTJm3fe֪\ԽPzyɦ3SgD} nuE[ʦs3{VӳjR,Y]:R'[vS3(kRٓyJh֦#{w'xe@#Mv Z?~VZyW:LU*QӢֈ/*\T~`o,QDTT%Q9R@ub{::trɳͻL`/wVlJ>E'AdRoAex罅i+cƌT $T2}dgRU?֠eaUx2e,F4ʛ5u}$#K)TD2,k;#x\fΒʲ6(5*Ɇ+Ivʿer˯,q#Zy'{++QJ/*S5l֚sȑ#ոrjo{b953_"SPGMA_ x 3ޠQw#I]=Pf#m: pm3-zz`[1;Jn}Hz-rz{6?zuįAXoMhg}z{D|'x߰ i[Ksg ~z =?0!S__y-Ȁa|n!cn+yq-uawl|J;\M|-S8 SASGJado۶֭[mܸ5ԠA?.1UԬ>۾VmfB 3ft A0u ji XD guE~Qkт ?j*.*۩{-^.Cme\sqV_k}IӠ|a euJ~ZݺGٞ 1_?_ wmg7ݙ3gnKHH 3{O )\*Dy,>;ʁXښN`t[uu Z/Pvρ{8(F6X΁]'y j&ncp#n$9#lB}5utucL64ƊK;OolNt-ذcLf_h0Z;6̘:k~ #u)e}u)`ϓ5"jM~uApuVov$n(Q]ԫZ'~$xB>|[WGՍog:0FFRg͚M1MPC\ZF4jn74/ZuxM1qևR(y~tMZؠ1zlT_Ju,U?g.T!c&z컙 ?F)p֏ 3?PPxgtߦ;(_U96p1Yg`6X"t}g2ömN[,L@R  % )D,>"#rw_W 0ghǟv WpJYFN)e`3<3kM@6gv@_~UoEA~9cۊA xߗa FL򾸏&̽d>9?eO\]W=0?iNg|cy6vXyV>eoxkU{Q۸Y9D>e0ZcchmZ)I7ɥUR8 SASG L [αқ0SE-XMlՂqD)QFwyѧ_mfalCr֠q yYρ#Lx%]<G,>w>OuևIlDFL̗`<>T匵yZKը3ˆpk}>'/䲦l_;`#|S8 SASG* 7Ha  L15PeM"~1cJm8[zyՔett2up yw?Xae)ˊ(eT?ɥyFvLWpgęv˹'w?."$ժYFO968.csI2ÛnTT=xάѩS'2jj^JuU/` M; =@1a޿.w7qvWY\MUМMl%2/w@!yt@p8mq\s4C\ÈCmP"}er%p#6R-E[h: 'a `H=iyVxI6u˟pvڥ.?6/ "36ud͖]SCN;k͚./LN]{ 7Ԣ˚^ z V"n=z6oLEdԡ~㟳m:v>).Gѫٻ([Bv("ADQz"RDPA:kvW`EQW`GEx<f3Mff'a}(3Iv;b šbŽ[bM :tD﷏amq~wCV_?OmMvXyIעB`qzn uϟgBEQuz6qF|rs4W)S)=jߧ~dC n!sFy~Ed{[_B5o)=cӜ[J*Ti;zGo٣Ƈq> 6x uYP(7 IdgK,-.TuDO-SYH7;g1:}T{^ȷPGt.~䑏K8Q+Zk(xǿkʟrmoG簖rGBW?bMVӯwTQƔ+ ^3&%BEQT,eHv=Ci 8sD&l~O9Ku֓1O7h؈+TJ*ԡպn"CZrooz!fp{5?(r)=7 /#If0; r6;hc~ݝG /K3I^6BO=r^u=w9.Shp1KQs=wٳxtݻP(4 @vR%v:/uC뎍|Wf]:0s: : }:b„KkM{^-ԡSS3bnKOuƾfA' i1T!u9 usFL1sυ mns"^)X?%Wylt(;O 9͹OkvVqTIY uꫭ/^o߾{3tС3]ӧ.)PE)y='"MWVCGhnr@tt/PG&J*U =FpP.aY+qB4`ME.rt̳7)\2jZ7Zn5pz\n_Q˞?҄Gm޼Ucƍk7Ǽ ]FR#rx~@䷷>o Y5sr~}u Dӑ2ip1ZH?bbGX+ƺo o#ΓduH[n>}5k([n5?gΜSOK.p ju egd1 +Wjի.OܫX^Q(UV3$ٯaح,eK*ԡҀC룎6z(":DJt Za>^nh%=ɗQဋaئ7=zt䋪ժmQSb-常kvy~vKF:\Xstz۳6;{u?Ȳ-ԡfGKnNc#]zu 'Ig}Iiм>ZPn[F.3c_{[Pg~x[nSsnur{nPGI'@8p%%O>ueJ]RP%yH}Fy;.ݮ]NٳOyovw5ƍ@GN.0$ uPET?Ǻ[@/'r={/'B p=#ԑW䮵ɊH0{suxPԩSǎںu뮼mٲom޽)Wt9>I&J*/(*%Cof}i?]999[F5P:PGRUoFkﺫonjtN˼׏!@ e] KH@~*QJd u,Yٳgcƌy/ s,!@3BIHKKfʠ#uT?PǫE%cc̘1lذScݺuB' o5P:u@*ONN^R2ѧO=۶mo8[Ͳ 2u\ P:@Y֢T GJ~UKQT*:v*vk 2DNQzݎ;N_K/Wz P:@ʫyGJMVKQTJ:zH|VF@X"ʴ`P̒9ܟ?[2oսM{$գ/|\uӶr>|6l{ s, ?T!,@tdkڬUo'D,OPߡ#GΛ={}y4 s, ?T!,@td;_\ėf-:lCY u8PߡP(TGm޼U_? HU P:@Y)22#٥B cݜ:\:T=o;:WB';wX P:@́-8?[ryƾ "|w~g.w7䎍 v|U.)e;^H&L#B[n{sq#v+i#&9CP5j$Ҙ#N~|K6),x|C y_Ծii/wn[&\g FhAC"ֿ]Oۮ /o0tZ@*W;u._{XBQ@PEPҟ?PǓ_u<}woݮWږ ux]>uNoЩ>ۭ԰LAx6rX?h`@8_E1z>{OrۆO# :z-VEg.)" aO=_s}p{QN)hR5Qc54B (PI[/o@ dD (RCy uPlx>,d񿷖P>o kt4W61Z(7'*x7N{R,:Ջ>jJXy˶Q)t kn:۠#j؟۾]:1B4F0rY{گV=}/qGrEAAD!޾EOS l}mM%6G>~Ϸ}-b!> ux9n_c_!Ah:/ )W m憎eŚo51?#ַ2c/2mHdڷ;6~/ߕYWdvOyn~sy}:mxۤɟy|/ۮ#uD<ߙۡ˦=P5͂ )W߶U_ݜǾ:Js,xi4i^ 2ac9(|GJ5fw~|+aNS;Gx)PG"PԾ ZD{zn}Xͯi/~+\Bnk|?bN .^Fe7 ra (([Zq4 [xh'|DC~:#_GxY6o:ENStёn|KbMGn I~mNgmN{a n:wNu?4oA>ǢF@0Hr"4h@R)EQ:h4PJ7o(PG~E/F#2˛a7tREt?ǢF@0($; AiA'/EQ:Js,xi4i^ 2 @P$3Kq4 iKQ.^Fe7 z m~Wh@Ҡ(B9О{Z+NoWf;t~4i$gxdz[}c/78ߺgR;5!x3 N^uX:u$|1Yr{ZB RFܬ·TRR:y?#&iiiRJEհre҆Ie5#/oKQP?S6M ?ۀE"ntKh]~R:U3_׌PeD0(@'/EQ:Js,B:dK u`㤉'IE9n@;QF}-Nn!&2˛aPFdgK@P e8GE(ϱ|P@9BUbRI}8\Eg3**W,U3Q}+<rG:Rf8u!g3CZ*xϿ|rÿiRVUs"4s݁^[.S[K~Z^(4RBqv_RZe0C"Eu{jʿV5I;d u=o uh[ ]pӶ9}/f E(ϱu>~ȵCdCyu S߾s]צʐkHZZAcѦO5א{FFbaxuh[匥gȱgqzLvBGQ4;:ڈ뇚#h@G0G8/XkxeA( M?7{ϓӗKCE2rH鷰9u{q]ё-(xX:ʄvX["}ޘ^VY{ux6cⶩ:Yu$QY3ޙnΦ_ѶpBX%7FN}mjx=:Jqӯ89p{cqVke^OB!d@PC[÷kG뇚S\"|{Ռh"O mG=*bJE;8$B: =l83pq~hFm1G ؾ8bXzt jʠd]#I&q6 uhv9)uF)3Ghn? =ÐBNt.ܜD,3p@Ds#Fp$QNj.aq՞::RC @ ̒S9H? u 恸#h P"\QFuk>]DI:4AW73b?fgͳzꅖבNtXv:i~::U5FvX{K -;)1ѡ -l/Mxۡ$mN^3r~H !P)!oAYǑ@ I8aM+3t"'Ǟul)*`>TYE֪*G8¼m9kW=PծfcFf4iDr.ʑ L89snf!u١>`AViҼMo &'mt^\%,L*Wlnvo߿VYqyNs1nG2:ܞ7:tD FQ4=|6'uz9? uA QIG)$NlTrؿ?ef)`~T@@>?PܬzPtOP@2u|*3M75G9Ʈk~it{'ؚwmnw(ـ3ߛ)OQj%1Q"tԈrCN8o$A :P>]ѥSlT%t{6eӔB)NI֩L+^or'd e:8He#넮Ҥ]QWLUI&cFYm]e0ԑ&'Y(xu u B`P>R]4PʴA]4PʴA]4PʴA#th4B(BRJ 7 xi4i!P)%Y2h+8(h:P B(h:P B(h:P BLiȒ8(h:P B`P E7 G]4PʴA Jl9#r.^FeZ I(RP BI'd %7׳G B`PNFҊ|B2u$A%,U8%d erF (@r :HY4 H.!P@ :N @J̔22*C9#d 5ʖVH0(p4G @`P:h!9#dH:!P@ :Hy',Y 9 2uR)/2d |-AK3uA:A JL 1u'!PӢT`Pnh@P|2uY253Kq$ A> :!A> :!A> :M0(2?Gq BI lAy#H<"A> :MVGq BI q BI q BI dN p$x1,޾X:(fL=;zy֬S3s*U4vU"_ B J Ay.τt%!;Zҥۤn%|1Yr{y'"dsF`?޽ڗQ7?5^Nj>;|MSR^J>{x]u9P|2u)S}jNo ?R+ԡm /BGFr GPǮPG*O2)ʨ^=hsZ wPĔ)5i40n,u  B ̠+[  ,"fTu+ d48,aQ:r? /A:@ =IG%碜p(qƎBZ-kFJ:ۻRd㲷/ ^Z%õץBka7 ; 82ubr\huj+IKK+e2~xGZ2}} W;PP)o@H~p9Ƴ̲4$d3b7Q&oL{cZ2ABE =êWCii濋 u4<1l Mˢm uhkthBQF{VG:.5i:A{F! S}>q3PQTCx4 5+Kդ2E>~N : J@Pg,q BI  c^UH@P|2u{HP|2<|@GQڗ8EQEQU{zHP2|@wPTR:U((e r#duPEQEQ3RftDc!PE((uOBPEQEQE=# B(("{F> :(BEQEQEÿ|2uP:(((P$duPEQEQ3I A(((BgA"AQEQEQ:{'!PE((uOBPEQEQEj\2rɃ_KH@!!Ps7ldɒ'Fk~vB!ӧ_gygg}uq#*իC?=;%Ϝ}~q:ާn?ѥ+׵k_۾ސYߐ ,iC.8Ð T1+zYOqFtTkp@АK+5kGo+"WL^fnYV2o/Țnmz&NOBF :,I23mm8CcLJIh(+YZbŊ͛7KQ}3-S㒳][ c_JvR uqp8:NuPGrS+ 0$=|:y}\tկ]s J3}{D y_SBꯍL9`>Q6mU=Cz-$ny_.~ ]\r2Ώʼo}_.|;Y''w.N{F> %{-PS:w#_eqуV:.܆:Jk߼:nG;v\Ϟ=ܹRu|!K&}:qc<֏r|Ps*;B/u|:t kz tCo3la_K?~v~[~<㮻usr͆Ovʭ\ecȬ'O$6-^y˝ʢg$t/$d`㨣?QW-_:"G6 s=gokt09]tZ]F0y5:4|Gܷr_2prs}իו *IiٲL NMkҷiڴTZ˜֦aÃ%'"#kٲSK0\NNΗ $xP}/7gnGbi:~{ۮ0{GC*V_??د^5F Ґkmy޼q{쎕g56n,} 3 r{mRpË t@}{:we_R!+rpcC.lO%YϫCf0|XN]ŁL<Ӑm")(¿ 9oX>ϐSϐyU!CN1d9fl,_5sθ=ݾN]sL(sF5n_!aȻwsP2U*(oXӮX騒n{o!=/ِpsNK;㮽e\p2eحɂ'?C?U?]xfݺeΜlᚾEn:HcҥfϞ-^3P(4[b 9N P[C}`Y*jǢNP|:%Z\8PNmأ[c:n}ӀCC?F|PXu4tYu묢n`}|?cRCۉ=!-TC+ک qns2nޡ_e?C4Sx9>:Um)CYh*ޱˬoHZZiゆdkN]69z[\9h#5k ii!Pӿ1 u$rݰbފwmp{NK-]s˙2e}Ꮁk>snPfH~gyu[e3ds?ok7.oPçӽgQ*A*q'ݺ]b<_v2te+U&_|GʠA+O_R(Pѥ8Go0E׮dGdĈ p Ncב:&O(qǝC;GIKK߻&fced[m2c; :ƌˆ <}XnݺBГ_Cې?NGoZ#_XYfoqzneHooi#;-a+k4ddC`{b@gܮC'N/d߆Zp~X;`l7½vkigFL}sA_Wo&b:o: @tqT{=:);Y^u=:rrt/AϿPsT#/7 [ǭ~s_gݾ-Eu=:rIBD^]<,!gg\Wr_ʸ?_&s}o9CnI__:|;{F((@:'3J*51:RFuڃ:}TX$*tئrnɾi`Do1Shĺ/7srhdTf|OVS#VO>{moN8a/\YGvXx$zw?[oRsZNP\ǮkT {"zX  m䈫}lmpo[اю"{',ॳ\;,CƺOmw :ptPvhڑ+ԡckC;n_c^6ɐ;2U IP2n~NXp#AĚ~m|}C:j^ߠLO#^ًgC?kVۧpN9:EI]^q;(9#%_]}Y]7-kjY uTAY[~ۼ=J&%}ݰ}mib|69}Qjs?7{Xq"S^.]U&N&=LqY܏şdƟDFqA{ J(ԑ|yYw}=\}Fh4[#@u*m%?ȑH.ӛX#GXPx u*tč:}Pmpoӧu{/=4iV Xl? utUv!!C$ *nǎy u:&~!TEչ} ov7_ *tč^}'d޺ko_r{nu~Kغo~=VCGnoFQ \Y2@;t^;-;yqoe[SʵcV o-{q;buz9ܞd>OctJ?B:>.xlz7?ú __-+U57W,QۯӁD\SbC8a˶'7PG k=:Rksҏ_4t=TeѵƳdދdS?eO(ӟ!.SAn,y_[co#[VtaBNPFh4:a޼/aCxC 8/'5j+Pmpoѡjj-+,#x)͚uw :z7?/a;㤂ojA_7LAM7;{'}d{7~'B u'Ufħ[uim;N`M'1olNj{/dF5 `ˆ<<;GL/Kqxy^Qi3OW|qzd[܄:lh':Hnkgi={mkn~Y$B>au3ۖ~:^7^ܜ~*@XըHq{2eF+)UfE._\2?GHCMjϛ#eͦV2B|H4Fh:P699.lym ·OZvJP12G7|IbfQxEֹzWv[+:J Ge­2:U~>~0r!a}ܻ`ge+"s x\.'˓C"ZBܐ&~xvPO/DFh4PYÚ~aÃΖ^rjn3κ19s>o2qs2yFW]ajSJ߾WJFF̍]t6IƎ]kSBT%n68ٷ>Bƍ{\Jrs?[Cy:J7>2|9ҧϼ{\FGJ9Yu4m>Pǹ;w{<|VwkCИd uDwhA ^kGtst[AN0뼂^gGk%75N ux9]voCHfi:w?B^ת!:|mK읔:sk7{h3/nrPvcn5<,kwsA+Q57W,o[v+]%OϿv[wtkT uh:Kv \l]sF=<9詣I>^ϧoL+N|" _{ n|ǐ6d#i^N'Ah4F=QTi`Ŋ?#רѡQЪ]; 5k6Xx4i6FOSRvz'h63Qq_mE.;xBPf=~ۼyO7nܸ&1otot֡Mb^uǣ;AoFwE ^vz'U1m(PGt':Cn0 7oC4C^/kK7~̫p{>x9u*X2 xd7mDg^: {oӎbkJbmFNa_n(뚛+n:BC .*5C:BAH7~n{Qcx uu|_)Tt}L׾ u~[dۆ4 2sHWLgȥy55.yԐO]n6h4F#X;6&i~-V$=TV[6==Bя1st*UjzuXSr RRU^9ży_F1 - ~9SG*T$ҢHn68ݷwSxSC)99Ʉ τwjumQC4Tra]dĈ<:գG۹cGoݺP(IΝ$r.V^/+CO}wԬ?jXw.5aM}Òڹ:DN% 9u~'Yߐ;vzAPaH۩̵%2qMJ:hF;whi:wBn^TEmkr ?:Dota# ֯ :b=SW:ר\ܞ_n_C7W,[Զ@ n#u.iuB#:>e7P׍w?16psN&k_:tz |}|6M>L0ύ`P׻r.::h4Fu:]o&v:gϞ?m޼ywQC9oܸ^SN&z.z`6v&EвEPTwjjYݏX:ŊvZ-%5XC%Z5̯ǝ5Ւ\4fSOU^6d|:N?餓;=&NFh4PuP!ԡ:u?''c~~nݺKDڲeGw޽{?C9r~N?oN *KGZ蟓Nyo7<kb?<0ڥ#XG6+۝:gk_۾ސaȟv,}oi@^T*k i4d0|;PFh4K7W;u._{޹}9dHNDDmSSѥS6{7ו8E^# uh4F<:J\C{:,+Ue<0ĐvRv73jҶ!35dۺݖrP!]Gy'/9ѱouh.㺸Hjѭ\3R-g~?t .cvۗu{j/5 ciqZh0AGvsc-åm(1^_ώ;o׀N ]e#ce]#c?9^y# C"-ͻ6Xװ݀v>fr{ u:t^㝗^;/ݜkz]v0_PEQEQE ԑ휵ӯ߼;sSO3;#ޙIn BGSЎLw*/5 cW ;\l}Noo:E d:ңpґ,pM{?_ϼM;e*hhGo;޽:v}tjzu QG"#eOa]oM#>bzNi*kǟ{|h~M49uTms>S"4"ΉPBa zmzE|9N^[^1ncN3tr^9=/ݴ3Qh*Qah:@"A((u\:GVZҩLXwĚzϼ>%~:ۦB=cdΎߑ~^)/jԫ[G^·c?䤉'ŜKGc[,:WQ-=,+'?_nPG^1ncN:KMGBTR9%2([q.REQEQT;PG9 uDSCN{cZhQnSҏWۦMz~irG=C|ä# DLٔn]~it>zM Gi(n 'i_ks,4ʢӯ޳>;~>5ns! μШ4>qѷ/(t$W{^AMrk4i$b]l(֣m5lL=<9j8H7N0rH鷰/8uҡP`΅ٜe(hNks#L|vXYݴ}SqXǂסrE6]; "4:dN[f/r'zP5kѭEA',ڶϗM'> :p.x?^|WTɹk` e u$2uPvXkǡQĈ/ԏn?fͳcN;#!HFPm+ԱU&in@;b:uJh}vKZvʫSbn[CZFm,jc:CM.=eIqБ'qG_QJqH헿{y碷k똓qx瓟׿xsm#[ Y Q.C;SBxRv5s: c=̄irHu;i` V*5ղ{oރE^"(*TDaU@J䥈R"QD>xfIӦI:}ʹ3iI;c}$NiwSSm31w^ݻwݻ3ߞ;;҉t5oU> Oo~{7J}K:[-K{CKkK 6OP4ւ!z4MxڿsQEeZ :Dz\1d1aޖ:uZ 'v,ԋB!,k-[z?!"B8FQ<^B!u _&SnJ$l׮oʼ)3I9}}Y+fInP4>R%McXX{Yr颎{E?eJo^j;+;=<+8w絯ux+,}K.W4~O5Hgun𢎸AMz6:<}u(fOP4 WpS& '72L[0-Ѝȿ)k_KSO C.°Kñu6FK앳Þ/ILnz2_e,& ^$yJsXuqxt|^ux_8# |œ[$S_yILrs9> 3< 8 IM5!ܲp+]Qܵsáo4闧Oݏ8_8Or9܋s8SQǻg:9z&[5Mޤuꇧ¨J?O|j{kcq:Omgvwǯg;E}qc-:z_[M<dNPQK|˶ضp܋}pۇK_aS_ں$cR,&~L4?;L|\]R8# P1UޓN#&_WJ?OlbxBI}J_wXlPs&ý8$c1HML g)c$u>66 .8/))?z̹~4nyA9y繖>$V{3~YZ:Xg-)khds0+Ĥn|Sɯ<0y ]XJ71irו>o/޶yL-۽,<򛏄nqC;vOO[j;AcQ\Zle-v~~glfY,N'9ם$׼[BSәSTMT$prw+,oQ~rObX7c"1񝬘pwYj՚J;"a/oSR֧t/o_w{>k _h*IW uxZ>+쎰Kv-)N\ɣ_Q|O.)}uA9y>$V{;~]=)c#}YlgVJQ2%'Nw||37&mc%._89ɡ9lW(~vDZ;:KL2o~sMx >gF +잟<1?߉o<{c^?`о_yIgΣɖqq L IRI$!?ås/&K%jSfY%9Y8n%N2dlxTѾϱ0ZbV1$nz+AόgZw>ylZї:ƶۖ+b"q,'^׊'V(z]^_yLN7%y36&cNGO;cgn{$+>֖ϓNǸ/-l͹ǩ^IBAge{Om]CĶe?wϗ)._hO7f9Y8n>W~4SQ앳~N%&~ߪ/nWy_HV:(g\y繖>jsfy_gGʶt夺uXshMW[,ud{kcq2Oj9gg;e|{Q:/*[m)~WFPsMIK^2qj ϊ+Wn]qgVx+O%kݺ"$}g[>IƩ'y/[!2ܵs+gخ>uux{/o9|p$ BM|#>+f~}h\ںdtuj{kcq2Oj9gW;~,ccQVR8^o*uN2'(f. $ P)2uRy#IaJ?v~~gh}5\|ť../#;[-mp[Û^|qOBWɮbpM|֙dȡwל43N={On+jC\}bKCSSSɳ,~>bpw\3\ly% 3{fgIDŽti TQ8ĎnH/~~7W- *V'nh}qjrj]gQG_X<0kdNP o>QL(ǤTg훧4->eޔmo0qmbJv7_{b=ܭYq$p{.Ha칿zí:qEBI}n>{s8KN4 hJCz WұV}~OW <\KCyծW:qtQ3x3Lh,1199IRiȈ!I$(݊%ۿZe%. n|ȋF&]vawY=W6MOG-b@m\B=&Qȑ/.sК$ 3fEKג4;N={O8-}li}$>IO?8Q x1{tC-QI`sItHҗ֗9?w[7iq| adDŌfTyw̫ھx0rd 7hɮ%ǩNHVv :y絯e3Ku&SÊ'VTJQ[}>bUW!DDAQP'R/B~SW3yLX}pu޸BE@ozB!P4 !BE@zB!P4 !BE@zB!P4 !BE@zB!P4 !BE@zB!P4 !BE@zB!P4p8@{UIENDB`java-binary-block-parser-2.0.6/docs/jbbp_fields.png000066400000000000000000013645101445000440600222140ustar00rootroot00000000000000PNG  IHDR Y=IDATx^{Ue8MMEDR5PKNcLbCށW68Nϙ3c%4sxg/^\>}myΊ"-"ewI`pC"pkkYKVt}ٵ2\E] Rv uFBѾZaeu)z:_h_v 0QײB]=}P/Vk:7T(Pg>W@ep W\Mu)z:sAh_v 0o1x@]ʮᾎ\uP(ڗ]+5[ ^(Pg>W@ep  ԥ먾P}ٵ2\èu)z:sAh_v 0o1x@]ʮᾎ\uP(ڗ]+5[ ^(Pg>W@1cN*9|ve{/uC'f]]o-wءb!8rܹ$ ˶p2d/_Sx;:FM*̷xQbBz5yK|K2d!+ 4'}~nveN|/9dC}'WwuaYqtF Dz ˴%ps<7΀9xdrޞiM%\gf~\uP(o* m8{uX1wYOdsCl^X7dyőG.N.쥴q#\syE';4>wi;!P4Y\u:t9m84k֬MtЛL4'>|X [x>d`kҖ&7{3j_>Ĵn^X8:#ñ:XdqGfۺI%lGcg?ʛ,jVm+{+7_CYasM^\ҳgt-ܣbMOrmJ>$(nOSCy&M6!}G>8`^_xn8q<ۨ(?N?ti%K,Ix'L?͛_ihh84P߫ǚqa{TZ_qtV7XdgO^tc_K=򝑿;w& `m(74Y<ǒ"+F<~^uUev{34Yﱅ]> s!;bΜ9O>e˖wĉpv>T/_M!c"y+k-ܯ8:#;"mVt ^d5Y:P\,N9dĈ]޽M-Hl9sn(yOƏ?0Wj,>#RvVł'//n~녹dq4cƌdժUrʕx/;vVTBz5/ooݝ.Iߞ},H8lRi4?^ 89Czv:!kyCMƍ 3(YA5[M5Y_'GO]6A2vd2}O9xd-ntO+yOF/gVmmjuLh,op2bĨC/hf{/]~Ѣ7IB,B3FhN(ir/y5oel⢋OzC\/eg5Y4t?&|0WʎniS7Xoyر/q<(M@ѷbpɨ G%~¤aw{ov} CDh4(o:`Ӵ [s*xwcM '9Ǿ6%ñKGTW)p>=$vz<|Eh`dMҹxD6\ps5].I<=s.ݾ6W7g􌧓79]6WxDi:/4VϾn_~\5MVmkuLh,o/?/^N2plo77,BuxxF~![k8{7*̗2ij&wmJqW+eG7Y|.\DQMy?6j2_Dߞ&?s;ޏ7pC, "8b#yƃFT4)4?gsw<>7Y||6w۲8 ƎS+2^_uܭSZ1G\[,BzڒdB~sY&+vyb6nS|sCG7Y|k&FY#K?45=zH\yvtٳ\dI\x8^զP^Md~+&nX^gGr?l|`ɛǽ}NCv*<ʿ=ݓߒ7Yfo*yG݇_XK穹|rړ[ ڪbNPCVg,7MCn]1Ges#F쒍:ɢG{Z\,? sMSLˊ+ =1cƼjP^Md+qvivQ_qlnc3\%}gh(47Yoh.;bc=V<1s$B~d̘1-/P&Ei9xd~u]gGr!l.4`r+zK^:>3dԴ{'ߛ7Y׹uܭ33)f*]}6';s3_E|llhREߞ}9M@G55,7M }Ogs h8짒#Xz_J?~2gM׾nU9a_?G s&~+"Oo8u# k9oF2lmKl5|F\h(7Yƍu|_6tŸ&UUf,xӦ0?M?ln9ᝊh:R) *7M=zH.ln?}xs?,lݧ sMeG7YuQ/Y@QM.Z8WD)W"4Yz$/y9ޏ7}6Q<3{x?5|Ky |3ϥa>~գWZ! Gy>=lwO|LSԇ=79&~/t#~161 ;$[icūǼ*rЖə.y o<17fo'{۳o:d]Zu&޽% L:r])G~=ztJEș3g8y,X%K$<@X>owΝq^nhh8<jW(P?I=G}4#+~?rHtiq͚,.WE=_SX+套wcaj3\èb8M4iɓ8qkL0ᷫ?Z|}u|r+~ 3(wHrg$o:j8fM 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G,ֹ 0o1x@]ʮᾎ\G54Yn,%\Gk g{ ez!.$4Y5Yo㑲;$Ѕđ& &-DKqҌK>w璏I%#t2g<{ramv[׸#O;0WXdI&77r-9)4o0td% sh<,.SkQj)|dCO|/RtU;^lFm`Gk'@BkS}3ɚo88C4d KB| ӱ~PaR,qx-|+-a-?3&_?-V/&}ee7z\pT:_j(E55V,|7ٷ=ל/ο~'c)7cތtn䨑ɏQa-ѸBN8>OvZdɒH|3޼y/?ã+SsQj*=,wc|WKkh"9s?}-[6V4wy'NMCCQ|5xBljwi9@T՜AW&Orbז&=LN:zǭ7Mқi~(-[й&w睅e[9ZқB^|hYʪCٰ]0^6wƵgT̅aҹ}jqi{ӌ3UV*ʕ+W8vYQ \|ǹ7-[~AdґN~!'w݅w=W:靗b-u›YJ˅研~zN{TX]<\)öK Ox3̈mG$}IBTSJsJU[_2rq_:jI>!v.wh` 4pag5laxc[͓cok籩{t JQij[+~"=7|ҷd-7IoLWlѸM5qx-|+&y<=أ&x}zŹ^{%@a-5?4sH`xL ݯ KOwp65Y& ζ ˄?Ζ|놢.8 ovas! [k*h)1zvN`ocǾ6cj*{ѷM>b{{q[*s[CJc[O"ֹnuYMο~'?HPP1sc MPU2Yɀ$ .Q!4 Ƈn:4} Jfp aἛ9 -޴!,=ޜ XhI&l3_zMȝ9y'}T{՜d~CBsŵ]o=!,›+Jn][r{y6d,˞[VΩZX>D!?P6TP` d}쏏Q=.Fc*kCc>c\61O~p…&j#IǧD)SS|FyQ|~w\xAi|0lB@x#Ai|cJ̐?q3ƥEa.އ2%4 ["><56^з_fJެPƙÿK hQ~ĻOd y d8磩svB|֯dy6Jcổ ~CGXѸMM5qx-|+ 6r=vl"~]gc~rl;[uamm _/T^}';=a65Yl_VϞ= Fs֦l> ?_m4ncٳ\dI\x8^զpLME{W>-5YwgsIxA~(¨Gc m t|/M^E#PX\ - emx7VnSa5oFygWW+_Y#[esߥ̃/?7Y߬7yl̟Y>K'VV̵tCYZ/ʷ31ɹ7[^-ѸMM5qx-|אhsޖ,  ݠbn_N.^zqaP ӂX(P.m'͏\X&R"'1}*mi?W" !~|O{|!u&\a'V1"|(W=eʔX@QMs=3f̫Q^& T|#,~ևsjlUJz. սNOf͟6^r'D?_26Yg|p,uʬ}b[%+oZyӏnJ˛,Fl;b{}p6.ۥcm=h꜅kBMIΐaC*okqt>B]%?c;~qGfX}r~V3I^n@R~D,XkhMZ8ZjPRbsfP|wUdxDi[᯺狓i~_Hߟ>_X7\~ˊj+* ~2ihϺsH6~Ă#ЄQi.\1K-ihFdqwoў󕏖 [kȽMu޺\+GnӻДұTLK{7|>ﺼ&8׭.ڃ_2пb*1SǤcm=ΖGo\hx Max}r~Vo۳eҴ +%_Wj`5kzصhkűg9fx_j1 CE(caЫwt[3ͨf?g_vxafm}_SW|o%={LkC#FxkRitCK/.W}QGdɒBE5hѢ8^զpLE{W>Z*doi"(vt?>?o$-K5T44>K?x#BSGg2۽len"SȶQUd-'ҦorN[bӭ6CVi<[ylim=?-m/fˆ&jq]M5qx-|WY/6Ox3 iÛ!XSE/$6m"!LXo7˶'wM;\c(oHk)_=%[7PGl;"N'l&S/>5K/3JֳW0-Zsn~:!6bt!Æ'?_m4s]pBE59k֬8>%MZZWy\~ea,5Yn>/xd䨑iDx{E(L .wj*m˥摽']w\{ߵ>v'-Ğ׳-sp =|Le M-ҥK;W砨6cj-zqMaR;l\aM.Ɔxh MfR4w,{6|& ezKvCrf<}FxCx#Bhmݒq^~-RF9>ȴ!69KKym¾|T.l|KX67|c-ylnנܶZe¹ M-AwdZGk'}Q۟Bh|gN ]t|WPXǡs-̷5v;ӧjժB3ESr??cΪۦpL{G"?/>x4YQ ']Ewn PJ1I>[[ZpEeE3f̸cڴi;xm„ nhh8m1,F4^#M5qk뉣?bWqĻO$_szz$:0n+~r3g[~jquњju",RʏNuL)^ *44\frwfr&ŋ\xs{dYf?mڴ<蠃\dҤIO8לv+7WZ2tm|w$?А~y͛e״&S7[sg=D67d 붔{yd/O/49抖Ym.JE8o5[3,jhg{Gziׅ_|(+6(4YUw` 'pN;-YdI$O>d?o޼ǏJCCᥓr%dwƏ7믟!n>P5"4*+]g=O^m:J.xٖw& /[,qњJٙYoR~4:Mp\KMs̹,[,-zo.w'N3ZBQ~s9椓>dsq/g-M!|mbxM4?4϶d5gg5Y>#_*egfGJH4qBAso8唻#vIz y2n܂(!>˫; gdvN:KɕW֮;ciƌɪU Mʕ+?_;vOkYW]}v9?[m| 3dqݫ.i߾iĸodϹ馊Ԩ0 sE].|)g_},%uϟ v`dDZc˗W,Zř>4n\oРm glj{,n^l|?)G#Q eyE&C0al|mJlK.U2lضܰa#&H4С['_5/M;S{m&W_Q酫iC5]{/>[yB3Fhj(t^H*컔a6/|tkOoI:C |sg&[&/>pzx>}6nQZ#,:>cs)b[xNGm/.44M!ŋI} :zH6~Qj^,N>.\X(p&<ȟq|JTBQ~Kw#=>ÛN 7M!-ORizl< #沥m6xyso/쥗ZΖGg9'gdAXܸ @ׅMWlnݦcW\JrߥexG7Y̞=%K۫ŋ<Qm E/'cm̞ݻ -eś,_bnetlFdqo~S1~#dloq;sOܒwDEcq'M)^)& eyС[WQ܈cgXEg?-/=Y1w?ja,L+V۫{wcƌy5*jP\9sf}Ǟzj6~+d=zL)&[o]1Qet]B&3}bg^Y~МQ>׳~6Mǖ/_8YԸЄ\QQ,^M&aFVyfe|yYg}XrMEۖd1~(l9sfq!xr?p2f̘iKDa~>Iu첊\RX?MFkxs !vE9[oMB^_i;g?TS7&3@JMUfᠹ,o4hX:mW#_pE…fa.{ˇnh[W;M_ӟ*oKM>~>˛, V1ˮԩxs !ve/T1* swjE/ʕ6BFi#,s?dPRE[}ᠹ,o(ot>l|ݦdGqm6]5ꠚ&:%K۫E=6rv˾SOƯ|s}n>˛,k+7ޘ7רܟ,&q3{vTl|yUweI=[m{dG\{m6>sh{,n^ d['{&>6\w{z_B2t6ܰa#/?7>:t_M/Zg}񷖍돞;w ۫Yf=)YmBQ~s#^z WHyCAa ]>Q3Wˮ^{,z{96wΟ{.iOx^>mX?xpV1𖊖K ;`Ȑt맍WjU 2tUm?7oXpiM7͖}=Zmt=7n/|ЄNk3椊÷Kfz. {jroֽדɓI6lOinǓRr啯ռ97US)x~e㺣)S_lY\tqT:mT(o)/LƜtR; !4\4n\r?CrgX?췔÷[}M6\ 1"}So3f2'~;ɈQ҆~&[zep<-͗+edV[}M7fjs#$;wx`rĢEuBcG{v=7n/|ЄNkrZPT\Ǐaرr- E{eQcq'MdQc51cӦMkmwqk&LuCCٕ E{eQcq'MdQc9s打'O~{^dIEo~gܹϏ7円+hm E{eQcqE,jp\|89q4iɓ'?=qV?0aoW>:s|ٽ2\8j{E6),RʏN@ɢ !j~-*Gwњj%?KZ $g3G&3 kQ(ʗ+5:#M#/h-+ e,jp+E(_v 8{,4YNpopLBGx6+|i1y:|ٽ2\8d!:1ֲ1 !?Qɢ !ZepH,yZ֘H|ȻKb2GER/IdJyƾ}>,Ny<3V[eW{U=|^|5& nȂ4Iޱ3J'_eM ?>1+^i3v:*lA[cle#zj :4R=Ts l;~M6 ^OOW[qYiOS+ u8uZ֘qha|nDW^W5Ho歟gWNRU:>ȟ dt|乚Yw]F˚,*kR۟nRuWnUJ^5~x]t]8ڴתUKm|/~|f),6s{/6)l_%]Tӡ1Y\:}]zwQ?97uTsΛ~voEbsq@oQۭr趖m['?x2h%,vY=buYWߟ6jԨÆ %\r{5p^*LSxT^~HZda_"ҵ\llfKDeHϠFz TVs}kEx'L4x5ZdP!Eo>Mu'+,ڽWl'+WuqȯZPV8ⅻՐ CHvbQ1Yx׍xr{oտ9_޸w$}܋.ȴ?fYO}m;UKyn}YY=GpwBY :wUSQdvZ.㺸B7VĎwsJ\s\Yim^^G*SNϟ GYHsukSGZ"T\o[I7W:MVw\MD󎇬~Y^~_._eM6d_'ET0ιO~oOfp0vXkH !Vē<֫~둕:|| tߏ&?jb=mٴoP-ذ OCOzmΡ?&Lؑ_r-R歛럷R6?^-छ+&_+%3Ѽ!_WhYe Y5}çwK߼d+.B $OO7ǓmdRiVV_E ۨi#7b 01w?t7~9994_Xc&/"~Qߙ/Ԣ͋z WUCcxWuALv7c<ٸ߃xP=3@&Cwq%kh:5kJme)Tqz,F(qaK>[捽߫ d{)Zo='Kۏ{۟ݮ(7Howmk)p;^99GI OnE)ι˺>Oޟ|SEWr|һ3:" @" WhYe X,du[Sj҅ MЅ}Yd*c)}밵g,gpE׽ʅu}y/olGct1csε(ɂ~,_Xc&*Rd!=)w>YiYF!ΊR< +8ӖXg92w,I'`ZXiU"N4z2=ti?YiAF#q~c,ܸд˿vyv)p׹/9 o02Nu}kq$n}VfʺsA{>Rɵ9JHqۢ1)TY5WhYe h,~*))I/)[QO_F<,C&?;^1Y_w%C&J9j/oȪk~Ym}̘1NJhrӦM}c)ND?Em^մzýB^u %8g{n?;.E>NkHqwh,~L_n+#HFYpg9ȿvOvwEiiAǻhE)tuꞹ_٧-"G 9gP=VY?Y}˲+OIʠw}NѤ5Y6ڐ}4]$C%u+2q&N\625l0=*NHqߗ29LzCB|)&mݫz?D9QFoČy;2m}Ȑ!ٿU@Mٳ󬬬O|g1Mx,x}t3=xbEpq*viLj`Gcf,aDSG&"_;h?Qsm/LhAr;t"̠ ;N:NuJ4nX,jk#G 9gPcϟ!}3" @AP3}!&ΖɲцY  4))/{<$.XN_c` hwx_wB '2W,(EV4plX'!E,NyXFsssÇ'F~СC*+DLS4ᝈ~R,\}&7/Nov빻J-N{BuNsԊVi,l8@Vpwϧ70{N' sKFSd1h ׾k{8# )J=N#zDNiO '}wܟpǒ{se̦AJJIeE`,UE@͐YiM6d_'Sd+B8}$d2/9o_h֪Z~r=ܹ!lڷjC_U7fUV_E,.i]8ڴtчӷ]ye{?JeEZbݟT,)HG>J|zioٶS>{HcfHϮᝏ>ͦ]yhDwEiiAǻhEOVX~HMHхڽ=^E?%'/ 9x+Mؗ" @Apɲцdy,$d¿'1$cwoJJJ2\ڠ~ k3>cE:2c=T?d7L9rA}]XX]qqڷo.w}7'N|G䕏5&Ƴ?y)lWKֶDb#(TYǚ,KB>/~4OKW_|?J>[3kLDXgmK$6@eEpɲ.KDjkLA$>%g1@8dYy%v"5& nPd k,A ;Ay7}@L(5Y …|^|Hm ćl &Yǚ,KB>/NDDCM_b6,cM%p!_b'R[c""!/? E±&Dϋ/1wӗ؟ Ą" Xe "\ŗ؉֘H|ȻKbBpɲ.KDjkLA$>%g1@8dYy%v"5& nPd k,A ;Ay7}@L(5Y …|^|Hm ćl &YǙKf"'R{BdDl &Yw,IFD$ʑTZs}Yw,IFD$ʑTZs}Yw,IFD$ʑTZs}Yw,IFD$ʑTZs}Y^K!;$+GPiQd@GF" " EE>,4,|+kz&w,$YS\E۷rradNy/TE1\bքsi)"ƔސS̚pN5-=PQYĘrrYI*"S$[zT&dMKy|}TCYĘr%ݓ5$kZ{K{"Ɣ%vr5$kZ{K{"Ɣ%vr5$kZ{K{"Ɣ%vr5$kZ{K{"Ɣ%vr5$kZ{K{"Ɣ%vr5$kZ{K{"Ɣ%vr5$kZ{K{:iEYYӝ:6mgmS3p=mM8k>X \Pevꥦ:uTzjOXۇʬgٮMrz*%=]Z?s+LtߛsMWjzyMڶUC,˽cG$7vx3pDVdQTtL͘艉q.Xes쌌V9~?mԨQK K.XjTeM8k?T5nܧ{;E]'}(a߫?t,{͸ݛun!YVdy'TA" L7nSÇa׮]"\>}}[Y.քp5}Ggmg>DGZz?nm[֘)Jz5V'lZ&1Ud cPd NYEJJ.l!۴iSmr\mZM] ͜ں;}u>SYYՅNP+V %[;x_gQїz+)io;t낋6\ye5h2;V?|4''Cߟf r_6={1JJR7jwߩFm2~CYJwEJzɳ\,vvOϞM{~L֭߫Lh{l'bxݱc:!ްa3VNZCk2p3fxzܹVE4YPP%7 r7ߨYTZe»|"=gm.ERAc׳,I>&/zZ*G E}2gչB I ox5jʴ/M{\gUҰY3\CkLnlgҤc ޱzW˘#'*Ǡ@\"A}:噾sjδ''7PEEtYUv,#'` Un}̘AE}3XqqU@Mnڴ~| rՑ#16jjnjmIuL)Vh0SYj#M/O(#E#.L۱C{ֶ-+: z3L{<9{,)+tH۝y>/`oܺ={̶c ޱF[d˘#'*Ǡ@\]+$QUx.E׫U/3KP_ffgWPI葯Wm"Y,\b$襤&991}2? (={|ϾѤ5<\7rk=۴c^+)I-½bd\ϲKndy,jӫW1l߷W<ۯ+y>R { +K׮O>u=ڵMeHca.c"1G:N'TA⊠ Ѥ \,4i׻Xײeנѣ7>)XcUn}zfHEr{S<.}ʕ+n {nn:|pPȑ#R:tHeee1bҹ7 ^jgA+.hҶmP_ﱮgٵi7 _ԿyW?V"d{ONJ{b[{]d֤IP_]rPndy,䚖~tS9xn)pˤx~K-wdoT-tPE>HULJ2 vn!e˘#'*Ǡ@\"u>WӦ4k׮zJ-]>vަMOeqko|l#`lmP TujŊwTJJ^BΓSRŠk?Q^{P5jZ :g4S'>>w\"5jK~sCcdM8RQ~{ؤwU)' X]v־veݒgyUɳ|Yz=Kz_\ۺ]eB~ݔ]yGOOIO+&x )E}NJٹܠu/y~{ZwR\^p^B-ڷWMS^^ ꜜ}4uވjm[tXkzv9.j93x޷3x=Vq{LRSՏ P7n/{SXEwߛhqq=2/EY#BwM2_`R#>Z_澟3*ĚpN'ݫu'+OERYSce<~uY%2} 6-yO/1&buv~VELŰ[oʓDTE7UVZEu-5k&N.)L9rA}]XX]qqڷo.w}7'N|GcM8'cK)8>PU~KҰnowEj_['9(gb͚Uv4m~KdBLi;hР>-^]|(lIΔ~=k9[.{-5}jխjиJ][fdv}7-Ǐ[+=x"*UEUH~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[Iľ!,bL~_b'w[IľrY  IL{*OE(5-; BE|2Ѽ!ɚP!Y'; BE|2Ѽ!ɚP!*hY3Ѽ!ɚP!*Ո$C'Xv w,Ś\NEPPd,r (" `&AY5 SdQۓ" `&a,{P}Pd,r (" `&AY5 ,XAPd5@y,e"yBMʳ&A*ؾ]13MEP}Qdq R/oWCL'I2)/;;<^Bb5W}+kԔ"eo._0\Aèu=VZ}X>UhU1$Pd,NA=%j{$I?}%}By'׫4iD%%U jn"q;XBձG})d!w뻷=UjT^Uz5zh=Vo_Y=KevB #WX]k\:Ǵuzs`&g5Z}uN9j3h,>X :`?#Oﱽ~,b}"p|~ E v=dp$qŀyt1@ᓅ"u7 2_VbpoRlz^)!TļCn嗩KDedf[ 9Ve*\tW쫳Mۙ4P}…"%=Eevs.Fm9EmvNZ$g>1Smn>ֺ֩Y oЅ;tʽexb>W]i7T"m}~wȹ9;7>.n'Ȣ<5T/3_b߮ NdS7_bw .wrybjحÂڦ<0Lv?wA} BH}6UinCoM/>{lA " I)4MlR 70c5Ӵϴo~.qS䫳=w܎CSM{f u[:jq,nO RB$y# :O=Ԫ#휈TdQ*P}Qdq R/oWCL'I2)/;;<̄8=;A?" Y^Tg}L{YE m/hke]V|Ig]P x"h%)A}:>wɪXvR-:0E*;o L_nLc bT灝>A RP1*gVUOG>|,\CES<鏐>@UB)HgNVCL'I2)/;;<\}Yg*e&R]_R$v)Rp*NvŶlS۟qj;+m"E}du_Tdp_w,NA=%vҵb28IOy|}PBV&8YfBY Ʉx> -,:YEM6 =kٵeP M`x[ZjM" }V>MIDATʠB,YC?0Hm9EKob5SXdJsʿUG*ʚMYT \O"']!&5.\2;wVRSUR:*yse`5'Cetϳl&Tn9~\uRF?BeoSճ:\cɓosα J ^X#/&m۪!Kތ۱#hlOk;wVz8F"w .wryXk 5E~k7xYH}f)pUd1xtHN,+wtBw$78y#S &Rc ~)`Zdqoׅ'n7*yzPۈU#I9眶sMYE>P8~4JRYS3fyyyegg_b2x̚>ܣzr*m]rѣ־,OÇj j.E᮱*ey)~ǽ<,1k@ŢUR"c{:tøvIik 99*9-Y7<5މf[mOVp8)ǒ{:Ӷ/+5TdM :LmjҶ. XoS;'ꓐ"Ι@uj٦ֿުWĐD^VMg6x"" jRkۗB#~ɾR@##E&r_e,b}"ph~ n+_UI+:1Y_)ߟX[d1knEթSOF_֯Bo` ֱ$oOOɦ-99/_aοr49K~9䨙31 G}„ ;Py p$''gCu'VQN}7Ev<.%ϲ^=]$ѿY~~܎ֳq&97/_!/>h~;ǭᆱzFTjFz :9SCnJjԺW^Yd]g4ؿ{ǎCOY+Xx>Ob2xתMϞz̵Mw#tLlC~]d^,;<%2,Kεqseoi$R!-tdї_ZvR&O,~޷Ns(X񗿨͛qv~*J}?vHIuzUVIo.+GF}X֯%]223}BqU_cɵʘ߯SuUAlMQ\ڗ~{˘#Yze5RS剎 ζݫ+ uK>YҮj7[cEj]c۝ʨE|GK`t[ :NZwU w_4E7nߴՠAc>vvOϞM{~LtdddE^Wg;Y;ގsMԩ 6Zcv1csε(ɂ~,_!&M|rfRjcU?}tYHJs]r_}"M_li/\zWp*cFZ%n[[M Rn|aFPCҽB`[=^Ycf6 |K?4̶}&M ;pwH9qy=2phP)DYȸs=9**:gڭZhTdq> ^0}Zu3cl / }̘1NJhrӦM}壶 *W9^psjF_Zf{ݖTNЊ2h% :幞С=DY{Ϲ?ɊN?n]_c;(+: z3L{zG /[us}ַn4{_"¥wY2Hlj1k@ZgVZI*5#UN8Zm9ڧƚ袐d} y RPXj;+r7*c:NEK`t[ :vJwѤUD.+ZdѸAuԗlm=zfHE h$jt / }Ȑ!ٿU@Mٳ󬬬O|&Cy#GٳM?6?{w'Eu}0,3 Y L \AE "2."c6^cb^%.OTuuUuwuO/33zjZ9O^yuY|祎syv\Yl {Z޽?ٷQݺgϨeG/|Ř^8owk)%Ϻ58i&i֣ܧXqk"d9zzR Ti=FQd)t$"X_dQǞȢIezdOk޼sԴӤcÆtuu+'W8_K_dw uv8p>x`TرcaHs~Z/jI`pwPNa̘u뢦;^ֳ;&%%QzMrΝX%)Y8ïpAytQqqԲ¢"i k_ۙ~ZO־g^g={bSxoYOe__**Mh4,8`YwR A{.X}׵Qrާ<=:ڧX'֓㗊u5J4" 8XE3(OA_=hnE~59VE5uӚ6mgO4v+iŋkOkذٷq'WfD۶}C.Z+w'RE^z駻vPIYYًaW>3cl'D۫3ϲ8,sX%8͚kD{p_ ruO8_uX"寿k֮m.vQy Ѻ[vf]{v UҶZwlGrеgS>ߺ՞oȢE1)VksbA} Ҵ{p9F-~ {)wnmY`ϛ޲ i?5k={^.}Eoݺޱg[7=<coo]W,?~.(hh>BSXXdN/(h`VlFZ{$uԩ̝;S@$ƍ{0k@ FԮ[7| 4s׫WgzzѣeA3pڡsyi\:G\9b.Sv^4Ɋ70e ]Pqj4"d|AÆ ]7 d`?u1CC}'lnzHl?Ao3=A~yFu[񺧟~!?ϟoX8 b/޾rr * "keKs6$c޽fs !r 'WyJEֱ ω֓+k@݃i4Epx<⬳.5tǎſӵjG "~fFtR=qNݤIYpQTT7o̳+R!xgdN1eˮz=U2q≧۔bs;z$I'gС޽SD/<_ 8J},$G ̙Yf4HEe?<˸#v&t.weIYpQT\lH1;ZƊ[;u2 gg,κ8t6nl.'eBa:\:~,z% cƘ W뒳6(!M۶}gЫ~;kb_c{rǎ Y}][f̉js1PE4}SXwz|YgR>Uxӝ&>YO:^OT,*Mh4,8"lG!doi쏪}̘1Gub ⡇{ta)H. N^ڥ`]d1r*tRuRqPirF}-MרV|_tӦmԩxe)cǎ>dȐs]} yw~=u7 AiiQG?5g08z" 9_Ö/k{O={uAfse{#U'rT_4\N(@/ذ#ݦ͹:?.,,҃;w̗1S>Qs A> >Pftq ]3T,{5oLݢK]qcW.,*mz#G<ːߔT7=FQdǁj_dQ"L=St`pBH#v.hYq",D y{rG7H \N(EY3L> !o*;և]o|lP}Pd1S>o@pBHf"d/BH8~ ETGYd!rT33r{CQdEY3L 'd&r@Ƹ?"PY@uDE"L=S< !o*;1!1EPQd1S>o@pBHf"dLMBo" (B䘩 'd&r*PY@uDE"Lvеg 8!$3M~T#,9f*= !o*; 0EPQd1StNL~SU((" Rr9ڽ/&; 0Tc >R)PL'C\CQdEK3#,r\s!$sg ETGY.BH8" Udќ$\s!$sg ETG*U#!$ Pyg:B!*,8r&&Pyg:iB!yO~UEBWLGӨ` (P8\r䄐{}3ՙvOG|d4;[jOqd|Qg]а@<γ|UkkYۯeymf:>'93OUjK{gy6~|s#m9>ř-t t^<]=vXmenMzu9%y]Pی?Y?,ip'Ͼڧʶh0*--=N(Z~V,^gb:r'q<Qa=7_]>[)g~XѯM <9zFŗ+ݡ҅4Wzy9V͘=iAǟ(=NN#SU~Kuc1„LD9 t+;M:\"C>pY}BGTg=H<mW7VD rY3Uu=굤b[?^g,<^#'ۜEM_g8YJq`ǨI.#/\|oEYmg/')j1Ƙz_ܞUW28&snp**d@(wyO6o?o> /'x͛71bSaˆx)#_Tݑs*qݳ5_wlYdm 8vw,_*[#X)$2ap䵭~wN"n;=SlŎ]K^g(t8Uw]T}w~6=w-Qf=;))Ɛ?w[GEaYd(rBiJ6r䄐{GJxYg`3[vkY*O5jzٯ%EҮu:SŴ{yKkݣ%__uc4HE :EzV]N-{;?jY4mTmvΣ;uede&8>UU?Ȁ@C畟A-:t藻w6 +b{}0JKK: RLW98R|):xVEN?Mw!A.ݯymJw~dhTQ{`*:cuM{?ޟBfT<%S_ym;:Y8QdqR.@NwTn] {xZ" ioT^n7W4Pd_deOU0n8=ٞ]Z;-b=g2]!USՋ, 6y8pSTᗽ{V -?ZGgbh.Pdql$ѵ$<6Zg*YHBjw:I.Uw]T}ItwWU;{Q#,'(P8}P 'ď;*.V=H.'.h|'ax𺝯]Hfh>2xx?{{ {BY>8Ycfm?1S:d>GN^П3(yY]Voo%6;^u9?I2\=Aݿ^~J;'Hwu:ʹ$gPTŵX}i%>K\\Kk6%Y8NPdqR.@NwTn] {xvu盃<1Gob2@_=*(;\f>o랾\׮[[_1\_pȞ ]SmJ=aN^;Z^Uj:U|sY9>j[-Na=I'*H>,(hX;YкGkrܬc#,}ۿo3׵M~)o4 %xyg9w2of ^-Dk6,nl/k\mx;lKkƭE6+HPy姚?MI 8^qexqIOnmyDo7 s'ݯ)Q;ԮnOeL,uzr*Oގ1JtvLksfq4-yz5X^ sId-ׇa\3.\=ၾP-_{02)Jr7v鿞;V96>ަuJݤ_ ӟ}A|rʾI>/)ݵbe uFx"ummCtmˠo TgIEBrv^s(_[96<]z[35tߠ%]u f*Z̟A6R9rM$Nv\uJs4S2qHd=[yK(p"CrA]3?r޻@Zi X둫FFM=agFMswRěֲkKgD~]i ̶X/j}Һj8NY$Z_sYHpE}|?<\!v}۾,D]5<ý;ӯv ̾Z*XrRxu=j;Fuӷ3YrZ:|lS?]v (eƍbA(WE `uNsu.7)q~ӿs kV#έ?$zDE{Fn,sגp|˽Ѵp~t9H޹} ||Qd=#?%ߴb@u\&Y8 Ei V/ߚn~Z@rl7[R8#֠oi'vc,RW;bYĻڳI:=]tOzrd~)`l7ρuHQnϽ5tߠڬuwNwuʹA6X$봞#ǝOq3>'sfbV]Yf,'(P8}P 'ď;*.V=H3<=]" 𤋮),29N*urBHȽr{i݃c-_lÖӭ{ Onhϯoߤ~A,dP_8ef?>OTd!jMkӻ?ӣkۿٮm՟",hܪqԴQ5S=*6v=O"Ώ:^y58xZ&EĐW5?z+=k/moO͞yY9_w28p>x`TرcaHs{_oTgb7`IyrFIQZdp_Z<%ߡUmɾZzw ݒDE6Qd<28~G7o^vFp߳wFof^$k롽wEŻ#Ӝ%-rlt*L y N˷;^;ӧk*'OS;=u.7}ddɓXvӊIQ5y}%AoϺ6q$]mr+gPe 9}uZZ֙>N3Ng#ԓ﮵dD:cȢ!zǑf^Hы{~>m<"EAu9IC=,EOΈ)Co׶{hA,VV7PJ:,l3^ heȢy; ~o_=9h9`Yky*MX:/2M }y N y$2\Zw*EM'z{o!is'Aob_Xs$zM6=3Ho}E{?u&ALΈL$EAS][17dK΋,ƌY֭_znx-}'Ի4=8ku=uNеuQz-xw̙Q6mD6Tx^L]<AR^^70W3H/QX5=1H0=${l|tϳR0o<Z's_{02O0; t\"u#9=z$|NYQBiC΍LsD> z|o}$oZeNAdDu,3}zfb猏Ls?J,~OY"zŊ7" gz2Xa7nґ#G\y=r=u3dy m*w]K:?N2~@owVgȲR\y$I~9rotD< x*du HX"ѽ:s2i&iYiV(pydӦE_~^x7^{/+ڷoٸ1cb`W.@+{߿ףE"9O~zGMsg" uE2?fZv5-E]2'(ӵֵ[?D/{մMtNHPd!IbA>$׶}.KKtǁFz;ķ .^]Aފ7Vɶ{:]N-_~?X_7<9}gE8CzVݺGk f,'Oɰ"ζ rŖyhdN|g=&kRlҞz+Kգ~y-o|F{y oGMBy57W[낆9 ?ިpQFqw/HŖϷ}>EX:ouVWd!)#V0]Gm_39~{n,9Z|lS}g>\޽SD/=`83H/_ ƒ7ӿSMׇUɶ=seJϾDG*owU \eYȓ4栳#ZPAV3?T_ގ Dcݰ^_1{BK"ri2(VZ2P ~^}M_]^2AxZۏ(}6[6 xD>"uqx% ߞ޶e)Ju?%!^R=6V^/2<CΏs .^3/\D#gG!)MrXۍUd뺶fvdAWǔϯשZMsx/Ǧs[oZߏ;AsotXgKuwNZoՕݦ$s?R6b/ѱwTt^ZMwf" ,dɓ5hӟ~;hРKKKG<5gyyоZt%=]~IB2A} 3.vuJ*E ï!&vIbA>$-x~.9D)c5᫢>&1/xӬm{fпίcL='OpXurRpƅgE~Oow 7 zܭ~Q;'&%M̂"dg=Z* x@4iRPܩ,@cٲkKĚU1$M9%)wM;ZW" $"9*ER!Eo)&'OWGl,=Z:|lt-<:c2Ɗ5 i&N%!9]ۇA/߇r|r\dPB6cYĺȀr/˞;=:iVb yr+{{dc:ν5dߠ?^A~K]]mJ=גtk}AE{2 ALS&/_=,'2Zd~?a'FתUG׮]5j;uGZm.X꯺G Q(Eso{/]x፺S0SN\p޸{#VzN~o^7myj;{ۉR)SܑgO=w!g>==V y.Fd.tgZuEћ?̞wxΑ5൑'׻3bwV ƬY>ȶkזupz]:ᶭYq  Ov}喾V5tO]BQ(tg׾MN;ߺ';g=gD(a̺u}2|_gu^O:^OT#g9@݃ījs>mlԈSj[+ygI1lS*➖ȢXE<!O:o…4J\2}С߿"ky{5>bO;=$* $hd \GiJ" "OJ[杞޻c'c?~1\!xptg(pXΝGu˖]Ϳ5irƗ͟?O;nذ=S}Н:]`juV׮}\ox%zWe׸q+fyi=hQ8a}{C.(htgzĞްɡ1eח(ڱcGcذaIY;>drB y͛Ϟ;uNԍtY/X.HX W~ڴy+W|`ذaGZk.o>?2Ʒqy):*ݷNO%YT/񣛕;_.S" +~#)]K< #V(pX<ȼDMB )]d1qmfΝ11%Kh/#OǏ/忭~ybFmt6,iG5qEEͣ9TW^ܹs=EA2a„ øFF OXEVA! E/ n1ó_E)lm_5?iD2\?<8͛{e91c4n*ж g#{Z-%q@oдW+?_{}> YȞi1)V~DsbA>$&E_Ök[}_Рosn]v̳\'Gwzn-F^ =ܬm33_&ȢV~-_/_/xngغjo5,W "*<صkW <6qO o0E]͘1c:zq3H1ב" ٯ3~ ?x2\Ѡ0J~ֻ\*qYQ^];IOL2{~q/${D]ڳtYd+rϻϋMyI_T6sM3Z{*13* E,pm>zy[|nOkӦ?};F\ 75廍[o=LY :?P?~} HtDqnUdꬳykKE^ZkxuYd|b祗ӊ8wge,d[q ]/pR8!tew׷/imz;QլikӬGOנEsbA>$NRorO`"*_{;_/}O־g^g={bSxoYOe__*ZA4-& !t R$Bn4* $XeoetСKuZu3g>hNWWdaGFvܹ}y֭ 6dȢ2Ox>O_VfӮ_i5k׶]޳;"MDM2b=a~~Vb7_4tq\>wR A{.>_źߏOx^U?l{~S|Rkb%޾ƛ>[O__*ZA4-& !t R$Bn4* B~)(5k nFa/^_3o忭N.mſ7l۸ݫDvb=]/Ow) دR}'{p{"|b>Aq2~m{Y)rY꫞eqY1|`Okڮ=mt_[yl5q=ߝ寿nzF9ׇNݺC,^q.>" I>}|#rn]#/~}u=ߐEbSDӓxIK:|T =HFR>D6Tx^NAB!-F=ē" n޼3=Aݰf<)b˖/tYal٫hɊo;[o=[aO,zYnܸߤI^M57n漱~?Y /j.0+Wim44~RSN}dܹ 7nsa\R}ʖ?ky,e~GezjŘu5i zw/ 2^zߺGsÇ=7~.kTǚ` '&A"E2BBg`z=zm^"ᶯ{isz$XzC'lnrr|" "pnԲ]k}1g^}~OMN;<ۿsl<"tDI뉕u,{8FK=}l3< !B[={*'cER aW뒒ͧBS-A W~ۜo;S[Y%s9wcml2d>uKN9{ n1m}R~2;Zzeˮz=b]O:>Cnݞ"xyjơPr2xqe$Ԉ_r>St^Z =/fΌIm;w&%%fEQqY E2?o#!ŝ:cyܳdz}+R`\^>|l[ 5 ]3fOy<;on7~}}={yO1kYK~e]ֲzK>]xoDsr]zѣQϹOu ?_/+_ !&Ѿ97ylsJŲA>$NRorO`"*)dU<,{8FK=}l  R$Bn4* ,29NcN2d9s{׮]z߾}fa;:u #]3d6Rd!~z{u|{.hosarDΓ YP-h qz{JPH!R"iTnS@<Yd(r è?xC yyРA_wyE(dXW.@N2e͜[t5njԅEEMz|YqgR"wJhԛS*؇Ȇ̀@ EB!yOr "C㤂}0StrBHȽr{i݃i4ZM)CdCef@g"!BHuQ}OPdqR>)g9!$~QwA4-& !23 3HB!EӨܾx(P8`]3?r޻@ZYzBҗ "*)ㄐđ{G2!rc t!BHuCe=X(P8`qBHȽr{R1TB!:Ə ,Yd(rTfg8!$qQwIIk*{C!RP{OgE'ہڞㄐđ{G ŀ@2dE'ہڞㄐđ{G ŀ@2dE'ہڞㄐđ{G ŀ@2dE'ہڞㄐđ{G ŀ@2d_JE$pr9P۽/ 8*@j {*ٗJd\s!$XHPY="5Be(S>,\s!$XHPY="5Be(SȾtY4'k! I1*P}* BHa1dE8rFwF b@ @eT" Qd=Ȣ Em(]&]3R5"vNWMqB 0dPҳlUindcZzx˓&6| kթ(g=S/7|!-˸ۨգ:=bO5jDP+fDllg3,T|3=$݅ sc" xO (Bc}.)g`7!jDQTB.ַճlUhZeWȾ^U.Eg_vu.9,gKAB:qd,]7k,'rX$^-ygQFJKuB,*<@j {*GE[sL> !U#r8.hX_^u&:~F-}~3" )"p;Ym'=v ۘv4rK_Y껾U]{L ¢Ba۫=Ņ#d_Ӭ'3Hf=23]њݵKۛMx &RYƯ%[d1fƏ7.}Ɛ3̧mSmglZ.-'־3=v7Jt^;г+GxnIwE2K}Kf}VKxjZC畟HxO (Bc}.)g`7!jDQTɀ~-޿-Ɨoԧ~A owB w랾\׮[[_1\_pȞ ]SmgNo2@HB a("*Cv! }QJ/EckU(P䭭RVJKm_#Peg3%d8 ~~ϑ>9YJ5vؠuA)rඵuV]KBw~vJ0Mo+%'noWmB-e)ZWx߱Y6L6F-6}\.]:}w%uR|É N'א}C"׳{Msȿ+_3lgEey2OpK`L|Qk[#Ƕ @գ""֊{ʢvM{ԪޟTn~jXPGW^"Z_n-u M'}Ktggv)O&{A{:RҨM#5Nnj)KHFmv߱Y򟷨'+<6հwQF:L&/_HP,"3u{{MZ4H[ɲ_+U<|lߩT=,(rnؾ,jG݄Gp^PIeYy=V'G >{lG&vvVZv[۾-*%#E<6hb: uE ].nd~8L66k?Xk)K/D{WX,B-3svsSPsiA}wFbyͣ2ZgfIMk2RE_yw%!2'7Ƕ*wi ""֪ jG݄Gp^PIᖍ6:\Fe4p&K'V7NZ? >yjI62mOhO]QԟM .2{lMR$] ;:U{_"<;?_T *K+1i\[ 5R,(.>V8'TWł/}fs, ^޶ѣG/6l8dɿՀ!/9S;BLG:҂^HEk?@]ܩ^޽W^Q;?JTZ6gɓ'Njbֳmd޾/y^͍mkJ"=ǚy}>~e)J9vnuξA+*HЊ#=Q,؇3K'UZ/lunnO4 >R_-u{/νB };Of~VIt[0!{ʻM%"pK 0.RNE;KޮvtU-&qvו2B-^H}^soBF mx"-kr̦1}ТA{Mil;fĺ?YzC-E-@հ߿"vȵP#" |'")Su7~=gk.~/;iӦ}INN(:jGҦgOuM7oZD*WtOmSdޯkLOH,`݃jKEX2{^zZoUrzu촯?~\-{ ;7|BR*};JC-L%׭NAǼ-a{Ֆjٛ˂\ZqdO5y*n.PrUFn[lUOF2֙.y'.wtp~);%o,q :~jҾ7n۸s&R"?y}ӶFY}Zډm:gs_h謓8]mjsF FR'$&GW:E))uxI}՝3lܶZr-e~Dۆ6y|[Za[cE2&Rk$SE&L7|wv +'&??U*#pt➒t>IJy,$R߹{YH=ǚ%Pqnb9?y>d?:ܲ*lU73^Νd<5a]"cKUz^z=cD5'r wp x'}v2!ˍ]I4>"nls?]92ݽ+S>s &R" |ד) dfe:Kq6}#"<|K555El!"j٭Sp?^r>.25xq:#ʺpK`|Q P5l/yHƤB*"rmY|^ *Ȣ_Aa_tQ;{5^nݺmpOJSTÆ-Uz"UzRS$S]~`z;ƸdoM-}uŝ]isݺT߾TZZߐ4ioF;Z:_O8qgQQ:xQT*=ܷ7pñV!&vGy" un_d1sեJ[)?{p^ΝkNϛRb_o{nkѰXkwlɵ۰,v\t,+=cs y)%%%ի_0U;4h܉d#-=|8bd;mܶ3\.U3zX7ژb)Jv1V7m \&1w~*K`|d?:^냌WҾO{2ů/v0rw \bGZʳM<.Me_휧&]g]ZhRKE{/jm%ɘCHED- AYH1ôiE۶}Z$%}ngU˖ݜƍ۪5a m^*#i%ZP}ަ:wZ2hLkּtjjٲI[FKκ= :WK.UnL@u:D͘h<7ƍs-tر]XXhwŇضnbbw'+wVfΝUڅW]nެ=kl*" g?CVP6={g8>y;NwrcDz=Y3PB)nyO{s,mR=5(X׿[C߾hswW^#emל1c^%M>G=S< ;k=̬>q.繕gs,y2&Ķ[|Pƍ!ǔ)tqR W9")˘{s|.'\8@%SIսL5Quh/]fW"w&5w/)(6HE!]UBR*XRn3{lu|j[~]*|s|Q P5l/yHƤB*"rmY|^ *B2on" 7,mcDRh!EEmsڷoS/YFnᶏSvcFct^H_Qg 33 sEdرڶ=*#hO:Wd pZꫪ!iv-Ho+֨N#;/%ؼqpQBǔT5lB/y m[ňAǐݨu>Gޡ[0//X%tm?noФ&ech^a.0,c|>*OE,d\ ՚רURQuTbc\|Eݴ&5>2푖loURKXbyr:V_XI;TDڲPUʕmuq1aNz;ƾV]isn;MHm;^_{ܵk? y ֭gTqJ)7n]vdmU>jGtB&]ܻVHw{`"RdѠAP_|wEan3^,V˾.wא6//v .ܾ(wt޷Oo3Gthc]GA z]q۶c3zI=vLkEesT >,PɔRuu/ZMZtmnQ[l5aaaE~FX|Q PUl/yHƤB*"rmY|^ ^J{*<KåEro7Q @lxRk[O3mCA{u=/uoCϱ2, }"<3S|bbbw'+h}z}v)HHJ}Ekl돷{'7߬tqKSt_ޞkg?s:OW}R8mZqb__Šu3gFӌ;p5"9~*J`|d?]gUŶJNJdL!""ז@+&Ȣs,$R,2֭Y_iz,'R{,pEcڶ,ԡC'Fl6"yկ_q%;BLpE=J#w(ܾ6[8;;v8+ȢPdGƓ'}܍!Ҿ#ܳG /XǙjFּXdֳφSDk~8",9*}Y)jw_T*K+1i\[joyWp1~? '7nVoUddb˖ղeo.*(XT1}/H,UN>wܹڵF=(8;YٳxŘbN~uum77nuPwEan",sm7ϡb%,KbҠ"E}K]&ú_nƺ֟woxcs!ܘ%X#ȇS'T8@%SI,,,,"?#,*U/n<0\~ tEY,_JLLv}^;t"@*\qEn3fnv;w#ůNU^Ԛ5GJ`?nڵ(%ůض}*#hO:Wdq a;meٛo-p c>P5AяtI_M:m2^"wp}|9Y,mEp'e`t BUҾOǑܾ{ϩwLc6m Z(%XȇS'T8@%SI,,,,"?#,*U<lN/Ɍ%Dc3߿M1i'~z$̈́ ;&Hn=.|JIPGo0'57כ 5_YH:uן&NMԩq)B-V6PR͜vSW\|sʛAEV8|cM}-Nу{Uq7VKv>4iikԨu:}3PrrӟHц|;sGmҤIO͝;(%G]gęV!&vƓ'սEȶnd½'6R ޵bڵ?T2ݿ?2}cmdW|Zzznoӳz挳M8W9KLON NQLOpd7=K?)n/\Ncǜ1clq =B-K'JJ#}^B̟rgx /Xe;yb "~wjزs\1o !Do1}]*"&1GOE=p L'UV_$''g_ɓ߿ߙ8wO6lppСٶ}Lok\^/ټKg[d1kֳ^4c ^A&t jJ/Y\y(\бcxURRÇv!lN}sW0HD߾8 U-YUg;{#xxjРE;9\|ej{t1Fc\rInYYKS]9l<_/ZvE)((vFE6ksrrzsUUD&)m/c=|4I[d!~Znl[BE jJ/5IDAT GέzĈ~gPy{777w* #nR+H>}~? N|d?]gUŶJGGaa!C|넚}. 5ƤX"/Г,$w񤱞$RdCjԨ 5!YNy""]TTaâ?~׿ԎI]T}vIZeIu8@%SI,,,,"?#,*U>դIÜQAR!&v)׸pr?T~X4hഷ:U|dY)jw_T*K &;wQDC2dm35&Dw"o֮X?\Eqilٛ!$=H{rr}5d 5p=*33KLM9ʕUcn _}UQsnkJcl~VlY_Ԇ 'Ƕm{Iw)67ݴQ9gu!cNzrRt?<~nWm:7G]{Dmξ87&>/|YQZ!~ϖB4hkKK]BsvMjG7}jѵJkH%$&Lծwo5Xm=s؆W=jYXXXE~FX|Q PUl/yƌJCQD럱m@`ƤhIkסXqDOȗk"Z[ ;Bނ FcMS'w{pۗ,y7˖u4F]73/w/r8O @d;n޶A&N[RR=]"w6lU1N}To ̄'i_xPAmS, l"8[zV;BL&G=jUOfaaaqaE-@U*kwI6<0 (bɾ}د1i'RBpOk|у??" }}zɓ {r+kwLs:Qta#r )h۶^wj;smiib>~Yr'9u(ȹ{ycR7!$~"QzF_B7s?ˣ5qwePĒ;vöveŊ#N:j޼_+g1zRU!BH}RPm5Y*زkl[KE,UIIu>OwEk~?{^c,wsN/~]נABYUgE|^u"8[+߳+1?U?#[/e!TVZpၹs[ny˶홁veC~F7[nOi-%ot{eY!yۻw!-|h괭_\5)4&7 \%߾[թ[Lqᅭ:sYQdGsk{ve1&uB'gy !x1*CܱbSκR'$$:w3fJIP<ϐcåKL?%SX~{;6lעȂb[Y3,j!cr;!6&ϵ"JJ(E@<+OY6:B=(CڴJUzm#O,vP5>7֋MgUzm#Eǘۜu~Sxe_-Z8m(C1_6=hn!ʻ5tQ]"9]۹YyJ9WWcYk[(y !#z߻P#۫:"sjU+T9E=ՙg7SQoz)Jp9:E />q,7>.xYj3_04鳳l' 2F]r#NQp֙w"&| C^}]uls"8^JoGE?SdʶJmy@"bL'Dw}@`Lnx Z껹ͺ˅]:76-Rkz1HI{zӺ[dImy~ɩI򫖫\6Su3Uzr^qJ),P[V-?YDVYㄐU]UoAvFvPu^ԷMO 0>U?`1Y2gu¿ͣUG6޸#8vVjK:_=mԀt[FAcTdQ7UlږGğJ-غ*%%CbVY?I͙Ve6oSR\Ԯ3uZ׿޷kqA&*!)Iտ}5jujW_ۄJӃ>13%>߰T+*L :M/X'\6}qbgNgg%K*Lع3hlMn͹<.mօA}'n=Ғt#TAcH!>)p۽ERfAz'c*xuޤ~B_nY~u_muWS :E@b[׶< TjŬYOK3˚ /K1>ٶ>zÆ ,0`?W3?pΕ1y<\F]W&oI%A}w: 1Yx)ZoܯEMH]P?'" oGMqP /?SdʶJmy@"+DQ˖ݍ,Lr׍79sԮ]/<ʿMa^^'999;fL)hҾmٳ%!Emߝ j1T;I0oqPB&'$^݃*"ұkRfݫSE r$ׯ[Nղ7߬s#:1$ъ,$y>G,ߵy͘^BǮL89!YfuxaպE=ՙg_+zM#wf7:|nBþ_R5Km'wx{z2̺N_Naŧ|T-[];띞|Z9Mɑ8wÐ/'6}e?yaq:wpǜ"_8O#w/ yAt*]%))N{bD8CM2f:w,m^SEQWw+k*b„ ~vNaE<{R<.G5{>!|'̏~5gmZtJy,$wx+B5))J"M,<'T8۫2/vUvFSpfuNz֍`ȶs1"E rrUZRJMJU7A.D+m>32>)츶Niguvi}n~^5qe;WO?il#wpYB??zpcvw?ܰSl"֨zvгy)@me[׶< TZń gM>۷jTϞcUjjÒd/V~G//oCWǏ紉',**R4*Bn8;*?cxYsqN?m/gŹ+I=[m8qB?aDxW4ܕ1;9%E5lRu0@ _:EձU/=]4h&5G>rVr-V\%kwy}g۶u[ ԥjSOFS,E rX݌۶-sXȢ"Ow-p^3&״H<kj,P[V-?VdQ\|Z-[|wBbʿcuWQ99w֭۰ᄚ1)ݗL{N6n(h}nѵnm1Y<םCa.ZdQ1GOE?P #ZfLn|r'꺦שtY7SnuvYc=R#צ)@me[׶< i7wXb,"Rƍt_vuԩ?QdQPPg_~KYcL/kzOχQbÉ:{mzzP+5։ooYeuM—BD\}u>ݴG-|啨ǖo=%6:\?6OHLKL1xc Xc-(˘#Ow-p^3&k$S}C8.EmlږG)K /Td"E1x\Vlw Yj*-X "R_"ŋ_M돫^Ʃ5k3)tk."bRfp$'G]s-F ~辱7dbq۶žrYԩSGC'nH/Yu aq8=ߟo<ٯz_R-wKݦBҾOǑܾ{z;16#.,c|>E1"Π?K>?3jʨ6\|CCqJc} >`./te3֩ Xqi׷uu}T6=uر[B ٧b$'8 jʣj̘M*%%Cm1H ۤI;wQ@KF;۶gKYE,aʕjő#j*ޠ8 _:hW^t~C%" L/wpBoY-=|9ަgOg%׷d.2!?9%:&G:S22;& " YS~68:25sOzy?ϛ<?_Κ#۟<s>vXD󓚙鴧4h)SUÖ-I}ԝeИm뜃-|chcTDwMcz>G,ߵy͘^rMGh-xvг=-)ؾ:Ұؚ57+2GUys=We)8yI Om+Ȣ*χ7c*]WĶYYL` $oѢW;T{+wꬒzT˖ԬYǏ]tE} ݽ{QD)=l>Vt|2@ 'd:HE9w뷿5"~Ӄrq۷82Iӱ́__iwב>duLƯV:v+y}8)^od_s3n4i^6M~睘} /KssKOW=FP~n>E.r]>S [rƛ޸ʾZ5?VϞ z~1MMUpS\l_ !&X5=79T |5cr{E[? 㯷B 0$:w7TeQAϋzݤ]tS" ɯ ~5lk UZEG6bĈÇ?{A"T{/;7eaL':cIu8+*,$R_s:6mu* ?WYU<mqm3,)**z|ذaQhY|Z̘_M4h?tFǘ;v)^Xq x!+5K,erכ5֨;:ݡۮl|B) q:,7=@D =S m[}G\KEF C[9E=ՙg7Sqο{%O|Zk꿜}s?˦LTGn:|sT5鷳l6h7U<mqm3,|lN0`A%!sq.쭷TU]UZF*!1Qffv{1j36$"+{̘.]V\hGdօA}o!Envf^X?S'wpFv""r/]-ERd!ijWQJ#ݾ͘vct˦oo)hqfyy?M7q%S.ZU<mqm3,^,qBH{Ǫ.a/ [ b);$xݸOoHGt{"wn~G].wen* >UǪn}Rd AP_~|-Mot{ZR:yI}=cfG^фt_F]u>ۂ%mcÿm+@mb[\"*1y=ޱ 51=\NM:_\]j,ʬғD)Rd!Rngt{" ׻Yom*  =tW'p\,Z껹ͺ˅]:76-RkzD:/y1h܉uu3;4-mcÿm+@mb[\"*1y=ޱ 51=TN;z5':_=mn[FA εBG+",E" v*a˃6]IIܩ~R}:·zF1h 3?V&ŵ (y !#z߻P#CD&8EWu{EYd|o~%ٝ[%I(gYDVYㄐU] Me9dH%CDʐ)C\ƊW\Bj4$[pe*)$V.JA<ײZ{okſi̫@:1 mTFEx;1k_q{p4LJ-m8y%@ߎUa^yqGfokeҦJ)Sdː^5{cms歓]Ǚ7;.yMճΩt4RM>DF GY/iPd\& H`B!ɋzxwLBt'He4Y@BI^} c#=6@*҇ BH7^Ӡ4RM>|B} '#A!:ғipm2, } !$/>kdpԘHO h+&AQc#=6@*҇ BHMGiPd\& H`B!ɋ4Y5A!:ғipm2, } !$/>kd  !Nkd D}B!2Ӡ4RM>|B}5h cB)EeA!:ғipm2, } ?mWXџu;!s}֠@. ! GoHO h+NV~v:杏B9>kd |ƄR7LBt'He4Y@'# ƚm:ӼBHFg ,䂯B r}1c#=6@*҇ Hs(#;ډl_UE^d=֘NyBH:Fg ,䂯B r}шKӠ4RM>|G߆&@9 c:M.G!}5B ! Gt1 ёLk dW|$spn"YyzNNyt^% '(YŋˌsMOkɯ]曏_;|!|ƄRF|_HO h+>2DޙDm!Ly夜ρ1 Jw4-)Z7=U/mb,H̬BZoz3 B r}шKșWtb\& HeuɶŁo%J-%~rN~^X˚輺w=!ڸPP!s*R~R]C XM&k(x|!|ƄRF|_HO h+MȾmDҺ"KNY^Gν`}i`ȫuyt^]F durWfmߴT M$jH4/W`L!9hiPd\& H䷡CesW|'bYuS墑,dGetYԳ )|?p84)_^5kZ~7`WOjffJ…lҦJYСouiŊIf t<Ѷo[zmNʲֱ_r5m^VivB^Y|%jdgߛXM:ƍ^rRhQ)H74j$;v w_Iڵ\'([C"ωwY;>;J㎳ƀח]WIgyӻ=f筮]V`?y8G:~Ϟ!k}X\&x>cB)E#/]L#g^}҉ipm2, } ͟ÇWuM_ M",o$fcwHKFI~W7Y(gWeuʊ]O)ZUzb|-XJJYu?Ժs YeO9,(J?7M[y9&?ǣ%qM{ ]vxIҨ|ykL icӊuxϫ7њ, Wǵx^6c!?bӢEȺZV,ӛ6+uάP!d>'w'.]& {>2~+0}VԩW駟,KRJHUEF_^xvּM`ToJ-$+̘qoȑ=CNC.TVwz\y 'Mpy׵̉XѼ.Mpx>HVgԱ>nv/[o=(Çp>ܾUk~mؼŐGc/^<˙LRG3we55k;ZoL*틝XO*]+D;{z\yt=iϗ.3>4RMߥ 8_9|77D֚"+Y"'ן-7 ۋqMyu]㍷8z[/Yխ[" 9qW*ULEmT7|t.JO}q'j׊;gH{4њ"dl`?Ǻ3=V>IMGL4B #M#O<:7HӦe7^u_e۶iG;(=ŝXO*]+:+RP#M84rG 6@*Ri? O|WMu"ڊ&} 7nWMvYoΣ貁utn"%Zqf!ӗt2Cr{Ůyo]݅v/YMOg.H&yfH7Ak|Y2]p36rDZ&\1{nH}M"Vn([xȼUsu^=O!7upϓ&ZS={:**ܱf뵓&q]&|!|Ƒr;f^vY7|xW /Q̛7!d=ȑ?h…3~xbVQW_"v?,QX|%Kj*Iȴi}덷(=6Wo5|OkСoFvNw-f*6˖Ͱ],;i9fL_>}.oiԫWKJ.)Juk 7\X-o\|q{Uut_O85tDVZmNffikO<1շ܆ φ]ߗ_K eIvvtBlYGo9w[^_ci̫@:1 mTF&Hi_Ap<7dTlBdcr~h֟'FOW&}aeu:uEJh;-\E8#dڋKB*wx:θ_`Osڿ*,$B1.l=O5嵸޻~wOOtg{3?Uk]sDJۉM{,L`?تUC"o댵'w#E;z,vuČ gW]M&hGL$B #eР!tjj׮&#Fg+rxy[-{7o޽dίZnge j[64Mԕߵѻm4jt5E?aqR|덻(] S\.[kq{6Zk]?,2xpټEܞrub+{矿dN73,3suI9N7־ 6=oXoY[7^;*_dnGmuyӻ!tDV4+V<< ϙ6Tr|qΟ?g(s Y_˖e+z;cgy|z3=c_2x-zN)/]L#g^}҉ipm2, 5FO ce5'|tch> deEɟ\ QeϷ֥u6f!񢋜4/X~4(d {KvbŬ,Eed؆ yWt@uUK҇K'|NyukW/] ݺɞ.:iZnIuݿRYݽsz1d"xuMEԠEQ^Ӵ]ժX%Ik}?nuHy?G-jhѶk!6hAZ{3oOsk>k\+V)wڝLۜqX۷㌵/zg{cq׻fxOnʐYG׮BEMN4;)&N=zm=-4ۆ=n_w|z5w4"xݖ;z.w,xt1yH' hd>+C>Pޛ\E6vs ]gc`D D6Ju֩m趼wg^V}_|~շ S++,)Td5DxZ$_hQ(VLzլ)i?\AZƯ˵ ,^DHδώ:iZnI ¯[WUh|Z#N;M #%=oX+g[sZШQHAzu#ߎw{pcNěHո,N :XAR y>3><_ajT,ɚ ,8d.9Hxx\6 8ZH[ҽ.) p_;b,Y\~io#5Y-\N` B Ind޼ eBv#Ž!ڷoۦ歷t˔)%weyS3_͙Yδ 7hq~23ھz˖Lӟޱ5ߺcwqq≕CE"Mӻ'Ǘ.,SXM׬Yg\^.\pδϮV"׃w[ٽܱJp4rG 6@*Ri$ 8Z &oh%?,%t {l 䋮Ҳ0iq|'Qu֩m趼?VYҩb& ry9Iʕ}ӏFMhH<@_q<Y^}vvm~Sg>o1o?_~y/zGjYJr\L_$kL.XwEFv{ަumNziޛs燞=W;D+J_ꡐ-R}reC^AB|vgϞeV\޺v22VB]i{\^fѢx& W{Z 񧟾-dpv>mn\D׳wEϗ.3>4RML#ߧ}{" ٬MQӿP.4^.||f?z.XnCı M.MSZiרڤoM$x>H;Ohqwޑ;fרq[L~ᅦ5/.6YK j;Z~JC֫ qǽZLs7v5zg J*!۷((Sʆ 8틷@d^NBA6;v Zwkʗ/L!4s7YXMmk|h37S!R|xt1yH' hd>+CIʾ׽ew.??D0xXnCΏm8-j1F:G,O>,n1p|d+f[2ķ\^K/ FLHkVH<@_q ]:wnS\ae՜1o16`qYuj,ʖvN:n:6=<'&s>xҙb㬦 _1g<MzN>w?>OM\mgx;N\'|NysL{mƦi[&3/]L#g^}҉ipm2, 5FO #ev9"? 6Pc|g{0tX|mt8p !-лo^lGFv4,_^ʗ,)E ťy2ys9x]tׄ敎}󒂝W`)dQXQy|Ӵh"և#Ft#Or3fgʟFG_.|3k}{o. ޻oq^iaXqoDp'ΰa /2tPqΝoXwb-n[^VRpakdg}тz{^}w3ʼn'Vv_}^g<MƍOunx^6Y:V׃w_׳w}ydɔ?mo*7/$Ϯ@_N~uoDxt1yH' hd>+ 5c[ɻΑ5k&k&QmM~BH~}!|ƑM:}bڴѲv/[Evu͚Ud۶{s 57h5Er9ClYv~]] UF4-ris3ޤI]9x]k~m>бG=m5]4*W.o_߳pq7Zsw֭ u ?6Y gk{֗,Yܚw󗬦=6Lkzffi]sZJ2v젤g9|VC9\C Ʒ"O*2wMKVIK׾SLEf}`RM84rG 6@*Ri$ 8Rv_z|ܻ٥޹s6>KvBOY# 8R&%zjǗ^Egy|ߞ޼ NQ;+WCFYoL,N:Yn[U,^V{Z~If ~O1@ﶡMctgȞ!ۼVAmzc̏7Mw*seʔFNŋg9ӿjunS9tyrPNgι_si5.t`g͚~.]:.}txi:=ҫW[y)μup捴>1Ď{lD:GxH/sO"wUz߿^>\u2ٯ}ȐECD8 »oO(/]L#g^}҉ipm2, 5FO #aìF it`q0M~BH~}!|Ʃowz*Fu i}'w:cu=߿t zSײ}?W˔e?eD,}dvd%uœ5ϗ.@~`\& HMӾ`B!ɋ}!|Ʃd{[79~[3#y'I˖} v[o7&U_]&c^|s&mg†2e_~-(YԷxF4Lk }X !$,>} BHF|@_q*_ AUdŲ;yez.\XVzHe]<|$oMzLky?oq*fȞ+roc|*(-ٙrСuHfӦkaS|RHah޼̞=V|׷ 'U*^)/.׿M]\oeK#k[^IfYfEfXM& LkWMHG;xhH zޓq !}z_G4}WL!$yY# x !$2w2e^iO2~ɏrdeܢ2/r=2aƟeW}'0O0W҉ipmқ uM D{22_Q7!$uQFO !$/>kd [`L!.iooO&[&.%{_e;{d?#}/o!{\vr7}'`L}3҈ipmқ uM D{22_Q7!$uQFO !$/>kd [`L!_.-wO?7~_F=;Ef,-.Ȓ[) ?L^|r3wmbEda'ҋi$^@~"*lB8zy/#@Ec'G:רHi_A0!Eg ,x :SOHO$Mͯ&X9p`,yzs~䶕d'om-1ܻڐ1dקo][W#_ 3ċBOWMHG@x[4Y$=v+&N5j$=4}WL!$yY#y ijf|jՊG+:w%O=us.O$͛_tEMvG}I&}^?e̳eіSd#)!ؐ|dWO#V/]PFE!'&$ם3hH zF}n !}{0BI^}H<D$JsCG*dɒYιȋ&^"7}ѤsСusr3-o(V/]PFE!'&$ם3hH zF}n !}{0BI^}H<D$JsCG*$ z5Y]E|7ds}ǛyKdxQ ug$ ,snBHD_F#L#ߧ}B}5>o߿&O%KvvvZM2=!jAM7 zjI%TRn-Kno7rdϐN:ߺuqȸ_͛77/%}vr岬ܹlٲΘ1}}ؙ: g["F5HŤl i,Yng߻=7#FZիmHt?=sd'syWzHڶm"%3\|q{y≩k}L;tZq[ZۨUMݿNhd׀DO*޽?9Kd:Gґ ug$ ,snBHD_F#L#ߧ}B}5>oW_-*U>Ԃg&?*4rw.u^-@߰Yٴyx\ǪW|"k^-<:Z|EaO ?E3g3iٲL~\qEog3Osi׮V=r5{x^VCMt9:k+][Yˌ*d~$r"%7H9Ht^=gŊΟ6{ z۷ZV~ؙHO9Z^smRkAOX0ޱĽHMy h9?q 2y#H|U؄q3{M DH޹ _Q7!$uQ#yӾ`B!ɋ{Ph}fN CC͟?)]`;{Xg\;t8M)0[䓷8Ӵgq-zǫVMvg;ӟy6gbŜBvݗ_ =ʡCi"HEw9GDmg8]PqoD^?R6,>TVI^~.wrW,S:j3{,4y $r~1cKd:Gґ ug$ ,snBHD_F#L#ߧ}B}5>oьޱPBNA…3Bo̪U|!y^pȼk>Lsk~{5h[g:O?}3{Ev=Oo۶4d}m?rs"ŻQ" y=~^yg=on.1c:WMHG;#yg\UW#%JMO4ƭq֗]7=;ifg҅^iǎw-:t}~ӃH#wXS?j0ȐLݭiZ_[OnYF/߶{\ r6Y\ܗ)2mXohǘ_ﰧr#&}:i㎮KQmHƹw>yM oH{iOrB$yEG3ނhMyMM}w;vuJ.)SCIӦCZwʗ/hG1G;?cK/]PF@HGNB Қ_g͊9-?fh!rjJyHh{jn֭j>7,L, Zƾ&,z\ۘ1cdꫯʻk=G]v?\uG6YT][3vCRwԦ 2z;ݾm-~+m˵o|!& -/\$7n2-],c~K8GM z-+UY~޹Sn޸1iFmhb5Yhrs<\G#L#g}x$ @L-0>eRP! 1;{?n}8=e9g\ٱ5ksk֬̿;!_eoɒY!y;"7/Rcs9Hd^{_?|L` zhLg*Uy&駟L'+g5]uD,} $r~ 2y#H|UIȾ{Sy"ڴ3gSÆ9oŚqefgs{wy? 2EK,qƌ˻wi\frEwoMK4yd1p=z8/Z;R^x۷>pot\uGʄ[^7ް[7|x s\ho|FN4YhƮ\g_s˖p/]_qΝoHb-9'N,y;pIiHQ" y7>51?6A?ޓʕ;j]wՙnk{Cq3@"'jHDJpz0#\BHr1h端X_%#[w]лZdeG̻ubQ5oZUeÆgeӦvjXoY~wO?|Jzhcz={ޖ{Wƍυ|yr໲mRis挳tZ^dqkHV_δgf i,_>WU$cizy XM}u=λbŃRx1k^{ҥ id;8|?;u t-ZHȝC[m5yf裧]r岬RHχL5I _ 3ׁ>WDk6-?&̘KFZLʪlm[3s>Jp=ԩcvX-<;~{90rWo 78|VAwp)_y啾n(%UՓnS)[;54柽7o{w5Yֶt @^zN|χK5`/I_ 3ׁ>W!o]w\|YRB(QB-*'+g?~ohѦoWֿ]_䆎ew[|2y5OjM˭tz7mnteR3plz\eK6* Fתcm:9,Y:'GŘv|mgX1.;ysgL֎ѣ~qhmʮ3|1p|nd๨x;ԫ'z7Qff;vܟhc=iۂEydZ Mv5Yh9M 3\?vǍ[ggWc" _5\;o߾~;xrF^!qڻ}6|2Y"]dцw~)S82ъǭq=U);jkxcO:ʝ>y&gLϵ{zic54;t& WM{gq3+Vܱ}o:[eݧHѦ'֓ :aק[`L!9hd4( 7UaGȵo5]̜)oj|uhޙ=FhӾ>*qm0ilf_Ǫ//ܹVCy?wCG5s`#ZM;tSN4Yv;kcbEu[ kiЦmfIwѼW>ZsαIž/dIiPLjǛԨ!~|Q=sϹ>6YZIp;.,=ԭ["|d1mgZZHwɢϟ=q{0p+'PqoKѣPBqhq5CCKgg+W扔;YdfLxNV,,܍% 1=an2p޼ )uǨwtp_t̐'pƽ ;>d~szBÆx{wZșGOx,hIKpz0Gc-0&}_4h@fқ#.[/0daÜή2-Z#Ei4pg_r3?z{K`}gm(.]ԥ3)*ɿw:u>i(ZiȰ4;ur⺻D;d;[׮nq=~CZf{]_}jUq׆h4W"Sx5&D9z}<\G,Xז-[ gvp}] k-0&}_4h@f9} eX_vy;.dZFH~.k]sl<;;0]5wp N3i)QB{3d?V^e[wpϻ誫y2gEJ}6ݻ9Kp~@&cB)E& l:GRi/㍯ ;B̜)v*geYwp !Gj6mر!koYСy[.li#ӧ-eiqe8q-2[;2lOc=>M=cQd5Yh깊ٸLәdQB4mLX%ṙzvZԮ$:w"ބfsP3~C֗ߚ,/\q'^.׽o~zx7Q䓭4kYרwnnvLf{G*L<>\rʖuǿb'XMsoqμk7vƻL茷>6D[_}n"v4u]ԽO[cϧw8Om_cMOd'.u$= OB r}ѠiP?L#n ;L>0)mud4Yh3ˮYγhnF%s+tkU Ŋm/ Xr!ۜ9WX |ڨ`'b׌Ru GOdQTiwu .1ϛnٙIdR.RhQgGwm4xཹ-}~\>˻KQSn{x~Xq?~̜Gf+7߼1O4o58}α)tڈQXIkp"VC%̒%dΜ|-7\xMyk<ӧ;s?_QwhֻO*S6o)~*=+6-dgr~%& -r;dhɤ m547nvի%rek\m1^bccUޙIQ l",;*Ȧ* UA@EE7!1{=,z51F%q7FI4ﯯR]KUOL>9N:U=}]^?? ]ƪqhHjn_5IaH=IcyIc'>'}mL:lk{ҙ#_+џ@Fƈ^ IPX;X&}KBQ)'S~!^yHP5- ܟH1auܱ#)9cȑNnUZ+ HZNٯWٔ)Iuws4uOH5'zQ,+e<}]Sǟd>{eN'{8 1n.`b:MꚐᯋbb.^Do,iÆ͑V:Jf1r%ƍΘ]-=Um`С1-uh3=Ke彡f3Ν;2eʿоw_mmr#ԝ>kk&)tyÅ?Ih|%Y^*i]}S>8wMeݞjF;~i|3s}74as9ر\1=}IX&槟v=X>t,'yAg׊Q+WW_qty3f[;կzm?>4Vw٫_yEF._.~3֝:IOvP}yi˖riɼݻCk"Dpg΍L}eQ3lV?}:KY., A!PwLS :,ե-[:w/<)+t3麴;:nuЯK]&8{{+Kػ`Ah9=voӼdΝrgax.{>}ke7GcMr˯&4oYQÆҶmNF[{ vlݩuP$ ]ӥ製r7v[m6[؆ >p׮жe31gyiӦI ^?njv"IIvl7k֬3f}ʇz裱c,//؟qucT~#GxL|]#D u #"z]4$Y@icB`- EaeD>Γ!4I'9O1>jU-T7<ba91O޾WO=z,)_+џ@Fƈ^ IPX;X&}KBQXtj+}D.d/Fu>_lrE];MīSL|odCv7{'Nxڵܿ</?_3f̛eeesbnIz^q\?o)K5iަM5SdQ52ߗ@"cDRV$ (m,CP,%(lu?صKp7-j$ U-mvԩS?_nxw ,bnR|Ƴ,O8񙊊eܸqoۯۮzn?^ 2z*qҪcGiبl^z۽[n2X\9jwLKVȈKY.\(!,Su+@c- Eack ȣ6﮺Ju"_=PoIXgh[o -& }ؿ7޳`?O:;y̚53g^&$궛 u#b52/kXsg3  0FD,ehueߒP6ֺxҷnLڷl)UTڭ=KիCm89$궛\u#b52/IS??`1"b)EE,?(L ;$YPoB(G=GM}IDDDDDD,Y, KY.s.bA!e߷$X`uޙ} "&>BA݈X<9jwL.KR """""bbgA$`Xuu -,%(lsI1m7 FQQkery_" ~3,""[e}(v,%(lsI1m7 FQQkery_" ~3,%D"uj_̌ aJ  -,%(lsI1m7 FQQkery_" ~3,%D"/b,>;̴ TP[X&}KBQ؈V ,bns!ԍţ&$E""""""N,fYJ DN/egM CL2[F,:L>gdCv} nD,5FX&%)~pg1ςPB?H b)8ͼ_|nCR2Bj ĿoI( 3AE uM}.xsa\ޗADDDDD©L> @ K3eV{jf'ږeߒP6byg[,bns!ԍţ&$E""""""N,fYJr7mxeKw_ X&~P@ma- Ea#Xw&%"&>BA݈X<9jwL.KR """""bb&\{?%Mj_ X&~P@ma- Ea#Xw&)ɢ׾υX52UkTB>+Y!"""""Y 4K S)Z敭mZ@2Bj Ŀo ;dxJ&8D,.s2/I,y"oev~3,R]vu -,jĚ4_%ɢ6 bq+>kTB>W,yu",pԟLKI,fY !kRۗIX&~P@maܷPC s$[ǃeX&POu͕NJO"w):m\Tb&P)mw~l¶7P[X&??k\!ɢ6 bq+$S]l6A4ol[βXƑyj|6Y#|-LرL~[!ք$ /$S?^Td|~fOoR˴Nh'N? ~ުUR8#)@`'zǮUơzyd~F: `B7KY=岲P麟N=wvI\Bu|so$Y`>t63-[{ٶ;~:jr4rY''yek.â;}b-+$Y'$쓫ZBp~EG侣D:imf zz`溥7j9vC:u c[$Ӹzآqc:N{Ks*\2S8޽Cuj~:NdQ?u.>_9IO3ݷL֤Gc,HY)MLc`â;Iʼne/ o/?\;D r1")V+7_9./et`lX3}>]n1B^_0Tct*@ҮiS[lY>u?$Y?s q$ iVnٚ6㈲QMCD@e濾;Iʼne/ >#kE~9*D g)sVuȁc,6VIǷ-4HlN5j$6cڷG-ߟ0kS匾}O۶ҺIiڰh)̙I}>ztoty2_?Ьɇ.yfhql ]4ya$?nyXE"q2mjqy*UunG(2tk;q㜠1 @A9m4óϖgfttY ou3dȑIS=T=$9wL׃c Ap.s=^ FVu&է )c禞7zR.^evgnf^׭׉}4_Κu{zltNĹ˧ u,zΟdu>dخ_+/wϳˣ\OQ2G6t]̓(s!W˛د4\uGMsٟ$ }^?\8\ﮨpFQLk~&ulu?qxfoe閍{:ҙnZWs:~HHKY='' @`ŎeoAA@qbKB>q'.9Q>&Nߡ" c'ˬdD~2Nmu]VŊ= KMoYI;O>Y><7}\`7n&2n{`}p]7kX\WpGii~[Seg\-k~7[ ծOo۽ǎʻ$MD(Mmgp-Ugُwx^{M WOo~u}ݾ/-ܮT+x5m𩁡sv?&՝92]&5 6Cս|ڂtIQ烿voa=}ٮ :\|>c:t(Tc2Oͅ8wCxmI.ny$8HJ Luw~#8#xܧ&DU/( 5;ƅ/H*$ z-#|aTf Kݭ;1ͺ&uV?Nnys"^-ov5[t!N͙|+~#qp{e#ӦyuEĒlۥ 9̔\6S^(~T8iR2zY]6M{?6]{;WI]4{=ԨR=&[ݹ1LW5B ;{=7s NE.7h^GzS>5u?}v'c>ZVW+q_pQIgM?_,6}zRSTZ{wb[ٵL`_V(4kݼyr/~q]kuDٮ(mrl}f2l\Q>~n]ƍ@``#ڴIӤ Dܮ3^=D~5=~Qۥ{L17r=4W&YrĆqح;X]uI3X;N<)*DltmgչcpoWnG1_WjQIg`/ߟ0+d?u2&uq嚟\6L=/ nG,ܻ·tft\d#zN6 P Xj+@c[PzdPX$`OT?Y\8J"Y H'FۖU/O%lmMk[]^Vоz pd'6z\[iݲYnY邮ҙ-+ueZF 3iLum*Gם'~yL |AeĢ[˖I˩f]{n9T͹o.۲qcŬYN},Oq,·T]\>Zx/vO [p{>cߪ?ԕJ>31<4?wD0E+.}h_ڧ\_*^dU_]Y3v4>13`0a.AW˺2-σ'{unyy)xu]}/WTxۤaCg m …IoĤ,[&ߵ0M.۞LfZ6S]cʸQ}e|i״iRj_e_7gOnj^^y;^s: Z:lXRwƍ.=7l}e|b-|Է?1/&Yęr|kb ̳]?Oۘ޻W瞏طje=ulcL7O2ͅ8Wm]4-d? :G}?xҤ8Ct<\̥lcd'Lۑing[Vv;ҙir9XD(s(M,S5ر -(=H?}KKNO?W"?k{ZόyΒle~8S)vڗ}ח _ޖ!C.:To  j_Y>n5qA[6b e]qr^&OD ={&-s?PoyNva\}p}ۆ⯻ N0Mmg:3-.ݱMg~LnyI2'\9ggv5X]o/-iW==U _@QFmm|qa2eSo#ٍ#Fxj2[ZIj/Zcu+v?Gk|L۟zu%NƘnd q֯Or_-d?ӹwHm#{>ݫy9?kk~:el383vdۙUݻ8!Ƒ.s:L&!" 2G,e0aJT}(v,} J,`lj@_"]lNHϝ&#D~iN_ڧ7Mͷ4;O;6_MW,i 5{+oV\f8C>]\^_Pw9 ͯl5oixAo:?]W7׫ӻM^i9{ۣWڙg_~˰[>#r .N]o{{[u6qҠw2EzcpN(gۣtjyL6ؿߨQf{~6jo>Fg5]ߗFh9y!UÇKyn^Uǝi+x5Sh9,ylt/R%C.oL+\ >u$-۸aä|]}4f9yOc·L5xFeඩ{Ǹq޺4~}rVwߦ\i\WnIuꏦN6p=IS@u}n߸HʗmnK\y1NeΨ#Ι8L';3vLs;[}DtR·tfچtu=cdb  sRV V4LWb2ܷ  ɤ~C{+O*9"Od)}ޮ{il=Im'We}:t]R4@KY[O {eD.:5o. <^~5ѣC~?(u/6 ;tp#5ppǎrĉ9+2eZ7\׭G=W v&̫b ͯ;|\.Ooa=z48=Q+JרϠtumQO=/45.o۶N{0}oxgiЮүٶ92^= _+ @\M.WuF.0]_{r|\Pf1OONŬY,x2QC6I m~p\Wؿ=Frӝٷ?sZ+ו8}cym.DY&/i=d֯ׄ?2&YQGjDg3j?qƖʸOE=/H7,fwQC:3#]]>&&" 2G,e0aJT}(v,} J,`Lj 3%Bf;=n\&,7Sk_vڷ#70#b s1~R brL,D"dX9a &!XIPO:bxly&KOE^Ԥ jO97K6+)veulN՗`Nu0| ֕\WstLgb  sRV ò X2@2ܷ  Iߗ-"{ ?)Ҝ~gg'm}:t]81YrǸq^0g6m{\WsLg,  sRV sCeVeoAA@!,,qfOv7-9u:@c@Nߟ0!1\WsL_,  ~s쨣BeOoM&wm\n;7T5o}<&zNcϙa?L;M 7TaQ_p߂҃$ C('o/^,Ol-On=YtN>]E C,>"""""b]W?L>BA晜|q#F-Ɉ>}䉍cqo_(/ˤ 9s aXߎI++~NX`U;X$YB>XX #dG7lp?tV{RvQNǻwK6mB ,Jm[nI[WåC˖V=^ݾ=o:due:9Ohע}|ojM׶mC֫lo&Qm6o.u$Rh2[>woۜu\XQ]S rƴ\}hE~o )dglq:#p.1Q5F>&`ׄ-ձc6cuqM+V*YEB-ctɞ=^. n!CM:^Mpp۹OpMzПK5>1'Og\8l083?'9߬^Y,kPXj+@c[PzdP """""bab:PR{唾}] ל~|H2 gJxuvnD>^y0![7ܐ#]ߣWw'-3nB},ڵhT7c:}Z[.MZqէE7h@ܾhQҾRK5>h.?h#w`_َaq1VۛlT3~_)M|łe濾;IP""""""V,fH3-KZ7kzyY& LI T_Ƿ/ i԰|MyyrGi۟dqDNIuۭWܮׯ.0N;ud~ԦMNQۥ{L ˿̓_T /LWcgq Adc42HTs|ikfn],(:jp߂҃$ C( ~3$Y@DBAQxn)ZC۶΃]o0?S.U~:I~in֐ڤvΙW>;w$$w3mW.`.BoھeKIqڥ_0ßƍIu,ècb(OXh?c2kejh*eA(R6ۊstH?uN<IP""""""V,fHڎr~LsϕnEF| mؠ|o_Ig LuS'sڵIس+W_-Z%'-סeK/$ mﯛx^ALw _֭uiڸwV.޹Fi`W~hk7)]_jcgqL,+jc,O$ST^)qʫQeߔA[g|^yR8cb(XLU[}>rʬ} 5rY)M|K sNp߂҃$ C( ~3b%Yh{M*}B#n$tq.XPeR{W^锧뮓ć={;w֭ҾeK}͝DweA6;c-wOo{hyNq'Mti[3è l&+je6Z$ ןm .PM,S9 ,} J,`DDDDDD,YDdΜ$Ly.ߵm['Q5kL*o_ ޼YO|Сrղ|H):N[}.Yc/0!)%]_aq5(XLU[}/~?6keupQj`Bic[PzdP """""bab&Z,@$=ܔeb0"XaEÊe W)S$Ust v)Ԝ?IP""""""V,fdQSdQ7L(XLBS)_}׶H`71L4@ -(=H?}g1- $__q_$T\Mrǿ?K[o 5qa2 be+@c[PzdP """""bab&Z,@IUGzu-֮c+)L`_hLQTp22ܷ  ADDDDDªLX9aa2 : N4X0sX$Y눈XF ѯ&V,SV_IhLQTp22ܷ ~{""""""^rKY='L?X0}1!VGSIfNp߂҃$ MQXxR E,e0bƄXuN6 fNp߂҃$ MQXxR E,e0bƄXuNڝ2ܷ ~쎈 %\RV +)L`_hLQ甩9 P,} J,jP@.b)焩݀И)SsX$Y\RV S1!VGSv4@! -(=HBvCcB:LiB`[PzdPrKY='LƄXuNڝ2ܷ "zN H :2; eoAA@  E,e0bu9ejwNp߂҃$  E[t˲Zۮ.++;`}MZ$ɧW\s 46͛ː=e-01'= p߂҃$ Z"X2i7ݥKK_<}Ce˚IܻW!UTx۴q4㎕+]S7z}to>TU~JZs -(=H%n*,"b)Szw;׾veYm/o"&YS*tk۶Iu?N,bSb\9i"2ܷ "L oԩM*Ry\-}<&Y.4h[ |69y,WNs -(=H%n*, ,ĉ Aկ~^u]>o(H>,~}^ /G.(U2N=+kݬY]ϟVV[ѩ4mXڵh!G-[:4tu ɑ]H3-[1&K|?7$[&MݺI˦ME&2n{ Ν^UG'oN;BҶmN^j?|tǤ|qz֍Z5~OW7+GZ9:4id3O9 PX$Y_Ip,]Z"& T__͢E]~}("O6~˲$ g2U>\"#γ,={/w:Mbe7$',9dyq6'mwݿ&(][G$|ybo]gsWZoĎb} /:]zmhI ڶ֭=@e:vi[MƸcJo,nſmmdMg|\ur̙rnYW6W/oL]p\XQ9ˬ)/6͛dPeoASE%Ωa"Tϝʼne|};`E$w˲L+h ,4h 5F zeo,[Zn^}J[ ƍnۿW+.m8eׇ[1`%&%_$)B"=oW?MpMdjr[&}^vW\!C}:M 2ܷ ɢ&BCܶ~{;wwVr~~>~Nv˵͛|$Y}W:>-~ _nݻfOչ PX꾥IPEp0qD̮; 7 qرc%%O̞=[, v?Gv?J(($m&i{LحWwN>5Aɔ< ^Op)/;]O^|qcOmr\xS$ (}zZ+~ K/"sرL}K_J,ΩaB㈘]=w''IO2e>0O&YFH +[ʏyRD&MS LD\\ۺ564.`B$`[j$,z-^].IqSb(OйmIe[ P*dQ u_vX1z=w "-Y%PD _-z0ѯ2-SNI `~/7,5f *S JZ>S&coڤ#=܌Iop4hW'k&)|b?>~ny3(M#:u[^$)s1pB,SuWR$jD{_\(B㈘]=wLힻ6mC Q\x eI+h fJۭ:n!Z9 6Gջ9'\;eI'9I#/2%iٻ6h1B0cˤIIuN,r|o&mIn;S9& 蕿u ^y-mޜM~ʴ\L% $ KX꾥IPP1z=w "}+_4m4CIs̘1o[:ѯRϽ{rQE߹M+{֬7l̖-^N[04y&N}#MFЄ͛;O`'coҩoci;wʁϗ:ȆC WK>Z7k&wZ-]ݻy?\m]'{̟$Rs?[>#ߢ'J@ IPL}K_J, }q#bv1{@D,k;sL9p@("nҤIuP4P }IA5_p|ڲ. &;6TjrnݤiҪY3ܳ[Yk>C8ѤQ#'Y>} $ +*]d &*//.u*եD H!!J, }q#bv1{@D,"ODn۶펩S~n:ٿ<NbvgǏk .ʳ%_\˖)- $ (f}eWR$jD{_\(B㈘]=wLힻPC McADĚ7 j!ƍ,XSNeY2y̚53g^b:L E_s4l@ݰA~wUүKgaP甉B3? zX&{1z IPP1z=wyx,RX ""֜z^`I((ϾsrJ߾ҺY3i߲\TQ!jCS&Z@ IP̄1x`e ^ )XŢE-5Ԯ.خ( E(xpxyu%@ι.oI-OP4b59edL(`zX0%Y7BŲe.4i?֭['x@|IU_rcǎ}lοwi΄3yi9cnֺ|ݤeLXJ Uǎd^rQ,4aF2= I+tEu%w&%׿Ir7S۾!K2GϴP tb'xjИpœT|"h jsD H!P>=8 +){1b$j.=E1cw}Xλ듊6k.y^m1#T`OQGɨ+刓N)td΍7:O:BMd.I Qo'h.ۓDQ>@'ЉţFkИ밉vLU[}'B:L, cSvOe ^ $ 4it6P6m)M &\,;w[~ԫ~Y/-[vm/7Yo54 Hfy6r gҥ_useXֹ,Y3gʁBI|衇ui^^^>Wwjө ^eK>ӯy>"_IkG{4nIvx>nuBuӷm ۯ>ZIҡgO8~ؾ=]7`O$ X uNhszjF(`zM{R,SbL%Y\{S#N8I_t)رv۶{:Q2sRVvW֫װ_pqN&pLvѾ}7om%K;c ?>ߚ:ugٞ`;ΛoXF(xm_˖}ëСW~ez={Vԫӧo]wT} >s~~PEϟeYkLn&NܴI6??mW6jrO?I~gHP5Aoߝd .)Ox$Ry /x 5+NӱWHK3 `˾ C}Yر^o+oӥSָY37BcyԥKӎ)0>Θ3Ie|"&ЉţS kNhT|"h jsDMU[}(6BXhݓ`¼(fj%ߑ x?-?Lz3+_{Nَگ]{~ү{HX`%PD[oeYSҵOpZOpU7A^iа#ɢy6Iu**ASz噂sI'JE._}56ݺ-y%¿>#|{snx뭤=׾d޽1U^۳cJgpQ,9S?ޞT&(bBX<1OИ밉vLU[}'B:L9TWb#uO=&=) ){1bMxT(5&YlXRMOy\|Iu^}L߾z˗)GknxH2eg>`("?=z& Ogaüm>r(\-6nܱ)ޙ7Qhi)m)BٷMDa" - "lBU]v( ˽rxU}AqA;Lgdi4|!9sd&INuP# #Bb{]wiu7]᳾$YּfCuڶgx<6'CuNP _վUwR6 CHM}yLck1g:1B@' 0&!'Kl-o @C$ktdo[6}z(Y{OM*b3jW:TsbD^1[d1iR^<*B6"3sV$N:ɓ' '&vGꍫ;|\u^9B_+Ku]&LP:|X8+9|<$r))VChuURSrWA/I~R1rew9 Bmf0lև 3O/WP״W/IZ |֗$ ^cƅ 7eLNţFn؜ /uU>,\lh?f>\ܾ]yL:DWcrf8ч>? :aϏ/m1A!,`myOH05%Y%{[@!;s[ ~f###\rԭ'zq'垦Z8/sP/s[:+IVIY `=~*揇.uѤIV>p7jR_Β,j՚kӴvFhH뮻(t36-W !xܕ NY'%Sm.8e¾zy }IB+IJ̗_juujuCV]SyG+OPA)sl5xoWJ_}y>}8 ӱ5]WӦiF讫|&YuڶuzmۨXDv1 \Dkmcrzf8ч>? :aϏ/m1A!,`myOH05%Y%{[@!;sۡ3n^3pW:R6o:jfdl,-\^ÝӞ &))KwǙ%'ױ%Րm_O} =gwۨQJ+Wz9Mx 7uM?w̝lْvMgիJ‡Z9 ?*&F)Y/*1 ʊ 1dvB_iɓP~=p c;zT[*+K'uKy僖/WEx?51&T)%IaCSjՔЏc4jØժ?Xy wX~u>tbpr"^(w/*??ldǣ޼aLȾI/jW@ĜZک&Խm%EҢqUzyaٟ_Oyo^B:>`myO B?hΖmy ^˔_{LIP5Lu(""B7n=_;$ wZǸ'Y&-B%Y}E/m~{m UԐb+Uj@]>%JkʔPA(oѢ ǩ^TDlR7l Zۊ cf˗o׶mۄ$ wnڴ6풛tbPԺ%⫔0>8|}ivGΝ>|'>Pݷo7-:ܫWC,3+㧣i5?,Ժ{hW2B'R郭X$Y|_l|dXj/eɒ:?8^=l~M !`ߝ,V>mBߝ"Qs[ uq<%Y7:m= 6ɟ ,"Ui+[чӯ_}~a!™SNuaavCSZGmz<:'}~@#tV<݄V<\:J(:5Ch*Mn#6]t_[~>qZZdS˜BV֮$r8sjMN(Sz%zoHcmY$Y%ՄdD*%eU!Yo?G U-ٯi@!]E ^s^)=/J(<}11SU/6idl,+ax=ƗJ۴ujs֗z u|4cPn>/G-S+g>P2/Cjh|[q +IVIx @P,'m'O^z-[чӷo-{Ŗ-[ܹN[qXޓ#_W tB= ϓ>? :CY+A&(C%PXeZRwmײM{jFg(Ѻm-{.WY5aoh@]{ʹ7M[P u[~&ye WyiQ%A-/S6II`9yվTVP"""hѳcn2V>g^bʼq[NȚ8MDxaέ{l?.$ oѼy_Ђ_)&kK YLA[nWnjgNNvڵ 6bǎt$ u|]R몍|]]*G~$}~@#t:sկ4$QR~K+mېFQjUVV|(In k`#esbTBR掮dhkϱ cTs>ܾ&WOŅ _ax\o6f7T87(μm8VC=>yV+&WnY69v_:?/aB=O՟/𘣊ƬFq-$QXzϞ=lLGW˜\ r(ehJ <9sP>x|-qw~nymޗyE\.TE{vh^V%z^ J cr>! !.R 9-ҵSM}^,*+?hB7SgTfdh3_{/ΪqpZK 9S忝l*RUǛkЕsǕ.Z9衖u`N*Ǧ^#΁yl̙7׽'FdX "ZVy//=l^4QLS_9 77 X۶Yy+yRɱ?4Ǫ:u'O'O6Mx_%;˜i~8FS鱑kbRqWs[.պ[%4p,{]]huk(est |z89Um&# zyl*686ouo'<%Yx3֓kmOP`''h_'%HΫNѷ+C-C7$gp(g!l IR $Y!`ߝHp/;Z]٤9~*L?ar ʰˁM*d~@[s}]W]›no8/@~dzs}ղמgwSOcts8z;KW8ɁWP .﷯N+o>Kg]]'XmIk $ ϫVC7±?U1H3B;rӫ_s{qJMI|p/TlV_*4 MfZCG&\@ŊE5f:t~߾}?֭(\L${ \@E>乐"B8г|HwPHt|r`~ߒC'5dMR@Qy^ɏ=owC4MkOK֩`܈>}؜ՔsZ?0:/m瞟srr +Z͖J3>؈{,s0o4w6$-Bs/=w |v>+Jku=%(e^tw'P+S_kLq%Vױ4e8s~l[Rؼ3o{~=Loַ]7%p/,Mb?:{@3!»,,{@AZY& )Q `'[_`CwCghݦgPsA2Jkni Z& s=rWu[sO)Ǎ\3c2| ٲ7JIJyDDܰFdMP#nUsZ}dd$=~ɺW(.ٽdlX4k *\kC+:i5ziݻw6vX⿩8sߦ!Ix @$|s!y!{G BBtSն5WPmAM%RģSԷc @fo enb= oW4ڽ/m6Yq.^i3yl!ŠC`2] crey 7>O9v`i^@O~ݩuV?RU+ts8UeNU]0/[No_?6L͜Yk++'my~4o#ٗkMzyՔaZy'Y8e LJ}WʢںVْ-B3?,'7a3Oڲ؆]_#j~u U^U_\esz*^/9U}{}^bNo_dZ'"u)z3͛9zݻ_Jձ|HR%Z^=D}ͫ!*?΍KV97ې֊13'/ͫךjk_~dMzdkUV[+׷YGhU0ْ-B> =yFB߾}I^ʿ0(%F z+An(I{(u(=/0Iw?kꪾs:'?V_3gvoD.HNN?|vUؘҭO/ȻWԪ:ƥ:omѴrrU%W@ϺoכkЬsǙSU5xoIDAT}rB39}mZJ@y3gV{Ws? /wZ*'*j <_ܖm $\\rR~E U^1ş}k^_SGy?{u[RtBުbgO:{m!e2)FI*tc\?o aϣl=z)غuϗ?R`s[$YC B8г|HwPH$: tw}x5K*_!YfmѣcRNN8p@ -?NOOٴ0&OGGj_U]IL2Hr|./pR0H3B> =ym6dȐocsWXqfJH ,!υB8г|HwPHSjB;spY ]S,V~~$?|iSҤI}=lԽ{߾}NǛq !щgGPj%Ǫ GӦ5$= |&?AX*ogJUt>@$ @0#ГGf Vܵk/ڵQg Õ@IB!xBYw޻($N<#KaLorQ&qA"i~IJ2;o`wZB[UuEdy CO~euԉN%Y@[<uy:aϏ/m1A!, K$YO<*222Ç >[l۴Wqëis~ꜧiۡ3ʾ/dZ~XbtG+R9&-,E!H aLB O~>@@@%]dM8%XS[ff؉J-:=#hmI*U6ovFJKjelDD{Y[\aj3zr˜cLepI" G~~$}0&!'K} P P]vd-(;XPJň ZNXE(Am=qdֹ[)u2^B-cMMKֽ"n^=gP*Tް_JY<1}βIx @$ @E#??>|A“_(d@xsO?Ȏ2V!`dSRkצ}GjyK;C}El\Vmm+e-14y֓J{s9h=SQf]:Y6od(%`Pꯠ_0Ba,Oe_1ɾE0"O~QXR#*m{>O<z#> SiwaoTX2oi mZ @>g${ \@ 02И!oM/?=n6ɶ|[&m @8-كx WO٩B/,_{Za;Ț@q j7h  }?,}kԡ!7iu7m}{K)u%ccĊ Y WP wP'Yp7k_RV֘l) /9&[a8O}βIH,a9<! >t(Um=}$Wsh.4 Ȗd@8Sُe[!`0^3T 4%B;N7H'YT\^Hj(+PM*O}T#޸SY%|rE-'poԄLF;U5LUVvRJ5JKϾ[7:_l,@$ @Xa2e1BˊҤ!uVBhdKH \x@/wdMurW0י,9&!H \x 06WꝻG޵Vv3{ m,J{liS]0#/yF{D{ж(8$Y;H(*"a>|cg ͕4⵲;-d@f߲[eB!`?\oԄbJR\|)3p(8rVhޣוyZh[u7,$Y}qw}dE=CL"͠h]+@I]d}S ,T}z(!$0!N7=ٔ($zQv$ &UeL.aϣ$ ,>B8;>2XwLRR"e~ҵH+P*Tj0QBIE!`B|0 !J:)팡LJյHuD؇'?,H(BH EL,Gi uյ$YPh+JsE"ГGA6 I @EGz'G>o(G,]zhe3(Z$YPtHl.Taϣ俠G$ ~ ɢ#C#7 h$R&+OV`I.s!`B8;>y@H2,*=΢G%d@Ѡ!CO~%=$$Y×$ ރ $B8;>y@ȑE%d}J+AѺ`$YP4"lsE#ГGA6o|J9Т<_ ,dqw}$̿! Чu٩"CO~%=${ \@EeBFz'G>o`ނBg @$ BòeE!`I~MǙGTLing])&!,Z$<{ ^^=+ >uOtlbʘ6/ss5K~Yh7?LG?2C!̿j~ H@-c("0Q_УM[$Y<_ ,dqO[@7ϺxQI)oeاi^Jnv>|?2聭[&Ydݛ78CݤS:N1_;TyoFa=%Y|_70@`!__/ H )l`փ!m?. ɢ4Y$:uh_ie+_ 7ܠrR~~ܨkWCKxtm r^dA>՚77tccjR_#Q%Kj=Jᆪmn8AG=)ɂ|\_70@`!__/ H ti({]vc$odQ|I{!`ܓS}/7S Z7xiڳGاjӦ>os3}I`''cpZP"=g\u֬'aœ`_TO{=~NZ_K%'+eK/ c6e]ɕ汚WpW͘q@C`!__%|l~}:}\&ԀEB_I-. ɢ0P11ꐙIŊS· +ciBWV(UPװKIZ |_,xE u>={k8Mqul^A_>f~m6hscn \W_jӦZ+1e٣.JX&Yx3fw|\'1(90BaW _I%}e I럷 H(`y&ޫ~Qhîoj7bҮz4E;I#؛KܸV*%ɂDj|O:m3g<>j9+WڷުKJ' #"#5(nLwr9&Wj5›1JG?@́B/J'H -{M0&߷x @$K kBfmhw'^ ,mz5/ޙ$r))VChuURSrWA/I~.&PbʆY^Wcpwlw 4'uda>}7s}V,w1X՛nq@C`!__%|$Yz|(\@`[ \@E%5!`ܓϧǎ ٹ7m2O~M%)"n]%A_ǫ $T L}E|rziu̡ g}I.hߔ1}:ycs_\:KXp}sypve<1=t֎\ɕޛ1JG?@́B/J'H h"a dx @PINAUhŔԣG3Cp4vnXڰ~ުs^ldǣ1E iizP<}$*As>DlEFE)w< ^ m(,+F RnZ |v;h~90BaW _I3eɖ5W&$o MfѢE{uf]va>sq㍆ҙV,8WPDﺋ;S-ٲ%=s횲ҫWic񸹜9 ?*&F)Y/*1 ʊ 1dvB_iɓPR+b,r=o'NYYZ?>U|/]R[-|,&&*1J)I :|TW=~L|cQ;vTV-e07ʫTW=q (_=If=HMԥ*YլYڶmFigk}1DŽ>^vwoK qh8^W&?tȑ} YvUM/i{U6% M6Bh9po_=[4/ojuμ7:_`I #l} dןkdɼZ2: SʼnyXo[ouEFF۶mS+\~}6㴴ʬ0\%'8P8I|WE# 1\IRRq$V}^F8j^rFJ0~8֬e+_x֭qs\~駖=qסrԢ_? Zۊ{<)g*0s_꾳?w^]o|rr-tϺuuK n.]*ω4V~ʘqc (JnѢְa=̙飏v믯#h_]|@i |𥙧${hmJ O?N7I ByAҵyl9imقS R%Y/Lz6&x44kezw4sYzi¾8_|$Y:T.0&߷x @PI(<,؃ -8wZ֭ٳ*w8ѢCs B8 :m+\]@@`WΙ3ZFJBCw)jdIJ Pt߾ĔH(^[ ۿzSYeF=Uʿ?IhZs I:ߡU]"z?D+&kO/6H̻DK~/+:_`I%Z\@ao ,b;̮xfS?HjԨ F+b4lX~G;9RRS|| .NUQ:;vۄ1ΚW_W hEJ*զk׮yZ /f]|C=99T];<}$={PKsw]١MʗWիW |Zjre&Y'Oz.]/AтN(łL0(l}؂`H81:_4, [))M ~c-\>nW_DJYkgq#H t,0&߷x @P`I0qy+Vg.}d wIj*sĹ gC&+e5j$>}Jk?n\W%b+?ߚ$dp[N+Զ? |uv܊K+by_{e[~,z-$QXzϞ=lL7qXxr?={}-K~=Ŕ*nӆ^*K~$ ( 6mh_b֓BwzP^D4Y3Po?rd_C9޽7MQJ1Ͽ?z=j*Pz-4wn0^JS2 XwoK~[/)ǭUrҥ$;kcǖKJҝwv  %Atf}7g/^}^0vӼycke7'p\gݨ];Mkxx=Z (4~JyŊIZ{sDAL_捯M$zSI$NMx>xmL+ҥ5{P;[nib>/Nf9e_\}V<=+dfPB%Y0RΝ{.\جըQ=J[glS_ }fra9y?(}}%睏ωj;x<,'p{sz\zोt艃iik_iґO~._#SWh$Y", H{u0&.I׾DWKpW ŊsNˋ㪞>ֶz$C]a'Yt\!Š;w}JsaE!`B|IP@90ؕ\xl}%Yp}@jsbj>}wrNNgVMQ9]-3p`̙Z]ժXNn_~yV._>u[52evZ9 w/f^EAߟ$ gYnR+;74rNqv|.7&Xb_X \ (kT>9d҉Z9[-Ur+}hpsLԩ1ov*V2?i/h' enmHv^Ds}M ~KOV'eE8e?Fd@h0Fl%saMopAMJs"+]%Qx?yCӏj'cOVʯћn9*V,-MK%v]I:u'O'O6M>L۷7BTc2y?W޽Hko_1bbo ۻJ(]:ް_nm:^!xEEgfTq]i~֭Sr~.꫃Ǐ?o4/aŊ^_׷ݳgIݿ;7IZWd^$7hPK+?rdjjz}y3ƪ +菩7VV˕_ cdae__ŕ~z])ׁkj%Yx3W?@Dw~A4hO7هQ+BKryW}&mnL(d@hE}s!aMopd9fQ!(<՟8>PO'GdquԪUV6q9ձZYRR؅dqו,{5+Y7g7p]]:f~ҥcu-?Oc8=/IV@Qe+mJB]7s,f&M%M}r@OBϜ}h,MF@$ B/dך Cl>+dyk!(u*UWcc˥P=sw~A2Me9)(.)Λ$\}q- d v%<M}Yf^9ػwoR>޶m/s{IwRSh& ʕe99A|>V,Yql:wA^q!?m{.I}ݿ;7]EttVWI))U 'חyͯӜn ?,́%:N7x%˗hm%Yx;לdĉi+4-Gz4h+{~] Me1wPF 1(蔢_PIrr)JWuTtT.ICr{Jߟk{֞˞~=ckX'2uO_[õ./ 쓗;YL b" k`+@2 >+!" INΉ  =[[m Tc7m^;,B 3jN_?G}o~Y\wuO4U@%#Gc#\ƽd T6~}jնss֭ꮯv$%8oZ1cT˞=m{ڎ/E7ܩQ?RS޽]cK{:OzH%$&Fg}?ErN 1c޽;ڃZ5qi9[&G˿UKW'N6>\Lv}r i={cjq O?=u? Ȥ6,OiydhJBǾzLHA#%&?E4?ۺbQdQFnE__ќ߼5R`/n>ܼ҉/ֿ ۊsVUr%?`@WsmL"heA}QY]/GԭnrH=Qu;/fK {W5o5W~uJ`oE@7_@2 >+!Yő/9//ҿQV{VVM՞ݳUӦ >{2P"WU6խ[ VjekZF :>\#=5, 6o߾j͚5"Hk׮ߚjkx^|+VTw~W wp)YB0o[vVJCE^_<7n/wYYp萫?])p" [}2ɺ|DǭqpoJfg?g jםVj}>x_Yg^|q+HXS]>sGBBxO?U;M6Z; zᒟ&wѼcuŢ"`Ar t_%F׼" B;HQw[9{m>[;ngǧ-wQo7,qk׸֗W~G (J}їgLJ?EVdbn4iR+bjڴբEU!iKkB;e2uAޓV!ENVk|ner`}hMvHR r[,QVSۈf֭/q"sws7ňےE8`~q=F|HGrrokDySx=%Rl!wY꺧Zj/OQ'C {Bzwîci# Y%߫lu6q4SdE="0M`y{ZJJ'OH -򺣅Ξz5azˀ>߿tߟS/ׄp%R#ɫbٳmmcWe~h_uEt?PUWW=\.(-W`{=t{,um}i%s}oH_sƌz!_~iV<)=Sɓ^:\1PsbpI Ekjxvu\JcWGgޯoE@6_@2 >+!PdۦNRRTZZjٲc@宱^#}&@K%$W#Y䖩Wk7|t͛UZ͚c\pq8 w\r]/w]׮i8n%o\FEG2c*T'5oW\8O}nU;5;5uu 2L9'dի?5j[o α[>zN5mZ_=mqM?VX(f/@2 >+!PdQđexVT\ ߇OJrWłC>[שcݽbjZmL/_enĉ^o8YLٱ~oeB $7g΄gI~۹|eT۬Tn^6EuqY:&⼎P)~ q%&}422#okS^~7 Xyb" d[˿@3 >+!PdQđexVT\%t2L"ǏSXW[*WZYۖ I))z~~כ["Dꟲ}=X!tҿD^ ϒh;yf4EEyrfZt::@ !7us¼_+TN+zsKHwU|ly^fK/vs_k8n%w^h,*HNO{\9xLwɝN}jmK7.Y uR]=_jz֘5}p; Gq^(Ôsb0!G_ o?<@Pr5ggLJ?Yq|޾ׄH2^k~~B&ׇ߸sA%Xۏvm't9;/8+\%54/uHaӦY2_rRԩ c|Rel'%+m۪k֯wcví.]#l]+BnyBW[r#-shXOq^(Ôsb0!G_ o?<@Prq_*;;8f|VBȢ#}&CDpƍ~.}B3K~Mesxhxu 2L9'B)xci0qEPr-esg%,8r o׊k¸]%SSՠUntKuE]R6}UΌj1&z/erN &R~` b" z%'((2}_+* ^s߯! sFGQ}WmZY _ĵ!řV3՗^y SΉB y5oLDlQd\Ag#LJ?Yq|޾ׄh3UK/U6T哒TrZjrjUudu 2L9'B)xci0qEP27_;;8g|VBȢ#}&B\GF^(Ôsb0!G_ o?[Y%Scmwvq4CEGΗZQqM'D⽎P)`B!~7 &"(J<9O,H}&B\GF^(Ôsb0!G_ o?<@P2]nͺ ΙSd90N.r{ s+~B /^~7 Xyba#ϾKsg%,'0N.r{ 8"R8ɍi0q5ɠ(((Ēk8!$udu 8qBH$7A$" $Zg#>+!.QdQ<%ׄqBHt(q!InLIE@I/u6RaY$K ㄐ"ב]BqBH$7A$" $|g#>+!.m3 %k8!$udx{P8%?B!ܘ[Y%OE_2JK ,:ׄqBHtȠ248آ(yRg>+!.QdQ&B\GEIAE@_8;Y q"5a]:2(L2 &"(J~lg%%,ʾqBHBei'# Y%x]tvE9Y}@`LDlM2(JK(Oℐ` bkAP,[g#iY ",N> 0 &"&Y%*_v;XLJ?Y'dqBHei0q5ɠ(i6b|VB">5$8 0 &"(J4CeLDlQd<b|VBeL!$/L?VX(" >(r4q ,Pf(5N̈́g` bȊ,["ԨhE(3\I|F^ GAEe0rOJ 7b|VBek=k48آȢ Fο A?;;XLJ?YpM'y-8_a[Yw>!(5:4F(@lO3Z#1 &"((K#(z篭4C d{ׂ0ܘ[Y7nE(3\I|F^ ϦAEe0r o9(y2_5giY " $>#ۄg` b"29<7rܗDgiY " $>#ۄg` b"29+!PdR|_I !%/r}:/X$>#ۄg` b"29<f|VBM)y@pM'y-&<[Y7yΗ74E/nBHɈ\ k=k648آȢ Fο=%_^q6LJ?Y \ !%#r}:/X^2V7qU4cۗS3Nu&GVϘn'|,5W؎E?E248؊Xdaf/Wweq/JZ&Gwe*VLVI+Zu2UmUAּk5_WguRR*w{̈́ޣq^X{J7g#4C5R2"ק/ l%?^zg$,}YuޗY\yYB zv>٫/ -yN^r]I`^&<<ȢK.?G<æM믿6lݽ޻_~/s..ݰM59ut 9g7- qBɕ\^#&K[c{".}FiY " kb7!dDOlk׼d[E nR"߯]U*_IۑZFPv/p YYt}fNNOk֬ +`']t)typt5RI>IAìZd! >f|?z{"ϗFiY " kb7!dDOlkה"HJ}qUdѧOK˖-[\EeƍHEvv^dkf:Wtxr:y+ĺW\}%%s?F_uzhB|ng#4C5R2"קw6q5kE/|AJ%%$Y׶V5v[mI|0L1-}]UjTV!M i2D=q|nE_B]j8Ul:=w/%Xc^󚵽,kzYmo˶,D7|kP/bb備u,1GxnĂ4 /h^wpG}޷엲|`Ҭizva_uϺƄGkNf=9+5jCՓ/lu- EU2)S">%opYP!IUNMS6VqXc&0ZqǂSvwU示*Rjֲ/{̵M,/4E/nBHɈ\)d{ -HOJW-TurRޮf;ul1_A`=WZF2J}5+׶g^G n'uYHCJ>3Ӵ .k3>%{쵊">l sơZTma;M+_,߭uVLYHe=繊6z4c[ť^:wҤI" 9޷oߍiN5?\]W=r3 f߳*MIf5UGICrrjrK_2VZ=*f =kncZUiKqǣk7[ER$Q~CkOIB#g]zjUY8)}+mFE+oiyE~,l)H8&vBJF4 N&{MhD 1?ue691x\q] 2S2mE mziOO۝Ev}Ou{JתTjb`aG`3_n7ֶ*=O>>>B- uǏhXM?VXY :tʕ+2MXkHAbb Qtm)g7TU2isٺ};t{pܭg7YmA|۶q.D*sc-zlm}M{yAR?w8;hN*PWb<'&\ !%#r}|g\& B{/>+/j YF36B#wl.+y狺ZdͥؖwkSn_i!\^u=Aj[E@em^o޼U@%7nK._)R4!wK{m)Sm:}G,bxtKuY߸)Odݱ\rnWĺWԍ0ck֪cv]X{JL܍qvL#/((Ękb7!dDOlkׄ4LmhtkUeՖo񹋭纶).'`=En-X.Q'}B HCRu:٪NJR+5kSdF5ѭ[7c[iH{}%Vͺy]ʩiY"'ea^K5ljsуu%$$脶xkE834 #zjZժ[ǔ\)EMy]$˾-G}e%4/ [S uvL#/((ĘkbwQdѣܹgJU+uU3w5>4wۧ*V?FטyxMS{Vj2Źʹc{SOu ^ӧ[$Xrf(ss},'׸x 6=9^Z@eq׺Zk[Ž u`|7;U|%Ո\⍋}/\C,ˬJ˕χnTp|qվV{7Ժt[}Td(""'''wشiӡm ϼ5dUD1E"._f56gՕ^_hEϽ:y+Hu#Ϩ붴*m˿|DS_>U__f cf~iyE4<8sM.W;59\uo|3*%#CUoеLh0ykłTW>^k5Owݸ Hsق&ҶKK伏]JG4E .D/7pVƳwZWq't[jVх߽Q^EsR Ds^QL_VO/?!_24PP @L#/((DGb5 㬘oti"I/%$իϙ^,$׿zⳳ" fOuEm\a>O^P^n޷ZP]hW$?S l3w6(\&ȢQN}}׺*'块lGg/x5&^?Dw\ۧ>MYH:f1ַP.SEh1E}SE\TE_O0"УvVہQ17z4c[M7ݴyҤI /9r4e½pM)H,_^[d9u{hdOYR  Y/Ӣu[.wMݥޯB|YA#F9VլUGM+-okn/"K;_,E_l}YL_zR@4Ls84׉<"RyL-Twن/vzS]r߯ڧ7N5n⮖E]d!ct7BۋBnۥ))JRPvF,(6^"_䭐PA k:LK9{noW:6k]0~kLngJJH;9lQ=9U'^1u>e$eX}iҬŠ#-9[TjJ)ָ#݃vHk C>j66V |OuMUXUϺCFp׀]lw۷ݦ]~(>HK .rVq'۶sG &ל<%VpU (AykۅynHEu<6k}"}>\~<7 XybaɣM馛-yBsrrgggO;Jj 3" g'pnJ/2ۗ .6x 5&iY "RyL] sM7io7ε\ۋ.(Ha/g.˖e"M¿qN.wpn/ܲg^l}?6szЂ gs}cxBUWOx}ެۉ^S=V-|ŊꮯrӅӦvŹ;LDf#00fq?@qM/xُjyOt}BQ 8R<ׂsG &ל=$Y%˔+W*0t 5O]cm[~ƍzя?۝n3C.w萶y|c?y6=^6}%}y$>s_YDϑSS l8ظ&UwTBvЏTG]HD^ M?VXd_srf7x ÇO>zˀ>߿tߟS&VV=2N=VLMp΍&E}hGWP@3 >+!PdQJ#σQ\ 3 md~=9S\Vÿ3x (((d݅3ԟ}rg`_ɓlq})pnϙ"YY#F辺[p%),G323]Ǒaݍ!Ҷ#Lx,>InE]Jܵ˕Z,"w# Oi_#;#>GOa$|blO3Ze#f. Nx1(uE$7J^|,%]M5]g4׉<"RyL]h~rζ ty6>w]"؎_n]s&"F [_~ti}p%)sܲg[̜'%uۖv\pVdqW__aXv?}8)tOi_#GϑSS l8ظ&ۓLaOx¡Ȣ FοQ'r/|ؗeL_\ <#/b^d1fLgSOS?޴mIZ1άX~;R8/NQ)ƹ&diru.]1童_;5vB|~ʕ+nٽ۵lh=n.EZ:d7brnd;ڵ69ÇIVA7/"]u{Ό+H˭BҴS\#rU.!Ak> ]Ppڴ.5h9z x #m &ۓ owX[ށp((o ;|a_8ʢ9u61 {<"ERd+XY}f޾"Gu4lX5u>ue矿{~شizՆ {e4qDkbwaG wp]>RGO&; Swgn^uն!dqWd6LܵK]_r61 {<"YH^tg%]}^Xn۷[lqU䖍7#كOj>&vҕлu4OJokY&4?\}HERX.{z]v5۩!M`h,$漪(rEsM'{NiFѼ^K|l`cyEYȝvmRS+d5dNYHA7Q-[U))IR$բE::Da^$X0vɇsY$ժU]T^խ[U]{)t}"/>G]}u7ոqMk=UTRպMc?ҵ_?]n[oֱgϞ?u g}MҗT#\Į+jqٳ]&𼖵lk}AmdGw/G|+zt#EQx^Tށp5Ob9&їվKoG_Y w4qċ"-T@P!QpCON*}Bʪvڳ{U !m VW}z5V V*`=hZT߶$\Ezz%[/V]۵k^&Rvt4Y>0Z]p0k៖ߛe[OA,.袕&MrQx}n4Ms?ݤdF,sj_Z~1f7Q-:rĵ)( Oo- TrbZ_C9{,`Y[2Bҩɺ}ƌ~] *!\Y\wuO4U@%#Gc#\d T6X~}jնss֭ꮯv4e?VculgRdQu޽(,]r^y}ֽ{ƖܸeԭC*!1Q5:l5o\&x61=?{п3g$?m9 8cGn *VSrWdQ%m.e89R,HE~ݯ_LIDATTWP9 rlc"YH1F~op_Rd&95@b/|yQ ei%@("NUIIߑVU7\[{Sn$w`_VXljԨ՗USzvglմi-aOm{Vz}=8Fi7۷}:Xb_*$a:Xۙs@ޮ]cu9Ll'9V˲)V{ZZUXEjSTԔ)vŰaWYUD)u[4SqM+#.RbEugu94 1źBz^YHw/]dUJ}>uݮ=yYH7ꗻ,8tMۊl`l[>P*Nm^/m`mgݦLSqKB$|ju쯪V%])mȢQN}}׺*'klm?{wjWmusOtI8cmYKHB,P5HlS,𥙳y˗9ؘ[ċ"-HqCvvsUrE.Z]B2XlEL\JjŮm, jWOӵks;H񦭭IZjQVA\dffk9_~/wDڃc>/jΪAzjHU*8Cz\aYHnWﯶl*-2W^?dgg65;RfU\!uӟ\,&%Ȣ_B"w։txM`[ 4d"E2{꽁YwdSIc]~H}D#!{#Ӫۥ@a:^# -fHOJWV]egfv5۩cc9]5.m>I߹qF 4VZ/+'ÚSRsΞ~m=F!&_L~LJC?T ;._T!Hʰ*YrnlQS)m%}$?VXky ئ,K/:;Ji%@(" b◟w6b)))gL<#)ҟs8;;{Z蓚ݑW%W[Udqk Q5TTU>)IUWj[z'?lw?^׮y^2evլkWk=ii:F?^nERp曭;$Xwx8E sTq- L|Cb /V ]V{kB,ʼjUgNζfX@}PvAe}U4( * (;JDDq(5HS=UT{sNUWWW̜gjjvyyq_[oƮY^'nmC2rzosֵ߬{~[w7~d>̾kYq.#F s ;7Nd۽ctbgegW?gW`}ȕ.;2ݫA/ RX mr'M%d<3=ì@{˚-mY[d:~hC:'ۏ(q]ano,(X h< ''$vmMmzCqnO"EtЕ*udž<;]j4S5N?t}wG Tte,9=@6HERH!{"Rѯ[rd , os~yV{j*ƆM:1)X|Ç;wޱc޵k]X!Ho1`L쎗mGړeɊw TdqUW2ڿ͞t/ce\-@mpnw=2aW_u#Μ;o-(W==^ˮTI?.Dm#b؅ '^v1m\z}w<#"GEΕB$,E #-ms[衷>.B-)ֲb {}㍬kX[sׅ/e_{'l[vcr}}쪣'd/Pw:mouPcF XY\%w='MrǝaC`߅M:2{6&'S" ?U# f_y%?<+@Y(c.kI*z,_`v"R"2ȯ[?6aIS uܱcGE=2iҤ/eYzСߍ;1c\ ,xI2މAc$Y_NZ62ײgO-m?4m/xiڵ̐kqϼ",Nni0xn^»0E)vs;pITd~_m٠&۱}f&%/cHߘիucc.n_mw^;9NrjGF* OfЂ^S&ɔt)'_;Wa~y^1tr$n$;F>~ݒ{ SPdQyktMuNNe]fUݡCc}qo yTKK`bwȕdBVVT"X5k뛴uSy?k֯rD*w<7?w۽E~P'ɿvf>ErU2r[`$8Ek/xw&]oc&%E/syC1r gLjb.{I'W7Eo.//6((7ɒ/9p5[轓o/606Y#onFߓwO`\y&?9Q8NDr$fGx?f#KEn=)(H}XZ@ʕN *X= i܆ TQ_^=xT-dRѯ[rd ,R4:pxNNYl;zTp?롇ܶuy edb}w"}Qk,y1Wda,xNʗ*\WdQU+2Rp5mW`>iݧZ ޫDJK_/cȢ(L"aK*#f䯣.W)owKbYe$Z_,LEQE/W@%&0ٞdfXP~y΄w59|Iّ>v_YhuP,-݉Ұ}{w{_;fvv#GtnÆ1o덕ݺ˜a>kvoa1;>m_.N5.Wd-L48Dow ]2ubYH _ Z_Ac SdQc[{vۺ?='M+o;}(LQPnIfFLxGYC9[EfG 8ɭf#KErd ,R4:pDj;y;DEƞg?5ԩWk_MAc*Vgfzi]re{Zwy'ym{{Yo-}Z%nЦVys}sA.n}nG Ng dӞ]>rޢGa{Kq$zn~Y6vmj͚vaź>W<Ӵpa`xor gLaKT zLy䱪R,!WYOvظ/tHAGUlF FqU&M1rs߅Mvd{cA9Q8nr">EfG :f#KEn=)(H}XZE&xYB&{=nիcOB9y-#x N\!|ܢ7V]^R:Kqc`]M22q_v!e"䄓Ng_}}1jo_-A}*V{ );c~zҶmv\nH͙o?p]+vg̊wսM mGٛxm _⭯m~lU+Wp"W嘙_F:؅85Uy1_t&qG`&)(7$3#ǂ s&q!1|f#KEn=)(H}XZ u/^1@`˄ss"B1[.RSM{"*%0;L re*5j}rYNӦe IHE#tVŊz 7x%0d{cA9Q8搎.T㺁ّFlc),y(H}XZfsׯק_x&v6+yG2Em\W\YWYS>L=c !#OUPnIfFLxGYC:ʎP$ךI1;X" d,R4:pnBHrDޟ|?&ۓ̌ */ϙ1tH1X}/.2;X" d,R4:pnBHrDޟ|?&ۓ̌ */ϙ1tu_$/I,GEߋ >HE$iMISd{cA9Q8搮nK1PfKE+rdYb~?B'e)0*(7_$0HRTt{|,}=)(HnBHrDޟ|? @%X)&lq!]v#I;c),y(HnBHrDޟ|? @%X)&lq!]Uc[h ^fvE<YWR`b7!$9"OUT_M,Q8ξd٘{qRY TE6InBHrDޟ*ω #@%X)&lq!F1I5T (@)" dnBHrDޟ*ω #T!$?0ecO*u&6R%fKQdC*0PJk» |Y 9 Kfcjŋ>WY !y*,&;$"yQZ D٘{qRY Pd L&$G( ҚO;Au`~$$BEߋ,E2E(*BHr" B( I$#yQZ r|2;P+)fKE+rd ,PI݄ a* I$#yQZ F- ɮn$(@%ɜMIҙO;GH,uɠߧID>" $sR7!$ybJg{`>IkM(cH'}ّڪ趞gvE<Y$#B' yt&&*܄:dpS̎$^E\(@%9!$ib( I$#l# 9Wݘ`vE<YX5=0 w5WYHId}/3;X" d,3InPҚO;+,Y$7Iǚ|,}=)(@Ln){|ASiMxL'yEO$$IEߋc>" -un/(b* I$#Es1 uV((@1&sK~ Jk{`~l9rnҤ\9GWTM׭Bi[x9u)S;{n6lo`lrXw4TW'L=_wooYJ=ɃfcꢢőfKQdCb L:7y1%nYV &,5j?>"׃bРAE=Ke&ˆ _fͺpg1U/^馷}\-Y=^ 2-{WPPŜ9cT/_] [yO2pvSd!/݂IAERmmڴ[rUODEX1 uSp2E)0q[S&xdާOѣG}Šxy4h~-.|:DXi.>;fz۶onٲ^sE woշQBEsF~_$]EߋYY*^{ SPdM^?n@ Lx7oMcƌO>d"Vx#guGyyy6|Egg1U/[3,fz^b=ev_w|߾3|R1{#Qe+ _o@`b,H lߴj˚ki?fj"Ymڵde\JUu:Mu++L'9x#ylLB{qR" -unp2oŋ6HAW0sز L^.4hF3]_rļJE"Mݻ 7VxG^޸^tOR!N{ժqNvs I=-a[d!jԨ1bn۶_`]7oo98}R$u /˗eJ8rsUY=~Gڏ۶삋 .]sj!Mvۤkf~Ƚ\1Da>00Y ;fc:UEߋC>WY }nsO|ޯʝ Qĉ_,kvzE&geUtHĉ}*>}. ŒX>oaaKD͚ ɓls]ܤ}ӦouucGv),qڷn=kn-z;co?_szY^ w\c}[y`a0Y ݭC1 P9fKE+rdY瘤TSm!BI2-unp2o)S>߱cG"L6meY70 ?Q8A-13{#xhB WĨT^,U[c!n_5㿒EM_'WR]_.ϷaDEk= q4OU/_/*T]Sl+W[ga0Y ݭ 1 !fKE+rd"Y;H)F˘B!$U0ٵk~9=0 ?LVxǾʃ\!'[ 9餳qfф-8wf`xE-׳dQ˼"iw]up&Ndu6Fbg}/++ۍ=/o}ŏ-N;iӦ^ uL3bY ]7fcꩢų>WY$ad;i?!BHREQ̓P9q[SYIy&0@޽W<1n8mYV s}i.I"W"}a7Vn ,u}2LJ/5n)fjeXIƭ~+U׬B\}~Ӧݧ,y-ի?2 ZYO#r)n+cϭ ,٘"fKE+rd ,0͝^t䴟B!$"(I (]ڜ-un),8WEϝTſGEřg^;uhwS>kРfMH…+7T~^jo,U{}r sxUF=__.#*UݻB1뗂K/]d!^ oɒaKtŊg̸߷|/JR\!c) =-Tc7I㼷-NC;骹U' P[>`L}&FX涵mkm~L8^-p?͛5k6p'O3}fP^fנ ջ4v̜^vrsܾ֭چOi+x+{Te7m^=[Ran_A_69cHGW1^H2v2;X[<Y$adGHYґ~B!=G hs~qn٤ul—"9qV^%_ކE/rWq@I#/Lg蹓ܫٳgl޼y00ase_o`~HOR/^馷ܹzZ[nq!t߹eoE}4Kʕ/y #VՍw#Gޤ۵s[衷l9voha2Rpi%K^WAڿc*|RRRU_ر|ymYN!S;yԴ +d\qœNzzBƜ| } Y* vJWP=9cHGsTav$,p,-d,0UҢ#B!I9GQ{@n6|;YHQFVv=i6Pdt,88~ZC Q$Ν;߱,HIŁI"\p<ݨQ]f}R<м)zv!3~ʔj' <-kV] 46G-_5!} 'Ek7yys|/}_5iҶ .)e L+{UXYWR]7mUϙۿbŻ1;RѪ ۶/,H{/2N74hc_-DR RѱrKUcv$3Tt;2;X[<Y$adpۥEGN !B*r ͉ŽBn w/oo@_YJr(H:2&^r%GO>('x˛C:%bYe)+L}ɞ}R1i*z70; E@82EI*viё~B!=G hs~Ojk뷮+V+U4; G펋Wd1a_ۭX};vyُ֍:6___~.~q>oy}-6[ cbS'Gѝv+[oe\>Wmw_/?L(Nrpػ|D`~<,֯JO|/n֬{`\*$9TcH#?QcўlThv_y(H>VηKB!$UE9Gcݶݦvmj^e=}߮eϖ:\wl"Mlu׭hm*`xh^giW=u]DWͭwjl*k綷Bo9}^+e}mn{[mXz找Ǭ]mk~Jsw}_f=t$a^wG[mw ~P0KEϝgܸqӆ r/aǎz׮]vau]N:ׯ_IɘnRO=G[^j޴7GW.|VtۺA6z{R!09s@x =Pdc)n!Pd}o9'BI9*s$26'Ǻ-}c;!>)ۼXER>vASh'޾qݾͺfn;&yl^NJwK fſӤN`En\q|lc[:a @,=w˲j VηKB!$UE9Gc2VRBeD=e򽕁fQ\١t8>^EZ|u<.`_b⦉em}έzrN{AEEXǘfJ 3Y*z$%)0 w5W~i9d1AE͎$CCEF w]Zt䴟B!$"(|ϑ8ڜ&WhqZ p6/.vǚE R̾uYkYϷ\=ݾF {K fݾgvۧj^PEIݼ2L*$|ޑ\J8=ۙIng̓6E:ͭo@Y[t>mu9Nc|i}w7{;ng=n~)2mrUW;VnS=xBݨC]9'GgUs?^w<{[ɐ-NCꪹzS9E}nc׬ 0u|u]ɐ7F9]o~JjpC`lI暗^ YYz#I>;QIb+N|曁v'}gl(˔ubs%8vD'Q_*̋/=yc&y@x_׃ ҵp#v^=,J:ۢbұ"Ɲ {K "̕,揕d"KP`>IkBRsb'I" Ku '),:gɍ c^ᮣz*wDؼy@#pmYV &,5j?>"׃bРAE=KWt䴿ln[:{{V:l!1-10Rѱr,U:CI$#Ubv$,p,-d)~Z}G+q|}ۺ7&_SLѣ>vaE<4h~{_"Б)b)b1z$f5k"/Sdq/oh7#嬫eO]E}g;}e&ÇݻlGTOVо+ɉ%HYIר()m+JȢB 6zvoE k[$?R1Y9q?M,dɃNz}S6̵j mK,c[좈uM:7qۇ0L|oey\oEܪqzM#uvn{-[|[Iożfi'wG؏O<.uJ!Fv/m8~rUSmw0?LTtd"KP`>Ik8P=ƻIngR|B)"Ooгf Э[7Е+WUVMg_a1s|ecӧ[nwo߃osjo'uT׵kCom`kԨk֬=4{?n[\h; /p1cO>('xYgQ^^Ut:r:ŝX썳YdM"22cdJr>=q&ZNJ}k,]uk]re]jU]iS{^3`\F9Y3}\ͺwq&Nپ=зwtuN::vmiPۿguu5w@./[ke%7f`dm|ezN䎓X<1'&}b˜׻^˖]V-./Oz!wLv z徉s\Ɖs@kՍ:tЕsr+_{쫯xanga#E'unիoFto\:Y̸>l[[&?aVR:Mmǜϼ8 l$.^b1gx)/PN{a{A]YZ9q?M,ݪW+ rU )"hЦ3],!lad3 5 kY/&E[׳O8k7meK~xo=^5/]cz:4+WUWM6scv){Ld9 L'yfGR|B)"?ޭvmf;lY_K/={k߱ {_gW[O]? U[V{={aԨz+|c"Jow>mUm·R"}ժUnznԨo[w}nr|q,ڷoo >ǂ`aߗeY5Tiwb7z$) NNӧPE-zU!+K_z?cm2_:)hڵLBoӷ8`2 &RmОX,Y}ậeٳ ]GKq&.׿͛}}OƬ^]zS|/+Udeɺ5m;vؓ{/&webO<9b{'^m}]@"J%z{n;{}d)Hs%>G/溼yk* {qezIʲv),+$piۼ^m-LB @䵔l|.yQw\:Yl:@_3gڟArՇʞ8/W%J>db,+ǫ\MŻ~sO߹mw&gTqEaw (% ~(di+~~veWd= {" R6'',0͛\AY/O[Inc KE=@&T&sp*UڨUw/UܤT3 HOrdR+xcϱcOIEu?uƍ%E Nd̘S"b Y9)/1keI8ND{ 3ڵkO?{Y̜9 Qĉ_,k*9N,Y$lEB"RPB{y*2U29>VdR퓉2Lun8.srm1c+iduY9&܋KO~?;%Ε$މa'v6][b[|۶}nwqIss]h?`-©٠&ĝ zJH}cy2f?8}m˿v,20q,dt*p"Er%~;׭ӣV-cxW=b$^Eawo /%PwgאkqǞyEnU#$@ d_n4@2JrMX*:V2J|(0 w5W#eԚPΪNgUg7F%d_eURA~ҀSjEr +#Ft-L8}>1,ЫW>K&Wrn{iYL4;v (dӦMZ*9N,Y$lEZNv+-J,x_:{^uԩcO&﬊  61e=evc ;^2'c寱cMn\%ripNf_vwIv_UW)Xnl.cְYH&nۦF:ەEr:i~nߚ۟;2N|q=Y3__׵E}Km*gxaR(|&f]+Lv0_GI͉r~iSE]j*Mۼ?ێn 'ӭ1߄ac Yt·IzG^s98RUnݞ*+hV$T"'(hc)n!8EVc?LE"W8V϶D ׻c [dч8&Q}gyfm?>r^EÆ Pɣ>e߾}?S/Lt?t/s2wrԩivoHMN,^oW;wTxǍsqN{,{bȕ&սiӻ^NB$Ag~+M&zˤ`gLuN]g:+;^F&K5k~qnzm=bٲ_78E\roO;+*T}BpPˑݹܿ@_y"B, l  эvz{\a,a$+l#sKbzݾqi˲v︧~Z a9^S˵3D* xEfYȤ99n oߘի6#9n(x2V"+dxs2wAcŚhbGq 31mt+l@ӟﴏ#ruﲉ8.sOx2״K^{-Ȝh=\#MA}'2$xK.1N=6JYX,/Nk…@L>/yyI68뚰s|_D3Թ0Rѱr,U:CI$#*ґEpɪv_dog^ Y[~[NK"YH}SP_I\UF7/vf߾M" ɪ߷_()gaުoLx"ESoM:wv{e_&KQ_Knyu]Zt8ȢxM ?~(grauO8i7? u$(!ڜ-unp?LTtd"Kh$c98RT}B@)dNcdgTc'R|B٤ǽ܇( 7߸YWm8xG>=s춗m;\6IN I8I4&Q_" Inn_rI?,?/v(7hYh91QV󎮐>ok]Z5}8됔ED&;ʛ3mmnLD5Fb99Umhw2Uڼqx='M'ƛ\M1Mr^%텙j^DӦ훾WrAemq]/զwoݻ$3|~OBR#YMxhIa>,{'w8l2Yv+}BrΪxH060L|y'ex [,p\fn]|xיuB8j:8oBR]ۦ|JID}Ey?30ݶ!JU#$@ }nsOac Y·3Rewa H"RE'ERͿd)n!j4/^6e+~+EUV۳ZoX?IV_|Yy};1O?WOopw#Sч=Z7M;dO?u{ _=g@w%]d1uԇ͛( &om=y7^S |2YD&FK~pɿmЂ:u춂&UO=eOzuըWϞ,e]Fp*y]+T腻wdJU2\&dԪ/Keo~X9Fm9|XGEn͛W=~…vщwua'zKJ.LJn~i컂{K^{MԮmWYӞhO>4mjD)hz$þחq-#AyLym/_:РMn'^s%W07m~t;7rdYHYsM2 mfN>2h|w}RTLƗB'?Y'is>^WユK»׿]-VY3gg]$ڦ|JID}X¼3YR}v`)z?%]%Qghs>Թ0Rѱr,UvCIb;怰h}}toOR|B)" )VW)RHѦM=}r իW]6ӏ=z~TwR7s7F%vcǞ?_NF*v=??W۷?JȢ~ v Q$Ν;߱,Hj0cQJ`YJy:a?{㝄jF&b:I"ͲS1C߶]p!뒂 ~S/˘2IU^D[2]&>]|=x+:tUuӮ]}8es]6i'e;IcdvyyTQz=؆w9Zl%ieһ$tx]&K3.s7o]Xv1z1NQN٦o%zxoR(" O[N:I}Ձ+x^L֏5\cG`w*0ZHDQ17Vd2\-_g\@'Ww0O⽮\+<*׽ϼxF}/RR'QXy?8)gwg싓:.42oeLmN:7yTX*:V2|ocǎ]7zO>d"Vx≯ 7//o±д9dnmz G8WH2rd 3IPB9q[S~bXD*!bTs@XbRȾTρ= Ku "8qźOEq'8i~+ͳVZʁe&1cfԨQ^7_~\EI$#>}-rQ+VIף"G$!WJ0 !Ͽ0?%DM^?){Ld;jL2.X9 @)Zd_pߏR|B"NBKG?MxfͪvgчC꾙U 'NXg5vܸqӆ r/aǎz׮]vau]N:ׯ_+UtڜH?2_^7ިW^o{u՚5g7:XNx=/Z^woڑ?!(Sڜ-unp?L(RlYc'P,@ٗ*@_y(73fXKn:;;K׮Թ0Rd6Kq>e)9$@)Zd_pߏR|B" #XXmNj$B)9*s$26'sK~*S,/@fC([C  Juaܺ{zG&/3Wkxf`7w[Kq UX[<Y$adIB!9GQ{@}nsOaE9l|eRsH>bXT-?ӎ/|eyhnɳ_&0Ѫ>(w=":zI-vSdCEF*߯ڜH!RޑsUHdmN:7yTX_f)·P,1(HVo֎Ͼ8YHqđ#?_yذ۾" 9f)R|B" #XXmNj$B)9*s$2 9I݄ac Y!-Kq!%L)&Ƹ='z[ugƆ E@_y(H>V560B!#(|ϑ ~sNR+X_f)·P,1(HDEzzO0#v7{W?/zs} *3rg!}_WӘ2ZwYdѰSq~]O=yolȾTRCEF*߯IB!9GQ{TsZRd6Kq>e)9$@@E[t /,j>]wƺㅷ8#O>۟~%bڕKݱ"o4PBvG-EwׯO];N0}}? Ku "$cU_c !B;r 7$E9l|eRsH>bDY-$*x3?nu \S0!n^S_mYt7m'|+#_[ȾTRCEF*߯IB!9GQ{TsZRd6Kq>e)9$@@x,>cn^yE'tk;j_hE4?l_svoE3GGzZOL1$| ٗ*@_y(H>V560B!#(|ϑ@D6Ib)~Q28BٲOX QE}q׷{G9~ȏn6 ӇNq{"뿼gc}c(Ld_*&7 X[<Y$adH!RޑsUHiRd6Kq>e)9$@@$*.[;H oɳvoNz͖}Y:˯W0KA}܀b)n!Pd}kl`R#!BHyGQT#%Kr,ʖ8|RRE3io=,BΚ0]n8EƸ7Nx6 ,<"R1R|B" #XFB!򎜣=GHK Y!-Kq!,$Ͼ۾l^| ,ڛnV7-w>:/޾0}܀b)n!ȂIk-B!ɒ]wƺIG߹xW.hOO߲g֓7v{>ޯ= ]\Qi&ѽG/ݶwٗ H/2OQ,b1Oy3B!$b(RlYc'P,+NaB,G/>9&Θ6AW/?{g.EqbQAUQ@](D$ jTTDqA$1\Q\iLbLSkܣwO홞sg̝3u{2O>3MNngSg>3g}̽`Yp'cG4OY;;~ŗŗ_57vz0XHRV|oAAEA!*@ъъsP|X(3I_*Њ-<(=!B #ZC9T6Z1c+Xe&KsZAB!BuD+~(F+C_bAa $}pnB+VE's=!B #ZC9T6Z1c+Xe&KEZG,ъъsP|X(3I_*,Њ-<!ޏBaʭ+$h B!P!ܺ bAa $}B+  H${rC1 BN2 NPRUnCЊ1Ň,2" ނʃ b9HH21Zr!B(Hpr3HV9(>`/AP^hTYDG)*7?[!BGh@*bAa $}B+  X$R>`LVrtD!ʧd>@̇Rc+Xe&KEZA9r~'dUA@*7?[!BGTaCЊ1Ň,2" ނʃ b9HH2T8rCB!O|Dv> sPt3 vo ڽ7՝) P*hTYDG9uh-GGB|J#!ThŘbAﲏ4"~;{MTЊ-<!;$cOs*7?[!BGTaCЊ1EF&ZVzMw{uwޡ} ZA9r~'dpNZr!B(*| Z1hUP8ooSJނ# rS! ڂB X$R>=ΩC+f!B(*| Z1߼ U7Zv0w4ؽH#z-TO e({ *̂,2;X6!B(**DG9uh-GGB|J#!ThŘbIaӭ~;NQmW;U,} Z]^*,Dn&B^@`9HH2T8rCB!O|Dv> sPVhMS`Y1)_vJ?2ZA;X6!B(**DGlǎdh-GGPyy!L{Zϵd> ;HV9( Ph+xʗҏV|oAAM!ʽ ? r~K~wS#{UϟnFwx_ӴuS3Vy6E 7t;i۩iԴ]bvYϥ6eI&zPN4`ƀ>{u-kб#ZuBkݲZUV-#5gIyp,GǺh^TWO)3HeZPLTumc?땃fK<{uJY<̅Sg/e7uխ2OU}s x~̯g̠7.C+MV)XzSw]HڵQf\7"Xiavzinjz)e˖wmG~Hk n[X}]6Dž6nm.D ysWQe*~Hi]3=ީ]`ݠr7Onݩ]+ |e5T>fڕϦll,U?}m~]}Mc**ƻ)Ӵ2w>)sxTw_//χWFS=~^> |mk ̪^^2{LNiJG*=m)lŻR_VʴjL;O}k6?` A~z}{Nvt~~W+uaS&y/NSIܧ7oT_]«w<{.M(}}+|x^v=Og(%Jd=}^Gv>sL̓HϢ(*}^k4Dv,'kϕ0J֮gcl3s-zfe7DnkG{ĺż6;]W[, 3Ns٦j)` bAyUiqJNB TYO+lB!{~,vܿsB/ÿJA][ Xjs w7r?ͪOW%/IZ_$oںԷ ^~1/.5 5p{%󏍵nvmzW+L7켯-" ^YȱvьtٻjoXeU 7 $e:m]uNaS3K_XJxZ,{mtꋽr={%[Nmߋٙɱ%K5m[W^ɓϟ? oGuzvMc2}{e8[~:!}\lnsl8p3SǕy~c/κ^~:~|TYHCnN+=o\'s9b80{vk&\_TAҎxu쏰6ɪ]vr/n<_׋Lհ*a0ltM(c9ϕ0Jgcɹ}\+כȤ/dLv}c9mvMO۹\\DTZ1格Ѫ4ƸVa'@ъk*,DM!ʽ ? r~OmȊL(@ N' N?q(.v%ȠUVor$͹oN,-Or{ꍫz8f 6SX_%竝4A5Xگךۚr}RY6ej3KŻ'9̵vnl~{'erlRMIj>|7>@FUUU+1I5'ʌ ynT}Xj*ȢMtЭ-Bi>dо|y6,^Y&|yoxo%߿<ݿY +mL&qΗwƕ|[ 0}#zx~f| ;JGD,xԯoo&/5S8[grop\Kf71|QtAg&:Q˂el$k/L_x6\ai.md`(AaCj@+7Zת4(4Zq@AB:X6!B(**=Y(%LW#{O/ MHPSY>aCN>$VֱODŽ鈩GڒU#$0D|t hwmA$WtI<֎;ʼ`-.b be ϗ`vuMhf 'pZ[|'x/LqӨ_GWO{v,vLN>j H=4~ q㲗~ٻKO:6^$A'FoMl?f֢߮OߺYO˼@y\ŖgyvuM5 愕,Fm+Ylݺ+YāSwìkq?O<AuW ^^6]ulk/U_3i3ai.ODY3V)V1Ƶ* ; V\+Pyd!rrZ?~cƌDZ«ϗ\62kk5&{$+:,ےzqӰq͔wpʂA`-vna,V~ fm%'V&ede &޿.!7~tU' [V@o#k9()׆5|V-NQo<}Htx68*N:'(} x}1djfw[UUubBdmS+_լYqzϨ<G4h?> QmAu89x~p/oe2%_MŤLZ}U){t/1 5p"= v3E%/UĨKG{u߬K[_?@N/טVZO6 J~ϣz%]9C$ =ohz+^ªOjW_ K~xw\V%KcWJzvHz^:rڑ 4B.ㅱֻvV±M'z#V>bmeEcKjVn]yU .|pVEr)ZϮirtL&yxS?x#r_|fڶTX˕\; 9r~O$B7d?s_.rVO)ZɗV~ҬtYJpb {řWLZ_ڑn 1fՍ1[7'V֢] `tKql\Šc?]*NN{䔏dN0I)A"t{R.: \W5mˊ"K_\j&0SYBӼmsiN`ev꼓 1$ʱJ5[W^ՏGej,4h;-GdL;Pq[/S͗|[e>}\7DZMm3/Πܓ#;nL\v2?h?ez*ڂ,!^wMe5޶7^٭8z~U>X|qD1^R-%*0+ί^8ўt2R+g$m[eθrU,8%}Ȫ ~Mrl]:mt OVkwe^7~+(S}?בU0ũic\Vj q'Oa/o,͉ۼbW)UE=ןmw2쒾|p&홎ToͤͰ6j2!', PhUc\ҰhŵGn,~qG}[~xޮQNQ]ղQi7g^n?o5mQڹo۔Nn;RSLY?n8m6p2=C_s1o 8pۦtz4Iڶ5׾F}4ۭckvx3f<4mj2Ԩˮt̙gIΏז8/zռ}3j||IDATڽz_F;VjF{lrw٨c QPWNm::7]}#v}9^mӏ>F✼4<Ujr|6S.}s7.S{WKFy￷㕇חtAP.R~vyX^n0E\Gk5M6Ke$XNڕTeuݧ(gy;8wՄv*3 ﺴ4m>rs%ETZS^JckUvVMGOkg/3>}*YgΗdkqæl+Ε~oۇ1I;Uڨ3gYK9;8pDŶ)Qwtm>ñH`*}>,> J'Z [wıU΃8 1RO~6ErۆK̟3ٿvHޞ{ҫn] ㎑#|o3ĨUFMbqfSP |bԇ~Gf#9~ɗ&7ben 2%Q#őXЫ8lt;ѷn:V_|{.dԧ_ŷ_$x~u):~~6D헰\{e^_ecOt3o"qn[oG5Da$1$ҏaFϗ~6!dE9ِ.U XŔAbLjĞ<`u]APhOy(G*1Ui PhZ#7Aux߸OWuv{|[qnjsoY̜9[hĉZώR4lRI ^tab7$:0ﯛ,Be`8z%ū |y꼗afklLXcϦ:zL<Vn]y!K.8ro͛g6l`n0&:C7Œc1)Sg^,PM~v2RY@u,϶2VQs]ޒQi@ŧWH%,*Ъ4ƸVa'@ъk*Y^G}-.Ov3,p*OʤMywtjҤI`E^Zc ۦd'׫?_ߓX. #>jϓeԺFۖtohoL㵷9{4k^mcP}udۉ?f~|ڑ'x;8t8NlT#Y _wW*ۦMbٱ}l%5^kaEwa!-/O/{~Q1 Qplf#" +( x,mS,bI}pqzK.̲ז՟[|uV~6/_aկg<\L̩?9ժ픭c9an~i֕W 6:4i#G >?ƍ0Rc胇ٸ,~3KGe^OϟhX͔yVʎ\U)MYyekeztV'E&Bʬ 'AŒ(nlc3AQmH(mD~2kA{ҝ}mS,bIf HRYwϲVbkMû&-m!JCR sK+fѱX4c=Ew_g ?)] 8w d nSWO6aCDVʜ{JOrϷYKuVQr2UsѪ4ƸVa'@ъk*/"tv,pC7;ˋ$yFݵ9׮]bN3N۷'Op F=#fERЦd :s h8^Ygkm%E2.9L!* mlyom/un9H%8-ާ<+9OdW=E`t\{euQLJnI ּIж0c(71ҕG![0c-Ms/S*DGʭ+rtD-!*LSCFJckUvVXq/:lt{ΒnQ}ܠ <Ye%{gGWxՋw0@A/O.^`Y [ޟjE'd,s˃KrӝBYӷ?-Iڃ,ܟC6đܞsJ~G2#GˆtW*+c_v0{T})jDwk-l(8:>=Vn]y&#B!P>%n>oXЪ4ƸVa'@ъk*A1>ڨv;LJU߻5qN3UT't6l(hժUOk飨61CǽIJ .r T{uO=^ ҨۿvGV캫{UrybזC;qd<ʼnݱɨk? :gQ=ѣgl(J/lS~rͅ=ZzQ-93N/mӮT>d E(c#LWV[yR)JQLZs *DGʭ+rtD!ʧd>͇V Zת4(4Zq@ Ή;L6lgW~ˏ_48NF?5/DϿ>;߰SO<|+"ƏzvMEŶ)"lӲQtq 2hڨ[X<=xY#ݾ%n>oXЪ4x* ; V\+Pyd! O5nܸ_3-~_wG^UU0"cۄP ԎZ{sO~f! #@I|${*/rCB!O|Dr3(*1^JNB TYOu 'L6lاs߆ ֭[ y[>?S_4h[UUU'&Nf6!Tz~ѻ6ګQ/fǟuQiŗ_ߨG72{6ĮWW1PX%ľN"#Sn~i-GGB|J#*| h98@Ui*UvV Aԕ*uˡC6l ^{sGSpfI_mBgxeۮJKokaղQmuF}]b J1&;$cO[W^B!O|Dr3(rxU jG+<+wkXlRWmB!PU9T;$cOUn~(B!)p!|UnłVYZq@1_dQ S6!B(**DG*7?[!BGT@*7spbA+, V(lB!{~N"#Sn~i-GGB|J#*| h98@AV\+PyWYTM!ʽ ? P!Vn]y&A[B! 0!|;>UbCZVq/aK]ʶ !BWPoH /%CP!B((hE@9 TA`iۄBܫsP7$ʗU~(ڃB!oZoP"Њk* bpmB!PU9T(K*?A!ʷ7(ZdPNhŵ|EE18X6!B(** dhʃ B[ŀV('Zc"Ȣ,mB!{~J dhʃ B[ŀV('Zʃ Q,mB!{~Ppr|sp(o"Њk*,DwmB!PU9@ъVъ rB+<Ҷ !BWG+~('Z1F+, V,Ȣъ987^ŀ`AA@8{ *, 4Btܺ G+PdP^hTY@hM$$\ 8ZC9@>ъ987d!dP;ZAˑD"g5x@+~('Z1 B+ Bc9rHLr/hD+PdP^hTY@h,GnTI hʛA rB+ Bc9rHLrVA@ЊV!Њ-<X$8\P2r|sp(o(/{ *, 4#7D*$׫"ъ987^Q" { 5ZAˑD"gUdPh, ނʃ M"3(Fb AV|oAIwOV! ڂB!Tib9rHLr*,CyC@y[PyddqVRH'ayƲ !BR$sM"3(Fb`UAbA[PydeI;,X6!BUd.@vI$Rq&^AňV!Њ-<Ȳg,B!*E2 N Xܕ{:+57+TIWE@1sp(o(/{ *,,`&BJ̅#.#w]bƯn4}5M[75o2-~v5lB!\Hv.PXuI?[m.}ڂ,7'VI&VyI7d4j`լر*R /նگך~zoיz뙉k&:<,"~9r܅Ǚ+^,zfyTXIi۞rSA}.暷I(Ky:b*V~L}ZYdӶl(>bdqd  ZAY w:WX6!BUd. ;({,G&oeOYHPHtsNdg 44^]f*NrVh{ӰqCӱOG̈́U̵^[mlkV~4ߩ$ YBIEc{'7rI(t;:3I>Oden=, hʛR { 5ZAY w:WX6!BUd. ;({,GAl_`ND' XtK;b[UV:凞rp'A=f_'u[bvwmѸycsYGau͜{Xڣ^[fի3Iw9c8e{k7-vnVKaJ|f3c~lk,RsCæauQN@B8=`jץ]lLmK:d!:,]%KΓ5zӌ[>YE;{ړ#.^YZ;oV~ڿ?I_JPl'0!N 8c`YBڗq+H]iKVꆱ-J{wTcS%ZurV9Zc @+ "˒QW &BJ̅Tabeȝ,-ےP&j{,B$@=t2 -~v5N`M'ǶsߜXT%$_}6j jNA~&7?076r^O.7{9hA6^#'"iȹCb$_Rt)X6gSceƒ?I˪$J^hآa#N;" 8qʼnfc>-V'f[2d07Jr+CyC@y[PydeIpseB!PHBs1rNd+W7~rRý p)FM;VZ?UE63O. L\=1a0Dʼj~0V7p/fOdѾk9XY>R[ƶiظU%I ~

c& Tߏ6xeY=t"cnyjپeBl^CJ+Z1 8"XPddނʣ,>xzt=w̹wff=N}MA׬>N(pf޽_76zVŮLP1r҅f=1 4wfnIbg&}ة Ŷ`lkWGS>tT&1F[B;eY ۴ݽUVt֌Bˑ;UZJ3!mǶ q2kzAcv]43`AAvJ(Kl_FVӪOW9PӼ8ӧԖd5 Vߠh| /%xDl *ҝte ̹oN,d!B]+`8jxy-ڵHO:2i/[0+Yxj h!Њ-b{'ܰsC[EM Nzqży̆ ֭[SO=ӧ5xwNwsX6҈ H1Kz kvfGc/Ʊ:BQU6fBe6gOeYpǶ!ȢTJm8w Xɒr PZъ-<>b];_\-dfC+Yr0goz,7%|e._ߚwoF56UO|kS1W];L!ٰ3~ıNQ(*VV!2'2, ȢU*m8w XɒYH& :EZ7:5+?^iV},y~I͏K_ Xcc=b#0WreҎdѴuSөo'3fՍ1[Nɜ-+0!er:$@瀞Ίztp$ۄI{}gITA;S޲}K)G^2* TUS9ꍫO\3 V6%QFNrťNЄgY9%h~ *2?ל3ccQԾk{gʸ[j5W,N͝Uܞ(*մύC+w.dqx  ZGN,+$xsNgj_٧{Lf-MM{8y'~j?[_0kج{z+We[^3927a.x}?d63s+LV[aTcszʔ)ǍgmfU$C=1;=Mt7ldfnL\c5}׾_oW*˷M㦛9va$džSf?uË1r9j;defMӖ;&-ۚ7n{%֞߃~Ӏӯ{q+\xIZ 2n8'`BU1%c=󀳣*IT  {%tҶdC& 0HQ)QDxݙ;sf7;9gJf*f:"8+ 鏝x_5 _<ԉ{.ꬨ ?~~EBzV'r:OV=5eWm^gfT@50wWv;l' -WWB{75?d!l[eTvcz6v@}xw;~qxݪ˯>$bc' 2Aj[nݗ>[685T#Q5u0:P<ЍSk'\Z\Ic-In3nvÛN>y[c:rn5=jǙ5] u6s'|smwSWpxg?z1 N9igV'0Τ_VZ22)S( sfwFj/˯(Wm帕ilǎz=Q*vQik栚7ϡVd儫ӊ:>erҢCo*u՟Z"?!Nsqg¦KW?ԕ'pK ̌T#C8mdrb4j(eV䩊M,sԭ_Wf=K@3J 9X@<[nUJe P䋓Q&YO,c[PV' 58p^= 3s:-cn^5jF^-9ˉ?+'޵WJxE679[\o>|ȕ}*>,b,VXΝ;atW_UNYars᳷J齟Hmmct靴lZ 6_Ue s 8q]ҏ\Ic)~饿N S+o9YQ^os;Ǟ{=nxXj])ŭC:r! )ǠS_̶cd<{n/'̱3&Q5CNHqNÎeyS9َM~Z}1Iꤎ*#"uIIR]}_HWm鸻{cW<{? R:N:&kpJ܄qiAWVP燍7+p v|7]OWɢi%cWa2/0=;T,Wy~l!7[<7^ Je PL51@a`[Pe^- eӼ˩߱e;^CW>ƫaj˟pٌZY9鎿7ȹ{ޗszO~]]w>_Y_!||2o :J{jEVZvf *[gsWߺ^Ԓ{>N)1@1&OL!+[{%sι3{^[:a U^#m:JVdιwjt]~^޼l {5 '\gKйOtQc(2I'7ݪ=+Wx0[9UٖQ{1F1sݸޣgm]iи5+P_Q [IfR1=M9rzsǕok)ll>ᔷŸUItYRUWy~l!7[<7^ Je PdPXX>,}ٲI ̩縍^C ;%ly~ 7,n|^Ns|w"_էrɎ>~%cC䛻\M=\nx\hYRy._;w(/ܲ;*:Ү/eǞZ`?[6-eơ:k5N]~œ)e;=`4W;X>h7|S>SnN' (ᄫ~3ժ~7Hc8bJ ozfjlyNhԊ/'X!{r2;2굦22)Stt4}U#ry^n˰ʴ{cW0dt0 {-M~L< UdS%2Nb;P jPG,|__B' 2`̖wRtۿd%<(%|#r#k]g@B>;… /?{ 8SC=IN0aͣB!TR'} -?Uj$`MWÇ8bF{NexnA2}֯ ?+mV`͛sEnɓ's_JmB|O&}ݟ~Ct5!,jVm@50fcc׫C @Lt=|' 2`̆*c|߫-ZaoΝ{n۱B׮]׉'>~)-]9uB! MoxYԩ+K/EiݥL;@#3ϼuhٱYrǁzTT#Cn66xnz>D7'又c Na -}X&ʸ?YJǤ^mYVSM>)Sؗɓ'}8"h"P'Bд7ҠIsiԼX|lE ݁֐\ O -y] !7[<7^ Jed1)X>,moDv0fC15:P'B"}25.P jPG,h  -}X;Ke)MnlH;nFYcj{uN!BE.dj] rs XcU(lp(,,s j[.x}ܞ/*IP}L~ !BH߅L;<Cn66xnz>` G#bN -(dS*%~3xvig&`̆*cj{uN!BE.dj] q ~X&H8Y6zd܂BXm2/M|nʤ֦,}P}L~ !BH߅L;JCWPX&@>oNexnAR"-Z@Q"Nnwӥb>`̆*cj{uN!BE.dj] VB(2э&9G#bN -IwS"b9*/Tal&~z,}P}L~ !BH߅L;JCWPX&@>@aa[WXP*7%FbֱϕQnpX& i^B!Pm }#Lt=|bI@8,s b1%rF'?I"7D<>lB>fWB!T[Bfj%By#y,h 'Q' 0X EtuKeCb_ܰmʤ_uX& i5 !BH߅L;d2э|s,s :ClB>f uB!-w!SbYLt=|' 2<*8W.SlcJ\S,45e{x eVMB!TTbYL P11@a`[ ]Y q7dCiA!M =8Y GČ#M3exnAJdtBm2 +/'VxRT͸T4 B&T@\ -FiJ6hǦLp2U3l(U5>!BI2UcW揈8Y2(h ɢbx2$9)XVR"]_$tV&CFiCuc=@ xKd F{P,28YUJ?{ yBOۥ)1\i2{X0Gcq,G ,a[G{W(n6we&`ď-32Aa$ Df,s+38Y@z,|W_BdIq, vibK=wX^2%wH%VeqE"B*g_< Q}fpX=(l'@8,s6gJSסD%4݇|2{0D! {M[g' He0ڃf' }GzG@ ṕD ~6r)C<0   "B!Bx0Gc9$D ϭĔs 憕\I ej!BU!}x@ HcQժ. #5trڤ@Uvj៿'' He P8N1q,S2˔ wJ*۱j#  ? D"BU=#>@ H7d g89TɢSj!I+/@!$xܝ,7zb@ sT.5%/a K/O*X=@a0D! {yE@wP'i?,uIqd1Y*tIG8He0ڃq83peU,]LܛꆕWL\I2> A$B!PUH3 +FZdoJCKc,@x F{P,7d)X&ߞ[2/;iSTٰL{C !B*#~&޺& њ/o5Bf>R,I捤nzҬkk;l'd12v:^̰'V uJ:}40lOIQS\¨L]A &9GČ!&YO,c?S"RYlTQL=@%`B!T w_k hP^])n: `P!JǺ`uB! { g:CkTu"Xivxߕ#RVXE2iҤS iAFn5 ĩN擋e߃)uqᬔ:@gU[ϐ , , TsHz)+lNb-S*iJsD#- Q:S:r|n"3-e8:?nri#~izj)gid -*mZ?(e *%^z`JO?Lw빂 C}@0Ϧ^K:vߴ4_\H_c5jNl |p$?_.g+gҤYU*/?Jv`Pج213'EH2U&LLsKdAB;7@a>`P!JǺ`uJ$/<gń>͚5<-.UeKG{`qQ?Y?[4͇}fEms'DUBʌ8+s' <"`ğMS~kڥcōn|mg ĉn ŜǷ˘ʷiun9=[3|#/%ORjIOWXKiy\&Ι*o\ |L2@2@|Lq0Bc=z>Huʤw^lG5UCzoFʀJ&W:;ZH@Se]O}u2wv6DTUBʎ8+s6w &igHN-܏|H0꒹nɠy6nݛSxduԳ;;PRV8mR ~ڎSĿqX?:ϯʜ^v\Gª0,LP; (lV 'NٱLV6%2ٳ^Xa%N,fܧ0Bc݄Y]IaݳP.(yat sG&pl}u+5]'(LߕqVe(T޷5 璮Tuו察ƭ7I 'q/?EL: kԦYŲNKp3p-o+gsZp 2 ;M?`Qvت+6‰[eN~Gy $+ߪX1#Yv |L2@2Y&Vv`Pج11w'&YO,cVzXnHc7D^6r_$=69ڜ+ 02FZtp=@2)1GonJ5 vH9Sҧ]b﨑m#cMajhԱmRc~]_ek>5U?=6<հ[ȫ/$^ 882ՉW?7؆W46vҩ1AlV5uGAn2Jl똃mc뮚¶w&ۆ}op=be)c礆NQܲtrT7qvA8Y5^Fjt|GW3Ik~/fix`@ÎdLU^y汥njLNZ7g7hP׮{k?8NQڰ2gR͡-aUW:n?ԤeKf߯>̘{0cK 4E~ran~RP۹BWz鵫VUW\ɤ8\KN]viOnj;>:xhؾ { N¶_|QkI]h7 Ga}L2@26nGuR\I:8teL *_x\g磎>bx3u+'xc[vLvýQڰ2gSV)ʹ:?TCk'+;4Pv1-]ȟ{>Y c:xs?õ+VxCni(}6]U<#mc{\*/&s "#~T=ͲKQ}f}L2V%)B(PX&9Yoc?"f8N# i{4IcJ\OVsD#- Q:MEN7 d_9׫v>q&=gaJ򼆽_,i(~u\qirp\gvx {l_ ~Ђ֫WǕ7xHIG f5継nxT'(䭓eKq*ce:n~WⵗRW˨sO-s밭vƁ{8N{' +S~6EoGatr| tԙIegQ_JFWp +3h:Ҿ;uljrӼy7Ag=Fq%{{ǩ_&=0l|Q}f}L2V%"F޿742 LrwGČ&YO,vQ,M|bJ5Z,ێ -}X&i!Tұn}.u$e' J0ҹYJ,ߚ.q&W321~1n5Y`_0Rf biA0vN?>, gʼnIڧ*,6 :Y?LN{e6ЮlgR~We Ueƿj¾nۇ6mZ?O3c,7ϓ iÔMQQk9v|^ttJ>]_v1׫li9Y:JNXdq%lNQ&=0L:=sWrj="0G 2ɴ[ B(/ L< &9ƻ#bdPPM =J7v9ؓ*D#- Q:MEN2S'oo?=M3Fz4;.q&s:}9nk_NGgk.߻:A5}>2kzϔ4^auZMÍ=#O})yym3)[Du/^cڜr "JG:y?YܸʴXUBU g5qrXAcyzoGǝpD6L)ʹ:a{x{L^צuCG1׫li9Y<KܰNį*UdQSKI9sŧl7aaE=/ VQU^NM@DF(>3>Y&V2B忡 ?@\'CM8Y@P"]zR7TzRE2`Kj[f3tB7goVvuWM&51*fVc4א?6We,:u-豝ҡrb=bX@Yα>`)2ovo۸R Z4o i# J1xv5Dj5i*5uTs$齪_t\?@^cE9:?m6LQU~wO˲'T4~=߽ iuʤl4k{˚?faj@cv[XoG6v5|vNw&ivY\ [ k[oW ]1ͣc ?K#Sʻӥn:vw&ekN:5~mr(~y|Яok72?V{pu>{tԍWK.<*%Mc;= aeϤ\r&E9W}w8y_Q:xQeKLѽ[ 7+ޥBUSҔIp?oY_>}Iؾ { NâWr9_F(>3>Y&V2B忡 ?@\hc<,5d7jS,3J]{3%r{BS|)܂ڇeV6jԈrGϬg=l`[;N`77޿똃]#a甫3k 576ICP5a# k+ 3a{q#'s?_\#L^7/<>7%2N*j;cfeت;P޴?.[_97I}vnΝKo:jFuPG:?螅2J:!-_V^'US{?~ɓ[kݻwc=&<]vٞs>dYk?:^=Bb jx8EQqcCp!o WmxF-[4DzNER2{ou0uzY#LSVBj-{}h^.8gLWq?yD;zN[5tlQesmǢSEit>zZ_'\ Xߛ$ok'3- ը ߵ^Q?R[nWE˗bʶ6EL}EoQƿW'o ӱ/vwg>rOf8n%ڟ҆)?*ߎr]˙\.v*9וڸS1;2vzv9W~kW@cYWf:=,r- $5Om?LfZp 7aaE=mE.Ą?LpIߪ$`B %T'B2 LrwGČCL8Y@P";:/ fHO2`IjNG3]ӨiwF^upgjAzE7Χi*u,xU9רQ=B߽,53 IAcߢM]c:G,OtzSe큻fScSo:MFjL U%NQ9~<ꨣ5kwqXI7xem_2GNi }m0+a8:#mmf!w /5k_UlƾΣOV2)lmEzܻoi[F vK qWJ zuf}]ӪQlgWËa*5&n{ jꤦ\fzU0>ͣFQ?{LmcχhlBWv{N:ӽ7.ӱ5I1jԮ+;\~ɸ@='JV|e)̹z+ss~Nrs0Կޱ)>c{^gzluRzt 8K8]K|KVpOoæz^Q*] #~oip,LUIp!Az2e6쏈 @,(D6E2 +MIu -}X&iew6j]Di™1YơJF{8V.X/W8,fVC'΀wJPǁ8GƤ\xTzSeM=yB,j5ԕFpXxٜ%Έ-g\r.Q9c}gΙ3={*i׮]Oń Ѭ0Qa~OeQ͈k)TW} xK̄gdZJ! wg,S=$x'Dg\Je)2iO Yj܂ڇeV&53F8)쏓ѝܼk ?"/*#n]i=RoY Kw,*i,Yp ٩IEg;S\ [:4cɕ8tسdsٴUL9thXUT^-Z6mǹVnDzjn*GN({p2|h@:µS{~(ʵjF\KyD[g&G8$oU0\F|޿L'@>cyą&9;#bNPs+ M\hJdV"%D\]O8`[PLq0JW_Xک6ozMdj)VJ6$?aj5MRҤ[Q֍ts,oSJ_0Mfeۉ L(J޶T珗=^ hk;EӪ29KM׃+Rl>~k u~Φ2Λ7ogYYY"3g.˲M EhB~hǕɁtVAnq;Il_VcПQkՌO}'?LpIߪ$`B pO|2 E&9;#bF_*TK2]a%)srµovtv|ɢ*9dɒwwK/>˲4#P']ovnj޼n^M^mO G{(쵌jF\K>yD[g&G8$oU0\F|޿L'@>cyX8NP{)Dָ2=aBܰX ܂ڇe~[(`N"B:폓]dRSIDAT sM!y,zd3+N]yWE-usi֬3=<{~|m4< :Y<Й3g~(ǸquF:!BU= "`ď-3#ei* .#P>H_& L<BI$S)69 +aO9WexnA2>0iͳya44{FO7=?@6wҳ5[r~:;[ #otN<*%͖2_=jL8J hҧw+y$7>W(jsiy%jX^Ӫ_9+`8QvxU~ΦC'M$>hĢEIJ4ܛnϞ=2nܸDN!BU!='1#~oip,LUIp!Az2e'1' %zwM}Ini; -}63QH+zXaxG6ݰ5c 93{)eR]/V!7_閫qQerDzps7ʹcU넔u;Y)uUƇW.E-C$y:Y%2WQ}f}L2V%|ۻzJ|%_/ Xzq$x;Dm\HHƘ9WglnXt]xX@FZFMԷ)iTśib7_m޼|NWpõS֯(3e^YYoV8GαU=H7w}L7MݺugV,>{kR2+'Rv]ql*իWs΀E]s5/[:/|گu MT]{##BMlP.сx^]:j#@y0,L,jP$~&$[_< v6z ]eMdےV8T#dئҲY]l[@|\9ZՓomk;\QU~sg,Pl313p )IDZaD=~Kwu CH+o ZF Z6tN m'%{7p5FlРNteen\7(9*#l8˭/>,WpO?ix sFIFsSo\zR2du=bX{w*93t폕(hժU,T@ U~hw 5 Ë<wiћgV],P(.$,<5k|Wm=0uDcI'2vO`7eY_e[R;RGLMQMmS~' M_~ ]|9swpȦDM8,"PBwv@X\ƵV!edEvH%*^qϦ+ As)׵R1V[Tn} AW:,`.SL9g֬Y#7eY?~ 3u,wZԑW~[>TF( inomҮ^ :Թ3?ˤyMrG ,j#ۤ)~\ЛP?WRfNn -0rIWth~ֵJ!mmW8mΩSޭ,YW~*5r~2h@[h_W5h٢mG+&Y+O-W}V{U3zJNMzljyځ- yx׼@4-y}?}:UtU ZGMz+_^'0*?3< ,={[1c;&LXb*ON2rBң{ګUu̽T[;\+QXmQy <_L̞=9s|vzZ-| .h폓nn;Wh-[vSoG]䒒v=qխ%ٮU=)]Jyp >ݞsQ&V0O|2?@qI$DtFĔݰm2$q\놁CH BuSM?S^TG\hY>0ak'*:U=_.'{C]nn=TgWNH&z\ڴn$[5z+ϯ -hJN9a 8=Nw[F6 WB6hղLw}@Z6r>[ɽϒR#ҭCRsu]:Z9t#lǢMK&e`rIort%~}[Njzm#V {nK=筎qNM*>@7urw{U颜W+ݘoPǡ{7}ej9ΊCNYY5ޕ´INtǞ: S9v:C?28GD:ygR~SUf8 a6J{V]1}ҕ暯)sxP$?鯱,돒~p $ .u?U?~Ӽ*m#WvK. yҰAڡ׹,q\~Av:^g$-S#6J9?!E [JhռL$Xz9FvYްڥ ދ;aܾmw'prǿ}ޅERR@B[b)ζC~Rȡ GRQ9䮈*EA. >&|lwv3so1I;eDu՗%#}ׁz#j޺ǟO_ڹ wi974S+Dc?zrD_Jݻvn]:ȖwS+Wu2oүWugW,Ae٤\T|MEezY\j~fym5y8^ ޡ}Skdݽ{x>Ҥai?JO`>Ǟ3;)&Ir)iV'ݶ-H14h_7gBUVyuNj!WMI3X`]ۮEo;л{(}: >džabJ0%#-Ujt r0$77Ww$=m+- {HoN=5O~|o𮶍KBaC{xyӼ:Y-Gs+mME>s:̧ɲ_iht>Jvuh6up09bޠ} Cuiߺ{iA^Hӂ"$Mn,y{zXjk;o88vxc矞{Wǥxdy,-RI-?"r{|ccYOKyi_핆mT\;i;/_(Hg͚u8u M \󯟎R \W,F-3w]y}]0zuY5MYtpѿR]r?:,mGFڋ!_ :7M>vP@̿}jq%{>/+lطo8@ 0ۯ.QQ#t )tz>~."/&'/.cT77`^,nm?:X^-П#|=8ʻZCt-Z~Wm#t6 t Aئd AD[V=N(i9`byC#S{]z$Kj~fymiхot}Z}k4Sw&*G+w٪k/Mk<iҰ^jOh>˞3#GP!/n /:YbJ -[@AZTcF}͚5|w/Xݙ3gq>}sٳO|9̺JlSsṻ+wݰfjوN_W8M=p9vBОv oYw%>\ oSM  ?o|TⱢ {h) i:WG_v]=hbwm㚫Ďs|v1AiWc}hej׮Xm] uE"HcEYIs}Ttyco}}We8YW[ߥykIr,۬ǫ׊>j5iض9ڞc9C^owk\x8@zKE7lwe] 8wwrū!hnX9(\V Ӛ*!aTDA$:=h?7v {] H^F3e?8kP>.:xدm:}~~' ׭zEh@v"jC0_p :h_iklat~迃v2Fu]Fq_ap|=X߳Wx̯ŗ铸ZI =lՒ\ֵ !v~mr("ZE_#M1Pu T[k ]0v.' "D|Ȱ YGkq - -B1M{b#O<\:bx|@=;=kbٹX0o#`:"jQ@p`zm#bےvƶ/x$A{e.9} }U>ۡ$gSc^UĞX~iZk?"n]F4?lM#M?ƊD8F+{rf)wwsY)Y&3 y H#1HjuӾT|"9!{߳gv)~vvwo7ul0]?@:R8{'&^x\ⱢTqشC,Ֆϊ.:Yؗ豶c~G T}סv/~9VM^6:Z^GώM8YW[ߥҒ-kz,;,Kk~̠j5iؖ=@&)SOx8N"s?nXobrm`z-$X6O8^Aeҿf݊ߎ?Omb}Y4"Woȷkyt;NG~s]ߜMmM19c3W8MuG,F cLjt>F[W Ȳz }nAt7֙vƶ/x*]eoKSn7YBSϻrBlcWO+DӵXZڿ4-|miO9Z4qWCGe利GD;eTQN1~nO, kM\?+(H\hEH5Fim*gt0+^7rm7h":`{o@iZMaTyyD~XMiO{0sEY[UNZ #FDۧ+ͱj*M-ۚE;',4i\.<`9~޽cؖzlOyiJmjd}_םfK%[cooSBof=^4Z)-9>֥i윢Io4lG{AJ3fWXv-f+mw!'D<amA[ Jt~+=]GFuL|-`8g^AݦVۉ^@sa}r*X0Xd1nL" exc9i~_6Ǫ :p?CJm=8?Zd5,Z?\>zev٪\s@UW8x%;EK&~k+KEe%G4'͕,5̫*c迨nw'F/ g׼W y H#1HjuӾT}y+,l8rX_y}i, IsE;Y&~k+KE& LG O8vxW{Blh\dO@˗MA /ܟ"s!?y H#1Hju%MLtwڃ[8lova{Ě{mm:%۷:>xݸthvvt_kAگ|RLSd>ӣN׈}aWV[<~|N5F9`V::|ږuRij٬E A{t@-KMPd4׷GKd svc"m?ij>NKW[ߥykII{,~sǶ%ǫJS4)Y^푆mhs4p4z˗/OP{qozM \n*C%Fu_PN1x? 2 NA{l5=.śNB%= t /T{dW[m_,55&T뜼C7}cS#lBwQ]4un^@=ū5}3v+!}EsƊ4o;һJ0MK|H8>O7%IۿgkYh/G4?1ϫ?J/W;rfX{ZJlFS#6BEAnugD9Fb!&Kbʙ}7@UWkovu[ƍ/_;cLڳ>n_X(cH rFm~K&c/_9#_~0 :/?޿r v]i[G 8Ɨϟ>KWcXJ-LAIЮHr v}]h -d'P]4շg[ '..\oǎ5+]zlm-yHۏK6#d}_m~J-kr,ۖ^+Ar|JmS,H98N뮻nYfmݖ(h*ƒE[yDΧAɋ+˺_YRj4ZE`z5fo}7ѥKsP9g~EpNj@tZ`WǥRj;i'jLKTZWS甬Nö98Nz>{lyEw}ߛ4i`梃u9^5cxdӻAL-6X}JhKKwo@xAt e8[bzU G13{yWЫ:UW tvo6S8Θ ;yG,xuJ>(u&nGKf#u+zX_oP$|/x5:zlq'9尾aDSҫ8D qzxi#m|XΫW HLoDWK5^[MW^"Ȃ=}Hӿ4|M6#cy'PiCEuxG{B 5v.' g~8n qPy H#1Hju%M2bTRȺ~ns4p5XEDjN=iEsW3f&M:-X>\M#+)Ϭ0V/EtҶGy|5Wb:o^Gi a>^kOș! (xlS';m=\>EH5FI&Ra2iȺ~ns4pL#UbժU7Ϝ9e˖ɚ5kdڵ^angӦMкE 1pWЫ$V1,-{wXAzTH:JG 4 TQVXy3۹ĞZLQU{#_y H#1Hju%M$ycM ??xd/y#,NKHkG%/ u{PI4ϫ?֬Y'| ޝ9sǑӧsH XgOJKeSS' 1rHV'C+9Fb!&Kb!BZ#{is*Rb =!@%8sLy$.BH%D߿Lm1_T|l7P?b{ RQ¶LT pZB5m)1 B!5g="%R*_LS*˄R /Ӿ@[p ne7PPdq=-SO͉#l+HY&[@K-%DB!F H 'Tt9s__'2o 򾛉a[,qsPd. oy B!=ä" )/&݇p)`eBiO-8vڍ9Y`Oh5Y]"\zVs -@~$DB!F H 'Tt9; B)G}mWlƒϮa c환Vl~쮱}=f@b;Z ҥLž4_ϫ?[SBH=?/nޱs_,e)Nvt5d\@5r -@ٟ#B!VЌ@̑:_| ԖYS 0R}ksL VB*%qL۽1_T+ݼe7PwO1w;xSVJgM[X?9 !BZ+hFb }[g],|W\mqn魑rYb:t(>V/͇pi*BH1iO9vۍR1o.2kTS'Gm/2Ԟ-@ٟ#B!VЌ@z;o92VÛm?o'FN=HMZQ_m_}9,f=yrlo>{*<&1=4YLKocv߰S{r -סF潶21T濹J#0+6ܷblzh8M1!JIcv@ks m7vLF~ YZ<-lpHB!2H sΗՋΫB~sR8]oxW1s{LA w'H-xjǗTA/}Ej:zCMerstzb۳nKciIE]!YȐcGQ~3;rƢW>x>NY*D Il6(! ۘAµRichm6S'z)Don 퀰mttů[2pb!eB?Pz +_cw…ˎgO-pXcD5Ud1EaTDrZ ޒmi,-)hA?vIJNNˆS7 i{+h8fI:Vd1r9vٍeH::9, a[Aw^C!D_8hDb }vA@;y8:gWh2=GMkO)we].[nu2j:$Tږoۖ2Ჱ+Y[o Ž;>tRW+VeC9[&rdx% w,kW H1r9_VDWx{m7W,>}JEstZ{pH /-1?MQ.34B %vN=u#`&ZdTwlLMtMmi"#coNv6vngƣ{rq /un^laɞz9DR%l+Rj.U{pH o*nzd cβOB8adl98U󘰭K)XРC#Whم^E4~e/o--1\.-5lZL7.Ydz {v.Hs% } 4}͟f$f7ˆa[Av|xD2njZ<Þ 1@#{#>X6zX8}gN w+trn64Wd1efˡk_֪rNlzKx oq'8iD,+dopڈcˎ3ev}!\fOd#9om{sLނҦh=k'PiCEuƄ#Kd)^-flo]5-9hDb }S9H!Z0#{][&Φ,z4Tctb{n>(\ߞ֒mi,-)?+ i_37lcG$7E^m#:msCaf𮣼y,!ix<,|O8v?ڍf2Kdxx^b 9NmKeY,3 by 4"1;qR8cM/0|aؔMö.!!G~ 9d'4p׮voD?/kͼشb;H" -`Z2f2GQ.F}z#]z[DtN=Ȕ]BjѩwWxFc3N+m" (4%sl]"P?%D6*VJ簭k,@c8oF$7-4#fMLrHؽW4ɡ;o?:\&^a'5ڮ҂n 4v-9̰/ne[e)]c=fvAktB!,!NbׅvurʂVyE/}6$];J>]e.{f=<| :iXEP>k'PiCE5*HdjS/{m~>|f 1@#+-?sW2csW۾Q;%F\Fclx,u,`?T.Q %M-C nflΓO 1@#+1zu[N=4yUqs 1ȓDQBtHOHh1Y@99~\ ς<ϲ\yn̩ݜg7q -$QPm7G{@>%R*_ EPNa*ch+r:++X}nsnη8y(J  )/" ('01_R仦N6 roFj k>_"$R*_ rr PCN&z+Vnv ۢTn[I(n􄔊`?T.oK7S'iV[!H:+_Y*z5JKF1@$h|HOHh11 Cr wR' 'LA>3m 5GJ~e{̓vcN2@c8o'j>>_"$R*_ rr PCn,esDmu2$Ɯzv#p OE v}4|@EH 'Tr9[=VJY*ۛ 򼛻Z\Q/[ߓnڍ9+F1@$JݾdqҵwW{t P IV1U.N6/]61r6&y}vcNJ@c8o'R½.=$gn71n T(C~B 8~\V9AMAvEfnsM7Wٍby Nyp}SMI,dUreE]۞,:h=?Jo^^n6y4LFm24lCKmvcNF1@$]ebbՋdbӾdyeqö; w;z-zK>_KS7}Sg65<Pj @q PCm{Z8Q'ssTV' nnson8y(J(uӫIA"Ʀ-~a8F>հOn7ln{ HLO{kXEdr9[~ZLQJ7Hj͇݉rxMvcN@c8o'R7"s^9'6za^"hoO{ʼ%5<Edr9 S/eWTXEBƜzwF1@596KE nv!ESE|pI8mnccn<&loȢn A8~\z¶\\h?/ݘSq]@c8oP.:]PfR4UdF1'PiCͦ G)W(n&FB~!sJ Bg7q -1yvYÿ$QPT!ES֥5GȌ3f;:1-1( 01I'S/W:2SxP ^un̩?n[cgE nMR45TϜk e‚ r_VۢےYcB1ߢlg;f=\,_1@c8oP.~M%4vd+cgp^jڷ>yW )tZѩk'FVJڻ\<6My65<6Edp 2ED7o}d.T7\c7ԋnn8f}>oP^~M%4v;la" ^jiO]n~z;J]dåpovˬzqUbA8*c֗[ަ^NtCN̅7Wڍ9F1Y_[=d?&pӫ\~W =ӳt" 2qLA@^8ZolUN %7ջnsnn8Z[?d?&p+7}\YnC8&  /S-^6bx ǹɰm n.sn~h7qLd?&r[rW%ڳt EZQHB*5i8ƟW1HOSf +7?\l7nn8[Td?&h(ЊDʌ_CpL%%2˔rWjێnGn.s97ٍbS -*cgE v}4YhETJdcdsh"P|`VJGJ1dBx(c7߰sY7w؍b1ϳj>,uB*%]cB1we -PIWP{>ާnεs7n~d7qLZ~M%TMPd%R)kw}iR:22_ynva[Ls3*nζs7wٍb-~M%TMPd%R)Jc})w]$͉#_<_,B7lZG 9{F1)y cgE v}4YhETJtDwLA@^8ob 5ɒMRQ}|:aOȩg7qLk@1ϳj>t@*TJtDwLA@^8f]J^ ^QERSW 9v#Ǭy DQBtMχ uB*%M?;&  /wO0KeS{b 򰩗etsl h/Zϧrq7?8߻(d?&h}>$R)k1!Fd=9 ۹'v#3+ 9 7?8߻(d?&h}>$R)k1!k0&(bˉvW,>qS[ךEҩ 0ka=!~a@c׵1ϳj>t@*TJtDwLAikEO7Φ 'y+/ЫY[lZ'n~f7qLy~-;&h}>$R)k1!8OHhiFLj XqE"@JynZv ړ!H%1p}}̱0;fLr:Y0~7,9O4#ϫ?[Sb}>0rF7Ff2uz~^*.[F.H׮F72msY, V%+lXZQ_o˽]F걘4ȝw&qӟ̛g_?#}>+ėό|ѣ?FmU.H}~Q#'۩" Fi 9u_ٍby rqLg*Vc?@v0xw/MjdD#GR\{ⴠwRY|mt`Os#i2_F &Zdѻcu?AF>n>Ns.%lj\qEq~{9<#Wlc]"-բG0ok>wYN }^) k@3~>؞Swyn[c8@%GAoxi}_!h+4벯}Ɗ,~bۜ9`}-0+PDm*mz_}uq)S?A} M7}\8MN?l }v|=7Pw̦c],D׭9b;Ed}Ӿȃ߿rN78γPvbߊӺu+ -(EzŇC#fY+$5,zOӫ<ӂ7,iy86ۯ2X5O;\l+tFyGh1ZegzHt{){uv,?}/;oOȩyn[c8@%=P)*g-s#; n'˷FEӶo{vmK>V4qv+xc]9L _ug4-}`D۴$X/hȂcڷy {BN9@c8oP.< e^*5P_TEiA4rIF oF * :_kYvimv 7ydWh,MƊ,d„bFJA[#sey0[lȂcڷyv#p\y.1pTZk旿,UlY|8~g3Ok" JW6sm2ͷEk'8#h"/]j8DSwLJy_l!?.TF4]:w-{Z.?0tfұKw/}|UKKmmIug1~՞W 1(pK \/ѫNl_#(+5toCt88׶h~;Y邇hEF/|8k^lW6#55M?XEbĈkUdɲmWKvZ=E;wt˴iޝ:u//2 ,^ܵ{<6q-{tGe9ku#梅=l^m4WdIe\MYz?"-Xiɶ6{ Yl${BN@c8oP.< eX>"k^x'yU#{u^׌le;1uNZvF~hB.AZm}_8?E:f=!nt 1(pK \o,W!G6^ z z5`z5Уɓ+)t_F`#;hRlEJ|{cunk{c9f22zXÆ){ٗ_6r1U%t첋_8)u\jB sqcѽx_Þ_Q1˲Z(3i<.K_7\m En +J[odԩoN8RѫRt3 MvGwU_T=] n|=Ls"4ˬq qPd߻rz78γPvTJR_|fϞ->`'?^{Ip%7XίIuWȀQ{-_+E*1؛ޔq.{$1O]~K׿5/5?&,}NzJmsF^ſt {~2&K/l3k2۾.=m&~gޙiQdo?;w>'?" `g=ޞSs@c8oP.< eNHDi SO=u3,r-4ilXob|S9wpRUw)+ V4*\@AT@Q5*/Db0McF5j1&yb4v,$~ssff,3}}_ >w.3]؀'];B2~?w9d^==邀-80]T#]@!qtn "KӢU/Zdye/.}瓂yzF*d#q4Hd_un!O1;yq}s,#R#[ 7O쾭߃}zC^zcA_d'W{}ؼqs0{D"24*h?`vN{ug-O]s\NPot 'c7?(Os6^R9 ~QG]rS@$^{틞ά8_WF\kO9hzC}oߦP"l`ގ &erbS|2Xf;f_w=Ԍh3洟Yv2B8wF11U$hxuB%r2݊%gm*w7nի$y衇>>|? +2e 'gnq=+Wdq-:˗bG{ln߹nӡnQ`ލ6[vDԵLîz*ެy !̻)ΈK.7h/AEwNf^3QnH@@x@~ʝ_@ NHDb2j(f͚X!=s"~}jZyd]$v`_v̻Iw躹}μS"vC]5}T%$ierq>G/;/)h=S<l{ⅫYOq\[jy*ٍb<-Su3U$hxuB%rdOX%U'Y['YH&_:6x^1tY|\Eu∥7E>ޯb,\]n Ydq¯ދO:,?>GuH^"$ׯN h|NP{Qn)}-_@iy@~ʝ_@ NHDTXɦVy嫚6mڇ+Wt (dҥz:^g|<^toL{_]PWXQD鶓o_yd_iq>[_d-حO0>o" I8rνS7kּ" Iv G$h}ꂠEI_1;Y^vGZʧv#Ok JSu3U$2>@sR-W%+9sMNEL2w̬84{L[ |Kf>z+|z=WI~[ l6߬yt nW~w:Fvz&)W>gܽ./~y7飖4*hkۡ/N?~J}u<"_Σr zŠoXr飺㦽S'Zϡ?[lnLA}yw{?kz8QW2Hr,ejq*F12 9 gI^/{ \'Z"ׯJsCxܸq;"|[y޻tȬ84AK] Fsm>xμEw;0]0Ъm{C W4m~2ROE6u}i{m65M.g_)~uܝ62otozOh޲Uz?7~t$:*wK^9 }Br!ǎٳrJC +7ձȑ#>|" R]@cPVvG$؍b 鷗p4rd/И]B3B0ڍbxȂT\Qɮ_12/ v2߻O>$:sM}{4nn[VOfӿ!wt:ݷ^>$ g,HE%+Bew[2' 9 5Z5+xM6NOSNN!7OW"R[)>^PdS( ~TḫKeXQ{ d"W|JYdq;ƶA"pvޱ[t/xnAnE۷b'1+8/Y?ʮlE3 @SLkwx|WPZż 5+ח"ZPӽ&vګ#)[;;|AchEa>sE-Či N^P,pB <ʣvc;N'O JV3?۴W $~A=Wu^/{2o~AeS N)hնUyzћEmke_/s>>Wl2a' s d AŲ?'MrXP@SYP.7Lf?0;]1h eN!G^3XG毩b7VÔy2EK1o8J?>\_:( B&b!n`(N@n2'5);RlHBHSJ3r'yEh !$YaDr(fOvcϓ]O?)s<'iڍl2?OȢ"#9^V3BBJw- g ?SNz),`qQBH=C%๘Aj̽|>;`wx"gwrqBHSTż gI^/U3BrE* BWoe'l2<E9* <&yT^t  g5!3T(7kwT]v(yV~w.g1!4O[@~ʝR^H8 !$W䞡}\ @y P|Q!v(yV~w.g1!4O[@~ʝR^H8 !$W䞡}\ @y`a2| 2Y{}mwZN׮yI9* <.Wg}n֤/>i5sedE|P|~7{?wv?v+6\7M+ct_׹"[αO4 J̊__+9Hs15T{vG^}o SD&v ~CK4șګS'dy^)S,\mFWn5[47};m/i>}wY%Do+Rda&J|4دc1r6dE=z~/Tv9+5~j}0螯Rͅ ռҘ&s|\J3P:*soawT-OS+^}uw%KySYdqQG=8qwygz`oqߌ=Çbq'2ެ/:lO2}O)n_$َ" y:@}_lJQdƜR\kȢRaB*;hAԕ|U[P=_UdAkUi!Jg2 W} )}"_r ɓcGsGI!Y۵n~ŀ6g|:YW\Ogݥ}wǙ/^ygݱm[Fqj/'^vTqG%Ďyaw;i[l79ylۏ}~Jfs̊I&G}ԛ-8Pvh# P3^kuyM[n#?Tz w9m[1cbgEO])d?O3[y7X#=[ vMW_O ۺw/|{z%5ב$9$<*3|Tܛw;*v|M)yj+ .H2;Dw.ToѵkO ^^PZfM7ME#SܹzѱMGwA~R/r߼+oML}Ἃ, f`j̀]kHу 0uqIdMɽW+gͲ l?fw\n(j+=hԿO6Rpϙs 2ߟGԿ3p I!E\7&ocmҗVC¶]/7w9W )x%ZIDATJo}Jхkt&+m5P ;=Ƿ],ŵBEld_oE*hA sPz:_x >ɠsy*Bt\{3]eP/+f@?cKGQz/d>DNPzT{O@[)gСf}ye=̀}B^w4짜y8}\8Jׂ/Tz͚p-Z(a\' Jnu}I~\3rmڄ}"G,:̽yQ6Sfg2'5) W)2)G ~x. s}vI_=W<}Rt)Wh2rI'=\[[ MS>y^@gqǙ=_Lj_H ba~w nug6a~܁a}}+2_^8aKA¤+sp`c?~eڿy:sJȠh L}a}}+uYHOyE[/?퀰o7󅟛|;>gy\' ةȂ&{m^yjsJ>P].kۈ?)y=ErV̗Z"R%z뮯kRy)&DPYN7 =z'Et~ڵk||sYopzoT+ݢiwQ?Pz 7|P(}'A_o'^?{~+T(x%~>TO΋h̙u{z-\†W^1Ӧ)=gNu}I -(VZ2GTܛ{F}>TȤ r%_Sy, \E%f!E/C"h?f~=~KmbԨQ˭ =CyN=:? ;x+#vˠg'~;{|mٟ^$l߶u%IŒy|}y +8rl7h+fS]ƎlS͓%s7׿sɖ,n'6%ي,?G+I~[o" s5dEw)M3!" ?T?_^S{¶.]螫-JHE /^tnJ?_v;/~?WӔ3~ 'D緓Pɖ$)OY|/F Q}=WeʔZhN?庾$,O?, =~SdI!fy:Dh>/;:) u)ȕ|]yn֬Yз6{8g]y\_mRdT֭ctх?`AEマI_F`d@/eд ΟowS$,/'ﺢm$2h[?IY|7 tm^" I!Z]ElKKu rQ7D z t7~|.~{bzx1/Sѣ ĉ22>g'Z ҶvGiswf>lԵ~HEaz/{^Ko,2χYH 9~Sdi2vGk>?dKW;/{\ ͖GyߣFz{ĈS׮cR`t9.+5, O" yۙ&Ku rQBH=CQdT>7\n7 >rUI&~?~kw>V0g1)]>B q{> EUd6no\!ɬT_ g5!3EP~vc"FGγ~w>C;ٳggʕJ앯7xW{#G|^8ɨ뎽G͕~Fr[VgY >}L[/ ۫""6ʍOJk$[eH=s ɓa*Zd!yxs-4@ &\{կSynnP/NA)}<,;{a̘1cǎ}akS2C#U}8b/cG`;SSGO;:'{;oUJyW^O_q+E2Gż\Q3BrE*@,(IvcXv#zq H#Y%ݹ\DŽ"?Uo1o15I^cE@ &\{F*ӭxY"xP*ۍUTV؍) gUApBHSTY09DM!"S @y=Svcx'v#zq H#Yno1DŽ"?U/EL$qqQBH=C%{?aTSynOv#zq H#Yno1DŽ"?U/EL$qqQ7tV_|Ȇv19n;O/PSzk۴QS'^S{xBS;tPuk7ܴ=/xZ뭫!vO<17LBcw;~- ?1C-0xcGP{<#Gmwh_-a})=[>3>^3UHXww.Jr˳E"^\O't.''_ɇd2_۶J1J_|qaX}5zYJtvJo(}J]Ηm]S*ݹ7Nފd֛CdOy+2KeXO!@8\.cBi *VפALkX@rQtϞJ_AR sy ) fP,+u+tH]Yǭ,ZHqߗȢ㐁2_OfYVEgxclC[Rz<3`__LEMJﶛ~7(+%Ru})v8{ݖ*Onnluי7"!Ec4RH1},~9r>Ʉ ff4+RdtׯWz&rMk/_5(d/0;kw.>R|}?̼r-k}RO>9l"J:|)3|TrXNQ@8\.cBi *VפALkX@rQ ώ\UJ_tQ|oAѾ|l~bCtb, =)=aԨW ۻu3m~?@_`A8}⣏L! X'.!c_ILIY$_)pGΫ=ȟΗmנ)JVh2'Sf^ Z)s_>jO^RyV~w.g1!4O[kRΠ|5kJw9EѬY8@k*}mrc~y?? ۋ)'7DE,ѢyR޾ҟn5n;wr~Ǘed|/bwTRynPrx@J}=zۗu`~I"o5\Fqٗ/wMoupy FmEX]s}*[J%z-Er^ R 1"z))쯜oG3ikքVrm%/׿~L{f? [meUŻ/[f ׭dO!̼}yQnK @ٿdCBJU5)gP~.FyLsebGk>&G,גtߨ9>?-:(XǙϞ_zӫcklfzNg ےmjJwY3POxbl =lSfPA"3OPbyZĞ{‡|O?{(ݮ?m_e{Ȁ+v]췟KۗysyR̀}پ̚eeq%ɱFM^_)o?Zd!mܣ%k<|xq+ b )‘䚔N0"̗k''q:$og %rK-3y{Jf$"{+_޾ܗGUTf7(^AC!M%j"5muρ=Mo~oKϿW΋#HŒ{f5=j%o_u,zsnѪEog!/E|٦ɇȞ2W7AvGY*oۍ@h" H_]wiOѴ`)~.#tӧ{{V o'Nwҗ{3_!W d}])2H3?W¿JS;߆E6sCdOy+`;L` ܘv#ȢmǶ}' ڋ-Fi7.1کN?IAż#f͚5KcZglSCע7};>jQ.'Skl/cR jBn\駞R/Qz:~o+W<ɜ$"{+_qܗ;Tڍ@,*G8sMbd݃; ڋ-F(vuS(g{jZ~{ز~{+}g/ŔYPAԄ|:(ݩҧ| Ot?,{;/)O2<ɇȞ2W7SrO Ȁuv#"b5E:mhkPbb͌m/,X>mԳSlv+>Y>SfI@rQBH=C%Sf^ +s_W4-^8sMb];v4q`:9h{[dL&y UDM!" CdOy+`ûDW6Rn S 6 ;qX`-7G.?Rw5]Щg'=bֈtрQ+-Gy2]3]Ѧ}{HtqIQ.N>EBյݥo@AA dO>.yw? mMs>.q3g떧ztʬ#cR jBgd"{+_Mc7VLiowep3mIg>4/dudJ9 !$W䞡}!TްiLlwep3mik 7H/z_$Q@rQBH=C%SBh7VZe~t;2To8*c}V/_i/tX%{ UDM!" CdO>P>n3S gj\*cR jBgd"{A}**q2?SY@y7ALkX%{ UDM!" CdO>P>rX%UgVvPf* gP>S5V@rQBH=C%SB[nӔ Ը&yUPAԄ+rP>DT6vc8L)j=P(sOgwTavPf* gP>S5V@#b@D! (>ܓO;He Ը&yUЈBHd!c2#*2?S gj\*c^ AETDJ$0 XP H1HE*YD dž""6 Uܙ=gw޻ݽ?ϙe *5YQZ)o{@T7Ij+,TIܐyMbBsȱ? w(DSP.Vk@*7 @Z1U>ew^bwsȱ*7 @Z1U>e76'́HyOl9S9!_H+g&;, c1U>oIb|&!'RSyOm9S@Z1U>e/'r9XL(1$q?d XL/!DLyO@Zlp2r,|h !#e 1$8y,0k4;)>bBF2b䋕N2; 7Nn3;)>bBF2buNfN b75B*{|L!mN1 'Nav9S|ńR#NI&^'@okr1!TkC$SLB|\ym'N b75B*{QQdD^9P`:yr,|X !G^EPHb{=hdv9S|ńR#" ($)Hsf'c1 B X !G^EPHW@]ȱF1/"|eM.&ymTY@!]́/'KN b*$z-]kƼ5B*{QQdd^w0 }N>7;oXitM"WbBFEIN6'_@T7I4Z&׌y+krqdd7`/S?;*:ޚx%ˑlݪtJׯx1'sSr(q/4f'c1 k=5d#yɚ\%?lWǺzzɲqK&NLEٞyթcsi&绬)yL,pbMVqG5FKQ}PQ$Hmv" ɚ5I((=RR^6x'{n}˚Kߘ򖕟󼓗4/f'c1 k=52 Y&'og,7e9/M7)j=Vozg۶6d>RzƌcUzxwغUի2i_G{ /+z{⥸C8Es.?TE7*}YJ^syE+}ADwqw\_rMkWo/+8elؠYHdr Rdr}4Y~O)^{iT,ZW@!*_}8M&w()Y?/}c[V~*&'7h9PLE|ÚDOkrͨ>opT$#G/0oNޖ;cӦ~y!w;3m) G&KfA}~&n'YdBܽO֛8QkI:21=S1/||/_uxYnU}7'p9x|s]3MNl[0Nfc1o󟽻|]QΫᄋ,v)qc3QK~#E62ٶY1e'g|:=;SHw8HO-Cr^+]wGyNe2-(k;YJe$$K?*^{-mw,͍~ b*$Bn~dc,=uT\3" P "eY?2;7ˉZ|,z\VQڹLs,r2!ftӦuwߤiJ))?Xc;˔v>-iEs%3LL*Jkw7 ; b*$dK e}wIfn|U>5^^w-o; `mv]躍jSeN:nn9e\W+y\,@A&'9y;\0o^?>tKxwM5J\Y7N?N:5Ɔ:,kGkL۟fIWQMv=j㖍-;t?.I" iqZn~ds}77ZZEyǥkrq$eYOdbz?~Y믕ǘ??q6oVG}?A|I;_M:>݄tI[odL_V 'r7Vr^'\dNc'jH_yW evHmI<Qa6O3ԩuwM]vQ_-Z(]wuK;-r.,-%K_( g)u9PNSޱ5)}̔5>UL']:-$ujWXyY{˹kVq5^vug?q֘ٚ\%c>JUd!؋uiڪn5e\W+y\>o\*[eyLGtINxI'9&啉ے3TMlBgחҗnw I2\޻C aY: 3-F;6'wW?jw8" ɻz[x~-xBW8ecHtcQt9iipT^^ƍJ׮^7nҞLϥd| "nm;RLEjMOղ?VMH/k5^WˣE)jUIk&%IWdqu*U"CEy6Pũ;EoI77ig}x/ͽ1M݁7[/?7hիM;yFRzPo9y YF&u/Zڵޝ}[N ^8K޺UU>xmÑߵjy]#-&ׯ|sL֓ 2yߏ;Cx_Æ޹HuLr\|p_&m5]EW+ݾ}_Χ[Y^&qgz.S=-ƲFRm+L]?05qcZp.~/{2[45t ݾoO֢Y-J=[.'wxkClk֑pq=xr )J H˔|gz.d? ÍN6H?tsȡއLYSZk馭SǞoz֙6LsI" )~]$SMqlV;l}ms`k>&a\FHqŀm^zwwbٶGE۞me{La'kQ igV}>o\LHE8!Ŕk/}0dYVz@Ŕ>T~fʚD'KhAo4<{:f_d!M~xƥ"<&N8Zgӯ9=uzٶGE!e:(kmy(bB" RRr/yN2; C1&ѧj wOW+ac JŌ-3tZwj8g9 cKUuS l焱!a_KF[ۗfsiE]t =xs|Fi%6PńYF|͛+bқ6R ) Nj/?9PLyC9XSF{7J;OGcM47 Yk֭Cuk\ڔuSG6׵5 ֫۸nUd!wmqT ko\L/ ̝k/KH1K_( ;,P(A@TJ9>U OoܢqXsc{GoWЗVbbztIY2F{_WQu~Zs姸˶,q\ZE]e.xsQ^d| 5B*{Q/ N&4rTQZIZxby֧zpoD-cZ=I 7~{cv'0>QX]E?AYdq'>Kc.˶5e\H;nQDž\l@A&BHe6*BPGy#́UCy35>U O엻G خ{˹AYp.B߭7m϶zzGt/XoMZ6 ֿb cb\00ϗ"pQEw5e\HrmfJm X !G^_@!h'N X5)Y^"GH=A!̭3umZ|U0&t/RpQV jzܲqVZzz "]-;4Ǧon/n!F- kW\>mE)w{1zꛬu5ܑܳbָٲy.?V1]fJm X !G^_@!8Ry٧lf'gIqP8˾螳{-4mo'sޭ%Geiu:5^WȘ߹EIF Ƈ:L8ޡnCs>G a(Eux ~>~Gso1Y湐6|`KL[6 kr1!TkK_(}9PV;5ڨFEr`s,-}˳]C}zgGi m@X !G^_@!^3 'w@&Zhuk+E>5)h}jk]zU.z2~NvP3(M kr1!TkK_(Õ](`˜5ی-3Q9Lk,'û|Jk,WmK1\3@&BHe6*B0Ofg{f'gI4Z&׌6P ńR#/}2;:94V^!¥@8^yv99s=!@9hB@hc;9tQ^!́";۟ ϙ )-A?"45>;@ۃP&8줪9PvU^ sefg"B'OE_\krrYD~r2d+́"*"Pp(": v,ڙE='sN+hfW0;̣N7;9kv|fv8$s[sHFw,N2Xgus@Eyf򎳡9 qI sV^E3s@7fUNH&R5*c'E2dʚxL!!g @I4Z>7fUNH&R5*ţ_\n9NVpY !2D^DȔ5F&׬ $yBT^xĔ$Mv˚xL!!g @Ih3u>Hת_Kwߞmv 2!'czrY R{( TxY&jrׅ:G5/ }y>݂s,j+K@iE'uoz`=sxlX3XJiFEVOY#k׮z꩛y"Y|ɷТ}C&ɧKD*UYf%o+W[I~}]oZtEy.wJ;k d''ۜE•܁NS1@!rF^Ȓ5=U" YޏHAh-nz^z맦kk7k7|nu: `qYoҊ,R/XϼGaS.?Z7]˶bKCu[wΨU> 0ؖYdq 4n]Gpφn7F&,9=xm1f5i+ُbuO>䍥w=Yw_)k|+^Xxq¹}[n:?cºuuݺuuztz-)Yl{B_}\lѣnذnР>Sg}f=Vi.H8H6>%6lrYgYE~>}z>{׽{;찄^F =x`ݷo_wuEE "rk_]xo/r3JJ0 Wޝ,v6*TsYB*gOUw s Q#$Lڌ3Xr!dI~2"_k0&wF|ʠ_& hgҲ)֨`Zk%YC^p '8Y|}n}j؋+-"< "'O^ ? c?0&'[˼l#?{bΜ9fEF8qX쩒MY%\PJ]N]Wl"{GHErH/ÓÅ $bYH} w1" R8sae7t ҥK1d41'o*L"pCZrgt-j܁¿ScȋCeߦw7M`B&f'cnY$k߹ cٜtۛenuR+HEai+~/#ҹs_z)"<ڵkX%\иqctR[ox@i&رσ̉~aϟs=B[k /t?wygn~:tQg}裏&~*%G(DUvr9PI4U{*s&BHe),hE3/: X6mcu+:MInf!E")co^A6~Rd1ؿ.fnф;~_l]z`ݬuYHG|Ia?on,uLW~nHͺ56|i8ʫ=JP@ gubV?+ӿYyI.^m֬Yc`\)Kq-B 6XCrWfƍӳgNKk׮ Ǝ>:~W￿}ǂYY檫GynҤnР[nä{L#J01'\`Tuw"xjZP2__M?2ꦷƳM>c [lzr}),hE3/: X톯n'u{/0} }-g˚,cYd1yd}p׃ujU#O3t"|l=`v<0aVZX!Ƥbk#" )ا>X%cܻ}uIxt8W+~K.۲ł ֕ǝ,ɹK,Y3);wn_Ej%Ɋ}Wx≠,E/۶m2D/^ؽ רQ?cDM\K?] '7"W=M i~\qOfQXKxM]ë߲ƳM>c Jr?޾D^E"nrʏ,] -$Y_"O~tt*bF-]}-B1GBZՂn<+aLZ"+'K4u?\d1QA]E?AYg=i9_w%yϤl???s̱ (27Y,{dS$t)xٯ`\h"?/"_m}AYPZJOG}|S$YR/}tn,>A߮]HkWPE&tLyOۜvRDvr Y\{w_/|,,$O.)x7}XxY1nόco(:\O/*sŚBgkI5#('Tmww&WY]p=`ᖮ"X"$au۞m"GOlY30B{Mg랳z&$7fYcnPf UJ}֓B^m 1G/wE⇝\007"ҝ32ݞ}= s,V1bS (2ɟb%%&ɧK⅟~IO ;S޴i;nb6m+"+^dc*tg̙ ;>*>)S~I'X}S4k,XVG~,@ZQs -6z} ߵ6Z)>aL.,^U;g#mƳi4d5Vds3.:\O/*sŚBgkLU5PԬ7` _9k~Ds}Όs&Y$k6Ls d]?r O56sL=a~yL/۔BjrGqƹ36ϰ!J+'Y" i}ܵW|n{n`)0בM;TwבO}WoR?V}1K[fο+][vhnX[_;ǓL=i lu:L[#ᮻ:SO?UD.?b%&'˖-[e]]v[d;wn׮]0Vz`{Idg}VﰃW$? /ݤI`yP!Uk6(H7o+WGqD?}tzjkc=|#$,^-8I8ҟj,>Harw~[;qװaCx7nܘc縴5S޲mN+N3;P" >oϖ,F߰^?'W7_M}_8~ֿW%lG?{;a_/?$ǽrzk{҆^σdRڄKnw=7ٯW6pf`wN؆#7~yn~w-K!£Kꦷc4/~x2)`b=oL|ϫlWzo#g>=3cט$k[[Y/YсlkS+3x/Ԫ7|]z_ny=WmWO65^52.>W9/4Zy6T^EzM4=Jkz^e?ҟjL3 +whڪ[lPNM!{wX0l*&w8vIW^սE[u߲)6!.w퉿=u{函:L8lu) 11ҝpx7q l# \{tM?3V1Errʚwmƚ$,R$ ˦JjN;[ny`[nZ^"2/bN:nݺZ뮻 mٲOR~,R)"\G/پ׮][p zԩ:-J8ҟj}xQG>vƍuϞ=sy)1dL򖕟?)=Bsy;f'+bNѝ.2AcYo7= nYG&_]eG߸νMi-Z.]z wܥLVqޅCD{oseB=/,[8`d1'쇋ڬ_q/,o+^@0MۄC=㡯/Ac9Eُr'KJUV@ZW1b2;Zx.]~j߾Hs3$|Ƀ>\Ks5N3%Y&tLyOmNsRdu@" 8}뼕m뿾NB:/u$k=; 5[eyx̜. )0,qh~b_B~c/yw3 ~1 },~9/6߉B.0'˱caܽD圇'Ç#)dn_oP"w>Is?.G pܖsۙck|dmo;߿ A1) //w@Ai?rg̾k VM1U8@Qހ{bwP3(\e)7nuu9s )کS,|$;C{VСCkꫯI~9䚎)oY ɿJhuf'2/ߊOx&ڃ߸沙duI8ޜ.g[̑2c[1Om~1y>XgA?߇0'K~7'G-;'c?#]d[r|/P-M)05c^m>zݫWi110F%ީccpwAk*|zMxKv \IC2Ra]OOxtx6/\hS{5 XpEu:5WYr!cWR, ={l?j+9Weœ9sZ6={еkןcܹ3U;y'J=M _)Dt"}ey\ɔ 7-\6]R9\&KqKzj;d'~sz"R#YO.(m9&ݳZZL)/#MJJm';lt!1pD"!"5 -YE&c7dyS}+֜<\cbqf5*$yBTftLQdfg%W쬉Ǚd-on{$h)t;Y^&ˎ!XuӥkQ'gL6+d/ w"盟-+-/|Gi\;sME^ ~sz,d9)d q-JT!mdsԯf`m/mKc6Xǐߘ1y\?IHI<۔B!)fs$/;fR?\YOkdfw7Xw3Y&s'lYkeJK{|Y|¾דǒ YH|SDx7~\"X/쇬pKu럸IrfBo},+emIcK~ϟYuʠ|kǿt?R4lỸ>r[wiם?khYXs^.>W7컋L6sL}Pttݭl\; [nG]V 7_:FhNO2!r.]q`AO 6yޮtU;[BuYz:f~fcuZlӪS+xSGY[kzFskVU?I%rM̮򖕟+AWs:Ny磓9PYLj;iߟ,}K}/ox[_:l^{GL@~[X,q2ֿcT'2.F[_;\ û'WN>![ϖS݆'/lgH8uy^D'HF&Ia@6@~"jR" o)6YHr1˝3 H^c#|Jϟ\3cMc]dks2&OrkBC}ٟ{7؎\_|ݙFdOx/2ׯߕ;w:p@=g`pBw>}~8k׮ ٱ$kS~{?;iEyudxlثÂ1)0MμLwzl[T?Vݚz#Rp97|u[ ^Fe  T Ok펟2}g׺߱IŤ53MxdfdI?LrQFGY[3o,Xoh|nrͪ5ID)5S޲&'6*^s&GdLrO|Ľ-jdw]4^&D}\Is+-OedR1C0 w`};ڎA!<o|`WNXp;^"c2_;r;]֑; 6"o>NI/zn{eJш<><^4,]1FR=yJϟl>Jӄb&kܾ`[@#Ʒ} sR"wxjD|ALY'k,/kO ;3=naEt2Ě*%@%gM<&^d"L;co!.w1 L?Uu릟y"Y~_;vuQXs^Rl, }y>pY,F=JxrT;0)4 AL3 ( Ȃ E1!b P YO1TA힝aw߷RWW4;.1Y\"UvRMN}T#UV~[Y/4󯙮aTmnOgȤ & %'YVn? ADpkڶbu*7cJL +6'#jjE;M\yۏ3.nIDATAM짾M7=QPeumz 0`Sq;X?Oݻwƶ5$FK,=>Nص3$z:Y#>-9Q鰩}L~e:rƑAO.]Q*1Yx)"UЭ6Fq׆6LdҦ8EO7 0~#$k#HH֔nMV@G *99gTrʪ3&^:դ߽֫޿_Y6e-o[]N;k0ijj'>@2n$9|'/8qa:M۶3AوyI&k[C/>4ݞ۩7֟vW&LbX?~//wuo׻uV``'p95nX]%FT,Mk췵OK?TWcȤt&(.o`F"mI֬F=cWV?o||jڷE_1czNަu}B-mڦ7sI'o6 T$?G 3gan]۶7֐1/Rd!: ew~Ψ,Bv8VSWM5\?S5mTu-!?a"6d?L d %'YVn? ADpkڶbu5(筵EIƤ|^w .8L@V9ʄad#Peu߿;YN0YJw,9? k61LI$XU}a|NbG *yx*. csfʆ7pnξpQәSmzm0YT>QF߯_,X`(|϶Վ7֐(=(>Yc !Eb먽ԅ^]6nԍntX;C]"Ltʋ|)E*U#j֯fN]x߮N: S=z\_>XU˯淑q*1Y\//7ik&~ILLҟmmb̐ިILZ5rsQ6oYB -]FB%U2l+Y^arȵdLV,82(N_+aza樓^PAϋcT1ULN蟋n2WfVM&J'YgrРA.]j)iɒ%?ݻQPzP|mL7?_$#nk.h]]u~F&$^1(5GԨSCmy[\!eb]]Epw Ib㉑~İ!]&gRBREpdکN Kj\ϤM:9/ɮ"z۰)Gx/_m5x*a[M C%c @ebU =^PAϋc^}B3]|l[J~_~q&^{c|[?rƎcoGnq&E-zz7\SKP&l46;\=n]9T_GVc%gx2V81뉯1כNWOW9zf>q,/Mgѯd3,(󒜛GHv\7G9\39?߇KVs~8p`;Z̛7ouϞ=,((x;҃+c c)BviݵnF()?eB[r>JdV/`r-nN* j,"b[џ(q}k5s7O~F _,Ɯ y3YwZ5{ї~ק??VTkqM/fmꤓ7' -zb2":6w>{t& W N^ϾOuyr~x71Bʂ:uny-NgꋹD^9>f< ;ټnc(|s.}w}>nܸusQ-r:k֬FݻSPP04NJ Ebݼfi[PTE)AJmE< !ג1Y|"G!mۊN^ra%?+ϝ__]`ԫ(Wg霯1+k V/`r-#ݢgTl+"#/=~~}_1hl,,`خ`*|-zwy^`|t,2~./rYA҅W Jd1\dzd'?-2>H+]rrYA$R61+k V/`r-8zQ^IYa=`[џǨx /&E&,d#4ҐnƁARLٜM <})اekrw>wVzP<$k9K0&\KdEˊx^)y2=`[џǨx Oe49wVzP<$k9K0&\KdE/k3*yxW T& q˗@{/K&U&aAwiw?\$]vn?aI^ϗҵ9_5f8]>5QYSn|/xyob,C%?+ϝO"e3rrmܶÆ p|:V}w/9?rMkɘ,8Њ8N/p PA4ϋR`:e&ww`S'YM$d9?L=V4LI omz5CQsu[֍q65H!kE?WNrrYA$R61+k V/͏?Ir &9sE_~}xz[+bM_32-}㨖^i9ߊ.@EĶRωVlJc J'dY]9Ƕ/'NKdZdƃ:hu݇y}XMRSVԴaԚuKx|wVzP<$kOc!E_99s0޶m{q_U?MIt{"Νm۫ՍkRi ԊVV<# @9d͎ Df~Ghsl+BU ܹ,ǃPPB( Cmi_p  iҥ",YGϞ=޽0+ɢ$c2r,(1GZv 9وx !*g{\TEPB( Jyaa-X܎=Џz`r tus'A(b# 9وx !*g{ b~C,H/V/vl+ŗC ݷoߵǏ{Μ9jѢEB^gϞc]գGo m-LeMsG_:Q C+;1Z>@yǶ?O!Ppϝ COc! Ŏmܶ}طo7zoիW5볎J&arȵdLVxc@r>TyT4pA{sl+E<BBr=w 1?!P$+;K0&\KdzN^B %;Xƨ?:d/RwtV'G7ۊlD<BBr=w 1?!P$+;K0&\KdzN#JO߈e* iG;AGLrmE6}BQa;@a5_cv٨EFVZVugg\~?X^YvJˎ uSSvu]v |c[M C%cr='IB'y9j,1Qŗ\wt]{G+VG 1g#s,,bFSi,D;L]>juaQV7-->[[yCqץnQZ>|c[M C%cr='IB'yY*[9jG{keP6mŮz@9ƶlD^ !* d1a]|]FӬn݌wفalǎjU~*fժjۺuUVe{eT1y޾jTjn>?( qWWn-ԣ}uSCU=6uWW=$ nsܫ8_O7p[sU/}S{}cy>ZH#Frkϭw]\]z21FYQ/X3=wQkNQ!(ZdU0_Vfkɘ\I pPIwV}ia࣊/V]G:hPb׸^P<iBTEtJecqyn 4=8bP psn1x{yݻiSc' ]aƤwmDNm`z9-Z ﷟1] Kwwe^?zp{s#Ùoss<  d.dNwiȭ+㒝& <NpQ_dv⵩>c.j@6n(Q/X3=*Çy p`ꊽV';k9uE>xVfkɘ\I pPIwV}ia࣊/Vv];Gb(?ʘVZgltDNT& }{e-1ڦR o6[KWqcҏ y;Cmzl?г4^_),4gۥ܎;zp{DtK3!bV=ۿ_ <2Cq%\㵛~\/޽G=tcܣ\T*9d /?3YQ#Lmk61L$ G}g{ߗF>neoݝwGk 7rz@9ƶJ@dt*Ku~//ѡmڨYݺϏ:#~O  ȑ _8P~@gH&!i~~0=qMr`ZkLPuI%9?[o/Om]0zSK7֨ZEQsOI3YQ#Lmk61L$ G}g{ߗF>n|2+׿5K,U`[l 2F`t:bFh\OKWqcҏol^^<ЄU|uh˓JWK7Yεm^jVoRfBFPuI%3xC: ׋z~ܣ^T*9H{,ZdFU0+09Z2&+s$!Tz}_ZK3m69bBxѓz&@9ƶJ@dt*KHLgvZԮPG$Ak_Pō)ݱףzwC_a˓G,o_??ܮ)K+{+g9a^^&*U6Yk0vvjw39tczQU2e¬00} X]y&!ג1Y$AғpG_rݭN8ZOG9[gcltDN4Y?xްa~ .?eTqcJulQo ?ٷoB2{N@/e>I@ nzt~~SJ@6h(k[9n8eax"˂ofuvj+_kg,Sٷ17v0DEM9~cv*ڵ.8a宪 T:Uugl뷙51;SXelzůI}ia';2hPyyyn;ǝ|ʦ9,aZnƨ+vm:+fXyB ڊ]<b[ d ~}~ݻKc\oO↳Zoz4L?=v-gd1V~WOl~}krs:m\E\/ed!c~ߙ,o߲iueyߧ${=?,yE>wt(4hY^w^OqrMI?:c%Ee~poV\Mlьb׹^PN Sd{hN@kt<u׬Z-Kv|}wZ1tߧhGbnJwseɓ)ݘ~:B[385H:ci ޭW4èQjijۺuY܀rOrz'h]dr帩TvX?r16xewض;=[ը^u^P>p"/CuI1H~q%Z jVsۑ#WX{R)[PZdFQt+>-AɰsrO:CO0A͙3G-ZHOgϞ :4Y}q[<6U<ѠdnTmߪ?,ȿk(QiNntR+vtU ]vvۉi=9wE6YdKW|NüSRNK5lUBٴ[MX]9h_*BtӋ7d+W:8:Qx9$#p?b[; i;wl ܮSܸZطџYݺ$_OeOӦnj!W':)՘R[v{:m[OWwe@դfMofn$cH& ޢ;ڎjJ帩k{g޼~ΫW(Dol~ahv 2:pU#nt^{{.<%̉g֭(fQkZS6!ZdUmOۊՕFϖdV994h ֩4z]AA`{#KBY*DѸA55Ȧ{3墤&TcXvZO힐mITYLXey'%{=?ҙ,d5'LedxG;ZmV .Vz )C^rBِ_pŶUv_ dĉDruZ9yC<C(K}oNL3|(a/~lm y9vJ%3'!f۴PժUWUuCMZctcϪǩ۵S5jT5jm۴SugztO>zޯ[3fݻGڵ{ϛKfs.QV~9=v4Q *mvt^PNwY Shʆb ,ҼdV`N&Ou7u͛7ݻcvxFx:E1Ylzj ԑR5PxVn Ie?s7WcLjyjPn~1š,[LÅWGrAG/tQk_4Ú,V=jUu7G=p0"܈ AeN&l7E5YA ^{KKNyy=6 ~z3xkutjU\W~jԓ[ /] /](矍Q/̽0ltJf,zqC_\p\@MtFg`գOu \sWVszoaRk&SϺos©1}B oRMA5yyQimp^rݭj#E{=vA\3wUUsۈyBk,žK1N~VX,sՂȷVlWmžגWbF#P6$+\`m񥹎mO;qD#:md]չ5WuUSWYAzOLAzvj0!_OSq&Lǝ(Q\y{nUͫ yw( @{ܡE;JD5Yo/;NbDOjWbn{p<F-꟏s}D92]e ^O"//OլYKURd@ Θ/݁=zud/᥯o9z7ijө^~\7h[ի;/2>}8,?_~칯/^x}y51]CEz&(f S-3)^K^ÒB2@FlH/VOۊՕWa[99rs1nx۶~t*dӝDc3udp몼7Q7ɢ$Ng(noiZ~M[;K>q}{/ שUE|g7ګ+:^dѠnՄ>/Э1e}s/L {=?& 1\w}I]5 &o7ѥꗉ1'wϿ:ժv~t&~/]66E&t#K\sWog/_ܹi7_78^_V7ݳ ˯'N4YD=0Q9X(V{-y x !PIM"a~B,H/FkhB 1[9hb7/^E}WPPu_#<3Y^yzUդ9 B۸;xN[A:EIƝ(:ਚ^PZB2܃2@E$BY_"ʖLsӳgOlٲ !C(۶ I~K/_OL_}z ;HeZhTX"Dp^ݽ6jK]6L%w*DTeqKں]_-ݨʝE&Kd1m:2_/jը^#N:ňE&L9;}(BW|TɢNլY~z==0}:1}_x~IMzuZ;W~Թsn~q9^k4YD=U~0YT|b׺^PwB2܃2@E$8y7TDm=ZkZWQKSQ0hdž :]!33l+ p͜9s@01c+m/kSͯłGk_#?OzxׄL?,jLl;FnWc&SXNeSu N&WmT|Avl%zdE4*fNAMc'4Yy`7_L M狉kK>]ow3ΏxltJg2l~yzo_.:+ʧz> zu V>9b m'_|˫d!cׇw~~~CE;:8??hE90cx?;Yk=^/(Vy x !PIh0_9F? ?𮲘,sjʖ, lYѶJ}=zda*>7xfm$7'a4lذlOLLA`mw{Jev6s:j٭OϘ`&R{]Ԋ~Ec7]+rjWub&XzjfQ=w*z/ Of{ռquoٙ:F}Aλf~̼ CDLTuwqOIj1& i[Z_P(ZOrm7\.d}\|RA@wQo^L nP :r 6rn{:ԮeUVUο[o0ay ??-R]Budg:uckVڮ/USI5iL vƧSiصkMKϙwS۴kl5'ܺszQp\"9߻^%ݪUd5Vvb@yC9=cx?K~b;_wl+E<B$`gP1? -RU#PUb]4I(+DQ\y6dT|nR%3l+ aС  `t;wǶmvT7ޯN%5Y%< d!A#Jb*8YS}%JW.ʼc}.\þQ_Fp\/ܶkZ]7dWf J7/m:8;V 7A0Q4YÒ{~ Tʦ"R,l,PrA&dW6"{az~iK rgo̿ẃr>3!= x !PI"c~nHu Uj q;;`#^__խ^]WZթ>xpBuL=}|jxQ1n>?(c<ɤ,wƵV[c]wUsB5cGն^=n[PcM~EulHծVMr9;>گ?ryHv3_rlQo켳j\ʙC۴Q_ߝ6R69W=(;FyCUL\{zda*>7|?y),,e=Џz`rN,_n=h׍{VuݩQO"Q^mj>cʿv2ꡲ\w;(_/(gVg#Š?B!TF{ų*>>\](֌*@ ?c:<5H[ җqy~{Ne?McJɢ~~kxx=T6{n0zfՑGfjev`vA_3#\sɻC sצn]E("FIY$|pݪ;w5x6kwqǩC&,L"F/sĠ!R\yԹyY r}%*( ׫T|nR%3l+6!CFw3gZh`-g^wǮѣǷCFx:M5TZUKdTah7tǙWt&{+Vz]>RR/Q}Ȯ5]ogj߮ErnjK]E(ѽ{=?ۚu{|lu˽mZn&NبNrݭ;(7b{'a[)A<B$`+"g!JEP%7R ڧiS?_v]4Yԫ^=,hߍp=ԯ)`cy^e/uu'9N^Zժ;sÆ~3yzz_2YD"XӶ|Ls/U3?y&Fx.U& 4r{/4Qŗ\w+ʎ3l+B2܃>0I$$X P1? U"x~H z~;X=P{5m׮Pzu}5&i߼`'T ,hfڷvj+7O?yy:v4ia%}%0?As?q~AcDo~46<01+(s#`^pX|Ls/U3?y&F8Bt%;+B#U|urlz&@9ö?O뤓7R׽fqVNa](Ř,&,կPVء_[dC;dN ,&Qy2YDnpc0\~Rk61B+yYKueRGLrmE6?B9鿨i7xNhUyZorr, &H$Rvc.@e$Tf(E;dzMkrMRA2Y;fv~ـ6mܼo d߾ mzw& 9,/{C|ȑn~sRLQF$0b1YM.T)Vl5ҕ |T%e_LrmE6?ʊƜY|f#3_^E_gJPRyZorr, &H$RvcUP0? U2n?KvUu A3yueeD4n? p s woU%//BԿuk^n;l,fjy?}Ѿ{NŕG.Musp^|Ls/U3?y&F8Bt%;+B#U|url9ي]Vz@9¶?OHg_N^/Ug_Q+~W3~&(+mMA-mu/kLsBOe=#(D"HIrr{"0? U2}2|jQAC ՝`}'!g˖~^5ԗ#F;!u:/<ahP~p <`n^~UڷW:ǿl|SIyøsnb9mUgudKv}_aT{gkQGuS"|zbNY\Ҩ/3|9.jVWS<5@1d+27 9+Sq7Q[gwEUʪ wfVl5ҕ |T%]z@9¶?OE֩7iQO~%dqYԝ~]o|K=Ju u^ }Hzܾy3V yϬyWk/j+Q`܄~7=: gg@=CW$_꽲MO輻q7]{ƞ)x2߳=ϗzg۞Jz~d}qz૟ekrUsJ0I$$X+x-P%ԚQԤwww#|qaf\E>Mժ&5ký{d׉Yݺ"#htx۶Ƹ@ 1c1ڊdN;oH]zdӃ f\ӹs9$9˼y"1w$MK 1sM:7n<ܼp衪{n?rjJc|b篲*>'?y&F8Bt%;+B#U|urlڊ]rmE6? 1c_`c PS'n9eʕn5w0O;{_᷷1'mVݻ $h«sc{K= 7\|1tfi#;mxew;;CÎWqyH~ֺ>;e&w72i#R_ݼoMB_}9usE_[{^ "꽺 5͏%3vGM:;JQG.Yk'1%<?㾗>u'>~?aaS:P&2}E܃>0I$$X+x-BeAr;Sl+0ҕ |T%gYz&@9¶?OZ6%f8mD~(`[%\ '; j/KWO9q)%M{`1x s{ߓK^ă_/?{r-Rum?ek͛.nI=G9{ZY[I*)ʼ$S%k! ~|YCASxv=ǯ+J7QIAPq{gFP0D"kE`~B,H/pgmE#!T{*[]wPs'; xm/&וIҖJ&[}aH$);IVn['!ʂb wV a0B+yYKueLG?ۊlDeFWqT >sdu=%S}\IVnEA$DN{{<I! X)ABpPJwVnF>nvAٳۊlD<JgLKj7( 2c+W !i&%AsBw{SD=)1< .e/U~~㕟yIQlm$ " TrQ ^A*$]$QJE\zJ"D@s{{gzvf{fTꢇ=kЈF9?>ߡ7u9yoKp˨i~ϺdAʑC[ۿ>@.߫ha.Iv2.䶾c%|2͊ CrN[) ɟDk,K$7! D w(ܠ_W\ ž@9@S83r _>"yʓuӜ_xjpL{=ˇM8R=u]ήdLJƆ촋ʯMQӹXd 8cqQ=}WSWnElS~PgԀU6쓳cv>9m/;!/5 ٳM۷/+W6mR?=zjР,ytHNvކCtJ)kq^R~4l>zhsD$u|Vό3xEݍcdt~&sEm_&?4wvr fg5~m8߷dѝ'ҏ/ڢΝ9/9Uvj'go*əKOurRήdE Uϓ .kqk1 tszsj=  rq<]?v\~c7l_;zzmӏFnS$i||CLܹږ-[_xXaEmܸ񁴴!%3S]"}.Cy)k)ZCHN3Χ~1rCE`sʮ+E7)Z7KVHE;UP9 ޸yc.}J4w2YP과,܍M - Ə>x  |@9yۈ%A\̇ Y+ZYo?Zk֬*i6lkzzzka]n-yM:E{uHiܼe^-N-+ծ .%YxK>.xoBA>hQ6~okAPh@@Ӟ ¼mDT!(6Jh f54o\,D-ZPzz/wHNR f]w1?RߤO5v3ګ߷TG׽J7HMJOTZ}3E_īW=iz@^j>⼬;g~ikɻռՄVTRFʣO) мmؿB笷%Y.5vxbTڢ+nK{¥`G&Ah}axiGn 0D"  rBaZ{S9F/m8ӘG=xPʒ(h޼yEY%..mv`z!#ۇ5ܰSn%X~T_ͰٜlSIL ێ>Yްwi0;ߋSGyTs6EJ A$r2'쓳yRE {uje>Rװ;;O #b 27q^pA5x~/tc~h'>iGA8vȠMyl mbE>[iau}QY"¥`p# A0F./w1좳xAz^[*JD;Mn vv[pɲEBk^k䙝oSG]zۋV(t^S6kvrܠ:v[95OcQΜ柟ϫ7vǠ]/#&A^ȣ ol/Y%/XKNEsKG&Ah}axiGnй.3 )@]4@g3AA%9V| 0*̟ lPpY~}aKEDvs>׫WOZJt K÷SŌ?5g|JUbػհGMI<ޏ klOH}&ssyr˓n#ԏ` p6,J]Yʒw˃yYΒWz9#Ow6 ՄVFsz]RA^ FảvŨp]"sWTƜY8Mwzڎ j0IڙC/_tQKHc=R}| A Z_Ea'[0>)@]4@g3AA%9V|=bVn'SAP6j6k,,yɶ9n:UʺQ2?PͶm%P̙3?ReDr KNNyvNNNXoYR{,#=3'q<9rՉ7,lAdyto#G"7xߍf2N8h,JI 7iȧC`2WQaΣ&_Z\Ck86~ȣ`pc' {2 <԰ANcM{cR}|1х  Ə>#D# W!@5e'AA%9 )s~㩭R} 0$*ȢGs|9ʁ;%^??87U5PXVW?P͎;.Ȑ(X(J/qqwl͎ϺC?j;:;:WfÛYu_ܝɛ'jGzJo(5#}jZc%sEГi @A b+KwXa  ]{`Z{N&g 8#,S2$D{d>'(5f͚Mlٲ5kHz9=={Ar :,`8#EvS;h1x`Ö]t<1V9ʷߎ\Zrj@#ӥzҝ/Q;G=u,BnRvYP2M?o3t\ 6oT}mcI<Тn,i=fm* !p´/, 5HPqe+9AAAPcLk>"9&Z/mq(mYG}'oQП_3Qg[-u(./}?/Z,ݧVuU.߫8@e?$GGO͹Ǎ'>ʉN;S[Ɠ7'-(.ⱁg kwq8o}RΓ8Q9:uK]ԮHn16(z|U-j|iFOFYz9|Nn i~i/.+9iĤct}/0Ę㖲nAHe>u{IQ8\5䕏g$h fiӦh"oIKKb]=#9#yO]"sg7WzzIJ@ǤļJNAɚsndG6"'%?X* !p´/, L1kEVrX  d=23}ErMFLS НU'nr8oYrGiї;;wYm)>qЈjj;li56‡>a)Ђ) ́ 7r'g$G4ʋAZЍx>+v:SЉ]?vPY7q vR:'IϽG r]W/4(u8nwv~ ݧnseںߧ~3-Zo@f:6jX^Nfee+W`C:t:==4?^$9#٧NYu52[i!,F|Ω9R_i3z!\,),andG,ǂcs>H ;}aO s5HPqeAAA.ze=GSpGn~AmvKwx/??_4ɛh x/veg2YOSA%"%܈W٢:y1An =+iD0 }xɀ*frRh\Ybh$9AAAPcLk>"9&Aܒd[l-:oSzx|PiG|YSuy*ONuzڍlϜyhcs 9o-uOf$׭+xREx|yy¼UxYm3s:95M90E H)c }q:%M;gʴhRD>7&(dNƲĮ!ѓƴgb W @ƕ%FAA%9ra,ym ~վ]t`}K !GlJO M6vgcnq:́b?7blNo-h'{GvkiG9 X,"N/f{,"HNtޯ11c GǾpx-$Z j#K!M&7 $`$$$$$,k<Ea@ T@ @@ p#G~#_ K"h$9Kgɇz% i|9f&ƛaxƧт' g;C>˙н7 d3qr^ڍX;E3Y&|S;<(cOb?v`{a\wui] ?f`~=Gg0=t rd^ ]t<7;͛)N7`ثV>$xccuq~9C•{T|s=JNA۾G];pb ځ@dvf8I~8]8QϲC<4?i sET&fO'g$h f S0?X5¢0 }D6F@@ p5NV /dE(E[n=E6nhwިQC 68sO7LlHkRF!g]9s(Er]7:񝋭N} EƬPYNNfgeL.ND~9Qb''y(v'RuښY\hlD'uN.Cr3R}3}ErM,>>%oɶ]Fޢ/wHt|(ԴF۩1 AS_vamY]DOϲƸ,EK?! džY/?ia!`hӱ9yvDADz~?1!Oe9.W߳.e}HVHNHHHHH$ZcYbx´/,  c:Eb #ĉy޼ '/-Pzw}},rJi&~G~_L'Z$5?tSOo~QJUX/wCU=:6t mQT5Y99y_-;uxSNS4s>tDs>΃Xuڇ Y~*kV"t/NSuًb7(u~)ؾ_ѹ2ȂQ'T;9nN$m9]{n~ =p̣]"{Z8{v1,k@ww)hœ7naձ ݢ\ͭəޜ'ʩnp$ؘvql,NGޏpB}v:_xkN栆ʰ 0L4мD)I6+9#!!!!he] , Ӿ'@44d}rRl9_tņG%R U11`ԡC-[ŲӅݱɮR@ sA} ":ԯ ?WYi'u WUQ*'Էph׳5ìH}wvd 8^+>6ıOjdANv,k1Sj針10;^;;yr&gTwAjΟA7eO4>:5%Wr FBBBB'@X}aOh,N4Yx7ҪR4T{3|]'})RJ\x}uKSR %PmC~fsR{QvwaKM0>Ю"73Sp4HѮi˵kLqEK2{X!㏴K#!k])ܜN漛ῌmxM0])]tM?b^E<tOP[fZYmn 7mI4TO d7q 79LD~,\Hσ톤 V2"NNSuNnuf^&|s_c|[GkCm)8=߳L<4I*ؾ_ѹ2߷1UVwmrħ靝;ivȱD??|=9m@A<>Xw7kv%0;-?MzQv/ ذjv50Ў3;/vǦ+N8'7I9Kuig E<&ݯv4߃nx=g=$kR^2`X\) ɟDk,KaQ>彀 IĐ/_580'MR_xÆ-]q5 OO *^2dq]>}3O<_vٵjy 4hz9sN#6=Ov׫׍v['>q~^|MLҕ6O?~VT/~tnEE"G:/w1'ZCk1h+^Wik]M{1rXLgSC}s+ˁ?"9:eN9cS^Ƃ&y=m{X 9=9NNs.p?dm OxC6љ|O{KBu~q w>7Vcᖌ7ݾm7N^۱ݗМskcIׇڧn9X׼F6uYZ^EZ.ꓝɩ7CW^K?v:9GNey ͼL7涿$ ǀ~ujl oծ-;tmY>'z_/"u`~=G 1![)D$4oh 1/Ek0KO5%v(LB2 39)@ n3{՟Gi8ZZ/6 7WZP)C;gh1v׮uĝ4jjcѮ}޼<51r7F:>ZNf[۬ٽ{222$/7j۶fEQz֜DNCho]4hz{=9̊uHt7׿%d!{`JCS;/Aw'd1ȂDzpo&U=~9P@mW1T?WCCmxN}&2B:7G#/μgSg|̣V۫cBm9O/K)eɁMu;'H_+Oʛ>/I{J߉dv0*S|3ys?̊fn;9G;=9ZW(*OA7}Arfh [9AP9ĿDp^[Az9˧ye%h fuIƲĮi_X Z?<院HN9󘺛DK [ʷ.*(hw =o3|֬ʈd)Y%uF^JJ>cQޫ׻ljϙeïޡ>nnX^lfvfeeI_n4{/EYŢCrXoƝtOŲD'AA1?s[wĶkޔo/jQ!{U!܍"9{&_"ƏNgZP9Em4=;`z;ۻފ'O9Ieȍ3o8eӦCm#9O/n=Onʈnjt\su~7T!/S+fʓ#n*S?ʻ'vNbHcOzouWkm{]'H☊s/9Ә8΂'\sMM?5xMĶ"c':z4.s휷:(^+q^7_G;T ߟkt3}ArF;#r] stqֲڃ_=zB9[O?6 }G{C74|3>l1S|oJU:JNHHHHH$ZcYbxA %/O%/"7(L+Kђ'_zQ$ L{")@I;.Ŋ]>vY @1q~]7ef7o>Wx /^,/\8U -ZZS\u#m٪vɠ1g-3[UWC|"F6j6kܪU$/7Ztzd5s#aNvc*O$IttdB=_ސ^!gBc3-d*8Q$qRʡ23!ĭ1;OW*gOz?h6co1>u7SU$g^;2hGQ4gE +j-/Η+6ݔz\9Fbwcq}ZN1=~;@#m;'hױյE(Mb^ 8_Eb9]֦r Y:ft҇ ·iGR,99GHrsu8/ݖz\R_7kc(JnN z!\b3-|(,EbT͛OBWYڻΖ:b! CTToh 5jmVH)6mfOr@Ieh r>(cߤv#)Xf ,N_?WE>^^=On%:ڮ7[^LμzC.''A|ё1#(n1;.W(8iP/zC;9QC^y]P^4xNF{ڛv<:^əN3rP ⺭:vKhKr3'Ȯ^];pb z#n*S'sI>ڹ(M5qk=:ضu4_yKH0zග]DpmG$ D?䷓zu99:tܖ渤xp-:K\ `~?Gf~!,\ABBBB'EH !%/f$gxh/[.D};i´  \qH ݣ}? `ѣwu539G}Ĉyf#x:ݺ)I1w=;Yی|Ds6HIlGNNNNjϴ#%#;SپGFIG>̻B)Y]k49Nk^OyNjoxf|b*3yg[_ )SRsf%;9>v/ΫnDs\9Fv:?!s) VU{*bnEcOm,TDq:ArSy3KoNp_Ωnv7;i7'd2F %rD+Խ^|4ӵr[&ͷht /A^s}A4FhB`1=G/YNGr G:r ɟDk,K !Cdh(!(&NlnI '@, F@aޟ;5j4_}u=N0`EUǎy(ܔ1S,YҰ թv< #_unQG%R[<%9~ѬY>Qea͋A~;6:,d|גP9''Huj[uZ-qu{ 7ߢ1Y{):-1ϭۣOHNAP`X\9 (.J EGz$$DE@ٌ A5qe3bem@_F@R0Ry_|)|ߥ2&J]ir[u3#[B;bۘ5x2gŋ_jaþժ7l PIS[2E6(Ȃ^Pmv8pCXeU{jsS:H fΝȐܨu+ҋEo߶5F蚑NNj1^Hk1^RvtτޢNoa^10^TWEunL9춿(|kB(hix)g?&nqΓ  pj+';ErR*?eq{YUnX#7/;!Hkyw ]"X |(r] sl>"9BA#ZY9 \tGBK4_XbB$gxh-T>1wc0H2Ӟ-0R@nTNϩߡh 1/coj˖-ϯYFr իWU~=魭?#9Z&?ysDNCמx/'$r:.[dQnٻ'`~=G+-/A=5wv" O@Tpё .|a}!(<\R܍¼!Z# Gaޟܠ31~Wx 7J%Wf֪Uh"xѠA}iiiC3䟎c'BX(#6i8󆗗9xdNk?Q9?O'y։ܠx;'hu˹i gju?\ϋۣOHNAP`oى ?QEGz$$D%!DrxK\rI%s7& ' L@% @BQg)@ 7h؟x1wc0H7/ Eaޟ(vѸ2wAt!)<%Nt1# 7K#=]ESrxK\rI%s7& H l@ @Pg)@]4x!9AAAPc Aeh fȥpё .|a)9CPv,hW @Qg)@]4x!9AAAPc (b X#i|d1 ծd1h SD`b@ vg}ʕ+ML>}ͽ޻ZQTN@rӧOݻwx%?C*kҥF"EX6l`]~R]ر'dt΢:u ÇW^y8d4ɦ`?:\R܍´ 'w@c @BPg)@]4x!9AAAPc (b X#iy|9O3I-ȂR}CEx6l8Yf/^:饗^Z(ʏiiiC&$gx' K|Q/_> 7`s pOz> ]nGKiٲe M6!s=psNKv7>1wc0,}͒_d; /@ƕ/$5  Jvs,9Za޿CA} \t%>7А_||ĶARn h޼y{Ԛ5k$GpZbVr>OOOomjFrwQׯ'O{#QDO oٲE! &й|e_Ʒ&ieofڳFK1( .WZ   (Eϱ̿hy~?AP ,pёkvT4I^,('媗 .*/2xpc>F :X!a ն/\0/ZhZ=> SB u3r=sR eJW._0?/qE 6֗{=! mfQZsESQM49^|wuS`S3zg! ЈcDʧ6"YٳGaRJ`/:[nq-yڵiӆ(QロOұ>C΋-ʋ+K/혃,<k׮UW\Ym+Pm~o}v` [.&vk7RX!9|hv_ b+s}-^HkAAXs¼? ǕAPH&?7]ٗjS(TS;$rݾGeNAN+V4@A#/rj+ue)>j2y9m-m]]jjB+#Ͱ]yӕ@yq{rFeNY"!W oؿ!Wz*j}-1~wcC]sbj h #yϷ{Z5~xUTk{2we}kk,y^ROC222$r:=PW)IN;Ʒmf'ARt{ٲeݻU9r$b>dsNdOKKS.}YӥI<Ǐ oϞ=ҷuݻnRW!(@_~j^d4$WN4f7o^{ZC;g/ йFy b`D${io# !r7m 4(_BIK^ pɩ9稁0mgM R"iӦgWZ%9ъ+vc3rB/b[Ψss3gcJ21sɒ%~_ ~Ȼ[ ;A6ڡ\w5?KE8}F *8^й\ɿY+o[OWbXl͘ Œ~"1m( .WsX4HkAAXs¼?I Ҥ_&NS a_ ʅKb`d1p@}'C:RmkƟ3:UbػU X?RSnwVwzڶE}l>9xKE^lquR勖.j))k{~%{9١犢H"W~iHN A~Ş/_>Cf{Ϟ=-6]Ç7tΙ|v#;B;ꫪ:)B/ov/At̑#G[n-[EeK.V>{m/sn9WeR(P'p*߰?bRX% ) ~e3G71m(O~9E  )9ZaZ{ r\tKSwk^ղD-ǵ(!&10>h@ ۢ5Ȣ׻ .m{YJ+y6vRwmEJ)"Jݼޢf͚+W37d!,+/?da',}޸qQ~ҥmٳgyڵ ](t[R\eg O\a켳/閵ǵw-ՉFvI*/m@y @0{}ƂD# C7oÔ9%;A䗢Ea|?Iݤ?G6+6zɹkxgګvSL8g0dai{үۢ.b  R&BlՏy]_'>i؋)I}框] p =J:f#,M=gcNeeeINnO(ʪ`S3L<N("۵o>}ٲez'N/V,69w|Ŋv}# XSP^Dt >̰)SF '|bdй:DOc#JhL&?~xa[<&q5ӞQlAa=H ~πQAZ##+PF) CѢ0$^9[ۏVR$p͟g49F\:ffN?ꍫj+\77͆73ukYP2eoM¶e~(Yaﻲa"́/-ڦ1nW۲c(U]jCs{!CȐݨcǎ(+ؔ $7{?;98|A5 RSu1Æ oѢE,>rHC蝣U;3XJ\Z7򛑼UeT[+K?wlk|b휶j`A?`~N6yōn,m.}V᏾-cԎQ|9|j͇}9W_Ͱ.QX9Ǡ,P7_^5`: /ħ=J%(5\:')ؾl|{Dr'}w(J`S3|8>}޽K.D*Qdq1m6`Ey9cAv})w^^V->{l5p`vX|9_d /[QRJ|ɓ!_v ?ko:Qiڊg/ O\aѱŽ y`m#Ie܈( asJ]1w %?AJdxZR>AEa|?IH0t :Xu]/?4u'ˮL . ]x@ hsѨ;`^5FJ5󋝍e5FJ .q?xY7t&6o-Z(ݾ6ﱤTvяA;q^N 㔯Y^"ol2 05ۈ?ǎUV|͚5y8QpzzzkS33|8c?u+ xg$;z+B"E5kKFwz!u' ҥKn/\={V:GQ^Y.a|EP?u븢(.\7hЀϜ9SCA$v`Æ [oUۡso׮ ٭u:y@XEX~DE9&qP@{Z+f´  CqQAbrU7>)CA*Z#$pёBJM;p!Ѯnc|/)8VQ9Ys2kr>o֬_CfDgxhu&!>( <ʴgb ( ND@қ?֟H #\scՉ_]jhP##ȢŘR~nH}QϯF|Ω9R9y333h޼},rJٜ>\F oב!(7& sm>9o$.PBU㳤z%e矟/ٽ&+惓uVVVAۮ]C͛7[Q޴iSwZz2{QN!k,s tHȮ1NjrJ(^/v *MEv-tT@@DE"R,bE+{{CEs9M&L>gI&dcpwT LW/Pe,CիT.k(_M/i_ P,GPkB)PP_H_ǰIneMn ח]ENpLZ=)Fpb[d('*ך. 'sF,݊?z~@u(+}M2|}@!co/g0 ^L~TEZ3y%AC>d{NdR_JX""WMd)e AhBHqD:&Bz#:P OHUD5k]4:3O& s#5- e("zM ,4e AhBHqD:&Bz#:P OHUD5k]4:3O& -"Y9g^rW JSv6#(]B!o dC"@1L]H/SU"4X4 ?@.!#J F׋ou^k&׺$htgtL~ @6Hގoܱ EDTR6Uo#OD7@Xv !GnK3Ɇ^/&3('*ך. 'sq&/# e("ziDE>jc1pXv !GnK3Ɇ^/&3('*ך. 'sm9a@Yv !GnK3Ɇ^/&3('*ך. 's֦|;-Py*^ ,GPEB#z7% dC RkZ9ƱdJ EDW|4Yv !GnK3Ɇ^/&3('*ך. 'sMeN/P))iT;@.! uI#Bz#fTEZ3y%AC>d{Na$OG,=PD2|} PK!8pC]H6z1@1hw|I#2ٱ ED,GPEIspmwʃ˳IUlIUn̜ؓ"q$ZF\ժapBϸ~(_ Dn!G"+G'$÷Ư_<-=P(0?Pd|ό_psPYm"YɼH6-e_'G/2Ӕd4ZYvXQ_/!e?||mfeS.$;77RlY&Mm,rרtEa&ׯG;}cmrdAd}dkAElG-zȒDV,&%L:fz.2Y/{ϻ"IW~l~}it;z_}Rw%+LqtYd=",yqǿ"zIYVqΝmǡ˽/G*sm懲_a9pWDϏ7fM\ǩDUM?Gۿ¼ysayPkVGf<:M^ǩDٖ)A{!uD~!6?kMJbg~\dɚ,V oWI߱yܬZ)rL'|8I:^9cE&^NEe(>OsqDa?v ydjld:NTIwU\-m,e9Mnhw7nU YؼygbM ~߇OĖ9Gn=&Gb[g~.2]O?dMӧ{8ɶwV+EofdIu}=m{?;Ŗrl^l7[/:56'žOGMbz>6驱VD\=?}eu|}yVsΛxɖ}dц/|8 9Mads?Oʞv͂"z74Y@$u<ea )X+2ወ@UHjD ˄(PL!ov`B_4lh׊M:J7S-ewŖ-~':/{ϣr2=68Ǔj{"N9e`OITW63Y&Q6RlQ"]ZDtwFk}V,]o:o'vb& Mqd9INR]c\e8MPFW$"׷,@q;!6k%X&=TGrP-,@q2!{Ue+s& &MTEȩl9yz]iUu^&N2MZ>,|"GsGn!~}lQ{u:sz,{~89T;i|}{BfTW߶7ɎCcs-kʎ-F|vly[I<'ێ.~dlڌ6Y%Iwչ2z74Y@:(#YIE Rk3 }ɖeR&R9?  Q8B=zo/ٲLVnҤ*+l>:vR'ۦ5˽?9+.ɶ-L>CDW^{ͳ- D2=s{<Ƅ/>-O\lDuR_#tW*#Qp/b<&6/ů3O:8o'0j;~n{,\lj$k]:WYD& (h#eՏ0B4!ej"˄(PL!~)P0g|qGu Z_V]MۦZ牜HME=,VMU63veqܶ}vhaDm.sDn}/oSv;"dlWD?}o>.z){G{s~.rvu3=u^ vk7ؼxRmOkX_G&"!"w ~Iw}9ɒ885"6;sbs?GY&ʞdǣO^H<-v.l{ ^%Dto2 fzIIwΓ&WsuԖ0z77PzF2o#[JB^&w?Lt@Md_ !أ6=vfwhA_,zS2٦?QZEZ4?&鶓-!(}j-^&>1ڐ7"-6iou4~ 2ex.2]/)sz<#EO9tI[O,ID+Zs(鮯Lg$;}2h߱sbyf}҂^'|>ÿ?ʜdǣT~26MKUEdzJIwΓt_eO!=@kɳ&">~qBvB !zm2 R? r Q8B=zo3Sh¿B(z77P jErQ$K#YHĭ/PNH!DMRX&@U*#? r Q8B=zo3Sh M~NJ2 ZlzI34JY&Gd1wy-2|A`YT浤s۟_g,:{O=K֫LL߃TMf|)K/%R{ݿ!(nO͗}?rZ]wȤ3eQNԭ+]x}Gy ډhV59C6mt=ꨣ~>>[Λ7O^z%3gOƍ[йsY] n,@qrTQ2%eke-؟cdѣd}Ϫh޳# OwYv`9L d-Ž_K!8pC(Pܞ,{AvǞn3 sgimޜPS18hܡCefͲ+;cY֧mڴ4z۬G;LRFʼGϱhДZgU5@T{d,Em&w/%R{ݿ!(nO/,q IɚJ{sBeBEaNr!:ujD;whѶmS*sb{Ryє4X&GdLDe^K#4YTmhZ&{hBHqD&w P=Yj-w˝r9/sڸdwuMt9?z%58hna9͑qRACw=~jF'IM66k--]Nʐ;"mTnZpҩrO&iΑO8Enގ>SxDZ{ ͈qcprLswW{j/䩟ޕ׼noя[oe2%z/|^_߱\v?^Rf,\Y,>Oy5~χ7C#pnf>;.hb:fgt^u{hy?,]Gm(p^3|.6T\z_6;hӀ6Q[9:ڠ{\eqిdv=;qE6Y9ωslWu59ufsZ&{hBHqD&w P=Q|[\r9uqQw^yCـymw=<{'_ 7-m憩MǞ6ѣeI/ʝwUv}<{NVM·g7,N^8_c:?C_MEϻ3]_:ɞ4Y s=m"gl6YdqoB)=@ '%N1eƍb'krh3?{yoƺ悭57mvp.|fԸ 6wĽOpi>û2HnOe_L !m^+/ܝ;ECF-Ϋ^w㝿/{.'!o~g痽z?#w#w^sdU΅??I UyZ݆;;6YS&-̿厧Fߠ2oO{tn@chB=*EҝD s=m"gl6YdqoB)=@ 'VM``s[}nN{ nhsqv՝O4|#f}}^qS%& Kǎc]짇h33=aUqgM{AF1.o١C>b+yE4Oe_L !*7h>-ׂ'>O |E\g5Z>uWvi?!>2}OMU&}3iEpo ;?eޗ話Iu.A&,"y7^a,HaUfrW k,]B!(@d9낞nk3>@׹~tVZ뮸ny]YBdM4Yk.;|צQ7m .{v5lI\DddqOKz9Vq=aU<e/slQm wT9眳|ڴiLrM7}fY|&LɅ\l[{=Uڅ-g=-Bs]g|DE~pѢF^^{S~{R*Y_10M}>1ޏȉK~zW^Y}צ:Y531|l蟝y]Gcsu2*df^?LY˲FҳM6 ڶmmTҿ.J!7Psۓ偹 eZ@>,sy>u>ဃs59h*da}/ʦ7w{OKwg_k.bԪdOzyЂWd{=6qM7fsw? o3OK6n;ٜLuʆmd7:j^22Ik[^z(2geY%&tT*? 9˄(P\ecZ}'/ 군[@/\1.V| Qo|⮫ezO'UyD g6H8<;ɢUqw==߽:yixmskf؝b=2=yo\qG,J{{_~T$HaEf|mڴzGz~sm7o=Ǖ7x]tyֲoudvDB'$Pܞ*;!>}BZq2YwHٺYsټɖ2宇vUk6Fnmflgmu3:cso-趵@S~w~y/ȴv;Η_2nD=!vUڜǢOps]v7ٴ} {6Y:?^nM6ۼ=OovQ%TWO?kNd֬Y&T# ZӏH-S*s Q89QނCVؿi_ _\+֢o-߾_7gp]py5Z-&t} 3#S~mu6./yEnGA%~^}Z|;?Ԣz6 KʐQ',/ܔ]^UfuOZ'"S''O:IqCl0^\::=~~ǞsYûѹ2]'{.<'KA^^m:yuJ1DWDY]Eی8UV7wt߃{_l۶gmڴs*+_K!xAOh'Hؾv:v~G9g^M]8nЦͶ;$]N)7-ډs}jNI=osAc}'/h3Hm7{wֹG'GlUS{;b}luff 6}+ JqghRƲӮXO$tX<8Xw|=Whu8n2o7TÉA=yI'ɂ ?N]_ڶmSLU= QiJ 4 ,@qr3fO-vƐk ߀Mle+'$o'<-C[{{>AAmFdO;חw<k~.}.'K|lѢUqk.v<"yq'A8C(^_bqd"w^ -?:k#{輳N9Q^D߼o7קhSJ"WFm{OZsk2iE|_K!xA5)m|ԧIy_?hE&;tg۶m/]9A7;˄(PL!EҲQFwq+}ɟ{eY)]B!@ (nI)LsggARv٭E`=M1u@#Fcǎeڴi2o 'BHGmҥ˝z 4QdΝ;Ϸ,dwoMdB!E@ (n)籧dk悭6BB`b->*jm L*74f;ui@NY&G0y- !TuIۯ?~"̛76m|c(PvB !zm, ,ϙ$PL!E# 5O|ɢOOG=sڴi& `@q;!6 Mk~}*dB)TZva}Ox衇C/{E/PNH!DMC/dS"L_ș&t 'BHGm<6mZ"3槃>xI@(n'צm(L/U8dY,W)fΏ=Kץ]m&>}ηIjF~b֩ {[$-{W^LrggYV{O@(n'צ}EiU8$ϐk8o7w+p\'pyR-KTң$mVgR?,[+n=5ɢs'vqYM2s̕GqeZӏ\B^& (d<u}Aܲ^kR-Kt63T]F=5B3`9't,X L(qK۶mOOZӏ\B^& )@Id0d2pҫTM%K}fՙtO.&ڵkߧO"-tyh۶eU= )ihg1y$y(N.m&xwW?xl^Rf,\Y,>rg 663mH,QKm;>';B]yy_oʣ-7X7Q_}˫ߐg~}Gzc4뻌_Ϸ?P;t2{WasF.[7ߠ{M=ϥHϲ5'?/ɒ'ڟL?ȇc}=w;& 'Cֱcu2m47oX便ڵk{6m%P )ihT7b6\sӯ;M.^okẳiЏѝęcMs>}vỳ߶B%dq̴"ղDI/9&mvpǧe-Zd7\G~7ݺ{]m'>v&??d꼯:fާ;n&0{ )O}^O}6]_ϕ6S<#w?>K> Dt^5zǤ {;5Y oO?:,K\NSN"o?:,#PNH!DMY ?l1ZL;MN5YhMLp_2/w^ gFo3FT˒%쾌G{^!E'8n՝#\[x/'&IN᩟qvx{u6Ed-':hG>Q™'Mɟ0ߟLWEܝ5Yd2!]@Rk@qr1&M}.Y.'8g>lc@%K},x|}.vwfDd[Ș?ŝ['a_?β[:OkI g˫p<Cǰc|'O?uQѺ&lc@%Ke%u~ి>!Q꼯}nO66*aW?41u2q &0n2|z\u]_qiȈe)P`05u=HH$[ n@T8OGpƵޙlE\ƀkޗ>Sm3eR}Iv|/ksT^]Z& }:+k_;;tӿsŸnd'sޗ副y,2b{ !3 FMzϋd_(*.R(ɖekek45[8~?˥Wek3.v,[+=JO\-yMY!w_/9}ΏG_C _^= -^qśv|m{%{tM6 Gt;Z)Lu`?& }~.!M:Ok32_}:>p;=pd]{9ڨf߾{MtOp"^1rҸsO~ϛ9zo3S`0j{=BI, zHCS*kL /T@qrF mDWaS"sgLSܜ3tg<}Iyޮ>-` %ݦgݗLe+>Ox~[2{ɇvJn}Fx {}}}6R,]ϥ%*-ZxLw5/z^GO6&${݆N>t g{ןL?dƢG?Zd_=G>nfF>V{E QSLņhe?V"gPɄR{]e)P`05u= LtɄR{]e)P`05u= LtɄR{]e)P`05u= Lt|H652הșEj(N&bL ~-{O"dCy&w\eRy2j(N&bL ~-{O"dCy&w\e(N&bL ~-{O"D[r nW .Eq,x>ns:ne:0vo{z\z^ l^*;Rwj9Uo\_gzX2!Isu@X&C'PL!Z&Eȉƍ(W,"aw 'ű}\p>Z3/v/M_n6s5MxnLZ=)^1ױVU4Y0ղk&5_]#?]hF܈GE_d7Y8ˎ,?R&x0>aG> L8)_6dB)2(BN6.GKٜ2w BGߧW,λ0CρhБMLGelI2mQ}[vT/ا\sG?b[66ڔȕEj(N&bL ~-{O"d#]w1b9,H&8cZԒ騮_QO ,!~Ŧaʆi@ 'BHGm&w @r[?xiWsS޸4۳{ɱ21zgrVv4#O;PoV_7/GiqD ۰kTOyww^cG}3JlQdM7|gzg;oa﫮ٶsvY`:[&L[XVM?|:T+7Y<^"nnQvmnC~zNxӍ|7YVn.,F~:2߳,~Y~dvq>a猰gQ ,!~(N&bL ~-{O"d#UGم:E0any5_^c;rⶵ[Q'Imܹot_[iG+: 7v_IMi-l/.l%{Wx]vv Ϟz]Gۅ$ANt==-m{m1x,2ݶn{v~MےvC6Yy+6Yۤ`oŽ(VO&ʰǩXOfZˡ*#%<\5v|&׊eŔuSk@05>a#3Ѩا\sG?b[6OE8B=zo3+L=Tg^Ӽ3vjWm2m\or;w4oc!ϰ);w;hFFu7ߍ纎*'<]G\^˼veǹ{x?i~L[ɢRwyĽWmh߸F)3o:}́4k' 3Nq~tӧb;n{?lС{gmƝ>ahTl#W\e?яؔʖnJZ !أ6_˄S9HV}OKZyoqٙuF/v_!;uNweu;oDH6?ӑ1p]﮿~ۺNFqKmJgν\w{lat g}}Ą'M<x<Ո{EzqDG"ǟj"쵢#22,F.Cg_6ը~D !أ6&t !' @VZ{aqhIDATEɶ#Q{g}-w櫪B)ĕC`wF[5 u^̝z˴y[ѿ:^8g9ah M'y?wپ[6qRAt9@N"ǟj"쵢#22,Θ|\ 2|6t> L8^ؔH[xG!5,, !' @ޙק8'u^m+QΝpywՅjбvM:.uڠ]-|u畻ryq2{]gXhx,M4:m+wٙ7x.W>],VR}.S-ӑb_.,f?S}Oht{:^Fye?яJH~1%r /ErRREȉƄdhu5_]#Mvlbo2ݡ2aծB&;4k&&$~{xiO u}+Ȟ׏Z޶ltw}#B1o\ߞר]?1{Aolܢ.*w#C"-l߬} o/[&=wl[촅 pL\}Mxcm7w1?/ra īOt|~u)q9Cj׫m/x݀RozyӍ|6YV}.u>ղk&81F[6v{l6z%SE.~kR}-z{:\Fye?яBHMHu !'gr[;uf{65h&^rۨj[NrmPW6k/;wM[4uM6*#?i?A6hs·,vLY+J@N::yt{`$5cqrؓ@hsAIv\lG%/].eFn㶧 gL9n}iܼ3q:ih92${5Plr;)L?g0JϥΧZ9y'7nzX2!} kR}n/lTl#\X&C<!ԄT@r v׮C:ŭ˫jh3>F,G.ϊO> M o$[_$G!5!)P\inv ̷-l?a lN}iL\91!:Ff{ɔSW}vڠ x~\ɕծ[ɹ WdQݍn2GH] -ժU&*>+>?4Yd@P*'@q !0iݭl߶ҠI` ~ȩNI'^(qO0uֵ?w?&'%#ˍ+n]mF& b,P8E QS M24Yp , ehf(T^|_@2`ԡR @AD}ܩ, dm*Er` 4Bx0SPYAYTL3MH#4hr@ ,Bx* $j80*4h ,j00M6r*]M Ba@cYP&J'S)~&Bx0ֱ ,Bx0ֱ ,Bx0ֱ ,(,ǙY$Oh/tY:aRU)rl Р(g\i[3[=РA^{}vq2ņ6HAݥiP{1zIE i۳u|_>FY2alLW2  hP{/{?_-n{EJo s9kyźoɒȘHYYq#N e, @Р(Q{{mjט؃,@ۯ]J[uY>Lsb=n$ Z2  TsØPF>,4f==KNKRjE匽ql=n$ Z2 TJ*{H  QYLgI[=H)GȪVyG\>/mye~n4YʔuGue1SLvR6I-@Bh@xi7EE iӭ8ܯ7ewkZ+_2d1&5ɛtצ%vbgk N:v{gsh}ث^r\ ReC_PAQe@>;Ԯd99u߬F-yӇ_6ܛG@dq3xIR7کQݍP{H,Ae}@ d }W RhsY@<ɢIEJ>z M Y/ݎf?5uyHo9uM('N$ Z2 LNf\lK`43|q24h S ]:T˼ n- ^]E=Z󃼶6?iM 80֯-^E=Y٪P(X,j03YzJL&@ѱoB +?Y)s^#GM?ʛw^7jY2{m4 wdqg{,i&n rlaSoH,j8 J)Rǜ-;'e&%CrŦ7Ɏw<|rfE㖍Szk17,Fm% ׎qx9a X,j8 (>{_h@= oj(++e/Zkl,i7.Mi+>^!;Xsuu]W߽匾.`X,j8 JMt4np2X&}AQcog0Ƅuĸy}ԫ# <9q mYZ|Ms ڮ.UԳ)-֜&M+d>6ۇ\8$NnZX,j/ŠRNJ1`X&}AQᝲT;o`;,6 Z.i&+˪Vy|D4 u,P{X&}AD}hdPX&~ LBx0ֱLbPL.f 7~ P,Р"^>4 u,)rCM` Ba@c6LfN /4h ,j/{_h/tY:_l)R'8 P,Р"^>4 u,JZ6] PY&}AD}hdPX&bs{ I$I$IsekTAD}hdPX&b˄,c<1?$I$Ido2GT+tY:ɧ&M>Y>x2lc3$I$I,{p5X&jC Z2ۤ)'?Om,T|F$I$IŘz{Yd@-cC_ ߓz'6*>#I$I$bL WeޗNp?PY&~M,c_u-H$I$IS}M-t 2mBdi{}Bg$I$I$Ypޖ{_:!'j8/ ߓz- $I$Id1{[&}ׇ,&T|OF7۷H(T|F$I$IŘzk wmY@ g6{4R)\W>Bg$I$I$Ypޖ{P,&T|OF7+G$I$I$12JeۄH=p^$I$Id1{[&}@ILbP=Y+3$I$I,{_S{o(I_l*'K#؛z#T|F$I$IŘzk wm%2mBrݺJ۶=~RҢE{p9_ʄ ƍ GּcozYո*'׷R5I;#T|-Ӽ|p?,< ~>/qwa-|nx?}D_meSߞp?M=kdF62jz}[ducBkVuWO7nԯoC?idFaav3r|7g_-ا m+/7ҤzI_2v>ȱwdʹw-Oצı(Խe+,,&T|)׬{/@ΐٳ.̘3+Z=osyƛެneք 52)^;#ho^q>}UV=r?lYֻ9}%T|-fyp1ok3EaGs}qNWt󒩨R~WoO+\E;]@=Nq>Js[ fucBkVucf/7p|ӌ|3AWsu~Q"dβ2g;1}gw:K~{4dE,RοirDUZ捓cQ{o8WYX&~M>SpvqP{4OwjvsEp{kj!w#?H.Jy]*P?\x;?DnȆYsͳˊ+ԽX2mBKvqN;.6j7e=+>Ն aF&.:CwqyGB]{[,[*Z8s-xS=W*P YֱSŎFfO G\Eǹ>/ي3oe>E3s]ݞ|*#mm"ss]]3?7LQ>78Q'dQY"{Lnw?^.y 9arƵʌ;ޒE[ޒsxS&@.߿eEJ,O_da6Lٱa^a! 5ǟLM)/38}OqҨQ -[vHiy,y;4….#)Z%u6 I{KޣeҤ#c߾c$ҥeM=ֱÎ`I lˇv?}W^4oNu;ZNNυg9F uvKme% wׂUld/~%sd+jw8?yխC>zE}'/O[3ONK7!]*zrN;O]iV.!D]lQ.;I/]oeS_Ye'b~\~-u,ύҝsI͜as<1nY82ul׬O}Y亇s2్z?t rnjgֶ|e׎_\Wԛro^]Yxࡏ傇(Y䩷^y_=rDhYQ2~WYX&~M>SrȔ"+V:كƎI֬2UكܾMjN\ >w2p1D:wRQDz5]}i:cΜW}r.#xd}=6O6-T!<,kO,[*0:# tpNѾ?>odN8pS)~:M e)"Ȍv!}X|S7/I<Yq6;'Nqk1yDdu͸e0WڦEW^gqʔsR߳~FN1riÝz Yj~Wۥ˛pR귡<9F v H}8ۯ1'8nzuȝUυ?s|=Z^y(U^}M:]Pr':z82[ss"]-Zp`Sp^+4y鶧WW#cO4raC3Sq~}ssgГ :ۤIc9(ܾMHqUw_E9oԟQ}sn.s3NDZ-^oS%3]vͪμSjQyyݔ/VT4O_29<7k[h:j*жCڃOkw9n> {ܔyF u>v޼M鲗-{O>M=&-ZZ(X澧z7oqE+VkYC&?l[SK qW 6};]i10H 99\N-tD/6h>C_ڄ=43:c]uЅqyy-ݹu}܈/%t}@eE943BdkV:iQg) &G=r-=pfiӮ]|׃8?-~'me׌w#SoUL 9{.ly]Yw'>UO~"zkYwհвdb um%QC$I$I(q845(*ϖ:}JɓOp6lN֮* }bEYYY\c?5sm3۶M6lcr_K1bx[ue;hРzPW뮻ٷoߏLlcdtgz?ҝysuOoegM[ 4r'VzY:eiuݝO`;J<鞜,泿Nl{G79ӂ^ۯORt$ 1b`\Eǹ>/ٲ:3Wy'W5;յ}xY2~nss"\~KkܝWT\}vPz8?>|p}<=r\OulƩm?=,`ܶ:F~=tz?.\瓛c25+[W&P)~2xF 糖,˶K ι)>ʔ.sHf̸w-SxK~d#V~SY νm>}|V 6eAAza@q*jEɁ& 6KY{5!8K ;a֬Ryφ\5smWpx޿d 2Nٳ sL,cO?Ru'eY~<7^Q3X|-s~7=#~o2S :7nԕ2k㾿f cdO>5>2-n[7[\89p΃ }[6X-v<޽ҙVmFԀt 8%[;ou'jvl_7˙O6'fPs"\c Z`N+u};%z8?>ps 6ɂ鿎&Mem?阜ܶg}܈/WNL闕|r3q,Tfelgwt ܫ+ 糖,sٖz?]|׃8f3k[^Oبw~n ~)/C9w2e=?K\=lxKO"_ڃ, |=D*,$ ( S3~Z@ g+>_ |WӢE{-`5k >ȡ׶}Y_3v+c\/ʥKi_s8pfIOS,_Ahseb}cߦe˖jʓ,HFe-]<93k!̑F윺o5&BKlcE̵]9E?)"Wf{tEZ0zOgUWnuݝbgEu_{WtraWs|<ي5s}^e>zGjlk>-@ XfLȴt{p>ÝWTw=LW\\LO6bOmmlۖF_ 4O3 BekV6>MLӫsO\N=D=9u9sd˚.As3}n˜?d96]=L<{ ?|"sXz Y|-WdoٷToZVLj{{$  „oW>S}t>(4͆ jժ7-\4g޲uI|RVVM4]zA':}\۵l{)SܗҮW:{{yO2c[B+;jԨ6o*⊒]vٛe=d*>˖q ߼qN;M2P ݾZ -FKppR:nd8ߧrԂOM-<m㾿f}kRu߱<\L-g"?&9NNNbawz>{d{]Su<$[ĞX3%[O>﫩lhd-.'/WfZDh;u>-v1sw U>]7o|F N׼ixdېٶ-F_kgt~qc25+[FW?[&`B3˶@Ё.AsS:?gs)Nϕ\E;w׬Z+;/|Krʛ/K"ڹ*[=+qoZ,72eb um%Q  J9(xBRYԩStl[3)/kqOqs<5m=mŊr Fԧ4;찣72> {윔 tiӏ1]:@m&.ee_x+~>EdNļ}N6-T%GeYM~Bg2o-bܑR O"o9W7\4ybd-ri]vr}Z䘩ҟn8צE9Sw#,.ϵ:|dcSۤ˷ QӃ# 8#FV$šZ`8Ku-'us ˾p\mٖ|ܯK=19Z3!Tg5k>Uny:_ntkQqkA;OmQnDkt;]N?Òӳ-+XuʖQ??@ ߌrmjzZz?Gw=snVgwNzup{c'uY9{e3Ȣ'F.eu# O;F\7+ԽXF*,$ (#-T|)uoh'92{srES .]o\P^V^^pik\̟W`(41jUrk2Ёt}:_-zC5kcOoР }g\i)qگ"m׼y/JÆ&+>~D7o'G=,2kҴt}q2n??㎓o=Tȕ-#в&?lO? /LBCd 7?}][8[H22*;R62ɣF^9$M\UN{SبŔtn}Loȭ8nEW_Q:T٬ޤSl4n#FC=kOrs"v鿞%7⿽C/w>޹s!=~u;te\E3o'}Z6Ok{s"r:~y}U,ydk=43Gz|VM7Gk#onM-,tݻw2rD#HNu϶E_s%?OU谛Niqomq,Dfe˨f@RtijB]'z^ݿo/M9k]sS]Y &$_;=JV`7rrMe{vlyr+Ϋ^:ldn}^NL_5Pa!AAD)U征lL'FR<4iӦ4i ː!ƌ_n}iРkTVn#.ٹs2i4j^F׮GܹS)<3d]؃():j.?:qK~ƍ[9'mS5,kbC'mh?ݞV:O Yrdb? Ι3ѡC#<*Jcϲe>ߙR PO1rHwX[YxuP?u37[Ȗ5F.>i|þ.C]wi#JfzO-Ի:3߸=X相@;{24u@nnbv\E;o!'-8zz-WfZ?rNdZN]SC,mfAot}zd+ΏG;zk v g._`mϥF~.ia>@SFE@L˪qnfe˨ >N!2o\^9^:Yyt`GsS:?)(}}.(dqc9*&zO,s9*U匪*yNp\XB{[kTB  R?2 @{4RIaÆfΜw->xW,[[MŘyμ-YS=V[%E[8?]Yrk\׬lu^0'Nhl:DӒt0;bo˖4 #kv8(w!yZD~WRykzyr#O]i5ҩk $ 28϶NdF޹:b4tco`jiy{dLw} .ǟQY2<謏__w)܏,b#^O )XB{[YF*,$ (# P{4RپH enq؟־+|yμ5=3qC#͚9ȏχtg[˷?HFZhNM{Y;wφZ8knsXh޽2ܗcp?r.Wu3;dO8gh}jR&>3<}*TXHAQʡGpg(2{4RپH $I/瓵/9$uS?_pޖaAADBLPdBdi{}*>#I$I$bL5 ,jt${5~;z;ewK϶_>HݚIujШA_;ovqPz }PwhZ!:5^JQ[M^1)YSmOYyT4=[LJ'KPdBdi{}*>#I$I$bL5<}*TXHlЁ ^]P# ~21:քA֧A傿\ ?X't;'6[nyo:8$^ k+TzSۣXϷB_/ ^WcY'K^bﻲ2{w>KPdBdi{}*>#I$I$bL5<}*TXHlXY)MֱEӅ[aM[,E2rm).^jQ[!ގpmOko&91NlHBݟȄH=f!1T|F$I$IŘzk wmgyUغQ ~5 Y4]tx'o}0&Ԟok(8Yrʥ%^j+(׋BoGmv[7 >ǣPg(2{4RپH $I$Id1{[YF*,$n ~H<@iؼ4lP.Zo;KŚdI=[Juew.Ȥ& 9gH-Avmyjzjt=ݿIwuuLW4e}_x|ʶj.`GhdIBN"t,>nak E,m~&->bUvߙO -KW.{ۛ2M/GُbuuzynrSy/=NGHiW+۠}n޻[&0u@h8w<;XiӭoX_PVc=FV|nYQ>;qfikO?=]z皔뜊<57xF'QΫ\7bGx E&T|OF73$I$I,{_S{o8רB֍`v>cHcyyH.MVg(x#?[3Qӏ WZ0.Z~Z@_Mk{zudvq] MG]].Ͽ=?]yaoz)M_<o~]ܶLEM+b'u?lnz|oiꀃ'+CG* _]hitzhqQc&io;}}=e}ע7I6z. Zܮm~_n}?m2y2i-h!KY-ן%C~g?;qW;=S"5)9wykO9G9tY2\ Q3P=Yl?$H$I$IS}M-,O_ [76oaWy_.:7}MZ5iANYq [~M'ӃEq' .~6-P:Be١vSmQ5`[f 9Tw[_~hُB$nRpعǦ,wQ)Cm ~;]ߝv'2-ߜwى*Tz}(o(M#EkR툻y?rrQϫP;#q< u"*'K#؛PI$I$Ic꽯)ܽekTBbFPWඵ?7]]-`yJO֛H>{ow]yovgxEq'Mn~}ځt}d&"iT]?_Otp7h@V|{&ˮ]we+Ӆ~ۯLzSF\>B8_'ViMexelۨ?0ǟ$}Dȶz:}ukxbFeΓuvsЈsMȶ,/xx`}\N/nq[?yԫ'>[GlHBݟȄH=f!1T|F$I$IŘzk wmgyUغ,u ~5 \?F8/K.*=G:Z.|BiZ4/lyJoh:8Oɵb'+e sGykwP_ELE-;L{m֜iA>d{S4ݏB 9Pio^6">sϿLL,r}v4ƋR;߂ˈr{Mȶ,/yܞ\$r42Q=W'>ޮM?l %M$gPdBdi{}*>#I$I$bL5<}*TXHl݈[ES߮8.^& umӂcwh:moر28:Gcf{}Z,`_ͺuTz Ҧ0ߠQ{Kz N퓭X<~Lgy2x񷌏^?\.eeɧ }_m52]}=4͛gڃӼg@LHىQGMvs{Mȶ,/ywdZFύ(¢BĶ(Lɒຐ$I$Id1g-,O_ [7)tx'o ,O񼬼.N^L kowzEQGm{Ч}vqoyLLE~V{چj7ݍ MHi7ݥ=m+a'~LMuձ~'-WuZg>^cws-nϵَp=S͛mY>;qÜegR;4^/&e3./ygd[Nkp^w/oeAlHBݟDNno!I$I$b|Y) רB֋_/O)]K駯?.,8O6khOhRahҼ]s9zvu_e׿|t՛ְyCY[u鿛}~kN&ү Oէ%MVQW|^$_<Λ {N\|7]{yaGJ7^E=N]lM+u?f =&:ng}gyNn^X$46h@:~>BfƕL{y>lwk9cR 3-+g'9sy-\QJq\mGe:o`}i9?/yd1ƱvbGx % %tr{ I$I$Ik2NQF*,$Z+FڷWh!~~ONؽq;o&[8U۞m >ǣPg(?.[p}H$I$IX3_aQ1`>"VZj3s]HGԞr>v}}{[퍨՜OоeeeO ]$~ %Yׇ$I$I$5eYE?X.]tצ4 qKYyzj\UVr5g+=8M6)4}{[lJzొ&rC j90Ȃ('X,~k]pERxXGiи4lP ok\c?Bc_&~֤# j=8ESB  R?2 ֳS+AA{$YP53'UPa!AAD)YPB56|A.M+UkCt9OδNZvRm͗kB)K]B}sv;[輛дP?7~V#vk&u׵7hk;oʲvsh!nݨ6L抚skG\V^&M*m϶2~Db0*,q3xEZlRkS O_w,{o]mZ)Ű۲7j|~L,-dyآ} o}t:ׯA傿\ ?X1 ^Bȵ |Vnz y^zv=q?&\u%Y^\47n$APaaM\ZZ^no_P{m~Cw}+vmY'7`5|d+~6 kLlקYY:Qu+yQod:jާje1*,psӳKOK޽$Vb;_tw:o cB 9Q?\B5FLFF{P{H,APaaXr&Yvagu^E=iޮt;ĔKYjmgK; ]wIwM /r/V{ Jf ޭ2nR Zye#BӃEZ{cM6Ra}C=9Y _e-z}xҰyC{.Z8'S-܎^Qnjg?;@Zvh) 76oܞv -WcS]M*쾣c(5x\34jzFe}_u{&KCrlw3Cۧy+Ms0M9ޙj>q-<]F?Q[]7~&QfikO{͍(מ8X,(AtMnvvѡya]{4ݥwk kVgt#ǟwYft-{X[0-TwMwm*K^b/Vگt^swh)^'_أ,gPN_zʲ곗 ]:Tϛ{xˏE{.}ntK^qQӏ~W]-~ZP_Mk{zudvawݨǐ<'3n9QGר}MM?Ȣi=ǥl/ WyY6wʡ.̺ߵ-wlciuGϤ?j?m}d*,LZ}5ŲZ E{cȺI&Nee}=v)yTJ^}}5귁۰ow~e36-w_ iqO[=l0{6yE҈sշ^/7$d˟sė0{^|w{utHw]tpn1ޓŌcS[uMlYW̒--u34I@v'nGhɒ$ߐx@K_=;usɗGq ʖ;uG͟ܜ=\fX5+z}kO|O5_^i_Z_/'vDz^p[oV_>]uGߴyd[wkn7ˋu ]L4z;GO3u𼘖7YLlYWR,d&$Gyq.ZtQޯqskj{u$)?n7O w3=YX=6xLcU>W_{n$$ hJaaɋŶ}i]ղn9V\UQdYb+,fE>=ٶFڎu[' #%M)].u BH*n#~{t]/S.L:!44cR}Q<\x͗ vnOy|0[z=uܷikxSASCf]6+}1'Yߚu|v˺WeƄO^>ws^mݞzd"u?xS; \uQX-/ٞΆ7J4z; f]Q)N(?~ۖ[ܲ.H?0}Yxmknǽ..oܫ#ٶv]dN;ǭuRL/d1cXGQ,u7n2RX.4sƟ/yi^l5 ekvmKxt5}ߺ-}oxdڼiyf8sʫˎ|Hx݊U;[OϖW˻ծ.܎z>Eqܳ>}rܥsuޒ//Fzݞf7tYQg{|Z]|J}yF$dKgd}le_:[ܫsFnHuQ|Cu:n,ޓŌc 'EQ^ҕc/Vɺ$ hJaaDQv">+rєty<跎}~qo2atȿIv¹&.w?OΞ4liSߘӾ<"7JGh/yϦ}Oϟ.`F$ OtOz<i7ևC&󯞟/mih- G]^DZK b1NeE NHe<=>sޟ qmK{6ɞ\txDܸ|}XV|=&_tWEw]y}؇'oȮ,ݎ{\uwWG^ɶd]Oӵ봿{m$Kd1cXg} u]W٧,S7Ccd T),l(|=oN~AZ QDEoɁbx|<:F1X;'gB5Ici(`o݊LH^57}27ŕ˳Fa{<{6Yo)^4V<ѻXeo_VtJlW^ǃ`M8/bMgv~vgUMG|k/Kn}u#D9K` ;euG/ۓkŧ+t^yeĸ=LXVm]N(6KV|ՕuN=[_|w{utՑnHuQgN[OrDz'K:|cX˾Sdq'LKm^ ԩ'ҿUY?B?_^w^^ɱx+Ƽe}\eee}11';1in2RX(g/ 5t޾:xo?[S7Gy4Y~g2U9m]yÕϲ=Coؗ; [,*rR,>wd`@?X'rrMWa.gf_=,Z()~u`??]̅3Lƽ*g+uɱٞ;?qg2~p|~'&8JdhL,*rWH]lǾ{OLw& ^:N+ɯ=\KIɄ5;t,>SqH<ϤzPrB>3RX(""">,*""""MN>3RX(""">,*""""MN>3RX(""">,*""""MN>3RX(""">,*""""MN>3RX(""">,*""""MN>3RX(""">,*""""MN>3)+$4M4MN5,TP4M4 hrQiiZPBM4MӴ7EiiMo@CF4M4Mki?3IENDB`java-binary-block-parser-2.0.6/docs/jbbp_mm.png000066400000000000000000003737311445000440600213630ustar00rootroot00000000000000PNG  IHDRB#CaIDATx^Ea9-KΰHFŀG )(@1UD9ŀgVPTDPO<=y#zgjfv{ٝ<خӳԼij" PfE@AH e!1aP@FH e!1aP@FH e!1aP@FH e!1aP@FH e!1aPB"EQ JHJi+BHLQ{@ !$]ŠST.Bba^PB)tƷnWPB)t|͸0 $BHLQ!1Bb*EH ST.BbaEH %Jw+Bb(!U@X@ !$]ŠJ!Um-}Z?xT:ʩ*]#{OebTEH {);еT&A]ŠJ!1!]{OuxcuT抐@X@ !$.[~?6_Ԩ*4__oǪg䫊rsTFTi7kk7^Y^R\j͊+:9L>>TڪUۙRkOT{7ܾjqǩ]j=^%e]E==p}\7@x@ !$.%ذԼPƆ55qUvUu[/믗+6~L쨗%ұs!uGѡ>NXuG:-8=gyz\Rv(l/{>ZW_{S9+]G^7)FˆJHvTʄhLV?=_/˔Nma#t ,eyUZt[ uܝ,]>""LKpY>7u'{gv1*.cؐ8xNG#6㒲Ca{9-lݾ8UزNlyl~\#BbaDH %D$;T*keBbI*mu#tZk3yl瞹nkpng\ocgrWަUM1Nmp~[v%UgH+}>!q%No @^N|SCBz gns(#Bb(! ١RY+wd[i}B #;\̔ Z^dʄnS"HN퓕lo[L~Mv2δyHBqفlWQj&d):^p~EH HdJeܐ8&Cb ,ݠ>맣Ugm;09}:'r׵nVU&LNV2uC5U{s Bᯔ@^NVo}pZ/n_rzu#$FPB$@CV^B▋e28ӧu;ajq9u)sFljv e;=\0HUZOz̉[rWn=wl';)1wHqفoӟt, )=?׍rJ)!10"$"*wzg8ĎKdJY._R~2 +ԫ~)8Y'W@TwԞۂ\a;_*#EH ˟^)$n~!|j(a]I;Ʃ]UTNAy}vWf쯚mܶ>lWRAx>pU:!1"$k!R.rw_ۧ-t{mA /'$B}n_Ý1\LU{PK}NOw8!t rH|T} GP=KXWREH ;V?#_OPbبAt K=~sR+S[E5{ܜ==S5]Uȫ?jrEz^/%DDYQUIQ -nNq͝ 流MjJ{^-n?LР1UaU]GU5lJ-kw˵/_W{JjW^?Y^R\zmV1yٗ;R-tU>'MY뮷$roSoTڪU =*ˆ|(XW4VvUupMHZg^.9Nn`JfzvyݿrrW[{~$׮ϝ|zVnNCQ~}+8$$Լ{^v[&Ps}\cxrMH,QBq[wDkĎ1**'ߙ<l:@]oys@:~>> uXl?99v:%#Bb8CbNmȭwݫ-r9~swu_ c},]ӮؐwH~wcIlOH  ;> hmw:^/3ݚBBY.';x'cri;;6fڊ!޹gW2j) y5=LڛX,=F-G{e %|au_^Lcc1 %@6_!Kŋ.v地܉-S{TZO^/뾼<ߙ>/njEc='J9=_"ܾ~`WZt~dekɶˆ|(ΐ;uH%L쨿HsTry ؤ_I`Vf:̩lK`UryUsu|Rd;Cvgٟ D_אXEٗ\KxBdkC@OoOs;z#~>G5k~a_lHr纙JDBZ5WY)ٖ@KrVw]&e5`/akr:PV03KxU&zX)1 \}ƖM: .عijruW%c}b Ob3\T!q!weK +S&6˽5e_ cFUu*5)n;$rPuGQWN,nSe!1"$)@XEEH Bb"$V!1EQv+Bb(!1@HLQ]Š| $(.BbaEH >Se!1"$)@XEEH Bb"$Vߐ]ˆ|'cJE0 $)R!1EQ^ €| $(K@ nJQT).Bb!S!1@ $E" Me}.#$hܡ(*pQ j_+#d!1(-@#$R 3S~ @i'4!1vHH@H J >TEBTGBbPZ FHLwuqU3S~ @iAH @#$RoUI"$LeyL $!1 Ke;\$$LeyL $!1 Ke;\$$LeyL $!1 Ke;\$$LeyL $PT HYaEw5<+#d!1P7EQ~;hTX*"!qf*#d!1PSa/Bb!1T{n#ϟN>yׯ_ϓvY !qf*S#d!1PSa/Bb!1D~z5lP5gџR}i_xD]qt?!qf*#d!1PSa/BbߐNؓS>uu} UZDTIuwtABH -2~C7 S1GahfaqեG楽yX!'5!ͺE~ )߯`/g\AsO R%cK?MlzDM B8kyc07$r^<6F}# #aֶ<̾2SIu MM[_p~]rA-?%nSTv~6jڴiz?r|;LU~C⦓ QNp+?WqCrIџ>m.د s͞U F7',Ω,y}y9^:y !$ءE04yøª$';KeUβjO`Bb;s:mrG,6QsN#,߼ ~픛6A)o9m&H_6L#\9.yG={9oc[ݼ9mv/XWݭ?;mg΋ssꣂvRrM̿;mRO6Elg`3tI7&^/SN9x*?!/ETN5g nDuZQ]]_Ωw:}Z_l#ٯ /h}V8g9av=7:mmDT^zg)# !1P(*L!$ɛƥ* ^sBOƴw$܍oD !M}Iɔ]FOg8ʹ2,&0Aȿw}<.g~SWo+6CB}{9ocL]a5Ų,ۚe8Bh0*Y/JVWl'eHM6Elw=ĕ|8/ 8Zw̘_~ %vl'bOHa}jA ~nyӧRz¹W%T~&$+̶9m˲l[VFD92_}z:STaGH -2~C7 S1Gahfaqjʂ/Y9$ e]&s:m|iZnL0w\f9Ŗ RcCi{WdzYzMy+'$r^<*~lRMhoJ`{=^qKc,sI}L`2ebڵkǕ|@/i>IZI/{jL5V~&$nw )WX_ !/fϞϟntRm6?Z;@pe_ЍT;LHThfaqۉ!]WW߄#n,h)L_EԌםuysD4{imlcȕTXsW Rc4RϸYSkbP.^6؏͔L51uMD{qD5;z쒩@;۶Y05HIc;MluWy͚5wUv $ء[i-;7oރ Y\p>MXWRsT $F*4yød%`SΗŖ 1^ 9K|;vܤKv'_vc="g?Fܾ69Xx%mm մkD<6n5K.eM5'qz>^BbXVϜw>2兜%SW~\8LcI/'SXȲYN@]ؗz)f%jΜ `/5wejz?r|;LU&$vf_9E^>W:sK["ꈞXEU5=i]ĕ[DTyGzt.S&$NUfJؐy{9/!f ;]s8[w1 x /%ڵ]ywI SM޽[GoʄĩJ•~!,N#/5zDth,} ܻRDUﴷ_t{ e~aBJKՙ6DӬ[DMy:q^Kw`ȑ_mJ5~zwr|;LU~CbJkgʇJy~x2 DҧrfBR^BbΫ+N,w7՜ҩ}ym;wT՝wީ9眸o߾K/UO>$T8Bbl-{Ss[\j"_Mtz?>ě7Ə;Z84uKu=5kVpqIo:j؋M0[5S*UV+sӦ_M3=ĉW>jР3TnncUBEU~#5xxnisKer ҇&ov2!ej_6wX֏9B=qJy,}Q=CƍQF/.c@H -a ׯArݺaqJU~vHTʕut+Ye؄S\A&2=.]R}.N霧sSBH @7 ;TSL lvީVZ7I >Te+N:Y=jwb<+Sϣ}i)K.\onwBe_-SgՑGQ]W_ oyke3θD' g\䶍wnV/.ܤI+}>RO!׮?תU7n?Ӧݨۓ=&S:Ȳܕ{l%u5=fcN^X@+YLѸq˴s_!,%#cp!1M,0XJ<ڶmۦ?|ըQ#ժU+`? !1PRrg쭷Q~jӦ~,2c66$V~!6lc詺=YH,i9sٵv}g8/sovɒz OY=MIEs/6$e eYJ2LYѫW?w;/᫔|I۩SoUJ5~a:tB>Jy90< ]4y8 be{`IBO*Ւ%KmڴQWW(E/vZ}5`Xrg3K/t!k:urU)LV'}=tUVi.$|0a{w|ĉWD^mdLUQX@{\X`[[tKZRa#;hTX*BTt|jȐ!:>}_.(Bbl kHnG=wشr N>yR6T8*Lcp$_`'Go2?_ء[ة?Qowy ֥SO4yR<(*mP*6nܨ5jF{5RO ~?jԆ e~ٳoFL'HEHH~󠨠VC뮻o^2?tB⼼ 7<z4v1m͚u{ZS'W{Q\˖9^խ@U\Uh&MJnN͛Wό>Άzr#h 0av5S*UV+MF=f{̉(2 :C6V*TT7RW}כ`הvv%ϡ?v};uۧLV/TpkUE5nR͘qGv^=*mcpd!17 je}>* &L4h^2?_ -UWRMvLΝDYJ[n}th(,O<ݗs/ U_YNvRCNmnQ嫯^6cVXI/{ݮW~oM/_A/Dx.{?rk֬nYv7e v&$>c;ǍTŲzz})_i#3$E>_U]t @)'d ~^١[J7$9sfE jeYP5!jՑG3~cƆķYN5g\ۓąߥ4iG'`d4w4 NZx> 6wJs!g}>&$״^nz=]O{:%"$F*6sLo޼^2?_ -U8OTڒ=<&>6 e4JI(_{*sZukڴu{s;|cm]wǬw̩ST:Imܡ+,]syKS^߱-wn>&$k{ŊlyOd{BbBH avR]tQS{W(%LW6;tKUrԴI[8Кlou:U6u (ݷ!LgawGOBaΜ{~%!}W":/sWrvK%$>f䓟w!7m?l]eǔ̙,<ʟrozK oږ-{KIT^ٶ=m !1@1xGϻ^2?_ -UI#e47.~^ZEsͿ+}ddž2|,Kh1st 8KoE^PuU'0V/?$>rnfBb͚X/mmMvm~|1:5R^[J ,wpYo?Lq.i;/]k)r]7n)n;WZ]ogyswu=>lKHTɖ-[Tյ^kP ׯlv薪N^{T3ϼ\]z ׀vժՀ#*J<~LK;>vHk頸N\Uz-u)uC9 2݁&qr/8 cjV(,$WܧϷr`#A0X\ر딯֭vv%ۏYת~z'lgBb9ᇟ//zzy)Bb;6AUBO ~ ]2]|/A q|m(ou5Zp]z| ]( !1@1׿4hJ{2?0RSLyСnVY/~9?Uxtӓ*/)c~~w}ݏaEH PN?tզMsN{2?߰2wLU 2| ڂRECC/,atc$"$FX3X>vm*!'d ~^a ).Bb | @H /!1"$FaJȂ TnnOUBO ~ !1 CH PB+͛gB~B&KHLQBb$MS*!'d ~^T؋!$(A˗/J7md2~B&KHLQBbk.ռys5m4{2?_Bb*EH:KuAٳ^ DL뗐 {0%GL7nh"~B&KHLQBbsNդI5sL{2?_Bb*EHdѣU=f!'d ~^QT؋d߯?*!'d zځE U~}u뭷ګDB&%/S_;p`daj7LLJ^^vFQa+ $ȒYfƍݻw۫@B&%/S_;p`dT@d*dP2ݺu+E@JL(rqÆ ՕW^i <^$@I`E`%S  H!q!1PΝ5jļ@24B/Bbd]VHݰa @24B/BbdS뮳W8B& x@<FH\%\{l(^}QG!^~ !1@#$.ko5ydo78B& x@!/!KP{ ]o|/}nS1a]lxQ?E}Vu$dٱ}vU~}u7ګ @0yS"$>!VC[^JTQ!K]gv_OBߓWx us'K֭:f'd ~^g@~UO~>'SĽ)-Gv_Z/ſ_zڏ?JXN3bկ_?@@ /!1E/ylݺ5,-!Q']o]7[yz}YOqo-_xOzsgO ^ ktջt {C5%@͚5K5iDٳ^ L뗐3@e:$6swid?gN/mum$6.~w:=zҿXjŻt&>ag{~W#ޭ뺍NܶKIgQś7=˖-X?X2?_Bb_| >Bbɐ={{mⷺ|!,YG{m^^~UE?{ ϿOה܉__|6/y9Oa5$~S1\?e|%s=q%T?*w~ @ /!1E/B#$hć.ޣS0'{C1!/~R˘pϏm~[ܞ?Ϲk~ +/!񨕻tovS9[lLI\vtJg` hm۶UӧO @0yS"$>Bb5$~OP鶥L/˗ɲ׾򍞯'>;q,,f/|.*/!ޝ|eVo2#dHN =Pc9F 2n@~B&KHLQ8אxϣnO[Sv/ⷺ/8ݻv׿|݅N_.T!/jݺƲM_e7; 8dH@9խ[7@^oi 7oޓFQY^?g"{אXߝP8ogûҾE/ö́? dOJ/ۙ2Ü׾Jg뛷5-X@檟^ `x/4į[?CM~s:xewh޼e֭N'Uju]Æ's&@y ?Cjׯo~zK#u_Z_=Ͽmwώ.EޜPWkw/d5$x/ն!/|L7o?kE[|~m}*{!^~^!$>8 ⭠g=WWTv]YgJ蓉9CH ̔7o?kE۴i~m]^ `x/4={%$R=jԆ e?GN xjºLϙBbFH\@Ѿ+ںUB ~+$9sjӦXjР2,vyyMoqSc9m[Mէnr媪E|5iUM@lJ0zիW?UZMUz-}n}ԩTryxm.-?Wz\nSE5nR͘qG\5j~gߣ4iwt7&L{w5}u]$n֬{~>Uc^7g3u>rVS1VQIoƙ.ٷϙBb&o$vIoy}3P?#A3g @^ׯk_kn8װas0]^BuGʲCwhv}n]Brv9L; ?WRM-YY π#t(,VPQupզMg&LH,sݻo M?K+φ ;_/oPRw޹A1wue/KT!qC>G1HI/@KH.mS\۞}+w"UH:ILLd!&U 9=J_Ԇ>h@{!qE/}ݫu뾏%$~;E%\=!ꢋ~}!E/ !w}v5k 弥Olm=fBnLH,i[w\ry=_/V+}d* ~RO@KI(/BbIa @bR[^߼  bCb)T/v ]f;s)b8My!\%K^Oh+*$Ӵ]f;NALH,9~^˵3^^ڮ/+z*$}^Xjxe>fo詺=UH:I!1 >4y#CLx뛷r|{@A=m6{)B7ysǎHϻr{z\i3S8ȗȑS3|rs6˝-ZW;+}d0&.`0V殕eqܭ!z7oay?Fyqzz2ŅrǯVih׮:ᄱjӦ_=]W/}OHavqHl]fݖ*$za @bR[^߼  <}Q\kƍ*R\oqScFJhL/I,_&ms\ӟt%;kbRs(㘠8Bb lL)5wnd!rmЯ~^ @@-q U~NTr7i+^ԙg^.v}>!UF軌%@yZGرjݺt7?8H=|qGH,=~\bN*UH,U*SNH&MZ wKy^ KP\N^:jߡ{vyN0!&U 9=J_y=+vء__7| @{!853eBb 0uT|ɔr'sHOJÐkxɓ j1>BbFH\ ee͛7W3gδHq(~~^S3S~Ka}׵,˴??!qɗ7o?k/|{Vp3ϴHq(~~^S3SOlSYuyed V' r1>BbFH\ ee  @{gw…5Rb|wq}0f]6YY&wn ^I8,tq*E/GH 䍄*]ݻwիSUÇUS']ydutn{Mo(rm޼'8,[ha7  /;SUY,Ucb|B~c)OCORg_֯H}+?ey԰aC?75eg#TTJUT~~w5mڍA }R[?CM~ֲe}}{P/רQ[/qcޮdt1H,M7ݤ_c;wW  OSR%1 n״{CevO*J&%K#⯿ڔ '+7j)qw35n_js]-_:k]]gϾ X\ڰa^١>ٱ3] f{U"{!p $7MvO*J`k=DQw۵usjذa ۴xZ\8H]W_Ёg9ʵ)SkR߳Lƞ~i{ Gq͚eطIzLUN;yf?-{Ss[\jt\&MJe}5fM6m56=ztOx66rL3=ط~\rӄcum_cl%ۏ#2-߄ U^^kW)&ݒblBGDd+ʕ9s.LܹիWP$;p'+ A˕+NUTSKl}bCb3` ˲ d`'X:/;62ٮ}nwdkC>J?s;nI[ui]3@X6j=޼ydz+l9ٱ%hZ#]^+:Vx{<2tD&mZrrrM%26Bb/ϟԋ/&?Krȱnpߘ{/xMO9!d`(Y!}Vx7a]ʿ۴wk~֬:.$^E'۞Kh[-fe22_SO}K+ eY߰asw=0\3@aȠO9vE-';`Ȑ>l h-ZP3f̰!1^%Ϝm3au|u]sq%aBP;yuEUO?E&I5fr^jiG1c.v[1r=:7Ѯd2yD,SuRO?lgJ?CC9Fl}%yz9/ eYJ}SE^i9Fls{J/q/Gi%ߛtJgl_2{'iI,e'06]u%&w˲a{ƽnr=k|c!섿Rmɒxڴ>r'w^~7'Խi6vz gͺ;1_aoWrcKm~1?厂l:tz95guUVe۶m~ʎ:J1n@xTH,!iSSK:~wkHh^Ǭ^Knhe3U$3w 1XD0m^ahSEǽ|3^&%ٶߛtJ%$6Bb/ $iI,u%#;-_Fh~o21JM%D5J߭!_&Bvctk֬zY mMvd5i/mvkKs2'|ўiݗ]QɎ-5iҕcڕtK Ք)S)N8]7k,.u6mڨ:K͟?_\RmٲE'>hg<(?{PTQOz Et HM)Hދ@@zo 5{'n.]J.|ܼ3;;{fKP o0ޟϖx6h֬)_b^c ?zƵe}m36(9J#V>{6iRӻUz;6gGUj?2q=#~0ƵΩOr* &߰YqD-Lb&1P/k֣nV~A'l߿E{yJ8-~+K8YK..0oцՃ9/ oLffڶ/dmOUe[8*ow|Qq$>xN:ejykFm۶jժQ, ܹs΃"w ĉ}g8D…0KP o0YzOO疕 8,Yqe*۩R{?y%x,@<޺uۊz#mc&1P###M7>5)Z+86 ,RM <7bN+;G 'ש9Wq"L7o |'0yf.N{a:IdbV72EQ޼EoejmxL|7bv*ڷ+{D-z#:z(-_\=/^\3yUR^hٲet]xsɑ#x w! -&1UjR.LmC?=z-[l(zi$ֻUz36gGUuݏ}Yls l-ZhYzG?p=08㠠T~q}lK7S)88Xo"yS7j/oX9n#""hϞ="?(Wy*U*aРA}W2tP1gϞU/w! ~j\Wݏ{q${0_58$d0g͚e}|曲W?S,mGΛ˗/]v?~qfɒ6l(r72yd1[nU/w!I 'wI )b{0"?ZЯX柛QD Ē|7eqѼyGD>3+Њ8*O$ШQB Zn-Ǩ(GYpӧ*w!I 'O}$8|WzxՃ1\t)uԑ-[VԩۛWo( px%M//_> WM=† ĸۧVW}^y?>'|I27 CxxAҥY>}zС;wNmV82_g7nT^ &1Wx,LbLbİ01gI7oNǎS'Olj pw!$ ʛA4PI컂I C&ɓ'4eaJJ-}̥ܸqC\gӦMS^ &1W? ľ+Đ;n?~LGlٲ|@fRGx.^ŧ>'`Ee W=;a(>$|WZUp2dW}\LbrB&1)`~y7 Cz3o+XXnԩj|ԩS'5 \]p=0!Ƚ Ic|' =ϼp5о}{U-Z5k^ &1W?!0}Wz]`!癷39?Ȝ+(_<իWO W}\Lbr|ߗI p ľ+_ޮz0w,>k.ʝ;7}.9իSʕ0 pw!$ ʗ}Ic|' = Ν"EPlhjuhԨ*UJ W}\Lbr|I p ľ+_ޮz0w,>J%J,YЎ;jiժ}jz`C{LbS$]vՃ1Js7nݺE%KٳѣGjڵ+ʕK W}\&1A} _~yX]F yŋj0`KN w~ A p$yiUԪ>3TVuBX;VuRz[jZ>rnEhf}vnIWÏӨtq]E0!Oɗ8u}"pddZFڣG*qw!X`C p$f35nu6qQeo:wVyaғ-V[|[{^DdH.xD__6ܵsD0!O 4iPf* M`,0!LbS8csGUzZGUmcJM{nU'Vf[I&-NmVx_>) 6] ?BwM}ٻaՓ=4z4Wf3W5V3glՖ8F5x \U{_6Ϟ= %"/^o2eDOygٲeZ;~Z0.Q&qHHAN x0NIݒgY/m͢&1=p$m{9{'n1Ux/DT Y+Mbc#X WuM/z^5מZs_!߭ՌgVmXldc/>E?'Xls|SZ'VJh a]@(|p$^X]{(w]&<WM MMh1aXѹ9͈"f$s |A_-1'΋rŬbI~iY,69Wچim{5Zm!gۓ3a`<˗/wAtǎb-[UB/0p$fhC{ZKv[I<,>cmWe^)ѳlY_q[&۳9,MFxfUmޚĎמx&q9#VmXnW:x_U~5kVzZ+ի*q$YQ$K HP j.SoeIƻZl~6UgrYl>o;jWt[&4Y_nF:@.GѳhU&1["zE6#"v&#U<{5flښyl`Z&O,JwܩVǢ#*I ALbp$('o3ٺX EzVa"$.b}ׄ3g^n$f[}]=i/WW1`UV~Ub}L]2ħȻlk_!n6)6&1X0}WF|ӧOʐ!ݼySO?m(LbrOI $8Lbߕ7\pA>|ZeEɒ%I&ja`C{e}3&1^"Sj.B!lNz,/^U|WߪaI AΚ Lb"S$]y ˗2VJժUS+#Sa`wp&țo@&>O'T 4[lcS0}KF$T`& 7X7j4ٓfͪ&!lə.]l57oPXҤ)worMEsD9mLyƲlx ](ќ#G^J((|Äm ,)ĉrvCDoiRL%qifbaطn} fMkMg9F9s{q6{ 0SkרQg-6l&s&>{e}*Lbߒ /a\|Cu2e~UPt0$D ]XѢEjEyp\1_XޓE6z DZ˱ɓNr|Dyٲ3œe6r-Ck ~"6|jРBkg+݄4Zݰ8Fi͛i&նMXDi.&4ڷ&b]f9`WFݧ$-ynPI p VE7,zVʨTľ%# *0Na~ p9yxX]`ZE&Luaaaj$DʘjΜO٘u ZuƎ]'b2r KXTI3ٖx./6)g|N gtLbNY!c[DS^$2tBHc|Dz/nEc^)oPi4h)~s-L. ک:"~lݧFDųLkkS-[6u-YYa$ ᣏ>۫a:ux U&ڷTQZNFXƌ9,kٲfOi+,9%l6ٺ!WTJڽxq֬E^c+̱ y4f908"&MITq6tW\Z[=&1k8o+b$*kn1\/-:A4~ ն+>uTv g G/GO.sϯuŵػԹ(*U:7`2;͛S…0͚5K<^tIxĖFG!˱ڵ[E'N*lby ~x~g84s :t G~}"4,՝x~}:,I{Ag‰_K2G8w_ߏBư=q}ߊS LbLb<Lba_ʐ7oZ9Ϝ9cxFh3D$IŜ:BgSʭSx殿OrEL-Y¶x P.NA<߿mPkh7oy((]5f*R%O/Dfy&=τQ)Ew^vԮ/*[;>hcji yVG_xkm.xJ-Ѻ4f7_\h0o\me _6&w &1&10/ewpGV/_\ď;fxdCqhذm{>ωomcM|GݢsӂlI-?FCoRuwi@# IA6-hwZLlG$A*:9]m"ժ hSJOի%E~)cb6<^U}x;gs}a3K$?LڴL:GGssdR?4iҍGC&]lnSwZ x-7pϟ?,Y Y׬Y#dwrD}`OeN;1uvAf.+PYa9j9 \sǯ[4jm.>,g4MK?[z4/ZdMbH"Jɝ / ܦZily]m9)\x:8 c,~f)X0eZٮ##Mbn)30`ϯgSr&1&1QF RE,((H<޽",0!G?E:O' Īm|QRϵʫ,2uZz:.@3ݦA7iֻ4z{IhG4S=5n&qĢtG[6/.]$ 9vEJ=Yl#G^4yAENaMq=}rرDG{=qb(9 9rձ8r<$._-曆3ܾP[:I&1&1ѿʚ5+EEEi1iDmݺլ%Ӭ[N3:#&IoS8Xs~YJWߠnS}a4|KqO_Pi)S[GTW2`*WX(߶mZ].<. +ӥˢK3O)ًXZEY\Ҥ+Wu֬j/Uڷ4{ bM165yKiR5 ӧOk{ƍZ<5 țg!;nG}ևQw׺;sm/%qh19.3ߧNSYGPkz~mԨ.-[J7ozc/.]#G_6JӦ$al#ѣWYlk/ivjuk^9m M)Saq6pd1 \b"#9%y;.00gϊEi1~&x$޽ՌC{b#C57 / .Zt}  z@MOSGRcUӯG4{>S8*i֮LVğ}~۴icuړw}ڵZ66SZ;is m<Xɳ1,qw9vl&ut$f#XTx„M̳y;N8I I Exefx]HHx]bELCqeNSWKP&[ΝG2|#=}eh#K:kSgE~ɗb16kذLlܸUFJï;"߿0dm\{ѺѨDmJ7GϨ٬DYsK"ۓЄӪE{O-Iu?.IܷTkNFXƌ9V\o6mYÆ-xq֬E>lw̱ i/gޗ#cor_[(/ /}mc~co ˨T}ZԩSVǕ&e˄2Rz [G鑞T\UYʓڵS&qRUECZw,Sc#Z:/N#MɇSŽi[G4 4р ̅gͿ  `=0 @vr;Qar{̋Ic]zd"|>s:I,SEdɒJ>ey#c%JTF3/ȱخ$ n$2:wL ϟs5k44\-[&c:N% V6x&7hڴ$I2ڸ>}"΋ݡ_~/bgimeXq-n e0Lb)67o ER1A4`sj?/j{-j9^z?4ŴsgU4wmLjPѵV&pl:~l;Qdd˯gρ)˳fu7{Auh/O?ƶx P.NA<߿mЪ#kҲe'KM܀I $2)UTt=Qrx1cIDATI\m2񿂳6mfJ8 S YL<\TcgԨ3'zOh]ڶ#/^0x6yE4*f#&K*WN@᣷T%M~"0_\gAΖ7oPժ[aΘ cR3>96fj KG TJaQPao΍1.-_u6lE[n mq~Z4jtڹ+ff#cө^=?q}lKR (88X\~=)69;U0p^֭[Ń+ϔbn޼)SLQZ3& Yfiע_Oju+? xs&1 ( OJiٲ3"(k"ƳYy{6ln,Z)c<ەc˗bc$.Zm)6bgVڵ??)b{gLYmx1<.w6V}+P٤沚8t &g1#ǯ<3ϯ6bz=._!˗v-˩"ńA,q)8G0qcWe8Lb/$[luDDx=z I\i2j6cb)/Fųk9r 80p<6~؜94gΘGؠݱ#RƎ]'^l_6e_uqimg>m)6k L5X3*ߧ#k1ٜ缰<{_hgωbGLnbG.]jE<~[F ^t)uI,ZlYzW^e&1R\9'O#F(ĕ&ϐf-M5_Jfk3(Ks6lvlNלG|;.Ұ}Rl&qb_:Qe{ܹ--\x\?eVƝ1Y5j(lnjar 90湔m- 7t _F$T`x!͛7%KQQQAvȐ!J+'qh!C6Mn+3_ifgSM}imk.(议1m8M{4l3rd$޶!%O3f(s~WNga^. ɘ&avy?˗M"Ʀl2-Xa$ĕׯ9-OHynPI !!Ca3iҤA)ĕ&Ӓ%D:N gn\ P*M9jI ݱws-AΜÖ-6[>LJimg>mIl6GT" rcn}{O>O%fr ^ &qEߪ=V=pm=lψ\ע&9czMbN3,W=-QF+_G]{R׮c\)O2  7oxxv(axWLr!96D?(%M\m+0y76(eJhcq1d%JTsh [ܧ*iSϞ,7ݪTd),wiτV#%S]d6selڴV 5ij_*{Z<@W_H|6;BF$T`x!;vvYR^VOjec3S/FhڌT#LbnSt51gSԑ1x5˳kymŊ憭vFSje~/x1cV[g DMar`[3$yhc/sbtqh"-]P:plv<_Ws Lb/u3gN޽ Id #@w]%+{a#M\/Z)qMƌ&SڴDc}b>5PVs^&9i3SID>!C88## *0E>}z>|(ɓt颴xwLEH#opp0ByK cTnnh7 Xʔ(w".MX6xy;SzM\_ef,.P0cWoWvKY9rDo8#ިs Lb/p¦vu|}J 'qAP4qN$FcA4r ҄ yv 7E\mCmş-b+W^٠3_ӿ-[vFpaMU4hpY{⨌<7H$RUF~~~uBM6J 'qAP4q^}^ բn5kZeJO)m[DgB]PWWO,[|(fsj2 Kiժ}5*nѢIa2A##@w]<9Rl1*2k ;z*k>g᚛T'j<沵_=l}ǩjʔmQqF$T`x) lٲŋ'. hi ƭN9sp._np"f$ }q~aPU3Y[S?$M\O*Wn,'?.*u:8*ިs Lb/e߾}v۶mT\9_Aa2A##@w] ٸlKmxo޼_硠;vMح[éRF¼M*=5kևz`ds"ȑWq`N)lWo"{ߌsPڬaQynPI \xQ<.Z0UpWGm 2 F _ߒ Mb0!(Ǐ)UT4n8TլYSm 0 Ƚ(#ׯosn_i 9-@ 9s~QjՄL&r4q<7H$*@ ŋ֭[S5rj5d H#ׯosnLb(I PJ[.QŊjAF _ߒ 8P\ᆪիG˗W&WF~}KF$T w`CqC*:uO>6lHe˖UY ews Lb_@ ̙3SƍTRj58c2Bm|o)x֬nA8s$^G $N0ai'J,V<3&-{_oÙSV?XA7Y~a{#w`C^'rb7oxtŋW꽯k!ڹ#ܯ!Zغyř& >q&1u/w k׊غuR"Ejqdz Qurg_+Đ v-b>S!!!ѡC!t@L{Z0Ǻyř& >q&1u/w 'N5jPBjqd}hԯj g_++)4|i il^MiV}~1*UPjqd$QQDOƔ$S ! {ҥ.;Ph(#۴FC#rbxxX"͛WxgL&O3K~0qI콂@I4=o[ lձm}|WӋc:U2>[5lCnܥ7Зy^CgM1nU?z_%2dSG nxI$tYVWaq&2AW)݈u{(mغf_H+V [vЦ-h#4sl8hۨ~9\T)ʕ+Z ΘL".WEWkQׯ y&?gե%JF"Nj!A3|U }0, J5h}:s%5v?D!0SnۨLb"EP%(gΜje2VXFQׯ y&q?? %+N<3SI,L-?|A37|ROI5mwLj+w`7UE=VSQ$oЇlϕ\OXM5 luZ 4#j݄hr ~ >5&ڋ271ln5i@. :X1i"~'/&/bl,JɲֶcJ[[o?cqz&.φVNjqLTB}c;z} 0@/Ӊ+ъx,p[FGx%SzOh,ژ oQwQv_Q!~\n/ӺKt?>-\6C6o3ML?}f?\x#MD{{$5X~I쫒&4-4zmbfB&]0HˏiI2;t+%L'p4?&*h` ~I$f,SY|iV1K˜vb_7$,Ybf?f' dzIcb<r/rI0XO|QKbςI Xa$~Z q1"#>JO[_r/:y$Cd#Xj5C8b2q㉎޹^~ !`S;A!O `7<1JE4 ~ !`S; 6(I A'čO3jd"71c^1Q.11gL&w  o0!ޓ&1齮ɚ5޻vZpO5[+r*1egL&w  o0!ޓ&19_L&Q5өiaeԦ5lU_z'G{իj %ʐw&gL&w  o.1!(s$%KSǎmNTlYܞ.]"V_5 :5*fz_v^y.Љf"tRlyY ڼת={]'Ow `W.ݿS^23&;p{Zդ ~6$:`rT$ڴj׮AL(~ϯuiLRU kXʪjoxjֳ 2U XKcg/2 zdySzf?~FcGڮ^B#H͛W|]tID  8s$ țds6Lb(^L3~}? 9VץYfYlJW>:֪H|RLݤ]Bhmy^}8m{N_I+׮çΙm{lfՏ@B`{ŋjIxpØѳ~aCM{FY ߑ0x![27mZ% w[¶4g1&1i)[LΛP?O~9g>XVHISghc4Ԇ6j?zd O?T|]pA86шAj~1ށ3/Lؕ-[s̯εUuAAwlٚ,Y !?VN'zqgg<0x![&qDbU3Ul0rmo J!%:}J%m;F^1ئq }_ty*ї {iަ$S,/he1U GҽCR$}Nn.f#}g vj|"{7[Դ7e}x( b)Sdom~Z> ~fI\Nkʕk1g=;? =q~vI +PMNڈETXl^J]tE\z;R%@h7{цE."elql"{BcQ_,~TM YԳD 55YN?lRryǽ?5#3_Z%X̸|s~8N>:z1Yld/s7a g1b#PniXt}6Vi[_LVHؼu/[QM7dkNU鋿/ҟYD8=yp?DQOAѳǢ m~d /R|={Vc'ObC>S+ΘLiUƞ}N-Zi3SI(OB4d;/QD4s^+Px9DӉȑ˩HrR ٨gI{뭔]~)c{ihҤW*Y&Id5kjf0¡6qSe)y)Ew ׹s[_F2ejロ4fSWTmxUR@o&JWXWgV\;jٲ66c1^JX2GJ&%03768XF=OOƙϢDhaÎ?e=:i3ilӸqXs79&1[N|w]|תU^ieѵ{D /2+$♲K>mx6ݬo[NIcl#1õ٦lgCxw]E=τeQg%⬠#EQ8`Zlږn~qTܞp;kab.Tm7F~KXo${T16 Q5bۯH_ϽL ]VkoR }j6TᶼڏOn!=ݻwOeʔ{gΜQ62M3&;p{Zդڵ[l2ȑW\͢5=^wމ~j֬֏4͝ -Z^{`Nݛo&aҤiVN8ÄDZK h.k7DwjO!Mg_ؤ1Β%*10ӡ1b3UCM?8le{Ƥc~MCI2ا>x|^KEۇQse|3?s̙OQ=OOƙb9 ]i!,ŋOK^Xϱ:"&1.U ۨQ]*[8nAQD|=oaڕ\@lϹve Gid Rim]8+ÅȦy^=@n#~e9.ǸvB9o,dJ-xZlҶ"ƹt1JuZb=R{nqi[Ť?8җsTMbG>'{+.REWMF?NݡA{©;:nmy=1LBׯ*)є/_^\OV/yrD{vĔ 8s=jҲeg>oW_矋b WmW>Qi}}I$[䔶WDLvoG1iqբn5kji27756ue>u6֢ݫ>/gLbx;uNΝ~r:'-IܬgX8S_OAsϤm")Gj!_Gxs)E5w:טsrlb̦*Tds˜Bde[ qaDyӵ:C-xjpW-fMCQiDNbMЙV, T9+,.5)IN:F+e\&1i[I/[ϑ sS]luG>'[:K/oPͥ(A$-Xz VoF!0:fG m~ǤK1TTI\'OTHDEż)і1eW~V5ilg7r[{2e֖g@9, 2V&im BDLvGrxl@sZ:6D=m̍3N_yYr{GW:Oky[9i*6]ǒ)+gaС Ew޷cE<6cҨ18fA݇sfK2||F3C|M6z>/{&v1i=VGy LboQͤŋR@V З,Y"L5li!8I)l Y(Y|0d9^e!I' 藺H?R+W2Ț+-}M~z?mtԕ%'9q|]p֎gGvl>s=jLJ4 I'֎/s Әncظef[bsXPz>ej|.lמj-9OׅR)"͚5zAW#B=mm-xUĉj 7|B`Z>1ށ3/Ӫ&-%ڲ*sCS |V-&q.h9sڳ1iqZg32yA.rv|X$"^׿ڭ}P%j^xV6׳ صZ6 yN'L$<ȗ}FTeKc>37[zIw8YHSvO^(PTXw6x;IX:"Os^`xj&EFF~դkLt,dH53|=099ߢuZ-6h0$K,fri?Tt (H+(%#VEFPY]JZVAbaݙ3f(bIS$FޣC1phw5urt#~Hvq̲MOo(u:R Ґ}OG_t1 5M#nDe\g'pe~t{Rk@45kѣG"b%ϞKzZd\ :_۵$W\1am۷MiΦYzZCۣʸN_Cow{rt],Ԯ][\GQjeWK'& 09j8SHP,Q"KH< _%W֭9X&|I4'N*̥S4kvER>f:NjIͲFX$IѬYv:zP\J\Y!!wq(Y0 tfLz{ TޕĬ3,g盪ĺSxr,.=eEKOƖw 3xKg i}~|WwmuuI L33i(~ފC6x jpA|[nogL9,o{}^և,$t}4??Җiۭ 8C\uEq[IիW&1pڕSK'& 09j8d#k9C`u~*zڊ %d8WwΓ&1,S0W ÜvkRb#&WHH/ڲ[uT5z**Z%I\_Fu R1?abu(Gfef lIoOy5z|EOy),솶Is8;T:Sxz,kLx<4i2Ч=Nj:&wuGܿs.0iqe&Q:_ev.a1|~EK^A,L53EH4S8_Au @lA8|Z@Pr QjDk%'& 09jZ$fcP- (vK9[<@tx1-ZD:uzRN:|X\L|]ܼw]iҤ>:<~LTQt$pd\߯`Jn ܆hyr:3"_w@s.zn`b*|ҽy hݺ5LbTD'*qjyI yYfq+ &1Jv=3tusS9/#|e& K`>ׇ@οD}':/22I yd 8չ*]+{|Mݺ+sGٳ&?xl%G%KW>.ȱtuI tOrOn: 5.=<#ܽCZz ɼ&qpSL]#Q Ď Ls.0IM]} /k.]%:r6/I yd 8I\p9;cI$\>g 8u9 AޗkۋcjɭD9S00/;ax_jhAifxS4裏B(,Qg֬}TTJJ01e˖ڶDw?OjǎGԬYs %NVi$~*}s$$޵ nݗҥB $y СsmHQr5SJݺBBzE}8phws6mڤ!5Ԓ&1œy'iU6oCU6JPX 7q`Ӕ&4۶=y#F,F:/ū|i۾0f%zL"O:g!t۩6/>N/ФIE\UnŊek<6}9F4۷.KYI~m }{+Vȫ^^֯_X:Cџc#00/;YU5i iڴbۭhZ⬱ 8s\6uyرky+W~QC,X?~(;/WJXw24Vu۩6M|fS_JjÆ뢍4p.ʜ755nQ[wކh@9[0'ur5 Q)(Ax1"j]o xz7 lvkޖ{oHj:rN^I'ޢQO^OWoӉK7DTױԽd׏tOrOnꞢnG죔2&G0vWx5fgϞXfZ CIdH@4Z{I ydϪIN}"0:u)2e)Ҽ5[SfܫNIi~o3WF#nfNξ &qs.~5Ǭ`|%&B+OusDI[zCn\|t m3Ʈ]uXw^pFlB;Uhˎ]uN:~ 4ھg uڏtOrOnꞢGR5Ѹ0/l[\+VP gOG4Z$00/;YU5i ߪ¤L0X+cf,Y/T>MQ8$mrϮ?)~\6΁b-2ăuLbϟ+߶RX vTCe!'8t٭[3VLtM; 8U4i jРk7T{ݪF=G&qJ?ů&1\ ?M)tm}u5856]%L,Ғ2HK_INwukxfLIǥR!ihsG?SnS ޡk\L~D鳤0^+FXfp?.Ux&O[n+]"ӲFf]*3xLzUۑeV^r9?뫖]+|KM6!{jU}|FN#H ݉Ot'!xDFGG''7uO9q9Y'(]+k4|},GѧOq},[L-kAtftzipLb̋'N~VUM tqhƍ;_ 6^x|N׫֮ dg$fTIģ)U-Pxe|h[3KmV)IN2)'l>ʗın|R$N2_7mlɳ.lrZ$T+h3h'vZ:Kŧ1{n?-Q7?W iђmt䙨:7E]n#IM_`6Q8D/ceOa\W\K,Q re%00/;YU5i z6eʔC9/lC.~3gv}c]/qLWnPiIzD.|GQ88pi$ۙIql!]6\$%MR7V~ҳŎiCoHs".lr^%l+Pgt3sw҅QwЅ/uڏx<:''7u|O#apN_"_ŋExCDwDWF^Ek``^=n,i[^vl}֝a-Nw 1L{!k6e#5YvJ;SPgs9mqm~t2eƭJNݹkjooW,JT,ѥ jmd€h"8s蕸D#%@O4A&Iퟌ ֦ "+K.;+c_jytʗڴ]}$UDzOG6%Mbk*v  wѮIE4_VSNb|xPҔj Ytj-D3ߠ%GnѬ?7iۆp]n#Oʔ)m7ogr77mgKnYEF1Vf=O&( Ehs$Qtt=``^#zL-!]D%:cܹjNz1D/FÀI ydϪIA/y?Ij70b#z#-k$^pnr"<48>c(I$4gb96 `]I8iB=iOɲ-Oza$f9,>*M2.ZG&9\}"Ρ>Gڰt#:}=N_Ǐ:\&1vh650L- :T\jÙMoɼUU _w5esar L7j‘I85D '+y/kۿWޞ '$ñ!'xll.#֒&1_zAa0U¼Ǘ6CJjݻ:}3cms(6SMFIS$FޣCXިOMbwM^/Зkҿyg1i tʿtj޽kyj`]_:I@ +W!G$vd°a1g۷=N04.00/;aCH&qIVu+88n?͋8oA[+=Ǔh2m8:mHKgFP 4wφ̈*sK3ݙ}>i@4}Z;d@la5kZ \-/L/O0! $y?I$f+]ϡ,^;Xi;1uWv&oEbL-1B\3gT@,Aӧ=Nɼ&1A$og0} _ī ĉGI.P<']]3'9fc0=y^!x2Y MӧE ӄ3[[lLb̋'N$N>03)hY-d@laȑ0c9OExZLb̋'N$N>03D%'ub >MCl؄&ϭ):O0! $y?I }LةL'*LFܿ s*] ԪU+:uzD ZT-Lb̋'NAoy?I }L4JVKL`ē:b'^|z)Y:k7[7ېu*H֬Yj1QDk($xkީ>BS1&1pI s!:gt `[u|饸(N<#dZLo+ϝ!\I yּS}v bLb(=86e2D%[u|o3!eM6!!DFU/f&1[N ȟ 0LbuhLļxk %Ą22+EٲeSOpNem```^5T]! 8$s~Otk5YA Sܸq͛3{De -yC F1&1pI gE6ԉjyd_*\Gr.raP\DԢ00/w-0LbwNg$` $L!wybQo:%u`iXYˬEve῎+ߖQԏ+h*oi?%4+*]E,I8``^0[^IA1pȉ}/TK &,6ؚk6ԏM&)ɘ.׮D;bl=#a0эjɋu`bIJ%UҼ^z `]:/[n$>w(cB]b900/w-0DE-y`̂M*oKDpuubJ(_qh3jLք[8YG&qacm3YxL-#_]I|eGӒt~]qg)b;wE_!ҩ"ZoC"Ƿl=4wGGaM=m|&1&N&1lB &,$޸mȻ9F?)s&m{KW(^"MWYOǑIoxJ˯,gϞ g;+i/_G=;;I Lb̋'N?@߃ %%Z[<xJ59lÌ+hރE^D#N4`Ӕ)W+D1Hw;ݏ#I|uz6 Wl0,[IZҭIx k9It;"FmE4D||e󜿸.PQ {y OgA{7p2;Ԛea Ó:'TXW(W[6mu^lOiy)"ͫvv_qULbf0J;F}g&qnuIJa؝IU6~>_O@w~y*>b=yA&1œy'Lby^\͝zO&lr^aS,6X9c2]4ս"Oؾ[VQLb^\gi F3g=:< qw&Wi;1[&[ɓ"$`ϺI&Lb̋'Ny ๴nLT5dIv~۾ga .^Ha"sy!+2w#XW3<$ߪQG5u&M궶',o ]F]2̮I&Lb̋'Ny <}Jt|tē:SxEs/SMR6b,#C*|ڹH:Ѯk"[s_. ۘv1Z$fc7iBCW^vCMԨI;6iWnezT0[|ЄI yd #N8a#}7C+$*S]Zs<<5_-QEp%JSb=:ػ?NIZ A|mJQN?z^LbPĺcse,V\lIa#OI yd #Lbn̤6VKb'u&1L͖u>s]q.]KWӢ}oaF!r-`x1`nDΨ%dSXq,q8&kjBI ydɿyA{7p? 'Il. ʖ(tZ>L0MKWD f)^vb"?$v-o_O0!?8NI A|\lr6'uLbPz"n{iݦ".Z'2LbI yd #o޻sZ0MtɈ<O&-?$v-o_O0!?8NI A\8GԿ%Vx2Y$nA&ky$x2I AεkS(dW Rȑ#aztja ;CQ4c c_$x2ռTcpғ4Qڼ]]ohyjj1}|ݿ<ݟ I$$0!ȵp^x5q׈>iXd?$?r%Y*Ii^/K=hF@58Y;} Tz3YkA6fC0]N5hP._ag ;ҕ{QFƊbO_5ZPڴ)^&M iQMb5k 58qRZ4pLQ9O,~XFuSJK͚uNwtvx-j 1i_uejΚJBSB &lR۶hgF$nذH'K=$}@ $_l}zjc?w A,}sWܷff&[5 n͂Qp5ׂ'uNƢ3Iϰ6ެhFZF#m{{|fzvj[: |.\Jf,tIqF̅ fad$?75hf\W+>WQw[L;vPZ5XÛE Q ɼ?Tʙ)/'J)Hq>jiQA5ݤf$ɄQiG&1oKf0`*};:u>C DڙI~K\`R,f 5Y}Nش^Y;i߭XNڿ~?llSޏ\Gf~On (Z8DO=wJ5IFي Z=;yg)bWMb3ǖx-#PMv+^,2;8`&1 V&qũ?D?ufxl3:e4qfÛf+0smf񃇑"Ky|c>r줈4lm;?ew+>WQgZnj،Rz9A.1U\l#PM#RP$I&RwЏ̶h.jؼ5y%xj^wMQfQ$LHda ;aOԯ_W;XG $x2ռr$P5]@ !fh3g>j Z]E5(]"ϑI\bm#l"MGIr|ҙ~4j6 yߩ!rVZ!"_\~!j:ӇYl&JDⴵIܵ`z饗D>q&LDߍfFm/eɞ3Mw(,\Üz,N۬u;BkFN5uy2ҥ LbLw˟uĿyJ`޼# +Qh>}V\5ƭĉc-L^&y҈dctڹ3Gz>Ø1kAuf\y0C9tD {hժ 6I'ޖ)SNp,uIC;: y+ϻUO_gxzlݟ;Iq[3Lb?'jAP Ys 74l1i?.uz:FߴKG.މ REgzɺxU%I\Χ3esX)YFLާ?1걑,Yy`I ܓgۄsp$Gp8^ - uYKUF^"|0jk(e}Q3Br"V3l;]>WQGxa6o^` _sI`vJ2{G"={FHf q@Q'C,"= Fzm"TN#fvc]&]8 {D 9tGW\ɒa\N@|N"M]Ly[ytڹ3U:@g Æo /߱w"_554C,urR49faue[O_gxzlٟ:БIq[3Lb?'jAz:]^(ci{˶۴Xp|-~Ů{e>W )Xt&5\_^! FIv6wؿ^\;1[Kj\q^cqL}]4\i;<u|lY_ eSR.wG||Lb#u5Y ǯoQWJÖU^/MA&?xp-}٭.C&uJɡŪbΟ4gȓtɨd Ժ}w2|%9ϨT}{瀍ۤI}*_8ߺk{D~}gϞ tn߾m! diirbirDيٍMUJQ E?y'N7ICL}p ~I߰1Srlao١HQv/sտLFj@_5FG?˃p)l[~ny&1t)mJd9ļYZkɢ.澬HfX/Eڞ$>rd->/S8 ~#NF<e Tc:q]ebfs=Lɓ[gNj2t6mx9Z~7F[g&1Ζ-_vz"x܆C-HcUl$oI2d=}NYF/R<.Nڿ~OcX"M[ksGIVM1]G6}xOgiUVM:#:b;Fc[Yߢȱ.ۛI.Ph+n6:XI6N6m{д3iρ#4xUQۨn^Y0jr׋Щ ;$#'PҜ%8~5ӕk7DԹK&1Ĝymcx^DLbFNlrX3/=؍[LO;o2O~uZy_kAczڂW/nMa -8s8Qۛ6oat;] +~\WNmRKxr%K6+G͘L[:|O|N>#}3;Xh+Exu5l9rvzE$qL3FSr(GUn]qP+G =(LH_v4iʱ'ƠfOrL]&ޔm;k1|-^Qdea6&JD939& s&mRFfʕw7nHsNIl'}'ĉ1Ic5`tozjœcٟ?5͜W;|Lb?'jʕIܷz_z0XʫXֶ`v>{U%]yNsA'h ryˉ:h;[-تL+:;,k<3Bflo&uXw^pFlB;W -;v;+oh}tS4<6j?:@ ko7E 9 @SLbZ9ưomsZ lNfSpF;p #sٺs04i!KW YgX狗=x)ҷcsdr|aSgiudQ_p>).wWfUcԑfd4 ^ ]Y D:1w|,LTRҤ)AvԮw5uC>Mtճlr\ϙIZ/T>MQ8$aT/E༶m+jNsf)[~$4B9WB]'hϒ: % e)Ds[ϵ-J<8G(eq?6*+O<.ryꙍU}|FN#H ݉Ot'!xDFGGoyc?pX^ ӲTKQnm>_090]p]~pROVSOӘ=7hҖ[CGhUteZd1}x禨m~t v$l͛>͊\gu]xbӉFiYqcXdl䘘,6W[!VʱPKxrF<?qn93CΈzi H# (Ϛ:(67ud:?{iݬdz*>GL=C3YNE\u.Qŝݾm<$WK{Z)XG|| WQ(Z E艾}ٝ;0/Fu2VK`ռ rt&[5Ŝ U U Xd/cPjq}:^ձzwLL9u8~/籉6]=$Il{D+ѸG¼UWGi1.q֞% 2oXaiK-r,g*ikw7{eޮ}X3TgEE8GߤGo2uڏx,)S?9́;&IzZ)XGhjBrv|#"тcTnm*_۱c;ZhA~A{7.0ܪ)H[ d;yd92fx.U~OrUQߪI,p,W&<^)yK#.<^y/19&CNÿStC _5Fhy$ vyA{7pLb?'j9ҩ(a'lto=Jם=wƸ;&3}&?5I962&q҄IE_2~ʖy/6c2ep֫YҙW9!E]6~PN(0yrb kG흙>+9:ԖSžH?XQS1i Z=M]/h"a/f+<A.D)[NW#v}HEC&zD>h]Uū4v.]N?N) އjBI @?T ˛n~On/i4BzA_u^C__I;}?3O1; qZӗ " yLZObN05ϣǏluZm{fqJ0G1q"Pصy|aI A790L# " }L|/:u{;: @L0IxUjىe.L_-ҷܵ/uSQ( [D%vu6l h%VޫZ/\Jҽ*0]˛&1 y $~1Il^m(cf9}dݸMię6k-~l׆Wip#/_鏍vm(]kW"}eԯ6VYZ7{ T`7/Lbb0!?8&I A{ۇ/|uL}-|NƢԤmO1󗮈@zL-6$CI\zZ2lEE^-9KصyDŽDnܢ[ޡ~Y||uC _5Fhy$ vyA{7pS;*,q`Ad W"Q+Lbk>$Y(lZd sO\׶ ޵]}GNua3}+g}ܢ(2 K2YѮM_xjW?P{ T`7/Lbb0!?8GxPZ7_F@3Ϟ&*uj zI̚u{!+|9v1$X=65(j(ǡ.xe'O)StC LbI @&1G޼w= \K+Cרh&1Y'OFxfWs`6a=~L򗷫k-6O(?S֑zsLEDMq1*3>'OM H>$v-o_. [nvUV {𕡈kT_1}2QCb7 L ӪoIuʜ%vu3GEZcWKT3bUD^6EE/ %W.Wm]@}(P(-o_#}ځWA+Cרĉ6rxh<5Iox16]k-Z&ݽw_2$W ب-oՍPLIrF43uR_͞?:m߫kYpC[&1@= x~'APt|e(}L݅WKt~-sZ5-^+ n;-*\lż)nW"Yo:?9u( M Cxƒ1αWH7L$' }8k1uI?կbk$f֍:׮Z}U-Lb֗=Љ{1TiܖD9 9X2l)0 A/F$0 ȵt}e(}LcʮmDW. 6&1f @5A/F$0 ȵt}e(}Lc+~;'QI2l)0,$0 ȵt}e(}LcʉcDQsAl&qpK7LDDD@.)0,$0 ȵt}e(}L ̮Be I&1~< r-iP [6/_խnO-tr O]: DU ڶY- >Uc fLA} Vpe(mXEkפ}Rx:wwzqXtݩNQզnsZ͡ҹKSD)(A+m.^G-zc"*_膣v.iтj.v`tI `&q>@d+G83MH ֡CW cؙa4c c[-Vַ(rlQz"Dvt g45Sf30[Y$@8@PD #6K;LaGz5YQIl~2гt48(2è[.o9S3q]g, Lo0,$lP1ĪZGu1bc'HԟL>Ӝ浙' J0O8HOi>rA$»ӓOo%uJ/!R涞kk#QR-(cʌ?n|ʐ"B7)lloC T,G1Wtyhh˩@"W=o{7J*b<'Ҡѳ t c,1QK@08Lb 06 r.Gө_v& .\(q$Y,jA?7N:iWqƆ&i*ȗ&12I$RmD>28/VFy܆'H,R[l/Z_œfc[.ϔ_eNYTc)9y^-%b9}7.15#ӈ>m`C\E(8Lb 0E5;uj+^J:ڴquEjr:CZ6u&~w0XU%MƜ> AP邈(/؀(=( H H;$!"E$PB!HHoYMrݐwܝg;wv;svN c/z4ErS:_u+EETg~א]ʰ,7\)T&UXnc Y ަ(scU`T& n뤤ZW(4~u{*tKRhݺ"&1!!uS>#mE7{R78=5`tWC~ WoR7Dy une#4MbaԖŃb}ehRIFyV+e =ك8& ߇WE++>3GwYM.f6 (XZ! wvRO?4q8-!ILw$2MbBB$V"$-Nlt3=)Vtp{5E8mvx~ Wq@U@ttܗƍũn MbB&13"BH@x2)S&)8ŻwRԩS]I{$6Ƿ &5_8x8zDRSSk.ϺEnq*{%S ĄxMb*#GB&1!$`I$ЦM+ Oh:e cDl 6Pm1ἂ؀&evp&QO8ԬZؐmkyjժ8}ߥ!ILw$2y!{h{(2Cz& 9sOh׮-4hiM=T%繁]:K۷Qr=> B`\&gILLTߍ)SU&1!A8tuV<#/bXg-qҵ5瓟">b[[fdvAsN II{$}2;I6yص]! +Mpd׫!9MbB#M+7łA.?qvctJz>ƪA=n협y!{91$Mh&Q%+0w2E?b>HTX1sYFv sձ#krVGȔ#vk.%3rI +H<} ցYʑ\9FonjfI%K7C(Nf4Gf4}HA]S&6ܶrxt&iѢ:u꤇ !9 MbB={V3#5` %1q2e{9eu &MR11^׭NF&qď?zY+޶.}?h=o٘1 Wv*nre +YP* <FԷjK]8sOեU//xR=z,U9+협<=4D`fE6]!C>OGǷcIW3@ ;Mb2;I;R.e繍sm+ G:ԩ{OBrĄ80晑uS0-8p *)c[[%^=G7s.<]9u$Ye; 縬66&6oy}R_I,tcvI1z;f$}23 xMb/?XEWb٥q.o9w'ⵕg 9Ln협ف@vĞCy/O$NK|>+ragfu1cU&1!iIͧ=Ll70GEmT`\0O</vԸ)ގmDZ&k+e5JbcIJ'dΈU CUY_}ӫVyCRYn^.J'{;`aatO 9Dg#|g4?Ϊ*Gr3GB&,2f5SǿKؽkhC~/Q< }sΥ oɋ51ts3jwgrd xzFvɻU/x( 7cs5Ku__V{ G|PT_o$|`@Ѡ<3:ǃt,b3*ǖ֎@&q`4ٟ5l)q߫.p-6N<ӫ!9 MbBdel.~ ܚUcQ;n\0O]1TeM=Uٮ+e1mjj-{KY gcõj!|8{ի7#Xbr3;?XbI?4Tb+eU ͚ fOUwVzޫ^̚d 9 s/.>@rb2f_`5}({$`U)IT1z;f$13 xMb/?XeVfMbgҥ _O>t޶n7\?o=G u;edђKf\s85qqt$ ;v㓵XfcuXz 6nۉSaνd)+협OfYf?MC9`` ^+kr/7^l۶M"04 quSГv͜KѺat<˺ Tn'ge{c>1z;ݟ{Ξ'ܘNo0E}Z)?Ow냳+Э'_e)TH XdhO!ӳ}t!{T64_em'^Zkb2?Iɗ0ud p.ǓωJ.mE&,2L!͇˜ jHڶymV>3PōÆ6Zj*^r=\m }gSv_ڏm@kD /LP+\I&Wӹ|-OtK%{=2_N_nS=>֊cC>_’aXq3n؄b/a!|f͑\9Fonj}23@4'h_dmBqF?f|<9S_$&AV2M4i0pj寬5mӦ˺e;?3v!.yz;͞:5j(czJ-%G"Ebx{nM聲Te1n%MbY,l9!F+Ww}VN/UJYY<5k\tuZ/Fy}7dȯ[p"o _%gĶY|ҭ32;7$xMb/Q5&g.8} jB c\9M)O{iK7}]z% n0DT.Yo,wqʜK]6f2bXElRx9t{|eYܥ/Ϸ*ԭ\Wug;k]B͈dճzmQ_o>St"/]ƹd .\sSrYHcFg3 DJTTcy&#.<%8(5àApaB@Y˸_7)[ą"t5nYcՔiX:w1"ٵZ'9]]qqmVf熄I%2La-)Ot|㳬Ưl?pwUt5۶Iܩ^'*+kU e)=tCUٌ\7L X/ў7x*vCТV e@ˊjuua^ƲqE8:=b(_C-/ϴ٠zc|yr]UI,b KYai>wSDޤ5؎5{x>~sź k+협@ +Q$> ÁzMposkxw@r#ltmڴÄ?&1!26qn R-Y* ;,kpa:FDℵΐIΎ XdvnH^.jL.3J$$1=w ҆/ESM1m}Ob}{?.zUUVe3s {b'>;n{Il~7L5òJGDJeɓIYJLuBb?O*GLe);bKL l#&%q=&ϝ.h [{_U:xǓsq<bU1z;f$}13@74sFќaJT^`B&x1p@=Lh +cs E9ܐ=4D]<\fYXϹ|YZluյivF&+Ǧe7I`9Db@/zsZ+&ѾEz&c\bёG8VJ'r#C*I ]xo6Ul~w[L~zl.Y3Ɍxx<:%N=j{"eNr$W1#ĉ]$M](oh~79x~ͧyS 11J”)S*BwQW$CILwW E]<\$l`i ̈AqbC7n\'[ VYi;#XLc "չoW*ܷed&{>۫s~[}ENCXr嘴F֟IkQy?>"XU7bov.YA9'*4SwcL(9K$Gr3(QE2LK&q#7kx9&{Xf{\a;}vd$M)ϣILe<&1!]uQ`r}b%S1Y-+>cYCrWo|]zj[7=s STV$&mjW};UR6n'I%2LmJY1xu%GleHwjey0e+#ˋbp{ˀ-*GVX2Rj$v639ĠH=;/zn8Qqaեd#rގI_̬$6tYMbeܿC&yrӼG8/PbE\%ք&1RGAy*MbB ")5* Ε}VIyØ|.$Ϯo=XFuy_]m[7e])>ƶ_BD-kT{=o*"7x gɗIW^,{KĜ \O2Lb1jĕm!:?YŌ}9Xb*hG jkb8wZy 8p{l![UlRx'U/y qr#:QFPe_ĭKcn^ ϧ`?|-^MII)*Gr3R}@ (Iy{MzMXZشN! A-0!OILG$Cr!HQ5&CB|fJTy,V7u~fLⴤn؋ʨUoː);ܩLY1ڥlz:a-XT Z|>o!8;Yr^gLbqB n(R*lSK%-1 Yǐ\)vx<.T`_1<,RqEKɥIL2 Mb\Hvҫ!~Mb*Х>>&1ܐAy0!+eUŠf}y \O2LbپyNrsH4&&q&q1Pk+':?K[PtiL0A"4@/4/?I%4)@ xIx,Y8^Ca(Y$33!$GIL|9ILe|%Mb/ILQ M#в!cn$]w$CB҇&$(ߋ&:^op^cCGIfػwL6M"4@/4/?I%4)@ xIؤ8Hxg'p劣L2\2*BA t5kМ9sЧOok 4>kTVMbH>CKnpQ8^hw-'5nl PF`H_ٳgQJ7@&qpn"OpiCg}+FÅ=qu 6ZF9z(J!${I%4)4X%Mb#0P:i^cSG(I#Ft*B&qՊUO>q_a@n-p^4@…^m-ǟG!!{Re =#бcNjl$!Mb/ILQ>X@ xIkGnr'mGkp~ٳ^EShn51[nuw Z5 ~e{Ҥ_СCf {Rmر}E1Mbz=${$^h|BCC}fGEEQT"d4D.EQw242 pk"HMMUwY&_\/Xa1Fo+[7ټ3RbE B;T[Hk˖~y+ƲUG֪,Fs;@޼yUyn}%+cSrתb1l<7y5lѨn]#5y;O<\yd{aȟ_R߾qck)Y:4jW9xo*]oΛv씜_oCݤN7(r;>3 ! 4D.EQw24l5Y'ߦ!~ʕz!Ϲ^&7lP1Ҽ*T[bn0_zYzJ/c֭c `3:q|r&1!62O#^ U(;UM \?5>fS0l2.]zҫ!a7kLcfy0<0{U㏻ ὿ &=rdQTglc=~ Um.^tdBX,pdlհ%K\2k9mڮVl^ď?ZLZ#oӤI˗a{﬘ľ8_M4F⹝.oKy gDF{\!34) =EQM&q"u]he׮]T7n .Մ03#σYIIlʞk؏ڍX5ǫb)UTzǜ%Id;\i6漺XVK̓Il&KvwϚe:O76FGa.ܬľ8_$n{.#C#^[Ey/$"Y\xi(&=kFlloLHӧOuX_1?f'&qktxɪ,}hbU*T؞l >CULe1_uf'VrtQJ;Ϯo~j?Fn'1O&cm>v*FV[}mElἢw/b}2ȌI^OeVW'y•Ąx- hTw PK8Wo>u*U`޽z5!$0L1y4Ӎd$0x*׬Z՞3O[]֣?^'5H\fclp_jxY3n?YE|gJj_cSWb{fRmJe&lr7 QK|R*$vm;?9l&O jÄS\g쫃Uޞ>n듽%.']RgH7}q^]b.{)gFr~k lFÇPGDH!$y1EQ"M ҵIizֹz4ٲe U{1!0w͜ Ya)8*P@oF͟o7o)^~3>ݞYk,H7}q^]w~3>3#9N[nR[N޵NOw-W7KNNIL!ӈ'Ey/$"]Y#zM0qDTPuő#GjBH&hҰa*pjElaۦMsiXZ۶nUuS[Nvy|*aOhߤZ,沘m_ƞs.2R\ɒԽ;_ÓI,oc=~c7y%GFE-\9i͞:5j[_W|X[LboϫK73*gFfMBHHf{u p٦IL!ӈE(ʷ"M ӤԌIࡻ5ӧѹsgn׮BH&q0`벒Ge,zw_(8RSNiBH`#4B!/IDʤI,\x~6vc;檵ófR[K/_kBHCط2kͣ2U Cnfw4z2QCE6+ hBH`#4B!/ID0) b5L5j>M4={BH.&oe5GeѶmk4h@=& $&FiB_@8Ƥ,Ih:<ٳg)\Mb*ؙjFQ$BHෆBߐIЙݻ}UKm^ij@QdݤTx M0?֬YcǎŋTBH.&18&'hy!7gZxЦMkLU:V 4mZ㏮+lq?>-u3Mɰ u'o}{… ;w.^z%TPA}nZ?/t钞Nɥ$]$I4 <B2'O1wl+˥KN$n?w|8)D2 bҤIڵ]6 ;v鄐 &1&1OILH᷆B$^j2Olt3=I:vಢ8K&qu*7/,e$>x9:OI|:P^cY k.L2>իW $]$I4 <Bt8))Qm1 b]w@6m{g$~am+תKѢ&q\,/KKrt˙3vy,gmm*K~\y`iB`vXƍs3ӧ%lmMKʰ. Kڶ!Z'. KU>'غ;G=H?o뺼}3LaY-o`̙8q~8!$IL|y=IL"BH<B tx):]7،evʣu¯7ńo ELdcq׮v2Y۬Ø<ٽ_ XZ9WF50etmƳK`3%V,6rFtY|7)͈;7_-RFaХKQb9rSP ?O?E~гgO5R+ `CժUCÆ ѽ{w1gVSBH$]YMbʟ6! AQ,CU:uYfhժ[fщԖʣ%-'"#m6'Wdۅ{mfl&nsԧe#0{,e0Msw|3'$?I|c%Ӫ,~ 3+k[:tPj߾=S]odyOۓXLbaa~ٖlO`{^Ql;p|m˪f1e_Νm1iSr0]!{K\HJ"NXճ],9{rILtgFoOGgUBGnAfE|y=IL[9~F#:kŬ92#_~ dFHP  (gA*Qg`w `3>|0BCCU;̔G3r6\,:ps6wr=znV|Mv}mKdߔ)}J$ѦAvbN7m K2r}4# ܿ%ѯ[f}(?4my[18<N`tN)RX3(>^ǭ3g`AirMAr|F]D/d[~[?ݻiՄ;T y4#$~ [Yn\g{Od;pa_~q۶r|m97+|L2 :ulmWK|<,Ҿ},8hF~U.ŘIHt@._)dK1;K0pM[;fcMBEB7)YH<&aa+'c6{r+:v Axx ecna&vOo gۊ}_k!O׭=TV vc>-#6" bflܱk7‚˱cAkNq­3ױ&! hS')QъݻW)xԩ.$Cm_GYntG!H뺕Y*n?$@o:Jǧkc=gz bCE V97oƐW^A2ePv1kH~Z>v,ԩAE*3f}8d*[\曕ٝq#Xҏ{ {TX\Mb3}KNW zJqu7]?ėp|psHH*>j.(@of@qB2nE'E夲JvbJ~BP;TyzYaȟ_ʕ^;f|K⸴i]yQ睚6U,QBOznbbj׸vcaiS!髴+ybS^ɓ=]dOīm_ŊQ9Rv69Y__2>7^^Y&·[e^lv?UW#*&VĦm۰qk+협'ƚ2O#$(n R8y|5QeyG}fG !Jv] D$>yլk+uFn e] ڨn]_Vy$vw*G7x-3Fžy]UhKnܹv#9TlG+Etq&#淔FD rYqzjU+jkTW/ln-]Z l<#yH^㭎1/^.>G[p).&HJ8\IEHML.ɕcvHcMBrEB7)YH<5ٖ߭II #z !$ɮ!ax@7ғ{MIJ=[ٲz˺ yM&֩VT0qI<~ Umd…j/X֟TuWaO˗]c䉉mo;wVq$67O\ÈV ~p|ףAGq ^%OGQ*Gr3XDirMAu5B>WFU:t%9-u2!$rlATdk [ ltcvI1z;f$} ı&!9 "xmYo'JÓOGYAܖ%a2t*૯tz*XV1;$vSX0얘]a훧srtU<1ΝW000:̋A'u"ɳ3VZgUD#rގIrX "fU|Kk)oAW,zε o^ZpKM LzniP X^AkqDJk:uQ1ցcˍqknj@"gɮɶn#?%߷9ӗQBH]-B'Mݭh@n><*8a9dObcj̵݆Cdz*^[1HJS'cpy>NWp!UHn+Ą\7dFHP  4L:C,xݒ-x1z#lc?0ЖBmx5[LZq Uc]X" k6n pX,~e26l9Ϭ9+9OvMwL)Q.%bBr+u"$XnIÆ T+eabn6M覰^e/Yxqն q]ֲ[_gNt)J`1me[1b_>QQ聲ֿMb,qkl/l[ZjuR땕m_%OWUbğwCϟ?# 7n@5ِ.#dIv(75 ndFHP  4L?t}mu? Z_ms(g],rƑS-J[<܂6ZpӶrn=c:}/Zт ZPg+s (j?7ًHx.\ruq.\)Or$W1#$rlw3)U.? $s;Brq"$n(5m[ą"t5nYcՔiX:w1"ٵZ'9]]qJ'ֱ&!ى "fI<̂lzlӂ ঻mf-X"R˂l建:r 7o\Y,fַ7e5ql׎o= Rn<3&:Ғhԛtcc۱fZl߽.'1g\۰QrގI8YcefRR9 ׶x"[Ȏ!tsߴoPLWWu<*}{* ;,kpa:FDℵΐIΎ ~X_#4BH覠Yg{mõ _N|-(]m刡kgVjaXb{2ԂoY|[95'M-#mFǥ׾kwVEG`Z4=c*MI@8k*Gr3>q b}( Hhr^lbp> tr"8nQ/$`k 4I  ¢-hقf+m&ĜG0 :C,xM-(\VW}b-Gb7MpGLVsRnǖ'[[-j?b1=[DXw`AԴOKKcn^ ϧ`?|-^MII)*Gr32;s&q`fz&{fwM异W:O?B@u62=!hA.)IYpCAab,vޑi`g6yܶla l79<5EjanWȓIL-]ׂ|El{{~Zj=/oǣ8\Lf?"q0.O_ḋtʑ\ā9@u̡W16h=J $\!i#oܣ(_܎&1 "iZ =jA-(Tʂ*7]3VL2%lnIj=oaۊb"0m[oeg{/XAv6e Y-[N lY,{-;Vnjq=I0y_]R'9Om ydv A8{X2(S}+p:QQQ)9/po`@o$&;hS%s;B$hILe$ ľgL>bpL\vlsJ&$p(ǝr<''4 Tv܎&1 hS@&ojP2ma=APĶo1!ILw$Kfv4I@Hfestdoo]3 9G<{ֶ.&/4 5F`Qϖ!ā\\$tSev &ާ"$NM^ĚIܰfu;))z !$'ILw$Kfv4IА 872;Iy96Oz05X C Bqw8BrĄxMb*dvnGq@ ـ|# ęaˍW׃9?&|G{ bbBrĄx)*dfnGq@ rЍ+rفMK^ۑC@ꮦp[́χ}pM^h 8^C5nB!$@ ـ|#F&M{6!}@W5ouB!$h2#3#899 3ѻwOm 4@m^3'ZjnmB|䟀qz ![x"BHn&1 jtӊty"=8ln, yh:\HޣѮ][4h@=Gō=u[$̅&1!ˎk€WeBr;4 9 ˟*G?B)PJVJtsPE(]yʋ.8u[Eʾw⥊?!z+/.};lpª;%epoj_ ~8n`~Tϼ V^鮻 M6܉;fZsGym2} h:,d{I̲ٷ4 ɽI|=! MbBb.Er]jF0wNN@ܩ,:]am:MuFNz6}&S?Y2+O9ɗy,{Ei_P٫#z6h0ۑveYw9! 47Wy2Il>72fdefP>pYH}{Mb4(*^ZrO99[OR9bNvld{I?ɤ@$&2~^sCH|Aآ1LroFg?毄N2I,j׻n5yh^/d$nKOԳ6_Ny(ۣ>wY拕.e1(Ŭ6%X4tWm>PxGV&`ֱuBܛFhG9gr;?z̓v&iy JPIZm#83lWq`߲\_6_;]5{lgOVs9n)syRp/6؇㏌ ;ѮX}/gIvvl$bsI&1!DX0}p䰙CH|Au\Q$e'^r~V)_Ɲދ.H2UiaS϶$ם&6U,+])Q?Rdj-R.2~e0&&GI3M@lQ[41Yd] 1'9eY#\կ 9\}~c~$4=jgͦ_Lbc9BܛFhG9g+[[m#k 5Mb)+fmV1o+s=r I.eJ}K0lhL: / 1d0q/ܥJɶ؜,¹ ?ɤ@$&◟oĄĖp4E&5#y%bV":sP5t6l$9`TbDR/qI+i+PZnlcwV[4Mܤ M].Il[v=^Ͻ=:Ħf;26o~ȟgq1{z.ݟkn0BoCf[m>72"+Xmn3 ksoILrB札23cG]hB.T{I5ۚkۅzK](z^6Vm)?UtەL6۪醌=#RNmfoE/֣璝XLV`cN|5hkeRlF-6'p.FO2)4 !xpcf!&1!%r7M@I,]wU#"̯FHIm+y׾]k/ z6}dKe,j6Ke$g(X]FFΊ'S ȿd'ݣ=%AkV}TL==5Af&m=mQlGrHٛbUό@Hzq]׫9EwԹCf}3+Wdْht# jgͦ6&`ֱsf!ܛFhG9gL-|4mK^'M1ĒwKoOz]^ y4Mb?NZɞ/9i s?23FeS$~c`]օ<]8jWCUBK[F[PeZҏHz:8ii؛~ Gc{*#eَO6'p.FeI@$&A'= %4 -\5i"+&Y[`8a{ߟILrB札25 G-pm''XE8[~LbQNe͜e+樤1mĖI|SβZG%*™eork'MI>3/,=SFR?_Qr!OoDU{00mtlܶ'NKۆMeRlF'E8Ą8}hxN'3&1!%r7MXDfo $Ǒ/i*38doվmD讯=e33+4qtk8E+1'q.Ti'iā0e$y{8lwP'MMpEu8i5ySK)l;l>xOIUFJIlN\;hB"=h,3&1!%r7MXDfo $Ǒ/i*TXtI=Zly+ieef_+SYncizI}nąGn$Hl6U;/:$ɲL!!'zx3X&̓Mބ{GnE[pM͸mkYv$yRFJI#4P.F4 !0Ka3Il^QTnTHd`&7&q`"L%4͹`SRs.*'͜BKs3./\'"WL Ɉb&l {$fIlm{Nʪ/&ݣ{ʋnzI&2Su:t?kMm/ c꽘n?~ڇI?pK򤌔:f;6>+VGrL\;hBBe@3~BO&y`䦐@I`0B I:b|!Ms..ovWI+uvU[OZ TKFJ^{ST0a*7OF7˅k^K#vM6ዜ41zuZNI\{?b F3/We`vĩSČxpv:| >inAё#T)+uvl$}"$&ds~]gyh3|"Mbj?8u&B\ 5Ӆ<[9#su=# g|(&q0SV-p^2.1y33oCybJU1޳ZM{1er.-iXT'MLoYOL≛b^ǁ#A0+e6>{ұqw:N8HYĉ MbBHvnQ3C=;)h3Pܯ3!4炩".͙@ .myV TF9Y&ag8\03d{hB"M@YLMb#57MbjD7(٠ILrT'Ci}'YL" Mb#57MbjD7(٠ILrT'CMN xy3I`I`0BH%4ICWd{hBɉBƒ&1zD&15"l$&9Mb*^œEILicbMb#57MbjD7(٠ILrT'C[\C4#4 F7trw|IDATd&1qhS*,bMbBHNǯ\ŚÇ< ČdW +%xi"t8SIf-ڲ`iii?~<ڶ}>իWx}0cJ|pMbr$4(*ēEIL%K?hĉ+NKpE2r/-9^5 yz~ӯt͝ KW3-hOuڵcF~CUΛpL@(*w8$&9Mb*^肕,4 !u-t3D$.[*}Dw{B2_u~*\`rsvAuPt =+/λNtTe.)i[)M4E*'/uЯ;-rS2YLR]n*]4k,f:oz&q(}٦@a!u&o½#-y&f6t,;<)#eَMH_:fͻq>ofQzu?IwG53g_OB!!B8"Q4c MbBH@"fMh*i m0ʈ^&sKL[Ā$[,/..3`&VL:Mb3W?y"oI}ٯ[oGo<r2͆,4 L5V mm fBYg8Mh8/CWu1->L['e1۱ I ­^ڑBMbEŋHBH˙B{=K,6G di,Lq!jY va3J 'ȫ*\ÿ.a=aV5~6C64j.7Խh9c7ҏ#G~N{aVl}c߸ӪͮIjr553L̴v,к4ĄAh4(*^Drpn !$Y 8u!D8-1L,Xؘ]wUe̯Fzԩmꑧn.!S[o8+Yjt]Ċ+YQj[N޸QH!%ye<r!eoU d!m@}tu}5͐5;:ہ 9FG1k<0PÇU㑱ڲ 鋾=j04iۗ$6yDB"Mb_)*Erpn !$i pC%(O%IM8-1Lӌe,?/CW_a҉L4ÿoMpKLC0љ!XxĢ>{_Y(&McСMbB4 ! lBH{ɓesޢIaĢs':w~Y=nWYҹ6AIL!&1!!mBwxCp[4=BZa;/y5@ /</?Ą9hB¹&D``t3$:᜷h3r{3!XOQTdEB!qC87ۄh= !F8-5M3#7|bm}b04#"!!mBI46T* |3!F8-T4 F&qd^$7sM!ȁ]&A8-T4 F&qd^$7sM!W\,`R3;᜷hP1r{$f0đ{BH6!$:=9s" *Fn #!MbB!qC87ۄl̙HO7sHy#MbJn&1!!mBI6Z&q"y#G$ C"+ĄG~ė lBH2r<129o9Ѽe0rSw RfMb!ߕ܌];!e2ZE%hBHX<7 xCp[4="i$f0]ݵBH IIL!a: Xm搜 R-IQM& 01Xpp_^0p5=HGs^"\7zymDLpDo9 M94 !qM$I;R7ۄ[:x)hCA[9r%&{o矋\rxgJw'WOdْȗ?*]] zק ve.)4{괨K YyqnHٗnˬ'8N$&mCͶݦ]βм]s/Y\McqC뮭U^zYOmjI[6me+UMw>pj+[ħ>uկC"pvѳG;:^/ifX^s?7\*W7B*Ko~~Wr34IT[D4ĒmBɭ|6pw5 3DH(*W+\iOUfzKUe+O-?sr Z7Pib\gyZ~g;2ڄ;+Y+PZg,)(˒_jE5,mYݖY{WPTqstwhXE-W^!+æ]m'f…>LbzYZr+ iXsosېnLKgqJW_ڒ}o[x `ǧgվ6e[j6LaYcXOJJ^X2I]M9u~Hң~ LJ*8ZM/]}qMII+PӺ9x1\ZծPTn߾y2ןI t||6&qbAD%7"*\㨁sV͓63)қoz9V޿-o&;m E46!DSimχ38)TPPx `gV>IX$&QE}MJ<8)+ʕNZ' VId]ko[b4ׂ_qs$6ן(SUsK/ue>afB>IӦmp)׹:H^mk,iɌ~ɒpWJp'F7:d[I\ֹp}\wsjw9sS%ʛ΃n]RRU8iRֻW\?d%1ěmB9NF3SI(EH!ץ`0;l7I$&QEJYD%bq]]:bg% sp͝ N͛ՆYguَ,wMZ3WbEBvew>8z]\0X'myuwPUN'mfwVgiӜ}'d%1ěmBc-9${XG-TRaqUMKK m QzFh۶mFx/t˔2V޿׻,#e6L?طMӤ5MbmLӀ^{Pu֯Mb))d>dI'tz 7,K毾ԙ1Ù>CHM-g+1ěmBd4qˁo7s7:w~`8zMM]g7xjV 8̔:z^`Xd WGj4FϧMӤsjK*3%s ^\ny+́k@.I2ϭnC2S bI2ܶdwDmfoE/֣璝XLV`cN|5hkeRlJa rkSz␏==+}'05M1c\1]eJB/9nd 1|ewM'L?X9ۘ".e]Q,: 탹Z\׭,!lBHb ssT1[$"r]jL #~dMbUdE$~i߲g;颱c#_^}M;M?즛|ˊXo[^W=UZ2Յ?zzTV8C'N`ZѣGq0;HYcc%Wx?c G./6֭ƍQ}q/Y,!lBHb0zP}w97!\H창$CDl{ā$|CâQ^ԭSNFU.sCd$YN7M>l3.ڃO+m9t ێĶ4vRRlJ~p4x۶.3m׶j AOT7μŅ upM_&TYC&d@ݻ9 [$r]jL #~dMbUejW8e48'ofQ=ctrɨ_3<^}UlBH2j(H7&1!\H5MCD=1ke*|N=it"y\[ FvʉzɃ$emLӦNZ&C7Pv ]o`O0nIRlJ)V(+իW'#Wp$q2)c7ۄ,lLM>hxui01^$>4IT6u6oIÞ@ <Ed~sp~~NלR=wI,cHlϜ95!(h*j$4 I<4 Fb|ig$X{2rWh!Cgu)M\d#WÒWvge^x)+k8:tps%Iyy5i,ϘKI\ֲT߈'făc8p^&Nu◭9HYcc0pnj.]^3mԭ[G؟qOzEW_ퟗd|ڵKdYCbͶBI\vgId&1!&$/i<+_>/ ZS.Мޒ~yXFzk%FѺy2DZ<LOAm׫)7/\K;C &nƋ{rnF¬ذ86I8q*#es$NK;^v ?83];[M5QS+\۝zY$O8YC4 !$H;Z$&$90S)J|СILJHޗ_u5yz9pլ ɾeb(UWջwdۘ"v7yec\ \ ]F{}2UM1c439%%zkmSvm~0?cÏc͎ eTw!GpMb9NMbBI>Z47sĄ$u*EQ/:4ITIHs/|\G`' SP`\9~*jIC1:n- ` 2M4rD53Aػ̉hB!$YILJ\{, hQNMCJtE뼖.)$>L9!-[C8v:3 =$hߛnr|M#y%A6ExڪWI fW/ey $&`F򱣞MbB!$ 4IT{s-8ʵn WMP!UmvuF 2 ; 2cUc+WJ]x1DB7^ m4 !,$&Q%=N_qr~!$vW8QD%rI ĄBr{nT7SMb"%ĄX_!FIh'hBvn,y hS)$& 4TNB8 DBHpE÷͜AH&1!$W8QD%rI ĄBrtO9&1EQ6]G$&9NZZIh'hB1G3._:sFG EEJ˄-hBI ˋ^ĄB@yfN$(*R^&hA$NY^$&MvyDlGHzBMbBHC8 dyKBHN[奀];{hS)^/BHIL{IL%l.ziB)֭zt1SC&1EQ2!D ĄļOIL!$VifMb"%ĄXCPT)4 !ĂӧzSVEJl;Y> ^"&1!$$&$$x MbB!I`>-I;g旗-Fлw;*m'g]~1_dMbBHILIH (*ĄB=Jo7s|Ie׮}jg6~yU4NVգIL!х&1!$!ٳg MbB!SgdB8|E$kH)m'gm0 ?<.+ IL54 ! 4 !#?7='L~:+.E|gLg\wsТE.]g\pZ}Re*T(;yGx;qQ9xA|=qb#;spȟ?*V,ڙ2TҮ.RL盨b cQ*I-[R+aԨwE-NvĈgy&VdKZ޼yÃmJ/v%矋ԾYATTqUwq]ף@|WGf-{S`~TTo N^.cӎ1leu|h$YzPH!-zz>$&ĄBĄB⑅)EK}ӣmsn좋J)SOtUnSO 2I+TVLMYnٲmI16wCt[$6n\S²,ơ{W^yJ’nͦO&^Yܺuwz啉+#G_Rƪe6)zUT۷ĹU;>iۦ+ZeD$-רqZ>:OU氤rKUemcMU;"c*>V43Pit;Ү6m$&ĄBĄB╿xާ[7G$>|x! .*mj}?0\#ܧ ν{Qꈹ&Y5iuܡĐ3-6vmukPiOt{>iQN? @тUڰaor=fi lYn^zzS<=]2@wWS]Mf-U(:RRp/;Dm4ŤIevDǔl$EF˲˲|<-f}?4 !&1!&1!DE$?*+8J)dTbլy'#u6d G1D&L#~ĸ6qF/_gʃ:tNa˖iJ6MQ[%] s"\ ݟuokJh@m6pe:Ͷ$SVq>m:-[4=Vڨ_.|+ضmzHH9c*>T$7sXY|{1+ZĄXBBH@BH"y#E$?}UVZ5%ƗL1=Sm}g.WsV_6Ew s4i2߭mI-osGp>Ls1vd mZhǗev iwԏi}&̝Ӻ9;}l{1,c*>9>6edy/ۢE2߱nWMlWټ R9ʄ-X>rsKhBb MbB!qMbB!6[ >mG  $=Xbts,5Uݻ6o3419umÝImkWӼGz6*e~emLJ?1;hP&ơ-=݀6CiG"zͧ3m;+S;4>hWuet]H9e~cIm2ay!bx$^l{w$Ӊ0~bqo؎[sӄ1%r*g EĄXBBH@BH"M۫}f#4{NW^i1jNbIz/sz|V :-,\ضO$R(W駯|fA @1Pbફ.CՒyzNȑEȨ\'Ȝ^(}ms"mদuAė]v23o^=pQ+CjWO?ro<ΦI=}f{|$$C[Xe*=曫۲k0 ]l?§+O]xwA9k;m9qߞDk"MbBH,IL!$nIL!$n"'uSeo2͡`B (#Ov5@\f;ud 8_;"v15mMb=LA \p2%MR_D&@}_?btU;05WҞ~v=[v?D.PۢMbC/K믻e{2AZnAɒ:zvDǔdì'$2p ?x}5Eqwzۏ}VCūiڄo֣a]9>3Mbq]׫E :C~/S#G%0#2ߜڃ7fGɪS(fgӜiABIL4 ! 4 !$Lba}܈dC%&f7s67w<>W|0w&ۏa>tK=i7w'>}OkF4 !&q_)*K̾PT&1!D"Í,;DhǗΜ&mcTfq? ێ=p^ZV~mH>{ĄXbwמ o5٢Tvd{$&Hؘ+Xk**hǗ1gK)-h;~3^CVA]h>|heGwĄ=‹7*dkE ~'IL!$&񫯾]yk*J IyA>}^NہWnG)a9?z/>>KFUȮɔ~mو&1!$ݵBxFślMh4 !$$E'Sgeitoڇ3=jA|0nҐh;|6]]MbBHkυ⍊7ٚlт *;=~iBI$fΜ[bTT<ԩ~ik&-ttM9.sfo Xc{7*xgʕtu_[6IL5vw^Q&9&|;AeG/MbB!D8-^S{SP^u}vAjW&G/c[LKK[[wv~y'㯝)9c|}8ޜߝC__g*}(?>_~#^VIL547*~' fB6 >I[sgy` IOo~9o͚ <8~SA}=_}kpԭSj͛W]u:uz¯L+\7zk߮/H;cFʼnaFVOⅱo`?]Jֺ0G΃eae&eB47*~'IL!ǖM};3~S} W1X_Yy-} =ݓQw`83G9y8rؓV%3.s̺=iZ=9o}=YC2 Գt<Dww3]Œ~md%eB47*~'IL!GL}{=۶81Áo{W.zt{)8S*гg3~OZs/x_sū;e Ł7{tLYos:@ޞY^o׻u3Jې^qgYMNy҄p[լYzPH!-zzwWWR[2<۷RN!z(_\}A){hQKYgVb߾wb K.)?W{oQ GJ(Q=yr:w~e˖DTGzקǏ/CvQdq5}wvmk[/meS/K>gyמyϙع{.pt|t팣ǖյ2!D AoETvd{sM!9Yh☎ǎyd4ie1h_zp}oNग़? I?KdBvTJ ڥhƏPJ(Pi^$)iB|=}Ϝ93s;wv_s;gfns{g@жΚFxZ|u xs,k`?go@_7ռ@GC:%.`ul)2jXh{Bl޵3oY,m󧥵wY /y>7*Lo?) 2ωDbpP3~+tRfy٧ycKb=N*+%v6tͦM厱%q"Mqc׮ДúKXh~u7giZrQri|%mvv%q{v{˔9Œ r=Ė} d/cd9F  U(c//ڄ3{$A_| ]֖ޥ >/m̂Ȕ 3|ԙcDj֥ Lݴ!rctY3uJakpgsIԽ*ٶX/9P;JdNdo:Δ]:q"9g*'{|"G}[Bmܢjճܱx:DZ%5T7{䑦f(%+.90=-i41w3EnjyVZn,|q̙31qzumڻw+o)re;X%q}&^.\ BI~&$I$' Mm9Kx D1Z1ٳcxy9oSpzlGBۺzYNj/=upբ:6zުtYоEZ3)7-N]@עղrZw>7?~ڶKxmr8p$̚y.tv156L5)~{, l5%h+T(^nK֬-iukI߾˺uv3FD׬Z5*+W)S͚ۖv횘JD1K#>A>/@PǠ__ ￳>ܮX(ݧ},ϓgm|l.yxɭ v풱cJ۶mA[f͚Y6nk 9Io2lNToYRKYqfZ_.vJY,خD m뉿t)=)u%"=mx-SuuNwGum*'s?EwmTw6mZLY-eu-e%-} d3]W ]_g~]8x>~kٲw̾_"'p.!agklI栯d߄m'V-]cVgzp6>~';3S\2Uu"9rEh3=j3Bn)S;+WK[k-)3e?x)k--uMiYAgz d3ٳĺ4w&o%ƍ$~/x=.gCߵ}zDw)^ϖĺĄvtlcǻ:Nolke?ZZODtviX%q}vY[I]yRE? 47P\!~F3w_=9g\T2;[8?_9iv/;m>l'@}_˲G|({gd 0n*m/rPamfYai犍i~O8c\#/raذҨQYt)ceqF2|Sre~1]vOJ,Ǐx9^>yz5II {oY;%-ebSR,_rNekۼORvо Ow;e$b?ƻ̇Zsho17#Δ}ѡm=W?v}ޓxcRR%qRՖ&no2ÝZzwl"%iU0`I>Am~^ښ}'|ޯ3_s,Ojv?~;f~sZzmv.q9ɝw`4NKebA1z˺O'۹s^E  U(c//A$_,=^:r/ ;ܺ}1+V|v̮̽U?#퍿$n*a:)2yrF6c1]`2m|>{,X >B}\Ɍ$X0ctiٰ~AD)-7,ƍ(ߧ좯k[ﵕY-su~Muپyrz[Bѻ|3[ԻlNɴ9ӝYe\!IυWTW;Y2v䘋Qh[ׂ1ټWN)>-`џsN); g}/סm-u޽} JE!l~%>+K$։2ZydIÆ2c.e ]ݧԓUcE+u}a^{ss̎ӓ>=|O'4tuϫؔv\8>{}~xt=s2 %q E;h+wOvۺuK%c^:睂\q 3‾_Zdz E-h:Tc3Xg/RVoϪW[m]V/ݴAh[fYhUvNe:Fb~mIx8xSZ:CXf^-`X:Wgz׋YپZhfXJgҤd%KteswL"%~?ւSO =FRg/^֢̚TD+5~;Z6m~S3;tg~2WKR"G~NٳZklGʮ$z QoJq~y@#Kv /'Doٕď=ߡn4臎A»_z챴T_d._}]*@O0a/ײH?q^p٧';}VK6~noyEmw7'X3nǔϏŔ5Ry yaom^ٹg/پkl?ٽ{ll/3Fu O|h=zdӇ"J ի>޾>Ŋ}=qa~5J ]kh́.O_bNͿG)?|VΪ1mvknyN㢽b%LJ{ɾ/z=]h D}HNFaOI"2V_EΌRZzR m4əꝽzi;gPٙ˾ sG\vn-IuwC^'жw.M.Xz_._0}3Bۺޥ8AI9%~^N=-1[v%W }ٯ?*$޳3{3agExQJ'~d=-.٧Jk.]lwvsm׺gRo5ˮlϢG_3ӵdzӟk%-cnǔϏIڐof/dn7_,[.N$+m|,߯X5f'H>%]Ip̙=^ڷדOxC鿹^Gg.h9[~-.{M3tknkTי+ɾ6*k+Vς>Xwx3'D{9%rAKa_Ilףum79WޙZ]Kgjk"oX :{\L 8sBIL{{[ ɇJh4Y9^c&rǙJ3gOO֠c4˗kf'~=%u{w-B[Ǎ{Ν-lq'[ЂKKI_ASG]WId펿M?YSdd.ٿ/3Fu O|hW[qD$[dPz- ׇruDZ\ڱ$W wJ"Sui-tuw~G̶]v%k+d>KJv8}'n W, ^?]XęJbߣdBICN>%ZknYg47A]_JU5ٕKv_ד4hV`f#~]Ԏ},> \zVfvݞC;v'T~]ӹ-+>XǏ&WuJH!ưRcaD/1:V?N}NM2Dv~rt4O_?O,?)5z52%1!$_(%q |xӲ[\7k V:AhtiE ty%rwočsc홈u޽ Ž\7o٧uVOv?|l,JAُOb+>XǏ[_%"uǬ_no>_#"|͍^ct^ tGŔiqRr0ӄg s9Zm]W2qD=9oK#c3v[>QGKX-Fu <k+d>KJv85ɼ^7K[ cQBr+@:QÛg}իW1ڶ 8+Zk2=V*Jv=KWO-4k8yogu^u-taL n]Uc/x݌n_o7cuhi0v>~:ٻcZ<e~Uʾ'H>%3UII<{֘$2;oۯ:U/olg6.a˂ognow\;־Gu{YN_ Vv}cqjy'n"ߕ?o3'2AX%1!$7_(%q A?u|[1k-qڽcnx]+t`-tɨ92z}yכoy=uYLLJ{1^' 7[ b?Z])&oﯗ?v)Gz='8M.߮usf$z{CG" ի3GobW:~Z52#/u/Z4̌>Z-3ܙ:M$֓>~{Rѣq/k^OvD{dw3'& QBr+|^n1'|;㢭Θ ~s$زX"f%mΫ*3ZEԽ皓8>F*{2,]sLcיza*%qۍ;~{YNU5j< 2uY^YY랬o3FuIsvseK'Geҩf]vShoheQ zԽLOvᇗz sM"%7mA|QN¦k!k{7gA{;~Ǚqy( !>/H7J֨q,YB>HiذvhFg›,uVΔ_U'|Yٮu'f{}^`l /dJ*=31cuPfԗ#cn7ch ?HYՔu2vܻҧ߫2c,Yq)+YܱÌ6{4eıDS4^5//(E5ntks0=޾>e}~xQu[t)*;kF_ӵjӼ4z"KGI$Z~/k!k|x3M+q}49y( !>/H7JFe߾2!7Oޗ%_MU&eּqn2iPYŻqO*-90dȫ(7X/oԨ ><'HN/[2t$o$ӒO`!ѓ٭Y=nAׯ6=Arxi( !>/H7JF2-a׮2z[Ҧr-f͚6mZرc5xO$^dJbBHnҍ8>LK?4 9I_%1!$eFIH%xO$ݯ_R[2t$o$Ӓ 'HN/@ILɭy@Q7iI$'ITH$&h2t$A@KBҙth=Art~HdJhGfZݿD.D&@^$BdZ] 9I_BA)/^" m/\9DIɴd=Art~HRFKKG^Fɽ@^$BdZ] 9I_BA-{u]"-E'n /Q@!F2-.xO$ݯ_R!Je˖<2:%r?!$gDIɴd=Art~H*?T\B[1]%g;v4TMoe*aJG{ŋ+z 2r] u% S DzbNwαj`:{Avr·̾ͫC "'T*+vnْSKP-v}ȔKN4e^_Vc ǿYrXfYcn[3xk@/ȿko]#[1NRN*_q{l-uL۪df9u i&D%18( ,*/b7}9!+ۙZD>֞ff|f; lwQ}%En ȴ}=c5{&}ZfmLcZŌ۰Ƕ%9WjfQhfCcKI " hϟ$vvoG.3tFoA;?v"iT=gm,]-[qcVhpvgklx쳅.s`2Q?{/4y"m {`3fK !WV7u:Ζ:[zy7k:c4޲2ћD$j=kKb-owEn;4BI JbHB*Kc2]X{k¯Вe^s1|ԂQ>-zMz_;Z:ΖL=i{=vLc&+5_WM_ڒX cs4En;4H^[<&0nĘdTvƩf]Zi8`cЬB36XY~ `.ZKz2{o3t)-&e-9u 5ԬNKھ:N޵K4xT=FD$dv_ڒ/\%+mޙkzH$|s+z\/(*-/OL^/Y]Z%>$=>{݌_7}$%1$!%w:0YVuv)mnvf+ Zi-R_g٠甓}>tmcݧE -G;vDXyj~>_y&yzvylzoy}{眏"HPʒX]ڡh¦hԿY$$e-9kk =T>YWg8'A-4/-?xڹKo?׹b}m&Zɩkt][Q8پ8YM+> ў?%Zvs=Maٵ5:oAhw/ʢ_fm|.wN:~VptR43;i&/nny~ O$$$4PYLRruj6'!g:٬sѓ=Y&hgcg}/әu;_fuɥAZYMo[Iq%Mɬ3oṵce&ћ ǏWk<>MXϟ%.rgԓ OxOx>^F~h|:vԟ.*NoS?697:eW.^[|Q^YZjıB( yQ]lIS^ﭐA};yn yV=stEz-3V)lgog*>]ZO$$$Pt@+M/?X+Fl֣WH?c~q˷ˇ˳3>gon?%&q $JbH%1IwW_u,n;nZڌ])C?Mz$}lm6yynx@I 9EI I%q߃ђȤMÍ a/2mEKQ=EZNuS['JbBbGmnSJש[˔yog"C Zd Co֓FG+H( m۶BILH~Ae$y{_tUY,Լج]2`>w#2ۚw&+*?D&|w\B['rIvYr]E)U0)RwziULdȿzHLRRrҹJwn As Eר7&/UYod2lgypAyz] Jb$^UDKk120%ngΦ}g\r)p ;lݼ٧e J &t1,6K_Z˰]#>w$<:P/l"~Tٙ|@3FKQ/U9٫7Ӫ|*/b4}zaEL}ޒ8tl;(w6CcK6RP![Ž^BI !G rIl@,oi *O5ctf{/4y"}0!xsr֞~n5KQLխ$wA^[~1x?45jof~-$Jb$w$<:pC0yn郦W5'\+\̹aG8-?x:‰EKvqfLic%quǞvwmE9g[+ѓ>S`n۵>w$<:Pؼ{M/kOXwujqi&9RY3Yg=뱽K9$r;/|<Ǖ4r:Fٺ?ܠG-wp$Jb#rILH~ %1P@$&$sBI !G( ɜP@%1JbB2'BI y %1P@$&$sBI !G( ɜP@%1JbB2'BI y %1P@$&$sBI !G( ɜP@%1JbB2'BI y %1P@$&$sBI !G( ɜP@%1-!JbpP@K*BH%1"/PPQ@K*BH%1"/PPQ@-[! 0Jb(($ 0Jb(($ 0Jb(($ 0Jb((W]ɸIENDB`java-binary-block-parser-2.0.6/docs/jbbp_primitives.png000066400000000000000000005145231445000440600231410ustar00rootroot00000000000000PNG  IHDR0 IDATx^ x])"("D'b@0*RbEP-,nPZD]_ֿ]JU\֥=93yY$}ב̜u_fƲ@* :LtCi_9r ZϔhAέczfi4-~"#`saMzyj9AgFFӹr^':@.2F:ZaMwӺ>aMzym #`sth:[k+@V#[- 724 sFHV1 0s弶\d0ҹr p+#L\o9p?WYatnC+h:[k+@V#[- 724 sFHV1 0s弶\d0ꂂ܀Nz{Zw9UY]^髮1]8!նmGoUU[ii۞jl_^ o~3ٞ2 0-H?d?:{LŘ&^_vL+1M9Z䩻θju8j[n1M*٤'],uN4ט6۽`E{ttuّ #`ՋLY3A7yl&jWk߾1?3fwY.^ϟӧYLXyСGV' #mmee #F?4hЇ;k{[R%JTf$@>Qk==eq/{>vk͘7VuѲ-?mK#x˺۝K,>m?<ͨsR@1x}{!L@x޼7U&Myw?1j\I?5'=NQ'|?FK@Kz}d罷kIw߷Ƽ=eDW_|C~9sLU]]6oެvʿ'OaZ>)^?VFTaAX W{>Ĩq醌lG]C%@KQ7[{ 3az7?裨@ =F gޡ;uD @p^3:\$§>.?s*?~3߭[ ڵk*--Waa+3F@Q#quiwwƽ5<ݐ1V ]tmHXґgZM۠v=V5k tg؛~x9K|+)is yE }qnU O\ڙ?c3}?ɭe_|63g+ d?WיVKu+y޲@XnK-lٴ7.H ɱJ#'9?ם˖}ڴ 7~o=ual2zM6m2ƻoG&l>2'^8O2٪*# cƌyͶiVz0-H_&2PZ=?mَѽF{'3.!i]X!ߛO:z7\KxVw޿3LY7NH|Q+~̛~âWQǥK{R @3x}pq}z?~7\b}W_c,3ƻoG&lov;ہرc?6 lٲڶJi=$[&Ǖ|:2rewxfm'} MiX[-o7k[WNp7ܽ%twMXâdGSl#`@Cã7N+׃lA[ߎLd-A7?8g;.++v˖-F7nQAAy~i#L zI |xã]pQoi;nٙ˜aGT.4o9ĺp ,t^O7͙>~[3tM-۷hBڝcQdmw3jܩuv7\bz ,@[EKJJԎ;QF)۶o߮ Q*iAZ2 {7qw%g,IXz ʈWq콅?;a8'OMNTof?V1깾rl6' S&H̶jCFp۶Ɲp¹޸e9ѣ֬Ccv$ `N?~s; =`Ϟ1Nl™\!aÆr as{=AC>vnyHUvX|%l%d_'K py2׼z j޸# ,C mՆ0k ,D޸Νƍ@Kyy]aQ:V-\sE?ꨳ#^{eOx~?p}-[Uzi/}ҥK_ggv;ꀽq/g؞ {#zAiNg;ܾ%Q۳vZcX}byJcw'^ g|7nIsƭ+]$P ~ _U*j=AtU*Y>0اVm!Acs^#!<7 u=_;,]7Bytx 7o oҤ?-5zժU/iӧEn6YߎK.y~GM?|Y ,uBdnt~S5\|'8W,_q֤^ g!;c|v ܩSʾYn&իWŶwj#LKW'>&,}1ized>\=#W ƶs"X3bĈW YfπYXX8g#L2ƶp ܬYKW_Ә.:?>o: Guɐ!C>1c͛X^W\ĉ*..]XXxw&1sԟGYwzac::ܖ"j`V @γ-a?ST]oVME_ٴnrma| }m(*Ϧwl@@lUW%nclEQ|6pg[b3~*9߬pCc( i<"4m~Ԇ۠ʋazvSO>0י_~=k,/Պg#mmee #F?4hЇ;k{ئDWK6-αoϥJ^_u٘ Vccy?wl6y?D%W^Ҙ3<ku9(ᕧuɴx2ՙ1I]px7v+z*V_:gyrEƿo4ƇQQդI?c ULpSn!YOTs:t3gTjj׮]Ϋ{ɻKJJ/,,<ʜM*ֽ;>hc|.U,ȤUjE/0۪M^a>Q %Hu;i?֛_'o7s+HP믓Nv=k߽f_պ퉾og]ޒ{dܖeGzDWռEsD9b}V:~ugS]tEQ!oϻ6ܥzYox d,vn[/%rm[׮ΰQMc31p+ ߖ8FZ.ݻ8ږ?O"'2`[?+?'*=NO<ĸO~y$HP.i-Jƪs.<7 g~iyR %;([+4~L?2W"Έ&yv;n䔑Qa4QEߧO{ذa&2X'xbwQQѻ@XC$^g_<#N@+K*WNOt?_"y/*w`]""2`[?+?'XW;^ ,W[b?ӍV0䶄A~%{O$4醺++VZԷ«.?"ȟ2eʳUUUFnjmӬ2_z?y*Rw߯2eg{ӻWjJ)Wz'߰jz|W{_PM}{Iv%񷬺~PUnDܒ+Ve^:.+U ݏ/^k֛'G}&7_-Gnl?Td矄0yoR*La UC|r U .>ģcVqwqrV+ Հ.˕YFرc?6 lٲڶJM*% NRkarU\?\lm'\O4[)瓻[KϹwN|۠ϳg=8owLg@X;c87Mu;kփ%ڏR:cї)^zZtY&0۪M^4\y*m0[FǍX`c [J.GzK+ ݒ@O+]uuO6z.QW&+ eIzW 8yGYntKHZe䗕}e# 7nhE%2`[?+˶R?'T WOP'd үO~5>HP+KI0۴YSgY+Yc U=7ƇU\cٮ3 xzṅ4n "ȿ >LK.}!,+Qh\@.Ymz{ST(8pyVp֭ᱮN-A ?#<\+R/W{ۺۜ(:loAxaU_ ~yRDC,`׿KDLu$ZVOnEE޴ȼ@l6ypVuX%ϢIn*H2,V#W$nF綻r\9O#r!ٴ_4%W9:Z;uPiR+{݀Gn>3{Y^@xnf&yNs3kM{|hggy֩}|Њ'2 ]YYӶiVzmJT/KjsBZ*<{J_ow_턂c{Ë+Gymo.%LlѪ3\ @6ym+K%Lwݺ]k'\[߻^ե{vmUe2JgY\*W0Nwz ,!g\t7\A33 %u/ sKes1HeD|>mVld.W4C"!2`[?+˶R?*7XKF4ӧiѲ6l `]9׹u~=pW E\yΉSK7Ƈ]r SPrW `lܰq>kTd9:u:uo^z_l~Z1)Y~ƹ$-㍓J!#ǪLus_uK- b#܊-'?42ܝF$ϟ+yrjɦ]^Ur\i*dpǫn$t$ճoO'8>#E\d |hCO-=1od;>[@ڲ:Wɶۏ?AOe%:d.?y?2Yr [~*Y0۪M^4\gaʼ䖯]Rc|\E-!S-Y {Fm۶o޺ug%%%oUF1jإxjߨ1"2`[DrmY7~SrVBuQWJI\0ZD% |***֌1"k֬3`Ή>}Sflհ@87*r V!c[֍ܮwYjƭ3qzɭaVT~nfiϨQ.2d3fouuڼyʕ+8q[Ż ϋ:sclpW V/aϙ<#a FHD{&}[b[˓נ)J7+F#4hPՐ!C^/--So5`j^<3Xglp ;q1-nE 8J#$~[cꙪ cg?ST]oVME_ٴnrmhclgFH7c Vuy 71K>V @γ4B"=m-A1rJ㳮0OQuUrY>6Q~g yFHe?;"ݰZ~3`[i|)J7+&/lZ~79϶p1쇚u}2Dac+{mY*9߬pCc( iV @γ4B"=m- J#@V1wVuyc( iV @γ4B"=m- J#@V1wVuyc( iV @γ4B"=m- J#@V1wVuyc( iV @γ4B"=?9ݩS/cWDm%a@iWfV]S-ZVM5Sy]cUS cX]pvL{2|:sTդGV s̎,O^3(Ouaӷ}Rp4^VvywϮu=_{,\ձθ{nue֡j=}>}Æ 6Uz?ލ 1^/sSmޯIVFtI zU|ʫ9jyQ#uu-qn*V ~ne~}rhog%\Ry]8{۶_r7{& ܾw2it,'dN@8KYfMcMJ=$2xuge_6m:xǍߛ.oPg;2eʳUUUFnjmӬb^h4~9.^x>^a^[~cZyuuDxAxΝaZTntZo&yóg#˓W4zHd: vQ-0gwUU[2mNyz@xرTWWA\^lomb}Fo9z@8KY(jC*6( F8ܺ? q\a6{mΜW鯹fWԸY^Lyz@-[A\޸qGX>FO5{Eӧ{x}oJƘWo ãwL=/T@XޤiSjDˑ ?{/~ F 9~O>k4< 8* z8zo;_iO@ .0.]x_-/za|._ J__{tKo솲:2?򬳼esF7e:a' d7|ȵzóg#˓W4uK:yj/kڴn*/7GS=|ͷ`۪C8ʹL:w>&"^iMt5-=oޛp}ud'2 ]YYӶi^M k q￿rUoՖ-Eu%~8apptj1mynbǾczJ1kg7}p/kVo儫umHL7xw۷wj }b6u`jفsYpy38z{w<u+}$2uّ+: . :uƎ m%n߾**cc;P r:䐾yVNr1jkԢE{Ҟ~Q ח#w鬲o֭[gqzmnb?.S]zva _%S\.OԽ|c̣/uScz}γ%nߵs]3~E}^3ܝW]sՖmڨsx'[lOu׿:Al싞&<Yudlcvdy F޲j:_zȑ˿۶m[~VRRvQQQoSatj9V1;cƌVWW͛7;\'U\\=#@V1w0@C f~Nv@"Ne?X atj9V1"|OzflEQ|6+E f~֥rcE La@&/lV@8͖`e/K [;l@Ò󥦻i]m(*Ϧwٲu0Dac+{mSuXrX;_alEQ|6pF"Ne?Xitj9V1"|w&/lZ~7^E V~rcE Labe|MME_ٴn*@8` [;l@Òc( i4zUpZ- N#@V1w0U%狕56Q~g h,ZFH7cle-aK+{k:m(*ϦwUYi+8nX-9f[^}1l_Wzᙔ/VtDQT%M+ѫiSav1۪]e16'O,4I&.s ՊWDӻ=r1_ܩq1tPoL4˴m^Nt;(OhBu??kL˿ppI"+[k:m R7=tj߱w\ѧITޥ:,&+T j5cl4zUVu aӷnx.۪kPJ @^˼n5owUMooR^u7݁t=e-^%gT8w_tI;b[.r |=8SU>5ҹzO~h̛IU^,W=#6"gX|:ju׿z_K"8_mn[YYyÈ#4xOj aiiΚ*e,0)Q=֓Ic"j do^Y>>*{~׹[gc^*X%ۏAV OUCfBv^˶j?~@xνsɼnՇ_{7\쟮Qk߽5^B`w\I篊*Tل2cxuU92@9˓7>xN ֣~T5iY?ǽ +0櫘\ٯzߙB:/5t/gΜ͛ծ]Wɓw_XXx9cUSK+S ׽ eޣO<K$ 2`l?&:ZHCUCfBv^˶j?~SyᙷTwPvڏE-Gڶ>xoj_;A,oG: ^}|%]\]P&|r\hVd޹:~g˿[nk~UZZ9VfmJTX>Q(@X+B=3Ƹ\x%YI 11Պl @5}æo:]UYנ+Q(YZWy5j|]r+$*j[E2yoߏ\ۿ?:['G-K䫖\5N qrU~[T*<0aŠ m6#[nfUZ3)Hez^iks5h w`j׾d_ZߺV߇:ԛGjwsmM,iwӸEs[W]smE'<*9\ުu+rgߞk.R,l\C]R[Vo7d;j ˝ -{붭k}hggOӨ&ۏtZVo{[}#SKV-T][#A+l @5hæo:]UYנvpɨo\Ƽn3S %;([/4t~8jڿߒ3g>=Мq댨q#4cA+>}Æ 6ٕz?6)}3JcnSW}uԸ>QX8ù&\ח/y浄y۸On.Þ9'OΫۗGmB\]9RnZ(^$Ts[6Hr%tr7ϬųצlrBZ^V9ǟ'pRr!ޭgko|ٛGvw R?.佹JI(,XЊ,@08Vg]^2~Z2Aa M4q-W]ˍ[+@Xg j?J\M.K_/ u䖬tKFTn/WVVdcǎ{e~k+=6)=4@{N;X R*TwQ g_g 9뎓`YC&h ,R_ޒ\:w}8egw? rgr7 eSN}guy>qNe%;br 8o+jEI @hl>3Aa?hɼn' l+G%!;M+n>qqcxݒ Ua`]txՇK-W&m̧>3odVdeeenٲ{ƍ?*((2 mlS_]핥N8o-R5^m`g^:}J]2oq-ϼ}ʗVF'jY_[x jڇ^yXW 7'an?b39G-m;|Ou c,+'W?vYQL" BD 4GdCYy;UVΕ}r{cwy>-MntHX\Эto- x\)ꎋ` 񧕞fZe䗔;vDFRm-m߾] Sm}i +KVZwy֭x ˝+ Osgrgmf׽XLOtg,#+7lAB8@ $wsVsꎧwM5kYDdDA~r DDٻ2:L?8 ?lx˒u4ȭF XZy a?hɼnKI8鎓\%g+KU-o1.Ѻݒm?{cU>==ǍRq噰tKhO]'%_vYz1>hE|3.]JC~p@.Ymz{ST( ],Wһc]h[L~F>/+q%@[ /{iro[ws~& .;[jv?߯rH@nuYjHK8&?@U/}1\-.Y}O cBϷxbop0m\ٞl.%LlѪ3\α'z "q,?>[ Z;w I/;otIoXށy}$$ڏ}\u o!?(!w:˳e\qЊ@0x?[w:7xUxMn1^zmɦ%a;a-Zp֓};V %@F?1^WvB'XpgZr SP/$4 =^(2vߧZw鬲o֭[gzmnkئD5 Ʊio\U.Yl,?VMe3ۤp ۖ OJ: wgι%;sǯzeɕGǟ~ܨ?HTNvٷJqk.2c |hCg|nyϏy oӎsЖ 7ԹVO՝'~N1-+'wyid%p\6oTd@cO%.u>/\)-!lÍ,NJ=#G\T^^ݶmی7Voݺ*D5a}6r^붭 ܗJisT^||OEEŚ#F$Rx͚5{ 9ѧ_ʌmvFE0"a?SJn;k,5Ƹ}ְrKU_eK|gԨQ 23fZm޼ uʕ_L8݅Ey1j%_z9TT!">E+9_<xРAUC ytOͿՀޫy}f`MT-yָsŝƴT96wC }WrX>6Q~g hcOQJ+&/lZ~70x)*^bDQT%M+#gΣ(QFDnF .A/.A׈$8ɞBQO_b73I8DM4,-{vݷ|BOս~SUS/>1B}m͞B$}|*}P'7&Y SþOO[SO6 6{ a􉿗B}Kܘ|||f6fO! T>>>RoO?,)Jraǧ'^ -}rc'^=0PI?TKOnL>>>kP`*}J{)Էɍǧxmol@%S/>1B}m͞BEjM=KXD礞6fO! G?TKe#"=#@7ZRPG}jxDgFK av/R-hIA! .E޲-)(ڍiG-;BRQ a>J! G)(0@}BRQ a>#p\~sO=e"6tfxB0D6 {anggOH7˕c"[u0S3'1-:^)ۙxrxd/ L<ɤ;1-:(:LsETCCйs.;c~qa0cƌ655}/[ߦʕcrݫ쒞3+|'>uj:^[/57w|5!C]o̘ܺj-}-=ϛwɶb!877ᪧNV!)ǓM6r!庬9oO_.O=8?{aᡇ ??.\q/O:Y\9V)WLvN?AÆ%wfo|5±0ܢ_d[6 jMYy@]aV[o pSOuڹwKV!ӑi--PA!δ3-N:g͚Ά R455zԩ{*;$WUJ飞w=tz|Ttqz̪sfӑB8f'w!˦wu!Si߽))Pk)j;Բl:rⸯ㉋ba,cX>|瓬s.&c'MJ։eOzAOדioѯ_jcZoUSdZk!ܞ1WNgOkiنB .+ojI$BxԨqe_*li}WSTkqTs{ZZhCP߉Z)RJ CGgyגEߜef-Sqq^_ᬳI!|wAB/έMi!2W_-;u)-rtv/.?tz0/>W|qpʟ<0 n}gJ ˶=v}3/<>ٲZ+cc[O̙6$.k阎t/*Xۚߞ1WNgOkiنB ]V_…wO~G5Ϛ".[ny+>..^/eoaAn˟I5=hฟG$ ]Jԧ [o=6̘4Z2I ,gɒ%̝;{ gOh;ʱJ%95*,ypφO{l稣Kg-M,Q]78>>x֊r򭷒unzpSObzdzZ~3$Ó;Qcp\~Ϧ$ka㏇;Lww{,89cҤ0/n'x}下]qx~cAÆ%u4m 췸1XIQ_:QKܴ5xosSNgOlC! І.+L9,va[w rxĈѡueq\wssbN;a艡-GL;)ser63}$|­^-i9In3g|{Æ ·Zg^lғrZ/”3{R5M  ~;N6qb]zk.XB5uNuӋĻiGO4&=9X6|a܇?UKv-L^0xsGcc߁C t&LhX.яqdm׽L mv)o<>rE|#C7ݔ~,m禭sS˘kNgOhCX}?ͫWx -Ԝ9s5k;>hm-<ӧOqn)m\9&һ㣓{RGkּ^>SOjir3 %%ٳ:cڼSzCyaԩv˕cҾ~}xɹ3.0-|9imQg>?=",]av |)\=)=?7/^ׇz()㟷~,X̴i6N:egcr嘴/+ +_x!ڸ1/'̍pg&~O0@zT!|/?.~* Q3fXr?j!?ݜE\9&?Y^' 92y#Jq{[o֑xZ(ף ސxQrLdsK zp;P"*WlnA!@ng[D1-:(:LJ! G)qyi);*? HsQCCйs.;c~qa0cƌ655}/[ʕVrݫ쒞3+|'>uj:^[/57w|5!C]o̘ܺ%Վ$~}kq@޼tcWN Xwƹ W=t M9lZG1˅𩧞zGs=7_>S~<=+*eʕ9n9>hذNٜB8y[뗬3v}B1,~@]aV[opSOuڹwKV!ӑi--P=BҲϴ:餓5k;6lHJJS^\iU)zQ16ṋMG ᘥ?܅_,W J؛ґ3M-ɦ#ZZz.++k/|3zB G eRN8pkɲ\ܶbz-WG/Or aQ5jd[mްaѢ{rXkZC'|mg>hn-<ۇz苍s +*eק}{N/}m Nnl6h.2)t-n._{-]ڻ_{nn^1G/_w1e|y8~Qǃ: 1"y+W۵aѢd ;6L1#Zⶳe~7L6- :4CNWr-{VWXcVB2Qw6ܓ[1RY*7b[dMcn㩔m( ]X`)ֽLU* F:ꪰv]VX/+JEռI1ܼR;$YqÇo}'nO95%-O0aBÑG ;&S8WZUMovdb8_~s$b w7[ƒ7˫*{qWqy1"/DZ+NIX .wRGu,¼kɓiO=t`X4)Kx"s3i=|<=Xrhx0bc?y:25)۽!)Z?q{o}͚VotI)Q\5g#poyn3jpgOlC! ]Vd-WK!sO$oOaȐO\N/^z)7/$]SN8JɎ5{nst񴖖m(Gƒ +[g=y{}d:->pfv.W^鼘8w'>jI˺ϟseo-pcrU1G_%Ki[_v'f!rx~b*w"}Jڣ.c1֋|0]㌳kk;;S[_.7s/]vXqLkp{\m;}NTԳ.},J'#w9u?am1/jދ8=[VfXsKߢ_4]ƴ[ߪ8JɎB=c>Ҳ 0#" %³RR!JqM,5<8]gΪUeKwfϦ5n\ټO,.+^ȋB]1 qVƬyte-f]ն]<{ٲ*5Ӿ[\VWeKs*Xώژ۳M=Ҳ 0# wVJ𬔋/i2jԮ>JСʶ>GJs %.o9,[Z!1-^@k:뛛To{+]}?T+ٔCG*%e?2[;utz|q6.G̲0sٲ;ךּXfNeq{|lYYi{߳[(^>lgyw.t'ӮpwNϜyyٺgy<[lR!| '馛~qsP^}w gͦNˍy۽镊~t\"v7~7? H+f²mwt/OO>tzc~bܰ!y,x񜖎oL;K*Rst񴖖m(K =lJKOBwVR5Yp ޑNo@YVwep-ouOĻ׮KoC0`P2?8QӃ OG<^:nib!r񱾳V|7osӛo+z*sO'X 4|xrhv ٳwD,N߭48Ͼb2yk&t,~>^{-,}0|j.y=UtiRSZVV^c6,)T{ho}d})Lcw^s/ɝ6xnjs[㩔m( x?0d&L ]eM,|L9lZ|&Cc9㡳)&q͋9ѣ'&0;vR8W_Nm)g,ǍHRfz;Ւ㙴dxn Ê瞫c1Gcc2΁C&_/0aBX^_&E6;플7~>0cC tSn͞Z|s禖1ײ8JiYW! ]^wwbqO$7^)Cs̹n֬Y<裹ⷵ<#oL>ƹ_CJ+]):>:9;_zNZ'0#Bx͚'|_ ;_nz&BI94{컎96뮻^=C^:u_AJ+9yɓsg\xaZsչs=)Ÿ$#^v,Rnz&B :cO;\x_ׯ_z衤~Z`3ӦM8uԏɕV3 }ya /U7S䝹q~4}褐z^__{aG :߮JJ! G)qy->C>=q,4CDڙxd/&J!E){Y[)([>)gQo;~|nr[o>0h0r2wgSO-;GnJ7]IFf^~yn-o[4r<ٴl.@{!|MozS!|O0bĘe;Raܹss18^o{1co?͛,WtUgIJ܁C} U/޷r:(\Ͼb8㮻#ȝwέ͚_g}wZ s?]'tޅ/njɭޒxOhOyzǮ^l+sOW=7BxS'm]ިp飅𩧞zGs=7_>S~x7A誖]?|•?ywQCM8thWͿ֚ t±0ܢ_d[6 jMYy@]aV[o׽j⩧:\̿ m19Ҳ 0'(KҕI'YٰaCRWw_O:wL誖X?E\Bw֬Xƻ6[KG ᘥ?>~]ɥӻޔGkEmgZMGlC! ]Z^а(lпaAadžgYVe ᫯~60xcaŊrۏqĥaa-7o;pu}Ǭ^0y0dm[nb}u2OmGGyC}qnrEW-q ^.sĥ'B{ ?-Bw^K瞛W˗]xkâE jmhX澋Y Ӧ%wN6,|%޲'lu{W?lX[oysƻud{46Eܓ[1RYw1׭kX1gtTJ6@e𭷾Ǝ+F.p %aeN;&O>=LxX:m?X+l-{2\qϒ9N9rp5,{yGwogA}9*,ZtO:>ɚ5_[iΤ &4ykl΍/m;sEW-q Xix9aĉa6ۄ?0í3[ƒOsUWݧNMGZ[7frGs8a,cX>|瓬s.&u줖Ϳ'H۷X{,):hx]tQ1zt٘N^>9¼k˶ɓ+ԳJ79)۽!)Z?qǭY ??)e8R{|Dt<7F=c+M9JiنBl\6f͋p,lKzߒ8.N?;+c}g'u7{d]3<_,Y+|kɼy~pvcrEW-q byZ_.я>GNpanRZ6NO CFOlq8K Î<2^)S>j.=/w$ٲ2St;N?MO>=~7NvɴU/\.{)TSdǚsv:xZK6@e3y{;pO:鶊~..sտLxov/',N_tKjV.s-onzK 翾~\[Kn744<\\UKE\B8ne]Ż]j)Cxذy{65>tzvZ;u$ףJڣ+;)[:}T{qv{ӋU^_-w.>cc:e|T)ٱZgնZZ{ /?Ӝ+?[K-n.ٲv͚yN?'Ӗ.}lm]t>>scBx̙{seo-~7eʔW 䊮Z-*e#c1E\[7BNܘN8!+\qܻPzp?ls߱,~QwX\'ٲ2G[?'sKߢ_4>}[T)ٱZgնZZX,$[~VJ,v.#FbVu\?h./J R7R!\Bx/+z=АK^c=LR6s]m%[U*kkqxoxoq^|pvlJ Qƕ;yc+1k^=o~+R8=i--P}Bi!iֲvퟓl3LK3θ3WmMW{dtw›rJOC}WL)otޱWͦ:jTټ}>:ו^?'.}6^5^,yg}2tz,(;_NgJۋi-]~o~3OvLztXVSTkst񴖖m(>X?k+?[˕W<7 )bKהּ9o&Ӫ1{=3~7.N8tz}se>|du׽|dzw͚߇nz3\qSeUW=n=:}޼IA5χ#wI5.,[drGvL9re7Y>VM٘cI<`d[ ˗?̃ Oxcg҂ YdI%s^CCم]|'n\XB׾rnXjUG<ǂ.N6[x:q{VHᖷJֹ7s駓q~, j5<OD͎!&qOZ-1cw{,89cx9vbJJۋ_L8}ڵI׋XGtBW^ zѰر~(S<>>/R6QKܴ5xosSNgOlC! ]Vⶡaܸ'wƻc ɓ+K;ʊb.Gz7'6~%}3v.Jj}qǻGXƂI}eb=zĤ88{4ĢͿ88;{]0}ؘwСa9s‚/})]v pn}睲+Ӗ8в~Kik;62ZS)-*> =<Zʤ9s\7k֬w}\Zy7O|cc̹o\%K]s=)>A!EPR(͞=c9;W9Nzqio\%]ׯ<97c:7_zNZ'0'((<2ұ{ŋ~C%%poӂ 6mƩS~wL蒮O,?+^6n |;sh.\陉SA!  .JJ! G)(0@:RROٱH @:TsA fOv7 xf/Zj=(fOv7P"sE PB%fJDzN5hQs=,W@HIF-@5 xn3&+D$^2 xn3)+D$^2 xn -{rkP#^F!܃meOxo({P-ԷP"sB}zpJ<=JDzN5Z@/AP߲'W@HIF 7e=([ (9hFL,YU,FxrK=˞\UK|aQws˔?1^2ra7803&L>=fg}ێ[[a}, ><̽pzj{䖩x#qe:[dԸqawʹ9J)ǓM6oB W]ty)SB aĈ1IvreOCCйs.;c~qa0cƌ655}/l\U-˟y&)qR]T+WRxߞ{&ˍ?p>ጻ G#w9N6k^=yj-Ϲt8y~{3&noIc-i_Zz)P7/]իmB6qnᪧ3QodӲܿB8W^BSO=#֯_z 3}…OԩS?^6A]?|•?ywQCM8thWͿ֚ t±ۢ_d[6Վ7eG꺷*]oo~LWO=iei񴖖m()71]YtIΚ5 6$%p}ijjԩS/NPKg T_<2Κ x7evzkH!ǯ6+tz_)c*coJG ﰵ3M-ɦ#ZZje)n;>~5ϗMYtի%e0`Pzaa֬rB꫟ ?/ usFXXܸzGqi=zbr*}{(\wݫrG<7ի&O^ }v-SL\?ҲnR|ɷ͞=;<裹<#oz/666-t\%قR!< e$[}a>oeRN[8k:ss9zܾ.ko73q&:cW<\پݮ %8`Pu1|]XQqٲrw&Lܡ=hذSz˞|2֘/wԮ&ޣ1,ܲmuΪ{ŋn]ǒ=79{n3RZjep,^.#-Auo'pyB[ cNJk' 7÷/~qv/L|z8t;lL+W*߷?.,[d⊟%cFܹyl,{yGwogA}9*,ZtO:cYsVZ3i„ Gyں38;scccKN\UKYB8e. 4sN=qb6a}(̻p;k-ÓuU?{w%EyG*l*,8þ(p jh*& Wn q7.AFDy{*z馻o-VrIUsssq'[W4;iyRKgg7hK]ɦ/9;`!,W:˶8>Bxo1۶̣Ujڵ*;گ#IN{^<ټ_?1sv.:e\n,5ר ;dc9ɲs]N{Q6SJTWRVaK*>vE:깉gϻ9.% a’ Wq a+ a)gB֗RX [݅q[oAe/U[q5.W"Fti-ˆ [瘌[FϜ9 x X2f̘gd<T,qd~UjZxZ쳪ݠAX=ۅH9*[FU} nxZ۸,Jzhe a{S;{Yƍe/V^};yW>;]wY_JZ~{{FzzLˍ}S%KuM;'r7xi?

(@̤)1bKBs6%V!,b*U5RjM~z=wYyW>¡vBMT!<R>\OdܹށTϞ=COn SBE +;s֑x*_k<_ؽ;"N!:o/kt{I/ϒD*WJ6;ϋyV֬rHeݻqwY?_W^dxus~wN~4H4xS >(@̬B-{_Cx˖-O37=fuj=o2XKp[ a$ȹ5%OQQцCܿ }}~^^HOE~W[˭I$>QQQgذa2${޽{8"e'vPz;zg9I' a3 4[U >͛;vGyDwɓիב!Rx (Rv"^Cu#jʝwgxK/K3> i9FG ܷoߪwޟ>nfv))HɊW__nܮ~q [v릟q 6$"Q#o 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d 4[#e"O;j7d$Ie !$aJR.(HjB+(?!$B8jB+(?!$+Qp#{> D4C! e0QPFQ@E! e0QPFQ@E! e0Q%)eTrυ{·ݦ2>r;͔9 BH(SStBG;/£NOELB8udȐߪo߾lY<)RtzRzM]fV<\6ˤ;z?;S3.BxÎZeW?x5l[DzL#Ә1vR˾#Ep"[|u{?hpiǝ><]x%^uU|]4i޳<$:u vjǎGQO=S{G;Qb/R;uc<{}>vn#ݶ_u+YZmb-0,_!޷o,مpΤ۳>Otv?Do8zT8t(aFkH’OA! ’E#nR,7,'LwС'wڥK`w}|yRJSte_oo7h}>țoVszȳMvmRBXt..%~I)IKQsS >(bBx„0իR:QU2SS?{x͚wlceU׏යL4iÆ So߾s8//oQr+ޜզ}:Y.zq~qsvV /@_m,ny/׏Yy{Vy^50{*3=EW25vӦt.5V ۶UVT:uROo9~\ߞZ& C)\ƳlKk[[d7HYy}ɟ;wVG}m},ط͖[+qȹٰaȜ&ءϳYVn,mz :~ BFem e hG* KR˕6rdrRJY̑ngޟ5[tq|VU^!;ϵrg}_/Z3HIKp18d*W21r/7R!s˖d…?r9Ǵiz;tзv)By%䒱ce\akܯ0B%9ݻO?t_c6R\Zҽ?筭[vs|V[s뗯Ps\JHss{9~}<0@ )pb\T;B8??_| ȸs={zO-LOpW KI,WJ'ﵞ+%o.BN!:o/kt_a()I!,t*ꪐZ*HtKwY?IB:wݥ'k?؞þsKFZG9dzOA! >lx`\K7ҲY ݻwz:.ZN&=;C/~]kJerfv}^NȲv˒yh)9/TbfsOז29.)wiJI]߯I)7'Dk9~}<0@ BX\K72BxzlÆsqj<);>Cx˖-OgL{(W;lݪ*TK7ݤL`֬Il-_~d 18 Gq8pt^#u:ޗ^IxeuKmpDncu$3wu9޼^O_"5xi?

O1vd?>o޼رC=#?o&Of^9#%)H#_~5;3se套%y aPJ%y&p߾}޽{b٥xf"ϊW_EnܮV*_Zj٭~'<ې|w0]xQT].@ƠN])!##]A!RStBG;FjB8u"D|w~w2pJ%OEd ԕJ=1R©+0U7*#I5|!a$ a((2B( a((2B( a((C~ !$7@FC !$);0BH"^ Q@)/!$.;0BH"^ Q@)/!$.;k& Q@)/!$.;kP P@)/!$.;kP P@)/!$.;kP P@)/!$.;kP P@)/!$.;kP P@)/!$.;kP P@)/!$.;kP P@)/MSwґ˘{=B)ˑYBBOy|UsJ}+#5(a(ȉbҢ:gRϏӑ˘,sO!e5;kP P@)/JfL1_nUFJg,uBHY0Pvxʋ۩S˺c/\ԟ(1Y&븷#5(a(Ӧ7R:XP޳T,ud]?m/;l]x饞{,;_MYӝLI ad a(;%rX-Бr-Q#{m1)m? r*$֕g1t^͚!sbZfV,JsLRYYWZBN1X㜫\9io[r߾~׫L_v\[//͹1{wzHycg9>E]NBr\-ŸsYx?$=02 0"ȕU;Zg+Sg3]/Tc'?{)uSdYF}Ⱦw#W`ZAE-ݻ{nw{]c \hw936շȍXsĉzc]fo#w 9.-K92|ߤYv&rr[Xn'I7\rlsm9e nO! B! 驦@Fj<:mzfU7޻ZJ=qR~*1ǟD/ZMH@'e]V!5"p G+Y qz$sIYe~qwB4$GL ӎc{>\2ܗ)X@eNX͜5~w ֒'~q~iέ|}o^&Nb}g9F{$ \_+g-mdem9e nO! B! )`8JX./b{oR_+dO3I3USf-Q3=~\.6液ٗ:.rŪ](-jy3G:H[II UڅXμ<{]>>3tY ~c۹D]3k-ۈjyǎ!E={l|"ݚHV=Kחsnq!7}FcI}a0{<غN 7)Q( =Sgd_Oٷȱ"ε/!dٮU\9k_NunmGYsLBX1Uʗt3Y3{{)w&| >|פIL+J%[; aÇq]Ok={&e񼿱3'g[4nl/UnzHrRYr^y6߼eoYII CYG?~ƪu?^0A\~}ٱ3\\UZ\gW ad aHO#󟫗MweIbQ|M<!$"02 0BX+\ I0!$*;kP P@z I, !5(aJR[SM*BI$J8?3VBH"0?9&t%QJTvIF}2N3eNK[ Xy B!(OcDH#;(df>K[ BB*;kP<!I|R#rqy (BHD/m) aBIA! O4F$9sKB8ȱOi)!0( !$"0?9A, 8"n+"O;j$7"`PBHZE~g a~r1"$ɑϝ pc7R>﨑߈A!L!i5;24F$93QqDHܹ/ OEIwHoD&By#B( 8"n$ܗ"O;j$7"`PBHZE~g <!I|Cpc7wKS@B'50( !$"F~@Pƈ$G>wF!B8ȱ;%)!„VY#q C(OcDH#;#qE!G؍ĝP|GF aBI﬑y!1"$ɑϝ#rF}Ix (BHDF~#0!UwH<H?D]!} uᅗl5rFTFHܹ/ O^|QϟZtj6j*eemRu/.Vxóͼ{]jԫW8LբK5|zo<ۄK3s#^'\0>|7n,/ ) mԐyy毿V.TZUNpٲ 27毾Liǝ>0NO ﬑y!1"$ɑϝJBx܇bJj꫟Wf#x܏#G\>dȐߪo߾lYx Nc@%Q)qnjD+Ks<߈qjgBBHRe4F &A}yseݺy{! +?DI Bnn~p?W8]!r| 84iYo]O:|;|c#zJ)=}#2߇?P~B9[IUs!s;|XU\ަ… ?nok!,E^ }eety{G|+qO>U+Jعтh$.}$7"`ڟY{A!$e4F ȿnU\ViY&ئMbXb-j{F5kz;HݻSLy? ={kƍ/S8]!,Y1&naϲxBx„ {zr׮]|c/vP~Y׿[@ dw6܇lӸ];{{y,:h7ݤhxY)%~ǘI)IKQsS >O, wqA!$e4F 77ҲL̞ycqխ[[ V8y{ۮ!ipϞmoqɪUoe&y|^iZkּk!?g+9Z>iҤۇ 9o߾s8//oQr*ޜզ}:Y.zq~qsvV /@_m,n/{/#=U^feU׶,|My~-}㪵ϙ{#7^!%Wf֯!(kդj۷fe?YK;F uQjon+_,sC=:͛ʳSxn9RZ0qX&9G+q<~ #QpBP)OcD*}#-PǗqҥ˵ .a޽/H)(V|jd·.+Vb/ -[+|Bxժ7Ԙ1BjC V^n5mўƍ_ٳeg7Я-ٴKϜ%֭[Cޝ{y?}w,<T5vӦBXg;گ#nǥ;lw-1sv}{n/yj6lrvVc4U$ t:;GmvN*URW^KY~,>l&9G*u<~ #QpBP)OcJ5U:uT*UTUZsKx֏TFZf-vնaCUUTI9,uM~ z׮>dV\J/oL-ֵ;sg{=kתفjnיg;O=0{hպ>5v{=RܽgL"+:Y\\T9sy㐢y^^ꋍ=s?Y^'mV:ԳXoN5.ׯ7oG ’ W a+ aָ*-:wZ˖l'3g|tsKƌy%)bIzKޥ/`o'nMYK|ծm7OM"v_\1wj[kkqKo1Zy峳uE7toרWOyݰA2zɃr1SMURZege 5R5ީY3ubvc=擷ުr4c{.s?;7o>X|!C(aprРA{CFChdƍ;cy)[n}<%)bWO*oU\9}R-xQϺȕrcY_n?|OxK5j,kc~e hG*RY;K]୊J]kLίm+!d~R K9w, 3gȑGJZ8lذ`х-[9*.OF!\XXc=9䡇gϞJx)MS!W:̭?zoYl,Lcu^!+dX{J|գG/l $DM[D4Fq)$DpCq4F>D_?p.]rBOC+}#-;hQӋ8gɟR-+/Ǥ[NOCgժK4(JWys+9yy8u|-Dy1cذauٹxm21-v[~9!O< T݅C\ 3Y{:u[~L+Ksݻ?=W[o_`[ceΝ!?.pjKX22."r.k7x,ٷ㱶Ӽ=.2vVi a)9:7U|yH<{"[Ly=.+7Wyήsy{Oq}_"5xi?>p Tq0$G8%XY_o_ׇ<vj!Jg,P%V!,Z[\a+ٵMNbo.ybwdvs,7;n-\A}FJ-% as2>H,˕WT;(@8v;ZKpŸϷ_S?>3δǗ,y.\$^{<;۰yZϜ#%c~VuoٲKS@\eDx_w\c/u`~Ga~_䙬f*Q!,cIl-_~Ed 18 Gq8 g8L{|s`) _{M7ו2ھoo.[f[H 7W:Hfڥ?9uikq9%\-gΑ >O$@^1 .YV)eY?@'tG}n-qU]s?̚eSGUPAyȭm/Ĺ+s>K97E[d:2:csermyv̘w͙4iv;!iS`IR]j~9ꫮzB?x˖jŊC!x߲Ouf~|n1t+/qJYz| |=zV۶}o3y)y%#Gy%)"|ZVZa7r5kBiڱc X a)ck)Kr[_yMkYN7Cלr)*eGH \)߬hnS .kDv]y<>l1RJK!,-sK/[n/:xPe7hHq#]H͝kG,+irk2PU&MZᜓǼ{լ?!dNr嵜m}9*WFZnXm?:߈@p' FsN8 a˖m5mb=f ]K:Pع,͹+*i0qWC aJcZڪJju^jҿ,%CQ&2fE-Y=ޡC=baö2&Mrܹ{-}ԭ[{aavHٹs[y?lQ2/rkh)䕲KJY)T9rUmI'9cF6nUߊZ!ٕIqjm3﯌[U /)MrrBnJ3Xޮu/3W3HǒgPTVzE.z\3+zLּn(yyyzUW6h:94Wko}ֻE׮0ɐSU}[x/G\gs˜cO"/mO$@^1 )V%󟫗NnmժZUrU-[ԡ+Ba+xeZfkqWn_,WSkj.{\=Ng>4w=.ܵo7γ_n},kdeBTK+TZu,\h_+W7O_,T^Cn,ErFJJ67W%s99z_{oyN{BX[C*q97'o8d)+99gK-Z[4hp0O^> I/RH6 :<}e߾}_翟7u>)Hُjku{9IߧDF? 0Wi|"8ҢV;CeHAy qGZfg7߬msY4o~UI~1dޏ)S\A|x:vWWrE_WX5i}{l+ ^)[Vr.zيqbնaC]2r4#vg=˜p˺usqs"׽],,zV wz^H9?G5ƒ|upaT7`pc7^b{ *{h޽?]z*)Hɤ;T+=|ǽOS~#ɟë4F$"%Wr{v/OˆUvꪫ!pA~#r솣 >͛y#<Ϸyw|3y7{u$77wDY,OENx׫~n|EADDAQpE@V,OEQ V *+u +m]kfmmkkmV[Z埙LL&37s̝}899INnzĈXgmVA ·~Yֺq9︽όysTwT? """""b%_m$FX}E~z+o-wΟoǕW,~pw/;{Fb2FiӦg}gp„ K0~OL۪mWi1(D,U]#,%""""""֜X3(^,k01ZW\v[4i[7k5xx!^Kdr= sP]`PX<9FX*K N """""bł@b|^bN8lxsTwT>?@DDDDD)9R #+ǝ/p Av_@!b(稊a|~.I38S~ rŋx &FV;_@ *#BQQ5R\fp .] x &FV;9(ۮ|0(D,U]#,%i'pb*X*`bX`SI *#BQQ5R\fp .] x &FV;9(ۮ|0(D,U]#,%i'pb*X*`bX`SI *#BQQ5R\fp .] x &FV;9(ۮ|0(D,U]#,%i'pb*X*`bX`SI *#BQQ5R\fp .] x &FV;9(ۮ|0(D,U]#,%i'pb*X*`bX`SW \\D,>FX*ў|F//._Y{[ʂM]L K3&C,rܩ~'NGp}&>X\拥$DkI iRXnb*X*WZ22 om\D,.R|F-?]iw:Mf;ʿL#"""""򻘊wA(0}5/Ě0_om\D,.R\b LTׯl[2Kvpr֛z;W:e2MCDDDDD,w1P`,k:_5aW&>X\拥r.\_Y3@oj84\T| @T~#քW o[|T?ŋ['7o<^ߟZo?.iRG/G/0 F=,^1OlX_5p1kb' #n5lحeK. - u)=Z~yocZ|!)}:Ar#X1_}oYyX#6:(0J|ygu~#քW b.1&,/>JUZ8RGjXoLIҤI@͛L~Ռ/%czyÆtyud[YM|I'9a dp]q嶊 cvϤ-#Gz 9rQ{ A q&{=mw.&ۉk _Y ւB PX*K|bb#ORSiGmL:RW S6pN/g s]vX> [/ѰwܹN7M3*\}I]lL>lW${,S@4{+۱6oMzd\LC P"X*-0@qb.1&+ۿ]ИIgB.c!4oMzD&!(,Or@8T?(ᯞ;PZdz"Gc+߾Xw:RWI73Ga9slҠnݸ>ukccx;aѽ>͛;e?Ji33;s86M8ːG zڡmߩ ^-K %CtۦMO9ӽ۴qG*w }N߾ΝRӧo!GÉپGF -|zi=t^:GGid;eۣ.m3hc M˶occGk_:5L_͡{/+c#/s(+ݰRO/r-ΝGSt 3wk]twa -#Tc"k.wf6ŽF=,knݜˣ\3 ۸tH{( (msD=,0{{1}wsϠ nPy<[?JrDg3j;[|Ο\O?wAc;Ӽ~wIY!#Ӵ&%(,Or@8T?7'7ozǡZ~8s|©iRǮ+ȼF(n,ݩY3g0E31ANqaN*gL%kg 23Cc)zye) ɀ;O{}%8e=N0x9nwqϞzL.i}ggkS'wogXd/Cd J ߢ '8wIp#n{;Ǐj!eΛ lʠ_m!m! {7680v 1k\=r3 ;qy݁8#1j?/2ݴ=sFMS'XvlXk/gPS[[f/G+O ۶S&LM9o˱> [/ NG\u [On}yOLuk:ʾc"k;u|n , Y_ ii@8f]:)?ծ/rϤ?Q ¾Qn}Nu%8r,D]OrJy#SL2i׭5ɥ?)M/;QQ#15?\ډne?_4o]QLfZVs:J|y'@@ PX*K\VUZ2RgZz9+Vh#Ӥ]Wy`Qչ7 #RmA /<ꨔ:'`r]ם&[d@fG(A;\Vp-Y2(w5ӯ98o^[.w啾!2(wHk<ҫPVwg w_o OnHneuۣܮLG JעW_b)xG?}-4x7OpWk^epZQ[?>Q}l+XO Sm\?n!Ku /S xXJIDATخrǝr]3<ؖr{νPqmpsNێbQLQlB.wC<~r[5ɥ?)!m#9=zxrHwzdDY2\9rY\gp;َy~wrrGp{#ih/G|=5u?{>ѷbpsNKY\Wr:F9NŽ\/w >;,N-+8tNu 3'e#@8wWC&Zu\MC P"X*-0@qb)߀FTQֽv`whZ8H뗆V$|Ov^pjK]ǞWڐr2$ dh1}z;QyDLD6pϲ^6y$sAys׮3ܖly.Q+J|=[a6-ӾMg*'wx?Za薇mW>+q}^+!%KL>VشlNm'7YÆ2} Op\t]\i R.w˳]3[y?r8V |S*ו\ [LIرuׯ'ǎʣ#Q3m|`&pưaC ^7\әO;-\ϟ|/ ێc;ۼb\LGش|\MC P"X*-0@qZwgV7ôP[LC /_^kVjGېMi;t*wd IBUUy$tD~ud<8Q& 峬yİ%|LƁi~?yl<[KHѨ~}gT_Oy 춣F϶gk3̰yæeڷ̵EyR ۮ\W-3tI&(/c#}iٔGǺ<}5a?r<5_K IJ]3?w1ߵ7=[1lYN\Wri3:f:NŽ\/]zDr;lMɹ?l\әO;-\ϟ\/avyl] [iӹl@DT[>a10EyoK{ߘێLZn. _t?JSOېlyLG8ᅮANx}dqAJ)J.mf[LIc!9ܺ׹G s=GrL+}ira9Υ?kLNud>O. ێlvؼb.1!a6-s:0J|y'@@ Pw0&)7o>ZJhxh1?>3MqZ4M=gޑN[Ҧ-.7r;y#$α{Ǖ0SlmSɝrNJ̝&j ٢E;{oAҾկ .5M'MA%$:Yys]V;˕tM'w]9R& '_ܽWpW.2)˕|{I}Ż*+u{1R/vE#QLgy<Ӵ}lo~m>d`ou/2h.!C;rL#(}C}u`E]_:yI'9Cty_zϽ-c#}c%x7wgv7oSĸnpVПL LuxXxrt®ඉL<0b, lw'U,G}i=Rfuvd;._|n.Nنm'Ojr׾lk8r=Grt?*[r!EL#xr3v[&s=r]3vavo#bwxd6dVs:a&'(,Or@` L)w^}x״~c?o[7io7i8Oe)m2fSd0I[}q=yQrpʻ2' rGu/ȤYлMg WEm/eecGc2 'w-#b9}EAw ]k2P/L rncIB$HruR5fLJyia6~(oS΋#]w+>%|e86(+^SEɾAl $Υ^rw{l3v ɝbq\}(w O͹KirGIrULw(C6/I *$mr=>ˑcӝL۟9-u%63c$۱e J;r|Y_sq;#gP0@XGc&5?QeҙEݟ]؎2KrU/BK[vҶ,#Dsp:"f}ܱVr]A_Ο1 JKCA Pw0&2)M?ϋ!'is3ҿ{=٬n[!WX}xF>ԵGOi()@D0uc`"]Pvk3 Z5-oOxao j-ːe2끈|gPCȹu19~&(,E 0@Ȥo앵j<ߝNlKw~yg\놈|gccuu19%(,Or@` LdosV/=Vn[yeԕ0@`)a(?D&?>$S?>c/'Seԕ0@`)a(?D&?_ ݿΙSPef/3.JKCA Pw0&b@d0uc` .JKCA Pw0&b@d0uc` .JKCA Pw0&b@d0uc` .JKCA Pw0&b@d0uc` .JKCA Pw0&b@d0uc` .JKCA Pw0&b@d0uc` .JKCA Pw0&b@d0uc` .JKCA Pw0&b@d0uw kVaR/o(6A DDDDDD,PX@aMpP /)"@n@DDDDD E %@b)a(?,0PX@a( ,E 0"@KCA e RP~@Y`)a(?,0PX@a( ,E G>&uA@a| y7ݤV"95WsTΝuKCA \ /SA"fWΝuKCA \ /SA"fWΝuKCA \ /SA"fWΝuKCA \ /l XA"fWU."@J_ XA"fWU."@J_ +玪sjKCA \ /TJFЅٕsG  vC%#BʹjZRP~WC Ut!bvQ{@ bJ'p5P*A"fWU.g%"@J_ +玪sj#\ScpVKCQ@xΜ[ssJvfd]ټ?UGhJ7lDw_sp=颋~/yݤyso>lN:|rqwFt^gi˖zUW im.;.[09`N&7~z3Ofu+׬oܒVg{&ۨsj#\ScpVK%ړOr`G_pR2 -j1}&M1c>GQ^TYM+ݱW/gw^+w'oެn1#LIٟQ?Xmo' ?^I-T~_tv5k7/wڒ@V8F0_YlOdƹ u #\ScpVK%ړOr`xH | ^pƍNӛ6m۷o/)_d{Ç{EEŴ]7F!#G:ܤE S;@۶g@X/Y8B^Ӯ¹_^@X̴R25JPGDYN|'68FG߀'@PL7<ݩSoݸq3ݨc^z̘s xҽ :jn֬mkݧx}0auϞt&-tӦ{g 6[ucl&u=ͻJڵӹs/FСCKteR[/;+zQ>s_;'vn`(ny%x?i,UK߾<VkN[)r}.ٖsN)G+v9>:+{m䄞~m>~OGzY've9AnϸԠQ#=3PVí%@ n<"Zv'd\9,k{2l#H1E,=e?t-hO>ʅZ ^|kr[޻m-\x7M^5?ʻtIǯ!>lwTm׾駟nQ5k֫e-Sa]tPױVs_}U>qW&w$'/Jo{O;֝',07SLC9lߑwaeMkwg^yýWgNY&MeNc=׫{܂)u ޹6=uk'Ig~~"еT=(j%⊿zy姟ÛGK.{/m[Cw6+_QKIb6q³gxӦMFk'ePa]Lr{7開nP+.I]~}'8viF6aW˝p\\O<՝)uaĽ=LPRV "U|J'~`4 W|!|ժWy9igm]}?yw?+_>쬳R_u/_WG \YY;7OlkףNiAW^ɺl 9НGK<V?ںq2y~ }LO ,^6ڬuk.װeWYa Ytzӟ`[δN [װ[\ک3F\??@09;D1o,=e?t-hO>ʅ7T⸗`؊f2S觽GFg ׯSJ˟/XpWG \;k~7s!CR5hț6˝2 VdžغuΟɣ [k2r׳Gڟ\W]6nu.[Ln{ W =+zؖSzY:}}N)a6=uk'Ig~~`XsJw@b)A4h۵G;Qtܺe ݬYshr3oMt-hO>ʅq/Q1LVϜ+{V@XҥW^Y+V.[)%?7yh'=oZ=i'|W)0WG^Yd= ~poi_od- ,-K?qfW>x︪f ,c-G֭m̍zc=7:e2l]Meډs{ҙl#֜Pw:|Ez̹dmޮ?bhLo.0\;htcz\-6yڶkӼmrеT=( _sͿ]? .S./ݶmW]nzoL\och[_wu׮Gyf]7l3 &ѣWC-;xϟ駟nQ>}e-Sa]aJ <~5K]_;)AuN}ox:{/+zQ>sviwx~t 5m;j׽6mҹ5R_T_Gz /ǃ-ٖ}sywq|׎+3'z:Ϻ:'ؖ_-wݦKg2$۱b6}Spl*wMw&:gk'd~~`XsJ6Pw @nW~sbwy][uy8ؘa[&;]K%ړOr`9xRֹر{F:vx3/ ks_-[cqo^yM4mL?Nn=uj{/nja>,]*?+rgm3tu a͛~{n9{wgx.a }J@3\:r7m޽IK߾)p*A p*eK%K.[s{Сr)qu3붜z˥-w ;=h"~9+vz*eԸY3}ȑ) w67UYtDY(ı=LP@kN ;Lxq)l{ޘ?Lx_޼͘-Mw\$K%ړOr`p1dUygw8{.T|??@09'P700rwny ^#;Mʅ:zȐu·fZݏ3g^3c\PiANѣG>vF=bĈٟ.;A|y/r㪭}\.>3RW @8M}݋9e`*w Ӷxٹ6X',ݏnܤIv;W 7fl+@+FuBڰyŇv@٫7}ϖSRGsL[ue{ ;dܹVUUfرwѣ?jw'@@ \ /TkP b]]9wTힻui#Fxozؾ}~SO=jӲw^p^#O}_w؉7^8!w-\[ǦM0GUB%&gNxRO9;Zn㔉^ay;(ZG3M۲%g"n]޴)en^ir},׃ƍʻ/?Y==:s 4wl}:e<y={4kw?Kpn~Q>br)פK?~gv޴ivcS)|(Aic,hO>j(}j"#Bʹjی5jmee忶n gzʲUTT 4ah~}:ĉNpk ,3zlLlwE59?'P0t^ws'ytw拇~D:Ri~n`]îro]3qޯk7ݰa#P8 R+JN;F79~޻}ݠaCݲUkݳaz5g^5G1Lz4g'OU-[|1f̘cd{ P.WC U?A"fWU.@e„ s&N]tn۶G :t#d~_%|H6rߣCu N+_{oofLEaSJ^/^|gUUs/G;J'p5Pst!bvQ{M}cǎOy睏}{4LʝgR_GF_RB:o\|]sm۵וU3}?g+j7iާc'ܩ' 7u|iŝ:5jj(}j"#Bʹj̙3g駟n ƻN:(KĉY,ٔdr)!q.zte>kF(z79[Wޟxw=z[w'lf/gڡ}t+x/?ӫ+LݫNNQ x%ݷNt':]Q 7;Tay KOُ}O9]a1ap 4 '@@ \ /T\d]]9wTힻu3gi&c ղ,uÆ Ow$2LJHڠA ԯ'~v< l;N/Vxz~^Yk.r~xuYga1,]x2/5pHʴr19?'P0o,=e?=? nx=c%ۓOr@J_1r=w&Ǐ|ǎ@kX m۶***l”W2)wRaݟo"([jw|9*VDOc*ʾYi{*^Soe0zl=OB񍥧G۾1a>q01+lm\D&Ty+aM7v>9x ܵŞ-0]\-vĵ7#u;pѫ/2]tfy_?S{xeb  rJ%*aϲ=_%½U"'ʷmIPqTb#m(N=nh%*~vJ˫U"=E%d&m8hw֬Y}DŽqy01[ eժU;N?tc 5Os}Ӳeɦ@ 7hP_~ÝƴN7h FWVy@Xxwr i%|u˗q7O>}rؿU\ի~xu wo|ez%K mG.;?ڽWTb [lT7xwv5*Z*qp!B`5mmtn[þcœ6m1V=(㷶 "Fwqĉ֭[06oeI'@&%r'7ܡoa'8uC\y,7oICYs'.+ٞ| qgm\D&āֺU~.pdzee'C=7L0` ^&.A{ls )5ʃODȣjR8k SfyטO2>@lt:rW6l͕U3˦Oq;OHG)='mK=yAul6Lnw=?~&Mz>ߩXvc%ۓOr!@ ֭{`„ _vizӦMz|/G.F &.. mOPͶsJ[Tb`Cv)5]J@+,_lW[^/iߡ}7asm\IJ'0F 5/|)%}CxnJ%7)‹Ց@wM8sv#ہN얉gPo~|ҩ/w&O9yL>y=;]&'k'@@ E`) _J*>ᧂJA_q|הQy\[O{ԱSh 2q)l~EjN ,Ӯ9onذ77ֻMK\$K%ړOr@#S_"'D UbM )/Tmf/GquGs)aqmqʷxIjWyqnS[ue^:\wfݠaC}cϦ0x7iar]&Y*ў| Pt֜*oc|؇Â){q$GD7kܹ3׭滽eNo'a{^y4 O}{쥷xٛG -{i{4kw?Kpn~]5sSOaӯ}m9w,vfN'gl[MθIJ'0F 5/|)UNR~84P^Գ}vGpBb5߸H׵fy4\gQ]Y SfpN 7.wsVVp|7s;8u%l0b3:uqO,$o=JzFar&Y@a(:@kN_Rtw*aVϺq|ʺ]i@88Ln\$K%ړOr@#S_Tzko>X7W7ô^פv}ac}O|7坿gޣQO$d)a(?>w"VKA@kN_RtUZ]3Hk0bח\s鞇G4޽=Wlߡ'wLn\$K%ړOr@oG `) _jFvkrbn-$G2] N}MhSO9{w8Jn]6q],E p17< XPX@kN_Ro4!'F~?{-1KOُ*k "榜G `) _jz38;/[b7U|$KCA \1-DM9'r= _j3ļ=v@ FG5R\ "榜G&`uj?V3>QU}^|:vBղ1F61?/cKj7FG5RP~}p sS#'_α=mP>WS_؝⚯ΖJ #TM딞.~pKOُ*xKCA \1-DM9T|?o@98`wP8r BJ#TL.~pKOُ*xKCA \1-DM9T|?o@9Ǹ>mFkLWlb~J_尔.~pKOُ*xKCA \1-DM9T|?o@4z}Ӻ\i[ &*1#\S80ѭMp sS#EJ-wLO V/b`SR.b)QwZ*ў| yMQ`0F)瑊 "fnδPۋ Ul&*1#\S80V+؁5n!bnyyY/>MqʗStņl:Ng=??__snyAyݪqF=PJ_mNJ_3E,=e?CKCA \`XV6᧝{nᄎQӦ{w-Z<+msu>0g~ÆzVt/<rn92uyg>-[W]eL/c,۟'.FEAvlp٧G;-hO>+9˜Pm[o9u^֤E }2L`v<؛G+cYAc-[˟|r5o2^I-T~_tv5k7Җ;7?_kGWg{ҙl'7Jz`ruj# s~dLdK_:/Qms\![ ,ԫO E,i:-hO>+e+ &1­lJ  _|Wz3&w 7/cx Y~(Ǜgd(n |rVFlP aie҈fy;w4)56cy zN$c(A+:+&QE{*ьM_e{4uu {lٍ[9:ize;{xr NoxN9K>#g)˜ M͑7i}+=*K{_ٯN!\k>/cx  U7n CjtEΪD&!Tlc6tdeSTy_{^k綊g>y5kRݩ…~mjoP.]R%Ahk.kI/EB5`xD~Ofn+6zL̵ kK^.7USG}d5K)C՚4yʔӺ5u~Ξ9iq/S*-:|Xj/h6KdlBar˶Xm[*SskٷO-K+uXVͣO#rr8~̓׬hbx1qվj^']ihê+rSbŨҔ~1P:KDخ}^wzl657Y a_oTBa0 e蹇:<M@m,dk7+*WF *ްkWTL޵g;OP-n ;V .u[ߠ]o0Xm(&*p@C8%"R3_{R33PBzذZYx-/y)&IIݺ\tebL]_7K fj{;7ϔƭ2Zޞک~(okμO}oyQ}&{_5鳯v}=n'1@ E˅)ў 3K% i1oF.k寿+YY)l}aóTܾd;ԺoHz^l#X |1b C| ^!{[í*gZjdZZɥe|j[g|fܛa fCN6m߱#MmfvƥK[AG=Qէq=Odkp0}NǛD9jLK-%…fl/C87j;uVAc5s{ Tq!*ߠ#-Y9zRɟ3O<_5`xDd^jV|y-Yl]6x^s_6V>7Y C\MUܛa fg6e~x3run_fu,fXڲA0.C3Q߭w,޻d*~ߊTj7܇lzgvʳm"K/@la yn9fJ RbbTrr%JOoaN^)|j-ut%+Hoڴavj\JJ^SGqw[teJ >\w.u .L} ^+&M˖ɽ_2Ǝ {=rl677Ð5TCve8uϧv}ذw-g&J=[%YvW{,^:n߶M/Z}iJUgt{7꫿|0}N8Ǜh1'4ǩmb.D36sSZP{eM~:]ISS*4xs噙_N&p&]7`4}s(ݿ摀I7J膛R[&?W[iFI-tS幍rWvԮǟx,!\k>/!3RE8gX2KYl(S2lsݺ}$ž?ZUy1ܓSSRáaYJow5kx|xkHJj4q^*_ѧu1X+7&>k'\Kv OqC@36si_ʳFOg/*Y~4zmY?8Io:q*vx/WCgJge\d"MJ ZcyohR7hx,!\k>/!ןSɒMc?W'Y@'Za$C/.*-m:tXvz۶m͛74 ˔rCG3fӒM^6ؔeCRzrVmnIRϗYg؃jbq,ói5i"JQ-kMViik}{+o@H;_r^|% /Ra{ &K/@la yj3|R a֋'ك\rCl׮̌?ݫi׮]):=[PeK'P^s/@EAPhb p@gyV[;\gs~tjҤ{g7n\M%g[˾o FO\tqJH(J5jޝQv>?=ۯk D-ѣofc=G_žšF:iq~7.X塂%^|:;AE YJflBR1[#Bl\|l:se2=MuVq6y~|/dիWYx_o^OC;wIo4,<#,{ZUbq]ۥ7xc΄ 47]eF ܂Ol=HZ|-9zFn,xkU{BW|>D,p{84c M<_5f2|{jVUqٌ[^6ϣ-Oܮʦ[2c^!/%\{6q!l5rYl_8ڶMVS{ҧ'BiiiB!cǎ!O?i1*Z矴i-~)Hs/@AzflBR1[#Vfzz7_ג/?W*W.# b.;sT<҆#Ά ³?vB]ǏS1Z&ZgPYJ(U19kns1fƲ?0~}/=|(N8jI36c)- z_x@¬ CT6=z41k+xI*n7&r*ed4dH{pv '0afÇ2 #Sfn׮"ŊQݶm)qcsm4zVo-9zTk .?LnA>뭷yU}55K+7N#6mEP6mh?h`>>D &K/@la yf=>c.[3q&ԩ(ɕҡOPev d_n#9ϣ]XdsdH[,2eJ26om6%g?tr0"44s˟n(]xqZ7Z~+x )Z_TdIǎx4O聓'=/ ad/ d:-<{}/h`>>K<(JьflBR1[#BnA^>7j1#7&[VFhڴ^r6|tv(11*V,MCg9_y.U-S&)!(*UZE;^(s6Y3g|o߾w^M\gϞ'SSS-_Zz)wܡYn3|R)}'n.=^q 8w!(DE.oPů--Ljj fn95nv-gO~Uf mҥhBQCf*7=1d1*VJ~Kh35d:YƟ3gʙO/̽qc1mk-Lyf{L?_`)~yuV2Ԫ*D%˖?r<;%-ue<#C1N59 F@\PU.{J t J*9 |1b C~xEds'ȟy9jVVɒ Z@nCdSx[/?rrrh#vw% 6-oZ4k_heX 'M^Ν xlN._i6U;l`vvjN]g0+ϜQ歆ou8sQzdBlNIh>-- GҼ_o nV6P^4뮻2dOAz_~_w%lG3i矫kfak/gΦ,ڲEgsњ۶ݴj?׬Ic}6vɗ/o55}xU<>rngVײo_8wbjկVfĦM2ϳf̐qʩ?8u2Ek7lT64}}:P WB39U>8TO/@la8>/gPf5>II%-,/ٿM<3˳ .UW|¹iځejyxYc3WkT|ž=T=wNk/vndSoe?eq:2KkW5:gV( ߏUgjexYέ/]U{Qj@n#114}}wMF7QЙB*GcOarQ 0`3)/@c8>/cx-ozUӗ5T<Ɖg۵F$c bhf!.bː:NwKa" okf C| ^!D3jlQ{ޔ/C.}TZBkbBb2Cm7722}姾GjZ J9P.o}for` 9sT9؞T6"C3 w{_\!kEBDꆻsDG{.q 1Y# <f'MKSkSZ/CxxRjᆱvsx/W {AѬ%|>^~E7^ρvy3+~k/sc8uRR33|rYž2Яɛ;u^4eq.2 C>/cx of~[TaCeGZx}_ޭvsR2-vs/#_YZߴrfI2Jh*Ud, j;dv3+~Ҟ2y9N{VFoF0rYž2Яˮԩ#NXKKߞ#]8::AaxpK7򥾋)mDNg2W4yD vUiô=6y*ShQ5[R۵jٳrbk}-{ysۯPWLN9Cr2VvmZ7!Mb%J\"EhKӶD2^[k ι?乸fտF7Vq~)Oǩ| A}]r#114}}eeN5zҩ N_ަa)KCNj Xg 0X<^Bh/ ktuZe7y9vlĵ:x:,nZMJ33Z ]x)j{yVz|9n& 3fHsMIQi,K'jMPф*^lт;vh5oo]ⷌ}ObS<:kb&ۧ ݰA`n'114}}eH!l&\Ds R˜[Z˝bMruw>s"'b C>/cx~v-xYviy68gj?he (Z矔ܾ|^{-Z>X|Pﻈ/C ;T1 JN4Ǟ@  ?`xDd?;iV ٺwj׌`dҍA,Zk#;⏐ a(hEfnA9}=o_ {)3pŃpC/Ҥ,ױ[dV|xxV@C82O4s ϑs G^ܳgfbڵ󔔔#4Зp,mf T Us-UM'_xU^jԪ2yd3\z ;&Nmj/ysc<+ !'APpHD9k׮~f#.ogwwSYK-mM^6q<Ϛy^37fkf.tf_0kX<3ٔ5(mjMe{T喬}#{ sn>]mIv/mƭp ?P~ŢEØXf& ˺^L5k. Τ/D{Gw g2@A)?-?G0h"==|3wK8f aVj*}m)*^n=9[7xq%}vuҬr_x`{LٍX!l=7˛!E+Uj}VveH~2vqt= p7>c<+ x]|fnA97hbĈCCxÆ _c ==K65ܓE9uN}l[۞xG/I/iZ2%kTגv3g[vM6z͸Z!X!*/O'Mp7>c<+ !hA#&Og„ kʾLUÇ?dF),%_ӻS˫|2=oy׍y{_nh-T~ (xqfB/_D)jrͥb+D0^B(]6kgP>s) 1D8hf!.`G5dReb*%SDg aWLs?.Rxxp$=@a|V(b|APg؍B(> 6)a,NF!5P?OFb]oPP1D~?+D T4 !~>,NF!78A~Œa" 0̈́ `O "P\ l"kϥ8ԕ g! BC3 *ө6&,,g OY ! BE3 *S`U'`$z};UY ! BE3 *S+LC8՞(8Q˥,!  T4 !~> a`㩙ppz,{SH` ! BE3 *S+ !l'68!La C¦E^D3 *S+: חBbgMRa g !t!>oL>P )IDAT`eɢAr^B!{ 0c !`1fBAT0ϧ! WD!Iㅃ! Q*h&T^hRŋN6TlY*Z8^vJ7wVުESṞwݥMnjQuX䦵Q^D24pr-å1U4x3gYNBbTdJ=;,c3=ZyVΪKK068p=+| ^!`1fB[K.oX^kho}GcJ,W*ԮձU~\OU/PCرcF5w[o*_ڲE]G0jUKʶؐe#8c1agvX KK0X3pa{0`׳GQ*N)?Lp׻Lҥ)qܐ}^| .L*TP\0k+zl.RD֩ٲG. a_&eܩ)Pu_Q%Uǎx4O66Vo/̉y)_w#֕}Ҹ۵o[&>3q=6Q s s% @xpᣙP|V)S(jMP.\CW.hDq|CD2ѕ))*^ukZ{,YuR'N|=MrժyiDN4=G׬hNŔr*V=;e8waHuVȵOEn'KSa@_;/ccL}ẞp}H~L *$Sg 0T|Dd ̈́ L# ӧӌwߥ*KB뵸FiԮ?^!bc_6: P{萪Ӫ_?Ǻs[:#jJ{{>ʕQCg7Uo{)O\YƊ/NK^ 3fF'o>sW>>j' #Ch*&TI΢"ZJ C>/R}5|4*bl`iYQrA Ʊ !ZsuSf*0 智fB I3@mfo晲x֮]Yϙܮ*/S^]Ѽ2>9^xAGɛ} Ͼ CsFg: חBa[K>wj=@.xpᣙP/G`rfE\Ed|S1ef}͸7Ð!ZM6MؑsiO;p@aҌJ{{֥u⣞|Rx\kE(Y㩙>4ɛ} Ͼ CsFgfQIuVA-%01Y# (YG3©V+#gZsrȯz術wΝ;\|޽{dw\o%Pl~KнrlZ㏴a;VƬ&6Foݪqx`^>‹:[sdUgJs̱}sb%J8Q;ϥqXL9\̾hS4u~*SQܓS}TJu8T;,Y=N^f4ilf?.._ѧu1X+7&>k'\. C 0 ptEת2Kp=+| ^wCsǝ$oCwbN ~3wJ*wСú۶m&7m޼ya_Lw3t4*/3k4^~9.ZTw t1V֪]z/zϟ1+իGC֯{ 9Yg؃jaq,ói5i"JQ-4n\W[llْڈk*np_ѣ@"))=zof]=s|%)PPԈS'-{*}dݸ`W C !*sp"S &1Y#B^&QSX<4MV-(!lir.9^:ire,[>Ve4w.S4ӇRRxu: anJ22n 9C#UoΜ^O4ߣK5oƜ &ho0L L!+{h#Qy3V ņ q PL:מ1!\ nѢ2>gHS#;իʭZ9Tg3n{>V 08*V G: ]SOޡ+Lb-y)zހa{Dhh&T05!/rͩT THskϝ@-~> ap})CBA;nπg !pfN&/܆p׮]iFo0 Mۻw/u٣?nBAT0d~ 00,zH8:{D%p=+| ^x^l J3's;^VCnZ,\ 1˛![ ^6 aunyv]72!v!;v8xӺ?OkK?0@\C8OU>Y4ڞQ!\ =E3's\K{..̦twOVq=[ֽhj=7Cbݻd4 lqG{ =͑g#Ά ³?vB]ǏS1Z&ZhR[o* heRw5nÒ9kDɔ6{V61vx4Po0,ּ`.zEs[Jg yfZ}PZ1DgHSq!JKkkٌTŭ{-[R{wVsuUTnㆿ7Cձ*>{vogmg&.\( '0afÇ2 #SfB׮"ŊQݶm)qcl4zVo-9zTk#iԖ-bWg"/ փ/]*z^ѮzK]kյMp"TMZZ>XC !_ZA'ʲ@T`׳G<52M-\zyڵs=LUZV#}F祝y4v}+VDYx_oݺ.!3,P9g](%xRR iq}i*լyMS5܆7< ##mۦ,g.]QZfBЍYEE|'a{hTΝfY5Cɓ"܆b%K;&'NO>G¬NG<ϊ_/Z>C !L\JP3V1*XJ!\ fڈJ*N tMhʡfMUO'VYčW%I5jTME' hM*ҥ}=T=YlcxxFHjʑq:V[8niԨTV/Eݾ}}iP_8I !̚9s}{jonr={<:PfB҂/F0K-ς!7*p^_!̳%=#}.q H1 { H zV@0)!{ "w}i$&&2e7SiiigRSSgXojh&/Yo,W_k /]&$P5dl{rc~ڣSMꖩR%v7ސ#s;%d#\/7Cў3gʙ.Ϝqc1m1k0k窞|yvU:ٜ쒕EOdmn.>rqbݺeRT֪~Kh55d:>2.e={ZZieU>qg^ƜgXsR]F32[! m?)l%yhck.ۇ! Gt/`G HLu&u!\ `xyO?r<%݆2{? Ǐڽ{4ȯݻw?}3M4ʛ_ 5ZZ?CI3nٴ l,L.{Nݿ_jηN ]=<}M,M2U ~+MUα8A{4n9Vvm6ۺϲilX3Y eH5c<2%EnMkϝSu|60q9UA+VhccB[=V9X[{}K̔/2Zwbu~OnyϰFZYWM+Wjcm#`Q NM@g 0AOn].I}h劋-t*wᓓz~Ȑ!?i~}ѷoYg̈́&6)6leX tQ[lZsu۶UVRS5iφԮ7lBչa 0r ah#lƛtپֶ1Yl^rgL3hYC7lPu| vU۞zJœ*~,{}Vboe߾?"pԪ_?̈Mdgbr*n gY2mUݰaZ>P~?4'@ȤXe<Ġ`׳G7^A͚Ք{('%&M} 0ᣙP38/R}5|4ʛxY].tyS a^^ :mewy&/k/]Gy6&)/SC}]Cm771j;fnsϩ.k͚*g5Y_{M/楖u|fn-lrܦq^z/¸9'!lg}[Mnf/R}5|4ʛ;L.S2,_>Pqޫvƻ視݄cf\j4Ffܮ]lZ>TκlKF[F;*^n]Uߛ!lڼwo dh;3d2. ¡g5derfYH~iw'@Az@MFS1K xpᣙPTaCetGZmYޭv䒾2-vsS͖-UVTfPq!jkٌ7Cŭ{igVU+Tnƍ*<߱͞۷mD_! v\L0p(\J>q1VV~֭m#w#Gݝ?xpᣙPw"et,__F#Hca׮*X<-:|XRmdUpѢ襴kפ}Բ< 4k.4Z[NW.9Y.lhƋ+& YPnMkϝ:{VlX>L9uy\HJ&p4q^*_\B>6Vykgeot*Q39B7nxয়Bzϰ\D ٰgsk}_QC*w?"30T_8YI)'xpᣙPĆx\uiyfΚew۵{a:,nZD33O ٻx;4wVr'$D]-)=7I- JZmP{HJ-TJvܙ;9Μ{s_>Ϝ9s3gr23ZxpկMFo̟[n-2YZ|Ԣ櫛l"{p{zSg 1={^ЂnGixnUz\2zYm{{5/s /.!q[o b)~Iǥ6jjjw~lY~^]_9fmaK t啱qKdy|@Q*0lBE8OQP`-ĊPE/량gZ>?Z!+>s }Xڴ~<>v2fqR CANɶ+B%g|_\p{]O\jc bV^\I9* cԹ 1-r{iiTc(P`-ĊPImev_ku/y[/<8BbM*=Dܴ~<>(wU-2 3K6PPF \d[矗Ms-U ;_[<4Pey32Y)s a4 u}LXRϧ* (e'[]H1x(i}0|bE(BHmD?&lGCAq̖U?ϔ&'G#s a4 ¤fe'V"Fi=>vf9K"](1gEp!vdj!|l2 lMΕ>&'Cr a4 ]++BBj#4PeiɉDs a4 ]++BBj#4Pe{ dy̖CK4:PF ܵRM"!6OU BrҒgflbw5(PFTATSEPh(~AxO YɴȬy-BK4PFTA'V"Fi(4 HEf,3se=A80a+V"Fi(4 HE679^=CA0u0j\ɮe-v80a6ƛ2_/ҵ1x(P(6[dB-mq1x(P(>BGWtƾ[NJOz·y-Iud j_bwh5W}yx(鏧>(֟$w17XG~b5WMxXҴ#68NS>~gc˘1c=?~lC/Fh63m>>m᠃d)S?_GnAxs GcuCNxA;.XAn'QAwޑo_QSS6cH?$yg^9^[g 5Q.*jZc)?XΓ-$i^33#^{Mf/^.?,v\[awߍmT;}R},~k c3iKx .2gqr+EuϜ#ݺ-c׎W#I3fXҴ#2ya;v؏gϞ- ,ŋ˃>g̘ȑ#jjjM>17GZߵ |Gףɧ; 7xp4_s ֵ'A{VpFcM]h`nuֱzNi1,uRuU#RQ9ϥG>nyO=[32ߎW7ݴr~5B_(<>WTzHJOzL]w0tM& @i>+'=MN8k2a8Y?+{+^,{X_ҴE09mڴ'LE"ptM=oXbRnD ZX /Gczv9IAy{ݎnuw!hK{lip4?{_}5W)JQ*jc)Jo[9тr-َ'L+2䩅P*v$ƧT_=0t>  1g1 E.g tjA7.YF,={v޽{Țk~={Nv={Td39эˌ }m։-\M= s~΋m;b&ҿ/Ya޲~_s l'ΒXGQlIxwjĉtXP,YQ^>|xG#XlT:Z _.S zbz@=|y~_J(T(zV3k{={J޽eКkc4seFΞy 6?:cZ&_,(tɳ޽^^%xy?#WYz?Yl}?d챚b5sϹGt<0XN m;].$}b.Ȝ+Һ^fxe֭z=I)Qͱ):q@'33 'gۙ6Zd(Yo#۰YgEJbeKYk?oPXm;Gb9䵽-O]?ccxTj||kplT:Z _&MAX~6uTa:ok嗗Sܽ?6&m:}/{-9l0?Gs~FtG~}챑ronv'ׇ^F7_,e>4vGmzk>-,snaajE B5:nZh"KpԿmp2حC=\ߏbsgQxGf{G^,}j7Z,WwrUz?96ޣr^>^^W>p9v?Wڧ6-`m^[KmM-kZK?[`T{ٲ:Rz ucM"ߴUR1 ~Q-pA8XJIRi/dmK3cW=ܿϴLQawpYџQJ&'Y2\T[vҤ"}Z^{]2Z ~fm݂iaWo/ =?;2[cAAtvѳpyWl[oΏ- {Kn=qbpt2 A淟A_gjE[Р>vI:(@C& @i>+y f:ia=3Whm}v/\i z'??2Lpxv;Fg*k^ _2 x ,{K/}q{Myb"H8z4I zJ-Ys#a=J'߻z֛X> 9̩wo3o[{yBM[3>׳eJO2-w,_e>g󵯹mzƜ֘f-tOߤW~HA^t[B9cwߗ4klUz:)-Ƚ}{oЧu;\饑mKvułzn]w!C?'֊z_~69ISCGi>vϬ~A}JuPvxYZN),S.oOXP ޯ}mĠ]vPz`ٴW_eJR~;oܶm[}nq}qƊIrwnm_&`KExh6;6ӧ.k_Ԥ}p{)Rw=7^?PL 餻߮"Z8첋Ci wZnoRw9h?pt?0uBoO:ohQB/o&ˆBƳ#?+K^zh]_pu^h=~kzLx}KkŸpMs~֤ šrHd+wXXNqU:Ƚ?{q{KzdqWZ)u sW,Ŷ)1X*9>+}S[~_?sri- 5/],wܞ8ri boeJ-~ۜ #Gx Rz_KCgasXJmScxTz|y(feۖΠ>vI:(@~A@cϊLE649y;sԸkaIXP>*>{,9}g g}VKFy`~ZKFw oj! u'ĖCph*U.>8 {xbzM-: Gpב]eJ*D~xy?4߸qZ~JާU7Rߑ+Kغ^vjq/}e}?jal+<SW~K>{5=&)N9|աKs-kNy챠_}Oz>z_Ov_n' sW,Ŷ)1P*=><{LN3k%r{)Ǯ4i]a(`#0PcϊLo["29ZEoןI ߿+=zts  &o^s{t^$*ܶw޾T}\5 {^8:K{yc&XmScxTz|Jy19͸]m󤶫Ǯ4i]a(/hwpYџɧEf]@Ԃ.;zϔP #޹L$gΑ7߸$x?2^O~ދx /t[u J.{.O>9sĊI2u?:lm/zA}F'8Q^t|c|2zAW^{=I'uhmYYr} r+^T 4.Ns%^E N׿E/TubGn -s3}V{aeX?Խ:oeAa΄ӟzS- Di'ϥgvk3vBio;z Ym nQD9wRZO{TQ cS{?~ۧg1ٜ+o6x3yKϪ_C8m~gL:rg}gѫGaM9B5={SNQT ZtOcbkxlx|5r㏻pa]/C;~69J%ǧT>#O9dMqtZUc],Ri]a(`C0Pcϊ츙'݃s29{@-贂Vg!;{֯-E 6XYfp;/f~zɰakjGӃ p6[lm/Own4i`z/7q^nOm;;oЯF^C C eܸq/Z(V- >8Ϋ7M&^vNw=KJK}=vt_Ο/-M3yFv;HYeⰮKCGoschaK J[3ʹ8Θ!bdy=;r6sǪW~aeZצ٨)xleX.VfҤ`Zxxpw{ڧ]u.c$e`Ecmˤ$Nu^p~>N;-?[Om*zlzb+Em=n~4_dblTC cs4_;`BaMcaZhq[(ZgzYr=pAY)?CzSg\gY1᳼|6=JƧT\i>~9&I3c&F)~=^ZKA "}Vge͖MN$#. cVYD.l zeIxwsҤIL0˥K dɒFçe&`If…> Iu,[oRi=Q60?"}Vg5ˈZ$GAH\sFo{lP9Iouҋ}}=6iZLtN8Ƿ{7JSSӉmoWYbGψp]cz_U'4>;O:'GEG3P~BHGA $,͒ZT"т3捗W}rOg +x7pO^o!2zcLKlIӺdɓs=?liilxb?ꏦO܈#^ojjSM-@{gȹ;T/#U%ޏsCFg#wg(@AMS[Y3sO,p#j5eҭ2p`_y e%ɧ]?IrM۷[, e?Xcl Lv=nj3g=|twՁ{bgGyrQ;p駱ǐ !ģQLH @RVA,ۙ|jZd_ dk0:&,!BH(&HP3?;D8&,lrr)mg"CKp Fd;%B:d ja dU&' \16Nv&` !B~G1~Gt 1R)9Z]@Ql';cB!YGl#@͢ $Z)fF&':p Fd;%B:d j:Gt@cj ,ۙ1!vAk0:&,!BH(&HP1$SaY|ַ(`tM XB!QLߑfm4jwpLs2:\YR \16Nv&` !B~G1~Gt 1RVS[fY-K4@AclMB!db5kMp@c "09{)i  5cdglB!$ww$YdSa5G3s2'^%B(`tM XB!QLߑfa4awpL=Úeɧfldw 5cdglB!$ww$YM3*a">ZY2. *$N{[!Bj%Y~Gt 1gEv-9v>_p0 ( \=1G*C!R  @Ryݴ̒BKqQdB!Z ƛ?ᘮZ|>3dm p$kB!Byoh@c nf -AA~5{{!Bj!U7?F)ۼ{ g^QRI'kB!B <F-7P({ϐ%uU 2 $F-rҒsYBCA:7xvP4v3$ 9=O'svP.Ef,53dE u0u/"CAfe݌.0un $ d,WBݡ @&g"CA8f9p-gwP 2d@c('7O •1E9N{lkf˖m Sbdo !B^U{ 1gEZ,sT^ASZ~28MBdXx= c 49:}l}Hh d&6B!w* $ Ցy:?]: š'D!O~GvP=CEFϔU<ZCAXdp|!;x 1{M|( aMm"BH'TD7#t@c(׾y-|BY(k o!B:?"7?rYCAr[d,CH&6ɻ91m,o_/'|{0@$W\OmYrL?,nLV9趭~|5R~<_:TK)g_xۑw*B&3>+bt7"]Hd8Nzc=.cƌY6z?cƌGVSSӔ'Mj#+>ӧOv;bWuImϡi G{_P)g?dHRǗjj)ko;(/ 1ބDI|^1r݅(kiӦ=a„/-Z妛ndo455TV1IRb_%MSNqZI9!G;$9f{l}ma@E>gٍ"MOԳ 0s+Ͳ-@)䶟|;*bV̔%/gO1=Ht_s 1{OߦR M|WM8Q.]+ʒ%K>5jԫÇjߦR;[b6PL^b3t1*ۼe_/֭g=[^߇޽ŬcĜuLnv|~ץEOf0 M6s ޙor?L,fĬצW"f>k~?^^x̋xcκv9c~kt;A޾Okֶ|H߃R ~.fͽ1Btsދ?.xJMslH;.I>s~$4˥ynM%CM#͓O{{˖OIƤ}OqLo:TؔdKuy:o;(9noBH)MN3-c )}Tl&W/um:<(1{4oX҂CzX3!._̷Nz132 Cu{/;3 7_=T,xAWWY_y-kݟX1¶1 ziR-ꄿ?RVü6A%k;x{]NtRz߿uh'm^բnK[ۨbvk3`レ~;ɎPҫ˰6>.:F]5-Ve/mRau=׭ECB;~J%\0+~^m/Of ESu| EBϛؐv\~Ҽ˥yn*j|}hyzcWKşKtLه4i1QcSm(4H)g_KT"z) 1ބDWM}G3OzhdSE?;[p3ώ>Ep]p?+hw- |QG3gΜX7IiF)6E})Ѿ/Ng-TI}C-D{{+lo׳v=]6 p׮?||aeWȶ}啽6OUj=~!+Z4znGA# ڮggj~xecҌW^k}cCqIK~֒.湵>%?~]?ha2{ےCi1;aBlCYGO9Z񞋂0K4sF&'92?jP[]n~׽b e=v͵}丞:*{}:|`X7I.ǹ7tT(zr˵ni_znMG/Ǭ+}*FVt;xu~O,41z9R=^L{~F;s$-Tz[1[n֮{ؗmڶv8R,~%Yp!iǫXϫ gy5dž_J7.i>si^wZRެږv%c{cCq_KϜlםt4]0G/-]|צgtstLmKG{R7+QN fi??kz+7:)9noBHQ-LNiԫ(t'SKeԶ{7ݷo[aO'×e|:zThx&ˍa#Gx R߷D8ƛҟhifl͔%kaIdj%"]K{ne[kmU*w oN?Cؾ]ՂN1?mp1D? ߏs=71--U,uY;ik/+Ӭ Z vWq:n7~AXw/i[GI q~%\T{߶>iǫX^WSl44;g-ria[y~{N1ѾPqLoR}nCYGO9Z~-{. .noBHQi18'fkwuQsϤⓩe]-~p7|;G^gr:|,+! ߓē}'ꍡ_T׳eŐ_3{7}'-.䯲צԳt^ ;~ +|V=KY5z)ZӢao u[{x\N8=i!Ajvy .Q[{*j{k”B[j4;g-ri[~X5 o/c'͘4F}T_{iXҬ#秜}-{PxEA%|yv#xB*'LkeFY]N5:mr{hy,ꟈybFlk4H̿^ zC^~gMӧ:gΜX7INGq[(6?^hLgW7zgmiAt1\]><5by+脺߮g}Тc=3~A_BtZk{;jQY'u}ku~_[mż𒷮bmt=N]+b6ٴ]7O/O[_۵__/E+%^{[bXӻy,LFs-4gs{Q>M%߃bB/mikn{1~U*b&gq~bcoҎK\םt4ϭ~XGۖѳK=4cvҤǤF}Sۋ%=6%݆RIr4A%ma@A>؍"MO3W<ρvWQN. aGZN^c^rn_oҴN2dq}hѢXT.\8ST*Z"UA=vǝI|{Y=Kx,k<YzEO7zvQ3BZD3JEύ:T=(#neWx -"yE9IƫT<}^b[l ҎK\םt4aG?Zf %݇͘$F}Sۋ96%نfi??kiރ{~ .\h7p7!?lY4ˢ|vk=I&]0a„/.]+ʒ%K>9rÇjSm"c~5>}l_a"IBAЅEv#PF{IO{,ew) k2xĉ7?3owv}WNQjm"C1klM|J}$ a@^>ۍ"CA#똜5:EAXS`2xɇ|`Yx[֟W_}GӧOnĈ755MNyDH#Ǿ7޳T]%?yޥ4\G>Dwog̷̻D80Қ'MNg+NP {3fΞ{ѣ.3 ŷF}oZ?zh{YR_y-1;}]Lbsq'}$Mԣwn Hklfrf8NPd?&B!t~@żev#PF9~E9&6B!w*|.D80:jloriJW(k o!B:?b @c(fʚ]i9fnr+P͠ ~28MBdy=ٍ"CA|$AAXdp|!;j>Wٍ"CAa[,#BY ~28MBdy%ۍ"CA-'[lwe&6B!w*\c7p at)ͽ|<dFAXdp|!;R> F j|aeU%5OǷB!P1/@c(@<d@hFAXdp|!;n Zlr:(k o!B:?b^:@c(#kz?f̕>ADQd?&B!t~@D80j]ir9Z]ec(K!BHu@]z&v#PF%(!vWBs bɚ=BNKF@c(޴4j zL!BHu@]z*F z4W#똜|"CKR0Г,D80]59̕gP x"[F 'knQ x<_ۍ"CA]MirqHEA./f7p at5dy3[~3G @h>w؍"CA]]kr-P x8F nheN0{n h4[~wIvs 1hV,>Cx1gdP8PFCYN79y!@MC>D80]vLNzHf.F -r|~^J.OJ'KB!4Z!ݟoF&'(J[dɏVXHXUmm"Biw!4 1xJm-2Pp,{ت,MB!.d9F4F Ɠ |X2:>UYl!B%]r|d7p a4taOl⍴E*m!BHD _QܓCv#PF \䳺jm!BHD l@fv#PF \3J5ŶB!Q߅L 3w 1x(W8:>&I6B!4Jd|D80 Gd; &B!F~2~ܚv#PF \l'!cD!(B&b_ 1x(W8:>&I6B!4Jd|D80 Gd; &B!F~2~ܔSv#PF \l'!cD!(B&byn hw5SXTb,2.' c~u2$u?dfa>ڍ"MOQ]A8w䐫-kbЅơߓC}Fν59{[re2ޑ{LڴnSlqSN=}1c,=zwPl̹s(1WOed٣ox[8:o -w|"κ3x\bo/ZTcrn;ٍ"MOQ]AX3wO/ȥ~$/~KN 9w#?}?rK#-t(ܺMI;رc~<{lY`,^X|A>cƌGVSSӔa.[le)7Dqwn-:rߵ0ǬֱKg#}N=jn7#߼ꩊmg9c4컂*r31vjq+5NcY.ؓ& 1k/mݏ(hH?y@c 4._>gz]-~U-Y&߽2W;_cKe2O>[uӲغu iӦ=a„/-Z妛ndo455rĶXl>+;O-cݽgo9`Ñ.|@vqūǴj#K}(n;Cmv&y;:rSU;j'˱s{Irk?mݏ(hH/@c 4N)gW`A~+7Gҭ{On>[yV.?rCo[{߻_zYNU9ׯKW{_nE֕$N>|WM8Q.]+ʒ%K>5jԫÇtybT*[O{QeG4_&+E{{Y~{`m[Ə>}$ x}]П<^ֹwAҫ@R·_bqOrym_׿"[3KV\u]wrkn5\=r~A6}Vpzo,X"?~:}/. q`nog{cdtvҷdJ|eMd1ʾg, %e5wP7zƻ[G׭!e!kۍ]='BIUj,2xMeNp`/Wh];Ku4kyno@S zC=D%_' {ɶq'YGQwQz6Sx&rɩSWG-?ym9grerҏ~.>_w'~]2|cu%I6 :t{ޙvnׇZ)ۦRvl:Hl#Q}S?˶{Ɩ ۧ->oЪn!Dセ{0%}%_p[}]eΕi xEsۡ5nk~†/ZXe/nc﵎$] :+;3w{?fӮvMf۩{!lGHjnS{+`E7-_W.~CRrR?mb6U>>;i޿Je-wK=wבؓ=ım]m}ů64l̾S ϭѢWt]z즣g/\d7"C׿ }סkXE_-گ{ۛp>]Ws _wwֶ+-%o;3mZAA@CZϿF&'(: G'uyY9w}ϼ[ߛ@v>cǞp}X^n "  $H(!:(]"%(|W^Eņ vw{=3;g7{{?™^̸:̸{_,W^?oO}-{'?O lA]xl?_eW|R־9we#_6i-*edҶW-͔l{5$J̶=l%V^ԕj{Uim[m˝mmviAʲ҉V^k!PQI$fQJE%^~B_;er[eݎf=!Ǹ~QtծUʎܘM^t@֮?*Hl -j6 ĺ^չmqϪ[TuF瞃׶_oۮ+U33$}SГn~#:I;ZVqD+˭;&kÏ&W+ceOeӪw$17\|R|=_Ee_}5_֟g|3gΜٳs*?\Ef{?ħ{xӯkI!o_>xo|OxϏu=eʔ>裹bo<7nܫ~:5{ͦ?-oU ͦ?}Y1BiX2PiamWh}ڛ퇢׽h6nnOZ6ٝ*ܶQ-elʊJiĹj4+ 9TeaECQʖU6/薲zΣdCv}CY{"ga LTW}.]{x򙟩h=}_4_piV+Oٲ2/C_kovlTTxݨЩ:6ˠ>˖vcܹ}/+f0&M6LV]Bm[_tE;]VvnK1nq}W~9sM)*{)INdz* Gx+n!iu6Vsjg^l6\E nwfu$yӏUgo#~/|G߈o[sE_;/ķ|u{]{en^UҷNsu<꼼BXI;qnn~,ҪE -yEv[`x^Ei[*GwY*4(瀖ͧles(ELS;nGEҲ[>vːene;{Bq "UZ |=o$u=ܟmm-n/>{{V}-U-pIDATln]w[F{)IFu"v7+"~:ʢnauL;li>pڇƝgYGu_{_~/M3>;4#&۞ZC8ɜwQ˭޿_ Knћ_6̦rInԑ>H׳yb@CU)VԹl?˯fu P>vuMxذ]kU̵[8'kZRg:ݒUzVz?mzigsV6ee^_֔s(E+[~l\zڽ/.W~/Zv+'-CeɆx'sӺqV^8:~_V[-`N[-|vQ U_KUk~*v7_l|M,}4SO{dߎľ*NU4[Õ_4LWϹPǯ}f}:9*9-|ڧ>\Anۮ+yqMS4<ru'x]*OѲiEv[ .XR@3|bˬn|dp$WL]b*Y+emj'iֳlMcMW[9SBrԱ-Oٲmny~ZUϡIۥ?Iys}hT?~ݗUk˖Zvcܮw?l)`˻4kJ1=˖jEJm|N{vz{[~խ;x&4ܖc~V]ߪ=nZO]yk'm~oJun;MOA@OFu"v7+"~:ޚENitGݾr &W#W )vu~vrGx֭?zn*~綍O}6?-_xoY%}3zey)Ss= e_,pu#oUֹehʛGW,FI!A\f3'p]A+4\We9_n*h>ʦ)+BngZu?)Z}ipwTtPE66)J+G*7mS ԏ7>Pˁ7}=~&>#+rU|g}{ysFyޚ4iK&Lu"B=*3PWu? l=a=Z70E&uuSG[UciӦ}ajzvqߍ?,NBH{߬{]{xΦoSГ>\˿Fu"^Ȅ7uBGM߳gώy䑤߷r?M8W[['B!rmLsߊϓ=`^?4;ύK&}0Z70E&uhz&.rɓy_;k0 ։B+n]<|E]<w"m˹qo羋@/Ĥ(P,21`Lyx#1̭!BHD߅Lg@|( 0E&uhvB։B!WBc.4?d"^Ȅ7N:B!J]tt&GBȄƀ.2}Gt2NB!}2.sIQ?@&21`Lyx#1̭!BHD߅Lg@ǜe-Lxa "~:HLg;!sD!+w!b1gE2 /C]d\l'dn!Bz%.d:] :4h Ld cP>Fc: ['B!^ ~9դ(Z 0E&uhvB։B!WBcN2?D&0 u ?so??u"B軐w1L2 /C]d\l'dn!Bz%.d:] :fIQ?@&21`Lyx#1̭!BHD߅Lg@mLxa "~ێ6RNvBB!kw1#L52I_' ȴ;H}:_B!^ CMῶ?@&2)CIdsn>!BH/z&_ ƀ$29N7RNׇB!=@voL{ cP~OC!KI;7D=1`(L{Hi:_B!^ }M_"Ӟ0D!oI;DzOd84iVPC =CvDzOd8&{Ld(D!ogvDzOd8v4iLd(D!O`u0 PC =Cn:@c0{M?  c=`kPC =Cv 1p^B!]fs~ u@ =0X'B!=0mb/d"Ca '2`%BiG=`Ф_gd"Ca '2`%BiG=`[פ_DzOd8*yK!Ҏ{зI 1pUB!ZV`[ݤ_ 1pUB!ZF{0ĭl/20{`uB!#a(]g~?  c=\-!BH;0u1S20{`uB!#a(]gI~?  c= ?m!NuB!#a(]]&?@&2D^>סDH7Fcu"BiG=P"&?@&21`LyP"s?:B!#a(]gILxa "~:\Oy,NrufɆw]-؂U;7`[:&>1z,@5_pVqI#7;Z]qHnXoǙ2N;.Ik/2D^>סDH7Fcujzls3 ;wJ־"sW|ml+Hnx;3c:ٲ>v/TǵGNΦ@X״ggMS`Vmǻs~0 [ @f/d"N ?sJtct2y)NE*WK7lܴU2 /-O>"iG?3#7ͼZ?R-+n2ojolCxz}H߱ tr  Ȅ%B1:Mg;JsTfO>O|ǿܴU2Ͷ(jHz<@8 }MR I+9?1:;`2IQ@LyP"st4NE)oH]{±$Ř?1bxwqҋM9rmrGvc6X2yp%N"׎m'MuXMm~ig.HɊ_oɾQ7ѹuKJ}ר VuUEit,VآÓe_}/cg.x%F{FOUV/g?<(hˎG/{7J}l#cHJƭr3YusBݷOrhйğN !7Jrۭ4z-7+V[֖/wgԵ}9_eUέ-jNn ŋk!uGNɮcuXyE}kzn?}e0h>=ytڋ -4_6XoC6ƭrl*!lWȾt{Zd"X^>סDH7FlGinRq+&Zn4읷NGotҦNgt6[-D4)n_~IPQ_7aɼ5*߳&j[m߼*^ָZ[*^NۣyCW!oLJ\?yv<6h^۾Ֆz#^QVJ |bb6!. 6;eլ6߽ YΓVbM* mY>Ηo%f ºe'v-W]|q *\t6VQĜsbn'?q&IT翞ת+~t;?[G/g-C?un|>|d=Uqې}rY_EUUUW^Ee^8 -+HL}sr[**^wqv~#lwYm?I!XWskUQj*gwϕ ג]_<pr4LvI*UM)!Uu_ǥ,}Zd"X^>סDH7FlGinRq_4LζDVǵ:z;T yvmWqök4[['l *B[R~IQ.p~Q6IY]U\U*d?abҮsN2i_O*&yWgiTYfǣ(|ծ+epJ+_VrwXLsͤa*i*5_QW.}BbEWEv`[Q^xW}-%d[U4]uŲU ~7G&=f|rǹcN`FȤ(oWD&u(ҍѹn:Q[U ºh17w]Y(-pw~nBWNum*q>~Ez%UUvxӫd陘jӳTխ~mv3dlbۿ?vs3C !_x4NN_7U*]n1N{"ٮS[l{r4:-؆V_mS(!N>\xye|r[qoǾWju:muNَR#JQx0힧~qˎMnW}t{Zf2IQ@LyP"st4NE;]]wgZtbxeŎECJŸ_]qTY*.+fܿ@b/z{%o]΂V[V(:y0/JqWʎLglwiѲ.{dn֟lVYBMr_tղ;]v·.)QAXEJ:~}tW]FQk zlV_fUe_o>`FȤ(oWD&u(ҍѹn:Q[u 볒g~lUW ,+vvmXnY񇣓[)o[T(w}oj459)&Ђ/kw 7z~ [Y*ǣQ_5aYE˭rܕcr+ bn5.Zm=igźې}zm%d[m/?89'~6/+>-:niT~ӳ6=>whwAS% zqȱXuR{}t{b-w2IQ@LyPju<~ݒ+t[#OZJnĤW[ho*}W/`#]WWW[,]{O>q͗ͫYԉj߿u^SL:!yQ]q4u3Iqmt?NE\V(wXn؏;#7/^eE GW(EWrCӷ\GiEr~X xm[u Ӿ>|d;X3Wjs -;2imSu<ʊm*tUmwuUkT]?֯w>Op"m'd\Usr<|ÒuE)?aٙn{ߗ}݇~tφe-WrˎM/'U)[<-v`[QZ96^E V}w8~3$Ž _q¯G.ҩג j?w^5&(^T}:Bʾ I|rs }/y@&2? ?sJeѕPt=乙ݣۯCh]v\5i0;_]6Vrik'+4?o1:VRS}x'GEYґicQaxi'tdt㴓G'c;#emSg;mÝVMWvvq;_WuR*YzMɛ瀢bNOt~܂pqMuǏow|#7Gy$~ꩧ~=(~˭Sugj|jˆb:?б(q}.:u~]VTP֣[09uqS^閰M -ݎ*ǯ=dFW$H}}hZ.px¸\6dm-@5L tdC{&[j(EG۠*=CO*)ܢɧn?IBۮ+^!_xQwɖ"=GVWu_^t,7wS4nh9{gW"VWd:ه̿QZ=6^E:vW]'$z]?5;.V8OzmIaZ߯Tz/l֕no2a2X^KޮYI{Ǧ{`B I|(]G7D&G~"2}C(*Hi]A䏣N1ui{v:J7Qr+GCu+*j[ƱVa -4RVA-^Ux";$q#g[<mm1qԁO]WiUR vUs}Csh'$~?1eʔs=I(~Qbg[F)^eT$hXhx+ue rtF,tћ.<7_?*@굯¤;-ͶmQ-rfan1C57*.tܦ ܨ+uՙUjmO*hϚQýj} *aADLWX4*Ѕarw7\%e =EBwN]w+eݎ" psNW|\n-N[96eTVwtųkaw|U[t|sh}.E_R[B_3-[P+Uz(Z{5`z-P?Ӹz?VqʱXu WU7 @zFȤ(oWD&u(ŽGWNWhv(-jMVvي_4臶K:/:K]P;, *r:us{u|붡e(n{ W`v 8®LHŽҨl:=ݖI:Hǎ{{ V~zzUI<:mӸ]m" _Wv{?wtK.1"E2nTV8oѻ}\Jߎ7UUIn!B}0ELbzEdКPjeVϦmrmt+0n[xm:E:4q6x 2zmW?*|o o^U:i}`86nawF?AItϢg_r61qq.K'M?u}7(=q'$:B!#{]`N-2IQ@XJl\RQ8Lrn[:&)|=xFaj46{FrHM(ֳqDZ(EESjn~A;'׍eUYf 4U ~a,L׎\GO2V~衇 aB! { t/@&2?@\RQ>YQnH 8ʉ6IƱU.vٴz^?_um7XvƻZ|UbwޕfcM8*_o[&乙ur|]}Wcŏ߿vC=V!|7g7fuύnݝ !.{wnx nGBϱ^JDAB  XC({xd9&7 ~רsR-U@^~i?󩝒Bk=]Ç?]`ysF6vJQTť ["Ͽ;o\mY<[$/ɶ^:Rֺn28o}F=ySO{C(N4ɭS͗?;{Z34+H0ݢܿ9a2TRvnwSwJz?OC=UzR a }FȤ(orJeywX1t6$WB:7X}T $EGڋ/ji{\q[J/"b/h;_7ۭ ri_͑K?桶XJVR1|Ňߝlv1}l=yt߿Mk~rk mT.Z/we 6E蛏NOL㎧զ[bYz7d:7ZW [9Uҷ>8mڴ: t l]uQy w)mds\9-v]j۫;*_O\=>isۡZ[wڗ]fM?-Y|*&h *I=KN̶i;sL:ξqR~*«*迫nW}.ύ?nJ@ݞ:WtmW=ζ lӨ OE,瞊]Ց}nvuWT=ih,'}.Jqk UmjZcǟWϼ|'ҷn9kkyoLbP,סDH7FlGin:la~{̱[mC>xϔl[o1kZ-Y;l1Kl6/rQܢns-l몫0]Ԇ {d߶]Ó+uWw=T-n]%.KR֭TۤVO@.7W*]Z]!hV[l>WZOfvQùv+}֍+Leb]+!mo2)z_mek+ mkh)^+293/5߉[' W7@&2?@\!릳u8oX[oHݺS,Ԉqvo ;?^5b;:zmRPB oFvv6nno/ˍ[TmBa^6L,Z߿X{^;_wnDWmmsnte^U ݮ*,Ղ*Om–Ђp}4;2=;Ow趽vעe{EynV('t_4۷JRVesϫg^k[N~0@>R˿FȤ(orJtctvi k6gg?K-Y^vk:bнS*BRetR=Sҏ{UiUfn6L笲_x)**VaS7ϞOF]5Xu<=mW}UiYπUlAXO4^s(ܳæv֮wmѲs{?M(ErBWyWrB}ooC-^+EleKiT}w"}9%&? 5:\7(ͭ@oO:G\~sWQwYءZPs}Sg5iA)Jٺ*e7ʗ7o2TѳAC[ba?ތs+VU .7We)6 7_nͪ[-W'.`80S1LtBtٲ[y9v?ErBW[rBϟ}ooCRVOѼF)!No:=Ф_HD&}7b%B1:Mg;Js4q;g4>rǸ9l2Y5!k?-*-,ȤMՕl*@~mwϝִU>}@Ү⫊MS6S& w۶ӪYΓV=cLݼ?kLrmvjKg=̦Wxj ݮ*(eӆ秺ų}TlA8d4; sO_OnkKl٭WT=v?eVW[VΟBl߶Vʖ3%5߉C' "ȤP,סDH7FlGin2{ؤ3]U>Is@<)Z9k[b~zhrۯ<S^xnf7K Ֆf]9>rwvu}ۭp-Ց"n?bD:/=R7uZl[txRx#3^eE[-h]t{K.&0n]׺h}|j߯b5>[{w%mXg)38pGzEZ[o1vhu~񇣓gqcjV/tF)VAĶ+} _2kB疝[t ?)fu%v>);f{6;el ,0_EV^Q<.J:rBW['d_Tٷ쯲J=BO}; ~_ 5:\7(ͭ@Grs>^jD|%W/i]ܨhۿ튮r;mkl9*"Óe4qQ缿~t%F,P4ћ.7HD0`p[B!dK]˕~#Dd(`{FPZn$"CACoko a aN@"20/ !Z$"CACwjyo a a Pu0P9"CACk  &\mZ~7HD&oy]m7k  &jhƍ7HD0Im?FqM-o `+vۏh`n\V˿FP`0PGXJ^]lN(0 mee直i?a\~0 v:-@AA&W@m 6:ŤNP`PGh(fi5 2jh a&tZ0Lm?FCAmtI;6 +vۏP@eN(0 E?x7GOjzn`6 h]M40LZsC'j" 冇?gf[|sӷ hȤN;{(0 E?pQ<%U6_%7<0&t0LZsƓg]_|/ j3v:@AA&W@msMg[kzMx[#\|dK}i6ILMc{f= W֭OO|)m3iӡdrF?׾}m_Wzq<uaxGjzOȆZ~T>\g?nnn-0f5v:@AA&W@-U ;#aKD~/ڛ2Nʆ/"UAAmI;N +E߲{{5Ǯ`ެ |?7uxĨ ӷ hQ&t:0hq~Ɔ$W@-ea{gZYQw7+o6 ^; 8E٪ E?~ѷ  nZ>7 L9{JWIZSV-67CgOpJ|GU[a۵\7 LlZSV-VT>QˍJڮxxGiKU.!?}ˠ zF-uTj"9+6k?E/\ _ǗxVg\7#)j*1顓ag=uV6?8NUAAmZ>7hT\u0UX5)掛5.7gi7+zv:,a"Ӟj|n 0W6igE= /2``θ@L{:@^d9O1\eθ@L{:@^d9R+N-tT9 3Jq>gyZ>7=.2tT0P",0;FEjJdkoz\d`DY`yFEjJd景mq>gyZ~7=.2tT0P",0Uˋ~#"CG5%2|-7=.2tT0P",0\S_FEjJdN-:( dhY&zXdׅ~Ll|#?CG?Ma?a D&s6WtgxsI;l=,2Ȅ抎 /sf?a D&s6WtgxI;l&jPMd?gsEG~II2֤6{jPMd?gsEG~W-C62iL"Q Ll2UMa3ȄwTj"9+:3xt aw'`r+wLBL BC# C!!++ \ [TS\Eu]w= E_otOw3=Iy3էO*8*`b=+;)zQk ЋjSlt4zWQdyvzBT@m Q͕F=oܕ^C5PBTgssGx"0@$NB/VM!߳ұqƻ[a^Gی?*M[L.U_i\Ky7Zq(zI^NB/V?d@c=+[^]_R!KלXA;sO=gjynmS-%>sk7/4+"w/H p~3.-(/[ziq"[[EmW8"m-GKw,XVhzL a$o$]^ *D{6W:6‘aRJpKkg|Z8r4^YiN[^?B\{LܙY~vZkջ/( xhZ=i a䢨[ K'_ Q͕lV!\yz/χ#oK핮-pn,܂i9<-[xqy޸j#xa'>1wZG=QG K'_fE.%ұ-}*+O݁ەϼ|GJUJڹi>sk;.=tq8siaUmcwMW 'Fmv.@/U EpUaZKck#[UWue!|.)Um'o[^xf|NMOc5A?ڼ=T!*w"TG\hmk~)U^÷ۗ7QZKqiy>[:0N]{j"0@'?\]^)({X´<v-#'R8/%=; T!R@WQбEoޞw+R8?N$RBt}֥cMi>)zo$y*; T!**C8uξ,)"_Gx"0@P  :g_n+IDDzC_ WqipMx/|7˕$""!/ |+R8'y); T!RȄ9w\I""_Gx"0@s]&]^){ddLS$ QWqGnFd*D :G\I""}*7ޯqQm "pLxus$ QWԸ r\TfBH!#^h+IDDzC_Ը_>5@L?܄"pLxus$ QWԸ  ?܄#+D :G\I""}*7ޯqd\T&\KzB sn`Ε$""!/jBxyA6?\]^){ds%HoHGx}^hגܔ^uq!+ˏ8"i!?jT|Ym9u̹/EgDNDŌ2|wb(>4o Zr傧?[~ʜqte3uo?~<}k~FiP!,]y~$BCf[\yVNg7w\ʊnm|u#ri? °s^u1aJ3g}Nz|\I҈QV j.x~tq{ ~}k{_#b/䉊Rq{K!}OBcK$7͆ؑlU۝p'E-g$_NB/T?Tʕ7\]\fCJWٳ|(kɦR.7yk/D~iD!?7rY[p'5/l(=;'avzBT:Ul)7{lURN>](/;x1ŻlE|aG[zѹ榛:(d?ֹm~W[O+V2%Iޱϗ-z_Ny=wߥǞ}}KmO=Ի̙?쳹<31o~_QMG ic#6*g,wrx,9m58͏?~4^|gO}Ϝbqr?|x\CmO{iϤ &:7;2-xӦMViY\IRKz?:͈߇PRrDq-l Z8MH8o((~BGDf o+Rw_ټ m{b ˭7i~:PІ2tBBW>C>sskۆkYQЍ·QQbVgڼkP8 iQ͏Sy>3_.{}z6V!KWdo[kߨEM ;cKY@4Ie)DZ)+U?gNgkϙ^m>|z8ڸrOHn?bȐO|ˏ?>cgY6ҕk]q>E Zr'~P(uL$i+ȺQ!Ea %]6!mnO߷ylTN X<"Lڱy>*_ dVk=jJ+̕7\W=ge:Yk~|eZ89{ZӴF}6Tqn2߅VdBwPܧO-*.͇m~}-/5\jUnprip:nsQ!Ͻ}Q/o\'ڵkseC-Yz ɕ$7O5!{ŷ\PnM64p *܊|:PWé_bq>z¸* p*}~@Zi-H/-5ڧO-B88}nZC[|tټxO[o}海 jGw޶yۋsZ[p?I3A8~mkMud~Tqn2߅֒-g+ /Z%*Kאn>5K~=ڪaʔ)a-ɕ$(p:7]KS{ڭ6rm'XD+w8si~{!7oS*\7-'{Хhhܰ5)^w9:ҥmGh>*_:({QY}=J Fi}G^;;M&Qǹg)D?ךlyZ*gF!\YVgOA6Tԓݶ;O*a! `(xBU}[T[G7i>CZ*pPo|y z_ϐ~+^Wg4S߿>! ָܻ3@reki 嵕?8M=pe[y'?<-+K͆O^Z<>ykגL:餓^5,WZpK T(2ydu9h`*[-_=|c~N7ԁb s/=W< ; ,CpPCqZM=!C_m=.l,~+*Cy=Yk>`ZYv4Q߿! Ȑe]򲥼jU.p%Kk:7˖6$]?jTݫ;Gs$kl~{M׬IkPm_{oRr;W\O0<7rȐt%ktiZn-mSkciآEreC-;wW yQJ&Mw_\SȆW߻ GNza?Za~!)Nc7+:<gPG)~~U?XKۇ#WixC }~%<GBB3ÇV [op}TxoT˥}v_lMMH(ϰ5EKGґB2s}W/G^__=WGw6ύ?k>c ۄXi>Kn'xQz4^!Zrv4Q߿ aXdEvhWl)w͟_U8ݦVmK?<-eCsvXztm{n&q)hʣK G7~|KG)+쳹⡥<3>}ϧM67#WH%g5GvfײpvH)&>6zҴF}*`j粓@r4>u 9s!˩S^R-WHˬv! aV#^}JiU:hNʕ9($B;3?W.\kO>dZBw}N;|:uUXJ)&\doO}wGFG[(M(Ys6nL:Իm{Z* AUʇd?#-%{i1gO ᐧTiݙI]R:GB"ᚔ3g\tkƌ/%9>C~Bs;q\I"ńצdovaCxFQw\dgN~!Ắm{Z~xڿYJ=l)Hs~/kDޏF}v}6d IH/)W^J^稅BJސ5C={} 1g6Ki|5\I""}*qMk=)W^J^qDGJސ5W!lFxo:]cZT1Ц\y)Ox+IDDzC_Ը_=ξtQǘ @r4>uWHtD$ QFn8ɖ6?Ƽ/)W^J^qDGJސ5W!lFx}hC$%))W^J^hX+IDDzC_Ը_=?M$Ц\y)Oxucq$ QFn8jg v|%sIMR:G\I""}*qԸx(d'6Ki|s%HoH6r#Q>ccUW@r4>uŹDD7$|EUq1qiTAfhvhU'Ѻ8W│q߿ a#7{53@;!3Ct}""қҨ.+gJctMs۴4n}x#wϽenn"~x[ &ޒZf[\ڸ ].om9j3i(Go?:ퟷnx7+sݮѓWWҨ4G: /tMֵ-EQ[3W 6:R:>W7Tw8/~~an~wQgmm43@;$y-ɍ.5keHoIG->cv?->^xp|o)o?KYZov]%_$j^|nz,-v٢ɧLέ4yۛ¹}k;28sbvny~e?_fşVۄk/]߱cTPvam&?6]K;W8IcӵQێ/emm'olxqME! ЍO$tR! :)wRM(j+oS*Nh _P^ziu^U=sqnkt.]7٥\oo4C! Ѝ>gIn+o4jmv?j|G0}M1^.7-ӧOFo/zjQnۖFw>?\Z^ 5\8]W]>F)#ItB^Pcrbk8<5}Oڷ[hqqok;W8e}Uݶ߾}z1FDe.@' Ẓ-[&2n[;ZvtyxeE<3cFT#dȈ!M)wΌ}+QQG p Feq:l԰=ޣֈS+F8bvުy/\5 ǦnEUwgɎ|^k^[^GYW͊_{xOyԵ3FBTߙorbk8 ׿7F0vʝ*N/ O疿<|_\^o߀~P];N1Uͅ1͏o?`nu)ܾ19{myvl΅Vn_hگBmy_v:wf[\f86lJp}ݫ{U=zGo?:>謃~v]yxqyhcu4C! 'ɃIBT,Z Dcݏp{mSΞ[g}D anTg' Bx X[zz=#'Rt;.; PXԢ7o;{)%Q񇙍 AH!ldFx#0@-TFf8RtQwd BȌG anYTafav:)qX'$tP!RH! NWGAH!ldFx#0:$N@"=$d' BȌG au_3 H!ldFx#0:1+*8v:)qX'v?μ3PFf8R#t@!RH! 8'H!ldFx#0:Dog' Q aQ#+); P_P6M΋?m]:"̨ݔԩ)MG.@ BXj ݤ_%!u*D) >tH$ԩ5$?N@ Bz˓dhvP̉?]:"08w.@ BzI^K,u(D a3cIC!R@$?N@ Bz+dhvjT#?5*D a葶?ޝ]"0XOr{vjT/"x6ɋIQ!*uo; >3PW.@ B>3\95(D aGd?DfʼԠ).3PI^K,5(D aG$2j$d'H!lt$2Fk1I).3):ԨhG!R]rtTeuQ!<=SJE`M4MKow;rx[5+ O?=㴏5Z-zzQnP\dh<9i9]okthh_0@'l$.@ Q+^Yu _P͠.E[ݶqҭ'􋷝ms5hi6oM_ZHee(qK7-R:ur&o6pX߱cc-ڟ\[F(qC>/elnpr6'r^nQ{F[g=i ay{ЊBT,Z Fe  0o㡛4qJ6rT)<ʵPnVw_WʝIDATnc=j{,fv&;m_nё׳Ѵ0@|"*PBT,Z FQ Ze.ʢ5[Sxa#`O\}b7?>}nڸŋ˷x덫nk{WF9WuQmwSnё׳Ѵo0@E[goZ+Z%&lRIµlKrBN]m8sv=pPw\zJ!#.nبaUۯB8\v~s-MV4Qǖ ЂBBq^_k5~TSn[h /,.߶ON޶3kMuéni)\#.;"v9Ssk}³U6ݛ]oithhڷBA$d'#%W6*Kѵ{GyQ㒯][5/囫oMU号T_k(-߸<-[q \vyr-aԴ>cW3h;_v W6*KpT6zS<<-G+ vfӹ/-'<9K47_A;oWOؼ|+sEZe[y<׆BrĘӪNpP s=yO[oGco}M9{Jn)jiד .@' mS%oa<ڷ'P=Ƴm9>0-rw;r6O] u`T%\O)Wc'8!>csk=q,iyܽ+ZWEO-jH>3B&Mgra5g&R4I^N?+ >3Bx98+ >3Bޒ/IVdra5g&RtG4; + >3BK+*gf:*WF[#|f"0@8ߓ\]~H! e>gIdra5g&RtyQ 03)̐$Nrkv: WF[#|f"0@x%]: ?hkLRGFa.@ra5g&RtI~)WF[#|f"0@[$gO(ILrIv:)[3I~Ov:![ QtmdeH{ L$deH{\I.@m)Rg"[$y-Ɋ'so&y3O$eo'7$H283zxdX=7IfX$gX=IFgXm$wgX]$dX K$/&Y`=wJ8o!ɖ5s&sdz 9u4):$Luf ܸX5ɀ%wdEvMQzs 'R?%%zn$H$gXMJJo%Y`=7#I`$q{^`}(*Ÿ) Y$&YP4@qnT,K24QM~X`Cwzד|+ɘZG8~X78$<ɤZrEHOHlf?[%s3kq"=!᳙@o2:Q8;usEHOHl&2U&4?|?q"=!O^L$O4ɠV8ٌP6<ɚ$o$a[+DzBg3R@I {^+DzBg3R@$yiW']EQ H! m$K$˒lQ+DzBg3R@MJrkT,$-RKMlF a˦I$}T,rE\{yoė]<~{xf[LŖcӹۭ9KEd9zrIקW@ IrNTg!GwsӧO|7֏>_t6n 4(~/nm{7Y[~[ߐspP6wܫ99^Isi Zz衿Ϙ17SN+Wĵћ)R>sSϊzPǃ N ی=]Zx4>`-CCK~6=yQs⍆5xZ~6U|u@ZΚ3/w >qx-Ƥ i=/vyy#7=v9^OUͽ]=1RfyLn.dGLo{G~K6>אp*]w.\Sz=??d[KoZzw͙3'~gspKygy衇jڴis#rE\KS*{DZmZˇx<-C)0̍ltaw|,} Ej]vTs]+>xx^0q^{iYZ2dhO?'-5K;L%~˷k㏔]'--L,\nwσ*e:bdZl?]oɦ鵊eseBA>FnIZxB|=kiޜ?6KnPs}onc:w~<};#jIL0aBᨣz#y~=mڴsMaz\Rn#"0OmZ* ۳..χ.͇#IKBv+PrVΗas_ۨ<ǽ.+•Yy'p[G4 .m+K=ڷ<+?X/k==΅#7yyPg҂4{mki<>s֜#~=-Ps ~i\B) %Mtg?hѢ\[q+OFP8/^*WĵoPh>}ʷ[rӭϥG~*m©Krgy~m-`"4m}R#OU__˷v!%kn\Suqw Q8J{WY^s_L)z\yz_zk8ҹNNl֓}N:_Yvm-P(JYz$OGK劸SF_U Gֲ%u+J[+dCBY{%e8uri 3|l-[e۷o9GΞ[u?V\7ڕLn/!̥ms~:8>= :\)w0jp4cҬYxseo-O>S2^*WĵћK_ﱴH`e݅C/k[pl_^9#CxnkJ-yl]zTv(Ku#g{gzۅy#4~^x Ep6G+_y--^*Wĵg[.B6!Ѱ͇nh|zv:P6<:¥UkzVܿ/n/M¿r0Yk-|x&Km λQ d6h|呭{{@yB63pʻ??W㷎ظ<tȥ|%Kn[.éÑǟrzȳ_:Vݶ|'ʷ1rt.r:[YD ls_Oo(QK=W{p(n^P-mΏ;#׫r?5굦qM:[hQ-%{̝;+Bz\^‘NOjq}mBA{ɧm(8CfؓU.oCλrAYK GW5\J86쯲|V.OC85׳朐/mNt~B;aWMئ[&w ǔ}[9zw\V} P}u쉹z_Rjy;v-=9lPϜ5'-ij%MwҦnYfmIPUaR"nCJͮoH %mx(:4=I!{ٳgߖ3e?6m:>"nCX֚>Ūk^pkKIlx4=EK[__[.C;3?W.\kO>dZw}N;|:uO8b"nC̣Kʄg]GI;-C6g{gY__YU'5gΜ֌3^Js|!6$f04q"=!Pq"=!)SrEHOHlF a\'>B:%WĉfNq"=!ٱ@ A4|r 3: r88 r88 r88 r88 r88 r88 wI9Nj 'zO8% pRK@#[MDC#IENDB`java-binary-block-parser-2.0.6/docs/jbbp_special_fields.png000066400000000000000000003034701445000440600237110ustar00rootroot00000000000000PNG  IHDR1ˡIDATx \e/CF6B aIHAI@dMل [ܰCd3xGq@\FǕ;Wq߮0WTuUwuwUuwR~AOSuN} `RPziZ&tWk)4!@S]iB ZӄMIw 1jMb4%՚&hJ5MДtWk)4!@S]iB kK7,-vO=9wVޱW_8=g/- 1$ĸ%l㲭b8h4d*}ern;elE`pG.7|t'}7.iiAi>їߖ4e4rב iP?}|:laگ\}kttв҆oHSM-sS1PLetHv_ˀ[;ɯuW?qu>mR|A_xXT\_ܜF1{S~NG$}/!2- 94 <0]=bEubĨswqn5߼S敎hf!F9ܩk;h{~{O!nL#vQu1tbcҏ]?4ii>+FODHqߟq.nQok$tbĈjXLsU|_uUm BADV6Z'_>v|[tOu՚dQ}s}GղC#L<Bc:&1}; 1 uh?NzV͝K_GQ3l((~)ꖟR1UUEQD{Q=:顢Ƿۣþ8 VLsaL(=Եu_4vk,6w>ug~KoLnUiJpaz @ϪCws*FCzϩ)V:!Pj$ĸ7u;hHxQ|~me'xbzVeFB.|mbĚ{q.=*m~as];*OJ!F,b:tc]Sz91bz}#Шe:zB{;k}}IZtR|]㦍mI<51N*F.T;Eķvה50ñwʊSWQ51 W6Mʧ{ZG7 h7bDrnź1y9/M>hrM {bt{a/;vS-xab}"WSP4kR㗥~~[E(^WȯWOIQF+m;K[W)wRE3 ]UOę}aB&5wr!U2wJq=c&c+:o u1Uy'{M~"ұC}~ꪯ7Ii-E q=bhu .襥 ܿ4YSMt>wU]!F#9ܩ-|QXutO=)iզU=bkc|4iȈ!iȡi֒YǗOw'(kΊ9u/1(H נ'FDtt|yg~1QL?bފ {|cQ_cҺϬ3!98'o>ĨOMǽtnk(hb4_;->E_mOyz #vQ1RoJۢ>g.1z̈́\SuPO;dZ>]T^\%Z{\<3XW6{o-D :(]:MӶB#_2L5UuS&5hW?~uk3[&暪xzĀc%T]4/; !26x1jMb4%՚&hJ5MДtWk)4!@S]iB ZӄMIw 1jMb4%՚&hJ5MДRêE!bM 1&TȄ@*dB 2!Є hBL4B&P!bM 1&TȄ@*dB 2!Є hBL4B&P!bt*)zjQz'pRJ).` 1ުiZf1J)R 1@iC)RJ14M!PJ)bB Mb(RJ 1! 1J)RB @iB J)bB MӄJ)RB @iB !RJ) 1RJ)CӄB RJ)! 4!RJ)bhCRJ)%5Xaitw?z Vޱt}{;Kg7m}m=.'| B!X⯯~vڛ[;kw1]oZueYr{׻ҥei=4h`׹קm>ZjN_7۾k}?K1!FCG/kw3!m--Y$exӐCiʹi씱\T9o OLoe]aӠ҄}&/;:;m?-yi)i#sӠ4~tؚҵ_1,'?xf1RJzrtM7/| 驧J!O8?s1 1*CSK_Sέf 1~,?6,j9v,O\b3?]~{|'e/_||-N !Д!埼<-^8mgNvgCm1}U8d`Owb}Pmb0*mƊcUƺϬzk6H) <ӓO>:\O-v[| KVCmƳtz?+;>xg~,su}Gkl_}瞬m__Z~&7~t}}ghM^}!F!FLtkHgn>3 3cȈ!鈵GsqNgί%毞kdV:Sy=/]ȅ+I(3gŜ>b]0{ToҥpU2%غUOPo?ۜק/z۷z^T}!Ĩ|-_! 1b=>q4|8:ǣc?:cBw䆼c9:ZvP i꼩czY>W|> )"n;vv_SNTG]]=_W!O\O3:=|: /YX*k=FcoJӏHsĴC#]5(BUqon=Gݭ{t)J\4'Co|b F`TE*[#?򮖎{fi쨖Ά|,],=O'Z:&F ҨYZV?y3[y32İ-ǯ<*KX!FLGTҽ׻`E1|l}dSbu>YzQ{{l{[ h^GfiYzӝO3KGލ>q\&mr3i䰖5+ӟ5⺼rNu=ě/jyGS'fu>TmN_\?~>Kם\mؖ_{r~C]Ǿnn?K=5Kv>5k[_?r=ޭ^k]Ͼ,џK=9%`o^-ww]Qτ:ϑ|la!B\i}oi8Íi.#N'4f.C'vCg,QWpOnx,x 1&71]ڞwQܿ;7?2=JϿfN̷blzvSWM[=^x;{ bO%molYz?[G}۸,Cn-bo^zga=\^;.Qh$ĈP&Bso۽3*[V15i#2c;F8#jM1bB0: 1[up~ߺz˭u_/lN3 3Jz;=vt<9Q [Wץ]ھq}^Ǣ1X"}b$F'bjٲCKwo]r׼Fd9Ӄ?اB/hw#>OP([!ƗۮkktT'@SGz=7]xcY|u[(Bg|Օ`_1j}#:h 1S:S*ю9:=Kwm eiIƴ[[GR[5YYZ<,҉䏎Y۲4qZq]ڲ-%ocBcSs#8)vƧ30>;njY[~[;_$FsXY@kYy-?einߛZ޺/,џK=b~wߥ?Lh1ޫ^r{u͟#3mbbDc>vo7]TuVWkbDSGYbGb 1$9; $: 1t_;Ĩ}$(Po~il/_3rDt4b O鿶ΓY}1Ej茭'[^'Oyz!Fj 11-GlN:9{|J=FT;W9鏢",k\qXjm/ktQ1 Rqqm~Q"\W,“%Uy{?к{5Ĩ 4ܦsjiJ) 1nM1.kaB1bZ[;ĈlOTϪL1H@Gu#zkZĂ|zzkL;U<>FTBBVHe˖H_߫w$s^9"(Nyq%;Z 1bjmƍ‹EglOޅ+;4{-]~-NUiybsO]pkb|_*?IY bSUĔ#Шe: 1jN{9ݎy޳l:%S/huit:WƔ>1Lo\b=o- ,g/M}S%Sw]B܍L'__ݷg?>_B7q]C񺮧v/nk]뾵L'Ub~Kݹ?]Lhy{Z7=hMcӳ*:Ntn;n4Ĉ6w܊u"b s^|'Ĩs0y:CZ?]ۨ|>] {h_=]`^OAUz3[kҬI4Z1V,}ч/J_ODP5lX9_YΨ8ϸ߲N딊׈5Yf.>|͓R xN-6lx ˗?Z(.'xӕP|ZGnDu¥Q+Wm\b|ҧhtQ)VLwūkM|Zi/: 'ʅwwԤ][NFwmYx̿l\9r콉9)^k]Ͼ,]b~Kݹ?Lhy{QQbbD+/)sHG7G1fҘ^1:j]w÷o+Ĉ;ߋ5nt3{WW}X|qKhoy/NFybh,~ ]v:vQCU'CB0bɒ%|'J0{챇8mZyw:t'Ĉd#(v#:~-d&._|vk1-Wq@yMXF&#>;sNnݿCZO>SI΍_j+ն#Gs.-.ũfb}O=Xoօ;{oj{Z׳oGZb~ԿKݹ?]Lhy{Q>Ŵi!!FL?E>i|ڧ!F6nJOf-o?}|}3PY1517iň/=:/D q=ũb^/+)Fk,bQZuMbs;q_}gztܛKIG!F#cOaɒ%g|~j 0z/ .\pUttѲqt͞2S,!F>҈aYe[˧W !Fq#<>tpt0љGk[ػ'Cx/SKQt X1RLY-7kY5*|Qb脌SvYKU!F=קXq=wŹ'ޮaY5Wts=X|hC dgP|Q=MD}{S[׺};;z]ꭟ]~&4s7|j 1^ 1FOWG/_јB*=L1GvLAEt>zo'km7Z nґj`Q)/MK7ubc-bZթߪg+FXϗ{1٧,b}$(qѶbF)Տ_z4k?c-uт >?R)l 1G\+:$yk=NSx=%!Vo-5u /k3,Y"1ai씱OL>`; 1=L VgZkF ok;ֈSFV_zbkE/9'-b~ܺߏ[ 124!b(RJ 1! 1J)b1@iC)RJ14M!PJ)bC4!RJ)bhCRJ)%&PJ)b1@iB !RJ)bhCRJ)! 1RJ)CӄB RJ)!~WJ\mC)R]B L 1 .w?]iw?{c]?]iw?{c]i{iw?{HJ)U1 <@ުiV2!@{.[Mӄ][l5Mb47wo4M@V4!@s{.[Mӄ][l5Mb47wo4M@V4!@s{.[Mӄ][l5Mb47wo4M@kK7,-vO=6`c8x`~\uLڴ* q@]1.|x=Lm꼩s|m1_ܜ&71lT!m--Y$e5]['. ~kx> >Շ{_ߙ](M=) 1$oiv?cuz_閟Rr3wQ#ڡOГF\ >B:?!v][[=h𒅥Q oNc&鴳k1Z|b?=#kZ殜[QbL;dZZ~v UÙkQ!؎ ~krmFL91 ;<.?cOww:(tv>Za訡e aqZuiĿZb:q~ڊF:2>pvS$r)"TXy6=)]kY5>'Bs|v)x5G戺::lұܝ)Ⱥz1Шs;7YKf9ktt/Q=5b ~ߪcs;v޻{~{O!FteD5aT3yL!wN; ء3̨|*o{ї]1J-9!FL9oqͲCacaM?tn[kz=Ĩ}n?A=b=_sB ߻j،Q1b >~o?~W}ڷisV)m>nx:s_P#U;vt_j=}wƪ[zcs&걖=B ӝc ӌŒm*>Ͽ땯Fo^-nӠt*ս@c#X;3y9/M={Zϯ9!v][uun^Kg:-MgB?}|by!}/q{|?BI1rtߟ+="h;Rs_v bZ/n1ky>~Y 1:j1ҡunni>:{e1FFC}^ػ4@*]I^.ZK6U|{N-MyoC!Fǝ-@'j:/|3;ŅE#xl,Pޝcbu5*>SH Aښ<kZܺ+vvi]CZb45=b~BK![ߚʄ@ZC+9Zt;Ĉbj|Z'hʝXйx̔S]`zetG~uLkb4b?\1ESid3+,x킪ӀE|*ZUCXO"+[s7jk9\ 1j즊?9}eʍ殜[Atbʓ\-~"_YycT@ycBG!Y1g_oO3溺#Z.űQ|yS~r~>g#\'2!bT/ ܝ$o-)VSJw_Fik}6.NfMXBVXW?^vM[K7L|jە/iB fVȪ)=Zre馛SOe'p9Ӫ=@UW}@ko~,YײeB 4M Y#4OOO>dL׺uI0 <,?~ܕYsHs ץw? ?~Q&=,xm_M~D ^'M]zsZ4m![yd8ppu׽ӢEWz+sM83 >6 00Qv}|Dw?SLwU~ox緼w/O Mg:{n{OȯaӦM 1mNiB VB{S6lH⋿S(..=Xg̙J⧟~_E'yt;ظiĈ]ƌe; hw j": 'bE!F{As:|oeA;;}x{m#/soz1mMiB VB/hw#>OP([::o >#"txC}rcΜÇKg9]p+Fad-Ϸ*]s͗or;_w:x௥oܸy`wss;򐦸#YsSySE=q㦥3x ]x#xxӛr]8bmi{,)gOvȈp#F\}i}*jզ!FTszhBuogB `eihR-_gy駟݂ ~Q{Ne6:cڡ/mFCSHqPDpub9>XLT|׷y 4OK#?;/ Q~n1ET߬U")>QK/X:Ӵi IOAN#)b_x}6o~1ۯ 1 uh@5I)/^UWNB]r[`=&(ùKi_6ІC ʿ[nIs4IY!Wq{Xk|+o;#AbtCq.m;E}P.9sqc){USF R~w'Ĩ 7g-R-[FGb׿^##1/} 㦛"FV 1vo~\Zh-_~K!F舎BvڹÊ)6mzbDJLIUjŊ[K.nQ]~^#Xb:^qY&^ts9种51PbƦsJh2TwCuOSN{uXYٚ]{tR1p% ge#DG̙CZ2!@Ublذ2/_hP筩3:… 9c2ʫ|1ѣwO>|C {ϝb}ޓ&O>b̜(p/kqtM o-)պ sbD͟fiIf.xEίm|o,^1jVbs![KEQ(F,YO>d] c=cǿ5uF/~:tTՅcE/ xڍ@c̘IbT{jv}9 1b-/:{q㦖?ӫ3e\![]E,Yr'{㩧bP… W=oMlCCpX4:kY^oѲ6'!CFCGYƏ^:+!ʕwz# 1]bڪzy>%Qv_芴ng*Z4f4p4i|Xx<b 1ŋ[to|:B{$ 69۵yM(tܕb hѢ/ӟSO=__>G/_јB*MNtQTDxq% ^!m#[ |jRQm5ewS,p÷B D!F74eԩkK 00_L<ֆblѯB %b(!MI4%!bДJ@Sb(!MIIJC 1`;IL 1gt+!F ':ە>JQv%O'b}T|)dB J}C!b$WB  ~F'b@PȄ3:ɕB&I72!Nr%Ā 1gt+! L@?\ 1o(dB mA NtS|.Jۻ\ϝ\K^J ʟsvXu)dB !V1*>W!_B hnL@?#^HCwZzs~? 12!C_#0~ 1O)dB 1XD:zN1CƤ3>#%\RYWT<׋翘Gǖ=gwҒ)K҈A#ҨiNA!Ɵ_tawCFw~>Oyi`|J3!c}1+g]YLk_0s\3zc9#GvDՕ_b@s)dB 1_6pX=G~soJCq_k>KE(kim_oNϞl˜;Cw.|gaB#=UCtl ~Bc'^p; pYQE/m/mmm]iJ2t+H9=WSyfƚ=b}֖/}awW}7}U<'DIK8>_^#ߖȈ㗫F9jJlz!F57Ĉ׏-niNykk!Ā2!LS1"j*>AD&M>}ʈ)w1:{?S4`PyuԻJ̟:emB؏ۿy7KĈW]?Ξqv:tC byRi7U 1@94tǡyC~v^;[|+-cQgB hfL@?Ӵ 4D׿%Ā2!LԎcDC,=cy'wJxz:B WL@?';_xu{!Y!bcҔSҍߘ?yB !۳B&IJQޒ 1~2!NrWBzJ 1gti$Fq:%2!Lu.+#5ח6͚kJH~=F1ao:?viд}Q+FODHJ *mQzO󩫲.Bb:c1U-z_}98.zoc.=G6 1^"*<惥M>}ʈ)wرbM1uTqoc1#c1F qܑ|;V(*F$4bOq3~R1UUEQ^Qz,Fut7_8u& :\kbkʧ?۟?]1WE1rSֵ q[KcFBnCZ|ŃB#j!Ko<|qS\a=!4 @SS'r,ޜAS p ǖO[#@*GL'#}j:)"3dLX+c[kMIE=|!xlir_6m5ε|q! @SS'g=[I{7^v!F=WVS@T 1b׹Ϟ|> {G ^Gige {?晚)Ӹf5~^;ժ\/[nksg5vVO??ޱ{]^ 1ao;;k.s{$Ĉ #И4|RQXy׏s*0s{ :C5ru1VqtEyM1je_j+hGx!ĀwjH).?|A(=nv>Ӧo#{~/44bЈ4rȴdʒ|QӫN!ƷNVZǢ*IDx#b)#f_#~1_ca>ň˼=[&밻'<6 -X'3'~Fs4ԩ[/V(^8Ez @49fB p/5Ug!ɧ"‹G_h>#{5TePy,=cм;MY]>mŴD>BǗ?^;ڦ=Lo^7k]:xZ  LSX?o}ՅUB; =/Āѩ2!t+! oJ}~G'b@I7wt+! oJ}~G'b@I7wt+! oJ}~')GL'//7@Vt 2hBL'/oP!ɋ&Tt 2hBL'/oP!ɋ&Tt 2hBL'/oP!ɋ&Tt 2hBL'/oP!ɋsN8>nKGEo4iJD)(4M&z+ zR7YQ,Xg'd7Mz^If2z3PEQQW$|M^$.oW":EQ B H(B Z hFREbqE3(7+EQ@8@\ь(" f$EQ!-oW4#)1hq~IQEDH(B Z hFREbqE3(76&Njɵ׶ܶ?TƟK^rr9 1,qi[d̘^ҨQm)^.\HK&u[:ʳN&pY_Fƍm֤I}sM6W^ 1"9boW7~a}vu.a6,azψcϞ%6J(ku2\rX&z'B K8@\Eܰ2݄,Y]fbޙqWJbCb$GM^$.oW7쮹݄={hs5G7o~J˗Oͻ0Ak>(͚51_\f8;dۤaZz 4Ҡ;׹nҢŅf6v ^^ԩ=R~|׮`dpg[o=m?ĉWeƌArTP^kW1sw7Oe~rypKZ59{=ѹOǷ^R|i)Xy4)Ç(GlpR W_fc%S'|ы~[yqa_k˖Ҽwz86 ޻>'_fo9/<'roW7b7ҚgmX{fsT(?qj֨ᕣG7ѦAT wܭ_,WTN~}Z;v~Ln}I=nTI?_~iSYK),+V8?Ą Ml,OWJŊeӽ;zLB ϟ?{Cmg=E@ *↝7`5zeȈ7Ff!폘WzwҥS\_a5r-;w>*U& \_VY Ц^IOʒ%[nZm\"˖M3AsZz^>m@so?+<2^^XYw9z7^GP׫?du\{tM@3{1rli"{E BnO!mRz7W\`Μ9C1t+ԩP,Z4lwaZݻ2Fh{>c=f7wXܞ"sB qiMU+UzFrBժ9\^7m+歡j{ K~{-ICXi6xZ/8^~啗˭E>q \!uuyg;b3ۯfQ:@J+[ߝt0 HݺդVԀ'4D&嗝w}X/CWc=75sb:Ρ.=/<'r oWwǏo3+<^m^faZ:.ݻKjcU׫&hҀǜ8{n{"=6(wcv\5g2YV0v$ ;oBps홾7#9_m$p6#1#!nH*k{ќvb7,7yyYU=B6Gnsϋ&hF&^w<λ2\B IǏcNNmz'n2%Ö gWq总!x`=ԩB6݄-r+W~L p]λD:wnC?3{hD2TVC̎e4ۋyhyr2e!+y݄@lq~a]zNWaOO:-ɑ18ѹcG:w˖wzNw~LpGn'[i~ 14@3s>L>;;uyF۵K?r;:uct?b#/^akŊٙn8iӳcbDСbDr,ٞ"p7eSS`)Lv\&;([Y)Y)Y=9uj!5 "n}+ k `GǨ 0O5fu5S7"mNkTzn_Ivn{ƌAҾefӳdPE+5t锈sv_}Qs QmY:Fj]w34B4watHK.iln[rJN`nCزeqL{]98Uj|slG_cȐ͉ޤIs'܈A/UW|/J.7ժrHXhH<]LtܺdܵK gO=rdЊoe"xJּ7\1A&k^l^DPWG uriU=~e~yh_wEFZ&= }/3_;) <%{B^zD^KD!FK'ֻJtb:u\h္?/LҠBËիw|XU$s-P<_n \]>Y1ܽ;lgY72u72saq2! 1b\{,I7ww{9S?Z _ zs3SdeedڦC2kܿ;YB z 1͟?97}4s )_ߜAr)]9¸qCNXM|u2f _ ] ח2/d#2ea3x`1'y1,qEC( 1^sXF>$#W+wAyl1{yU /bS?;ZyY 1| &/O潼VzRmAY}9ƘG=eE%%eekd7b)r| h~Rd]2mȤmʄ?M?ʽ#"͒/+VՒɫjʪȲݍe²VêA h~Rg\\{l\F+mlek!Ͼ-'oMzB~ɑ~Omgd|fSyzxΉ$2A∦vטDkMԗ! ȡ7e޲gd/dn<)-[ԑ?6z]C}ېb hjǨYb7K(bb`:G~ixܻdܳ儌bXJFdECvl#u{ZR׭g.{ͧc]^ )_,X 4)Ç(Gl(Ĉq aF?vɁoV/'vF!|!\`-r 6ݧLo Rm܆[H²bMubh?tIKk:4MruîWJ| b v5$ĈxEs,ܮ2%%75B ٱPAzcK\2|ᇭBHnSA(A(W4o*az/? KN1?^VTN/#+WΖV.GnJפ4:!V7-峗0Gk//Vl]O7^G`ߵq^zzC^+ ˧ {<#2#ͱp~N>{yŊe쳓3`ȎmD<9# ^W?c#Cן+^ΜBHn!|!ots֬!amw#h|lRc?|w&u6c=X=Ԉ@ܱy睥Cii`=+>O42#X]_v^VC/ACvl#z{B ]vn\Ln⋗v_z:씆)i#Hh>Q7qgo|?kKאu6i!ƕW^.;~lѢQYn,'XFbDr ۷<`Yp_Z2Ff;gfkn}qs{wԸ 1"~Dsb gb8J;.9AF{;s;X-\8*РLaK U9Ĉx=n׏EmdV~0lNc6CYCM:^ޢŅ~FnќDŽ@B rpRgMΩS{BIttQ˺v^G^5Ms9(2NJ'su<:gUfͱUX]?Ic-a8̨Pkz:1^ɾ:wn*p<& q+:ᮻnիO~LP윌:)96X۴$`2#G6kp4|nj%ӧ4k0*7ٶa1ms m7>K֭g6uRdPonr9ubbD?neBh:-ݤz7CFng62+"V |}:})sas"HBhn!|!\ q;^رLo{zWFu+fܐx*L#x*zq kd0@jcp/LNna"`ȎmdVz'R9'@9iӳ]|?'B B G!͚51:TOrrs[iҤfV{uVMo \ȡRhaW&vb[PJ,24:qU5k\rIcs}jsOlٲ8f!F$X*p{,Y/3߿V۶˜YpȨ0\Ӆ6;!K*IfԤIy]K#G4ljHX>Q7)1 hFREb hFREb hFREb hFREb hFREb hFREb hFREb hFREb hFREb hFREb hFREb hFREb hFREb hFREb (3 +U6^xeY|!xIM|ֲSȄ R?Wg SrMN˼29:|!$ =^Q-31TIII Ϟ[IIR|!^\"ʗ31v))zzȩE 31v HaW:%'Kk#]4/ y2SSreI gbȫ*UW.E=^ ! B zu){e))rNrܒ$%8B@31$d ubp B vm)ߜ˼^yͿl4G|!3EժR͞c-KJe8:@31<xr.GU|!PiAl$CE)pF4) @6QMQ@|!j""31hTS!)AlD1H B d#EDgb Ѩ(B R>وF5EbF4) @6QMQR,9U 2pF4)*k=^GWp>(ݥ@qUG=|칰32w'FyOIJ'+u|gb bPQ̉W6:%%Ie~ B d#W ogΔ!-[JT)Y-^4zƋ/Ç9jÐ!2U+Ջ V"~FF{W(OJ<^31"n=[R˕Ucڵs맹s99b8y<^_C1m\PviG]Vx\ 5*V4`  %DQJJT!1DnHCzO(R7K̮TSS%}I)ҁ_=ȣ|!QwK0\y?@̞C1㡞=Bcf.^_h@IWfr#ZԢEҺ~}۵II=hJw%U˗k9+T_/CZv(wBhs_c[yɻƹ^7 .W*UJ?Izz޸LgvNϛArY˼^aBp>(fٳyW@:Nd%d={wL}&e=Ic̘= ߃Xicp}9;*?g亮./͟nݬ3ѿλG51[zo3uQ>UГ"=^9j'"Y?9Er @6rm]&JL?|ω6ĸA\Vآn3vl9kU~]3p`uG=Ծvl81{:Csb5` ~L*tȯ?uC}F<[~n3M|R Gn~^]&{rA^reIr @6ý;C oZJ)b7qOD39iB!Fb–|sn Hλ5BC|lᙆ݃_wh<>;!ҹs#~NAF \ߓS&kYj;, 1Naϝl31"npaIln<8Kɩ#xX&(z,w{({'6NOsXA}AYG:4s~k֌jHr^8ZLݼ͉3bz\(Gtf%{ъʐMbvN zsݺws4MTuE7NMuWӦdD}}>9J{}VJs6&hbꙓι+^qE!F4۝ڥyߩSeUWLͺLsV;8ul%fn?/7jdMt 2j* tr]sk8#:k!!cOFV3?~$Աrn}ջ"o(R7Kk!Ȓ B ^Lo@ k qq3qdD6ra\yG>_2$cڵ )dubۜDw,|4gD$!֚ѵiQҿm\*,iox]7f:tԤNiժZ|%;J"dܽvsN9t(;V7)~lU~W_m/e˰aҼn])^ǮMʁuBݏ&>ӧa;W֟~,g̐Kjִk~+*ec;~)ݜ9rn*a׫R|>yr W:S:wFgu,(+ b " %|鶓VrX?7JI!nΝO9r$СCҾ>1CZ}}ha/֥{Gn^^P!e et _wq|2ij^w0dnR{}mkJ^0@~B߽u\,RUzr[BA6~Ƙ1[ޱczz ew^K_sA nժDϞ=+]ޯ忞~ݘMV5hp>;A{ye")91C!Y3n\PȷtҤ4֝խ[uoy7RXt!FPZ8jڏ=׻w\s?Aዠ^_y;Y:u,܉m,x;S{K0iwrBs"}LGԩF"igL#D@\zs2hoiթ.pzh]Vآnrn7G}G +yGvOΉf͚7b9'uWCǞ|Nw([9T4M2Ŋ-(B;2NJ#IslcaBHs?Gx ׍K`=4s=tίPbEXW0sbL9JL-W6z~טfbҥ 5hӠAGNO7uޏ1]1 1q1n GƎ{O=N;v,ÿo[uc:qz.g6bDu_<ԜVU(Q~\mg*T=3GΫV-kL9nzOu}Xhyʔ%K,Ĉ"3B 5dȐ'zgfwd۷m~6*ubd:NצYQJJ7NZ֫'% ٔfu&Ԭi=%.mnC2lXDKWYZR>0ff=Vj?V-_^ns_[zYgIBcwD|9ujD{ 1b}1n!ׯ;0ad߇2 SÆ e˖Һx,7ݔ9>+TbA g 1}ݻwu5׼۾}?˕W^yUV1F7\Ȝ['s3Y4 1b@* EQ>##+#3zt|wv4 yIDAT1B!! G!EQTC ;C߱\>!!bP/EQ9bP!bP/EQ!"Ġ(1(@S(!"Ġ(1(@S EQEAbb!E@QEAAbhJSEQ!Eb!EBQEAAb1(B (14 18_(AAb1 J1{/CߍpCV3Z~_),t(/=W7`]ܗԖKI oay`Xއϖ6_lҳm9l^+~}틮WF>W<b$^)ܿ^eمlD "Csв~nT6έ[,lJR!|e=5cd+uݼv A=j~fD[D1Νqĉ=_P.lPjHCu'+f<4p~Έ+ABr,ÅY}=Bjzy4H=&8J@ "C,*˧Wv}9q"Y+L{]U^WZWZMbWzͰd|Uֲ+xݼ5+_?\.km%mD>8Ƿ'tѿuhlܸsϷrkǿ>m/_CN&}T0~yիJ+.(iEsE_ѩMOq}^,V]fWa[5\<ӾS<-S_W^>m'ӻ yKeN-U |p>d;IfkS6 pZ<&UVή!.*e/;եKSG*8|񴴴ݜ/:|Oѳ{3}ۣCuZ&h:iCX/c)7ukip\ix&}bu^Q^~%V`fÆ9&]e=n~g2 1[At&뱇TI<=]ʼO~߹ bd{)D_o_e&Ĉ #:#˽^/b!b@ y9%=f8V";WH6&ulpUѾv Wm֩yn@hÉ]WV?'z)>/]r'~/W;Y&C+s[RR!Egs[KMER]:ֿ1EZ \<ӾS8κC}y^f#v$x?;^mG{>n ,p?xy%m Z_> rAXP!FV_+޿-;;ൾXx\u*#6!eWksU [zR%IW1 ADՄt^UdXW*b6WAGn:;l$!npv̞:ut2;1މ zlSÀ+u%khB_!‘1"9TVC I}:I6gF!2R*cΦY?bb;!s_.k9_ygskel 1}(O))"5U[K<zF*P$'KuY\DTMPMaUtȖp p8Js5.nT<ֱ cW8'ֆkf9Z) Øh9ώ91nF;'ƲeV#x8C! b8w^1~|v˻N*V9juu9&aw|w&_ktmu&;O7|^n 1z>깨s29Cҡ<i^!b@y[9:$qzo5ȑCjjW_h]v-#--m 0ְ ;irK7B ^eNJ)2p!A'BVolPWggҺl>ڴiWCD~?x/Ν;!t=*'K'W։u:U7UWg` h8Jvt?''C s\_ZZFO1 cgI0{RAAg8B H4~h:SB s~}bPT& hР|(oh1 AABQ b@C(@!A EQ#DC  *(FT/EQ !$?48_($B H4~hPqPE7H4 hР|(oh1 AABQ b@C(@!!T+(*7rB Hrhy !ѠdLH38O q<@Uxs+5y.OL:=TT}p1 Rɓ"Z$5Nե(GH8rT)Z"=)pF ;y+U*Z*92@z+O&H{kYR\ kKpF"4II\M)d4B 50ɹ!_rVW땎U@IWZ?+xR b=zӹ,5UsdD z^Ȃʕ%ZwWB H^i)Grd!HI~W>׋eSS%c 1@(*G 1@)(y!4)1bA#( !4)1bA#( !4)1bA#( !4)1bA#( !4)1bA#( !4)2.o>(S6+>M3!qmMxդkm:z{qyv9]x;Y!5kHBV@~awɪoȥ;侵 GeҦe{e^A3!nEƌ%ՖŋJ…$)4iRGndUn1z۬IضsD^sHn;.-_e!p&䈈m?U>zF.a6,az &11bP[-l_)Ǐo~ڞaϞ%6J(j{e7-Ky{%LZx3'edoe"<%/䈈mS%KˬYC;3J\|C L?-b<~塝˚OOzkLXTƾuR)ʾod#2嵄Ht GDlvq!~#jn9.ϗ/̛wW/_Ai֬ʕ6Cƹ!c& 2yt+ Z9xp&-y+]tz8yyī2c RTqsحڵС7,.7$ݏΝ'0`Ȏm~e֭f?j{GΎK.ilށPWx`އ _feKIϞ6eϯhqdw=ơ嗟k-c~EUp)Қ2;\kZN푔v=n~ z뭧:Y/^^.L>2cDzc2m!wrߖeeod22䈈mƲ6?|)1J[MPFx$!Frr30ڨ>zf6Ǎ zSQϟi#XTI?_/)_tI!F,\Ƹ^'Y5b!ߗ+~v;xkm-s&cM{}UHaYbvLCH_J_KC%" 1۬|Z _ zs3SSɄ˪˚ȴd#2gQwx/25Ht GDl*f^}ea 52 1oļj\֦ҥS"sù\k[̫w|TVMt6Of^MKb7%K&Hn-wsՀ޽=4: Hٻ+o\+^Ѯߩ^^bYY`9Wgۈ1tg&һtn ʘW-`_m\"˖M3\=ۨ^kFu׵  /#+WΖV.]..۷ 4zwuErN kO||ڴ2#+V/a۬~]'c__})^Bo\<{Gy11@pp;v ڠAM30N_)l@ 1DJkh+q{@J^znm6c~Ǟ{n=ďsc-睥\&اt/ bR`tCvl#08(C!t6稵=ܰoڴ(}|oC뼓 ,3[=r ,ΉqIíW^y +a͉f!ƽ~k˨Շdo宗^:(9&3}/u\~C3#E|iuVB *˟-B #\7ݎf^aWV_xatJv3syǎ x^|myݻwt!0gP馉lj(c6?d8v}޹/^ ' c%ȒwE^DOwk^J|B!9"ƛIyYU&=B6Ahϋ&p696TJ[qW[5eJדR;׵[pTȆk2%Ö=n׏EmjqGװI9hbok^|?r (!Ff8x:VZ&G1q&Ĉf廿dWe. ~{7G^E=?CfX&毪>lO#x߉1 @"9"f6w=!^]mnj:TT'u6W݆v6LﻯoamС2k{݋/n|7:M׵&0kގ|`g>Yѹgb gbqG ujɓW9Q f͚̓q{lyt>}`λ@tSwj$zDŽ wL 77n|[7l>:9ԉz}&q3t;;ck83Y;0)0NjBkGbODgcobbDzٳ]; n= Ĺ#d۶!=k(ͷegd,~ |~l:TG0w ymC~9yB $2B #\6WZj;lD?{Eq|@!d7B UlLBo!P 4r;َߝ穜bo6 -/UcB;{OqiB ճUFnGmo4G`7UdyN޿%}n7ş^l~Y|6Y,릻 5YL<6''@"ucۗ_>#ݷ+p5!rzXjoώxxlqRej}MPP 3Գg'6!&gn5Pᙻ4!}%S@WI2~yG`/}]nS۫zOLsb_eϧo^:jԯ!n8!!RO]U_UD=ν)'wD~߆FA(Յ o==͛70޳77!B!B Vǎ.2ŢEyn11@@bP#22Ҙcٲr[<7} hH("CD(B 8 hH("CD(B 8 hH("CD(B 8 hH("CD(B 8 hH("CD(B 8 hH("CB(7l!M#@`b\i,B K6Epɦb 1.4B !%F" !̦b 1#yTF@ȳi,B F"1@i,B F"1@i,B F"1@i,B F"1@i,B /塼a1Xebp X?|FQf! x4C8+@``Ob bh9gG?|"@#Ġs@L^Aw} ``Ob bbAy>?*sDEEI:u=eڭ!bXr_e+bJ{!s҆k>o]kܛV}lر>G_i111<*i1y\j2Rn<22RzbooW>&x 6 !B!!X% 1yuut٬ov9vJJiԤOozl^%b)7ֻcϣҥeS5VU[V?U{vn;\{"U5ż|mrM6[l-UccRbI/==B 6 !B!!X13NXU{M1R62 PА3jUQd=//c?}ݾ˼,!"\+p{ rCOr<PM=EGA9ViC>.["$L@,_k&Pzp˫רin7x{>1rC2*UdͲwnCVn4:\>jU^'rKfAvp<PM=EGA9'V >V@ 䪧7!&#Gmwj,cRvs-w{̉j,s-XwZy)w9zܻϼՋC(v>#/4Sq@Pc3إܷu| C u}}y󲆍oXLC Ly-^=,]6oA UVmx{!sj3_1@~oU/p Bk4Pk@yKnj!Fl8x!4 +04l22h;w6kmWn섳2,i\T *"Kcc;tܼy33faذal6 :+I&-sw|HFC9?&.{-!Ɲ{1y-4&jϾkq8)51r #ѯ_k۷곥wާ/ 6!RK+4A0\t^bʮ+ 1P 1=޽{#˗/^{MN:%W/?+ї ;I6n*Ujj FM^w*B B Tsb9:okd滌px4lt26n&Km{9dLhFcbFMx4qĩ qٲe =zٳg3##˴ _ 1ׅ)$&*Ƽw&XI9+U#@EV$۷o?1j(9qx.)33sQS/KJTc7o6Cn!#5α*r*~"BՓ0y"w^j^x:'$Zr<9r>[>ozEZZB P/؄+tb*bOٳ IOOx|kܔcW#dk=3HZu>![w=,:w3Z B==4TTm>9Sݖm_DU$ԕ&[cdm;K}338^7r]a_ޒ=qqڭҪmc(5c.ԤZ6ƈW |F#js5K5K/5v)1qS6ʻ~T1IjyЄs4Nx4gΜZC 9g|sd@)`+h]V-Ҡިz#jWf`_^/vomtԼLĪR?_Kn>!ƞ=|;[! t1p 1l+$fΜfi?>z{P׶C'yWꯅ#"",V7n{?>!qW=H q PpZ@s,B 1.]gرc^6a„7>[~WwJoٟ_w_oH/vySþGs1xM=$uHA]L~PCҥ^mr 15g.W=1ToscRItk#>{?t[{F^7=c nG ?ru6]ebWk_^RUc9;4B c̙߫VKW1|Z}Ku$gx#b=A;vKH2e^'WaUZ䪉k32\?aꁡ滸rݒFryņw-6 HD6޹GwZd\B 9!@SN=Ep{١Crl_!Yj_eX\Mm_ާ~syy mQF~T,#7/lgưNZC5쫡6ouT/רO=YG 'HnFѤF#T_gD.Cib0'dY>^*gΜ)Qq>}iruN. 1N=7f̻Fxcپï: 82*J[~Wѷ~_R秜7{OGj􈰯ׯAbWpj'Pw=XN7{[Y)Y.Cybq=%Ν+2̇j`*\ЯƮzŘvаF~sܶ׺lziOQ=,<1lѪM垇% 1FR]1i]>E:.Zג_[J+RzJrEBU -8||T!]W˗uYfLb,C5ցņg'u0Nv\ϲ_gZinr߃SO^Ҟ_~KUݳ)40߫lإVmYl[-m!OhLm_>Fuٸ1wԭ,B%.93sGZA4/ß&z/d}*IxvA%Uz kz^@Y9x~_Jo!FBRuj)7IjTcoՐ땿{NJM2'bɳX\0/Nvb,har ~eOO!CsӜ'Nsb߿̉z_c:_ܣ`A9.0 ec'O/v8RIM>ȺCX nsYdd[^!FY~VjQ ڣ5R`S}k^jWo2\Az]=Z^/͡CRG-/\XO<"cڴi_tN#G͜P/[qjbYmgfc4:l3:2y{L1u6B5魧Eu/*zNmt{Կek%Bpbm:dX5CM$xmWb E )ĸꪫVXdl㣷\j %1+Wΐ{9Ogݰ:w3 Sg9o{#-1ԊU6jWm\j;Txy>Bm7!QMGN\jHSq2poBhثnL]B T4N!f0`'N)x7B]~|5TZϾᲱ~RJL$$ZdϛQWᅧQױjҼuMYl36iֲy:5VzOCx]zRMhy%밟}P$U#αʻ}Ò;*TJcjH)_?[>Ó'>[~W_ج6k-UOwټ~:| qFy92u 1T!6t $^bl*)6ɸs奁/yb!mw1Q p: f<(.2İSbbEa - T4W84B OTq7Q5p!qKEvEoЬzy+~ &mP)rop߰cSBҥKgo{g /K! -!BVCZfZq>R!α-jػG}Ӽ[1/KiJ EQ-V7nzIsK{^qU`;ώQF4|lӧWz:Zg /؁ 1"#"A\Yer()!Fi Rz] ;-FvE;*͖{-6+U-wQ9lܰtyAFOX*ggĚmJ$sQu{7Wǝ<.}ֱ̅ R"sB,!bgƝ{eÖ)[w=*0)O!͋7  Ѱ =cRdpu+9V0ΉpzsN.5Ho!FBv~ٌy7Ҩo,Sa:ʾ|˽U`e;xB!j떬6zx G/4SB _jo'!!F׏Z|!XҵC UAb|-c8'qϾu^c]/tT-6Vm2nE.SaJdF*.تb 1108Z:_Zϱ1P5z'NWbpѱKwVmYl[-m-X1mNR-z@cܔ".]V_D_MR~VԊ~U50'v|V*W6RsUQd'C] 3 uj<{ 9@E][8br]6!!!!FJբjQg4/':-| g뮇 1Uc=u6nr^FѡS7c 5EόF/{׍!ZioUԮS׸vN6[t6vIkԬeuKVs< F'(;-uJlXYY&խcOZlwDz$0nC6]*I#vO!%#9\m5m1ן~m~\-ټ!oSC9oyϲ:ZT\]#iw!!F6V< ZH"vܕ)"gZ| +k  18 j. Cú9)xyF#Z%l ='&GmT|5k6wшboWfAGZje,6z1՗OFBpGAapV"-2Bd|3‰Td&'<(_;15%ohs_iVy 54 /C"w %^U3~W=8ejx&\ל&vvB USzz3)]}#紛c._sc{ޓ{m.ZI?k>\GP8Ob<ݚNpGAaPRDRTDZ s`|mر=bW51M)_wWZl#maڮoBKvi^f,P=+ԐNTj:h.Sƨ)_91Tas Ta_EpFAap|B015SJ'ϱ9Ĉz I2zT9[_mԾUErO5w,.dz*K.eltuxjΎ@}uqx(:?1Zz|:SЯA?&~wػ^O#@) h('|cJbFm[."i4]Hn]65j/I+uZVv]i._eo?S% 1jFt[b ">[ "J_Z7b$%:5llDe#\2ެf3c^ _C:U8t%(JpRL &?CsF491NJ .|bN 9Ag+!!FAlo33n= ѪG.˝&vJ4'iN<cak|vWH#{&sW|j W{ڮ2"}2u_]^1}1Mǘ^|63y2tEwVBB /6j׍q;.c(qvYݾc|Gc!FڭK QufCrba?O!FMlC(]OZ-^SWa}AJAbbFuIFq\1񒑜!2y5<FqNCP}?{B \5S\2zx"Pf!7ۥ&C_]vn{F|k.B Uj(+5%]9$&1\yiKwVBB /%<]Tۨb t">[ !14B B `bO]ru&n^bb!J}AJAbb|X"CW-(hW#@`D}Bbh|&(#2"R5e]) 1cD}Ukݾǟ.˻י4g !!!_e  ۆW/#/4Sqֲ 1FL ;vư1|?¦M=E>l-i?ʚmjA1,!F`Yڒ,{RgP`Ob Ա">[KbTktquoco/}o*#mӽ6=OϾ"|r nCM.vC~g9ߩ3F;$_I*"`߈i1tbķJ!>Ҹ[bo%嫳 %m 19au;KUWOxK*2)B :Ag?Ęd]G4=q_|bo/OuVf,X% i!FLrc',w%]z#O>#҇b@RX{EzTd4Su 1j4:ʮUϿ5>5n e7vƍ4[Ν;_$ĨB[#wpTUvcN ?Z%"@cD}4ĸmanJ˞1ĸ(cxkиYnomԵӶ5\ 'sρ"sc ;I2b? !B <(b|nkؕb_?.gHq-^SGjXrҒ,/ !B ?6 92n^0'3e FόnFK 1ʣ11lZj^ZO"Z%mHxBh3 Q!95 /[l+$&Nfi{{ݩ0a~E.߱,]Wy/Q2j6^>kiҤqE TK˖Fӯ1̜[1.j%/X]\m۶,99$5T7JHhjφ kb5wTTec(/]-֝$JΦN<׿#Ġ1(NNzZM]:fٞ5Ͻls߼y,ZW^ljYddqyIF ƍ_bX,0m-[zzruGncӦ~n׫]ZCvUB lZhjOt|C$Yd_mH4S(Μϟ9sD!ƩSN}n{{ح 5M^&{`Ņ]1]$2eW=7g_:u32fbjӦL_ [o+oK԰/:syFsСke7wrז-?u>\GP8V1\ޢE N"""eu64 [䦛"'Ν˒%b%gB+Pu"BNI|X!8NbB 8[^۲ܹsNرcf)ܑ~_忘>4xn1CWzk 6F_R1Ip\^nScwլi)P=+ԐNT궮C\nPTkzN d,Y72X~ʕ2QCHGپW͉aj_6-Ba<*ԞD6$)B @@g4`Ȑ!R_'6o荠z!v3{kڴn{=KTemtxjoCm:]8p2k!JbTZmC!CCQ*.dg.vDibGkA;ɷDXrTJLY!|•E mŊUcҤIl3so/UC…oLD! ԎCkkQZpa[J1TWIZ1 ?۾=uRsUhsb7ն:N"^ ƍ =&7&'KDI(6$)B @_dQCV!UVU{8q§?<==f3ϽlV]hFԘ|ZL2dt0ةG܆۶$Mp'cÆ>OݪUu?±{q9ۺEz@,ުU83jv?cc^^f~Z̘֬{ƌ'f._dС=O:'ˠA˝&'s Ac4TJJU'3)B  P箕yYFM;C n,^ !@4-^Ϟ=Uk^T1St^ BMw4ybX,IK QqB {Wxs[7FuoILlQdjꚗw|\꺞3B >1ѫZ 7D*18!`x&?bg۵^'77Bq=2>Eνl^K0>uXmzzYɘAMz ](o4Җ-{b*8h29_m(|[74on3onKMm~rr;-Y{J8!*5qK,5j$9W{\7'gk6m9Faʔ}%D#iVz}W6DSa!|y`ǭqJtlDU5ō$4Ywr]!ƼU#""dԶQ/|֑YҤG6bkJ݌ okdTyEo/*qRxIԈN9dkնibq[|FB 7͘1c^|ɾO:e_͛Siii]{rb(wFEbjNsYq))L}6$)B uj<4ЫB/7z&e>5jGo/X!FMKM,zӚbT65ň#6.}*++U3(zMQlq)&NLl?aw|@cCPnzݘLJ-ULshCq C 4h^NN{;z}&##bWvـ_a]#P= nM2qD<,yPW*qy1eCU}1z|\RAsy21Ft1;:*Yl{7˸Ǚ+Wl#:v)'_$Cmرܞ .177gϊ7~,==}l 8x`ݒk[+ȇNl&n{vcC+qQσ1w4İyQ6 11'u^ڡ-5Wt]]jK-Vy^= X^7 ?ҤYKvf-[&O+bby`C 9W\>l/#%n@_j 0<@6e㿚m y-| թT ej%2R8WGyj aN w6rN |B ߁;>5GGjrڦ,^Іm򄩳\oٺȑ~Ur4iJbJJa2 m=t]h!­[mKtt$Z2zwyTt̸jqŖOURJU!=35ybg´FpS56qX͞Ǟs *U2nC ueN2o_=G[m桷mw= 4WQxد nϋ/_GqWJFMUGuyw0ӥ 1-[wŊnÊcǺlĉol Uf+zP4zT Z>2Ur}./󉽽 1V}iGm%_o Um$(6;ѐO;@zlдiEu\'ahyCVRR z`xsոҰۆx5M+7 1TxQ:9텗_rD:u%""uT0~ 1:p8宇tϿeyF*xq\!?/cZg4 4ϋ_]= 1NzZMfs{١C?! <̫խؐM>\^c}c!Ŝ\ܱUL&v ~\C "v4HP l>w!wX`ʉwky6Ը7u^GzWi\hysrkW~-izkV0x3{ p>cMƤu\0w 1TnQY`Zp\^FMW@Ý!rFO0zvKH2Ul&,Hٷ\! :vn^6ɒ\ Km!yb:W-wz:tv m.?uV_ R""TXw9 K[lpBgΜ)QqԩiiisTu9Ү;#4Wh-eʾ)^jNCPm~!o4O5QfU:l}^ tڱP?jx(kD=O$HP l>w!w+MJ/WMbjy ;iyZ^^KI=Է!}W_;9떬vLF_6s"!\ Kd_raoٸec}BX~ Sծ1Pa:ɾ|˽U | q{PΉp߻]j.}y?[Rwzx ͉%Ν+2 / Wᡥ/#xe:ti<nÖ9*`Y|\Fiϝh' p))IZ,Nˑ_h=1FjyA* dI~C2뉱Sxu.2cz2׽nkϛyoXec}K{:ݷIx{UQqxE69ϒ5[.4p/!J Q= |qly񫠦e*LQL1:Kzb!BVY  m#_ʴN4bpbdI|"u (m8r])96;"VmY#ZZ|Y5b{tidU>/%yƔk:9׸)3KbL6LI8|_p 1*_ʴN4bpb"U%ېm@VsPh^Xn۠pR!C9Nέ1*Cݗ=0'O/pRjh$WqRmUm>(}^=j aN w>1I?D R_x&Mּە*Xbz * !hҤIl39BADAr9w!weIkSS%&6G~]4k&Ԥժᣥa|j~5QrcKz'؇*CU݌ն:0`hn{e56nf5>rȘZMT&oԴCO"I::Ms~CO:=sn՛2}*-_GY$ϋ_渫1zvuW*|)XjU]5ԉ'~'m6[m >1P.N4bpbe*bJ(OȻ>4 /S6r=pʍUQacsiJH8ͯ.P{OYݖF}e!>b;zΓ7,r_k,=C o_>Oz31CYx͹={֫C_^zcl H>1P.NBQvV RmԡԆkCc/3L:::ZaEFANԜ A T/!I.=@ziݲ 1m۠oPDTow oDj٦q}8jשkFQszKۨ[/ tTpM{{[=GgCLX!*-uTWyۇ2cPۭ&Qq?-Y]p\{{rss/#?o߾KKK[ B pA b$&J$I P`/ODEx*pEsf6cƌYy&>u\7o333O P Q!8w Pw 1;*0Babx=[$jmkJI8lq#FX8lذ竬W` H?1r޿+\O Er$7!F׆ s&[E @(|bs'W^=I C&!,\52zT9[(;}(;C KLX{U:CsEPQ!V1޿>İZ%Yn,5!8NbQ!8w Pw!;iWq@@>1ri"u,Vy5)Y׎q@`>1r!FbTXϖd!|Q!_| /Ν>{3,]gƯ!/A;kab$&RDxh1|  `DX #*[<9YR*kG8EKN Z '%Q RA^;)B _8wB<ĐHUnh@jW׎q ΝA1$bߓv4Sp"]pRVd %s'}Iȅjn:%s'}-B Z=U>mT%s'"|C?[,j,^:p"DaHC^*pb b1J pb ^=IJCR /A;샨83,yi>-C ^=ZyKKN * 1ͩdSdY ΝAT\Ab(K'BQATOQEQN"CV\f#mH4/ ɝ?bq((B b!ULN$B ~!Ġ(("1 1DIb!EQEQ!@<İXd* x CAQEQEbd"HLvd-B ~!Ġ(("1 1dDJB ~!Ġ(("1I")b!EQEQ!@b(;XeΦJ-v!?bPEQEb@q+HJ6Iҟ@?EQEQ!#9Y@!!EQEQ!T!*-V9of!?bbPEQEb% 1. KuUv%$H"O3~11(("1R)@W .#}pρ;7ǒ3~6l&C+dҤI3Xq #{}Onvm9V6 :&r5iIJy3*p$1>ItuG#hEyU#u`mcݿ-<8@iSbICQZ*h5sZOiЩTO.QrRI=>,boC8}񴶱L52`V!TڻwrrrdkɩSDQ^zVC5zWW4fT+Bk䪼Z9=8CyDkgh\&Ġ}_ߏiҺqcmɯo8_%X1@bd{,ɲ 1}ZɚeԤʬWsf`(fW{][b p/)I&Yڍ&O%~1U//ֶV6z-H!.n$iWɺm33^7xTWjҶ&1yC5%a|̻n{!6=_xwkh2Oސj8":o?^ڙ74ɹ\/8ԡי9 .{rSr5GK:hdd&/$.VZ5O_Hv|&=G~~=R McKhZ_c49P:=/ȸj&[=.u -L{F^iJb86p) y_~+.HFŗRyIV41}ܟKv+:_{Jyzu[5ҤNFplXWhr?|}>}.8{zXQ{'1@>0ֻT!FX*y*Ca_{ 1Y4!Ɩ-[_b̙3?l3} 1TcR5pzj8{ywpR玣׃ۙ=RD "U *"%hbhϒbDMb$jb{l߽{_ m3<~É)#-K Iw}ì }fN{[ĝw$kB/~d7-j#-\7%~Mvl݆߇4AǛ}5+b>e{ZC>y嵴=o~~-U;&~]>Md~p1Li-YI3Ng{fo_gOztmno2o{dRТ j||8ay_?"(bA#FACUBA 8e =}^ҢEѹG:S:S)yk>c3GO}ۧ0WR]ވTG;xrgǎjnے廈?kgh'Xu}wEO;qZtm m>uiUB];: -ts1:CGql}aL^39}Ĕ%S8vgg?˶mOCnRG$ą^MzAxN8M+Obw]?Ѧq+?ِ@vM予dFo<+6o%.bx#"^E|+ؽϱcz_}d=ĖQӲ]$ ?0dA-*ddK[Fgz!?aȌ-dMpUO YR5ёlk~⊖6ܴeP}tÝO*b}I02s|Dl?Wt[3۟uL䪈K|gHGdh]G&舩{Րns>VP'RE }1,KNoh49.bh"SHiBG\kLk_$ҿt?Zz9$}ޭ~BF(TEluL:2CcV:E!U ɽO R=:-hq7.M{M -dQcy1,-|m}qkǚagFP\`/y睌o?_S+u* -B뉦Iu^}MIiJ}4brq^^tv!~iܾ+kO@E f)b(gxH"^e2ytӭ/F .^0!jHD{&EQ3Gozs= J6)"|onL媈Ϟ\.읏K|B6ʝ?2iEt^7}?4F8rIDATщEF~E E ˒̥(@#hŗ/6dҐ y]\Gg] E}$`I] eѥb~We]L]/$7{trKv+~j,]tR:Z%YA+cW~%51]#Q!x`oF+NNMiAdN'n"Չ9 E}O'Ӄ$+Z.N'坖ESigQH}siqܩ_Osۚ%6yKU[箹ٯr. h]K~ohL~%N*^Et[^ٿ`JE\Lڣ}&Ͼ]1ܯyM "P"iwC`Ph.\9},d3Bz]pP"Ɋ|t <4f ߾޺:@nBMDZmڻ:#"Pit{-Wz9ێ]{vⅽUoWNlUTrܔVb#PNyg kZ citZ-=^cmY{ڵoٲ j/X>۶[Ę>!|e^M ꈊ ]$4YFN y.> | ;e_;DBIׄ/~̀B1T(^n!^ckϫ.> 1nDx[QCZNQ];E M 'ZX4.L["ư-R{.>'E w-u#q7~%~'џ3os?9p?-&AE{5N5k>|g?2XH|{ >&1r3.U{uٷsH'6-ѹ@8 QAvi(Bce)4ݹGg'qݣ:mWgTC:C4RuCb_WU횘ӑ .QV.=8=5Hf1[.yڢ"wq ^C_.N&ձ&*w &}NQ&*bMҩ†3y;8#N=<]6bcVǚ";wr~Z;wڶLYH7iŁ9= ]8Ӥ`TN7Ѧ d}?^W.bӊ|)tNb4}-"֯o'4y4tZ%d7~[u_|5E wJ]oBz}6א}l~ m K8wjY?~筭"UQ]z3[MGwEH -#2 ՞Y緯Wb#t Cև;7΋ GgZϸT>ϊ|wDSE )@P6<Q[F56ȳo+-@۱fSP3ftj {^-`477Y&!$y-b$+D&}PG2NSʟa2>/N2Uw} ]3'eTV/|EyQ3F:?B%׷>ɢ pOձKǘFX-bӧO=k;wTD>y7~ ܯSH}*-!WռAH74>tፑi:ŮRnE7Щ6IR| a?_4~z]ubˋSE ]'Yt9Q6EBY;XrQpXПBȿ{"QsV{W sDi|8D!"(bSĨwMKQ4 1%(bTUWIƾ2oOZt"絮x 9YuӪsOw2<{:31T@lv $f[n?QFL᜛.ȞK߸4x=oFBO:ϛeܱNqGc܂qhdEs<7s)vw;ZF[2󔙲kf1v JCE @NDդ"ɫWJ|Jm&I r E'%SIi?u 3J#-2y*_8c㬃y wh^mfx1.NH0VڄE5I >yMtAȴzR!tT7pvH+z'Es :BGTGPyO4 ܵ"t 7Q1Έ=M6nפ}:IwMƒ+Dׄqu!U~|8tO;1\lݝQGLNzǕs[~󮋑ndSp[V޸2شǟԹΈw{Ǯ~:~1s{׭} kP~x?:z⢗/rFYԖ,Zt~^"#]ѣSHrvH+z7D+ߵSOǔ(t%:(Qޯ{ʯ }\$544o$I;[9>#^N_kNdMbk]^/^h57koxG 6g7>;>N?ɦSa`MϹ{]s⚏q_s}^]H&NAJӄ&Q/w wf&ma'KP6lscݺH8lDzGWk#Unį 'Q>vؘ1kvu Z$p |'z^]ߑI~N-dwZ)#RH["v}=pM5Iw.in={ts7,z.dm.iO-y)EM9#}~_g2MdZL (+o FITRj˜|>Z%M\ a;NEu+d(5sTst g{nI:EysxWhQ!φ7E/]t?t1tZS7L)%yr?y/bxM{Qĸ+bFh,jitJ,nׅݿL^3E GPl"F J?˒BBHp8}vgqj#B.>1- 2Yo\B{:)]}N㞍ItY³HzN7]۝FI_3c Q{L]ǤE ]O9t.SH~-e򚙴ykb(Ae:TkPt#t6[_ꌨ8肃ۦ&5 ]AL}/;dѥ$Bʵ $tMuZtjZw,v^IȀlM&u7ZqI|/nCeNygjC?7+::EGh.<-8fP>l"Fbrevrn3FId= Չ:O؆:M)xnx鳢Rvk >'[:h#-. V]yg/hCovqhjk?t:DӘu1_phkf&mNh7l"FbYriuuҳHd#$.>x-tI+I|4RuCb_W(?}]rH[_YaDwޟsMWWuj:8>L" p?/-W51zsg..o$(bzO.ks4 Gy3嗑}Lڜ"nEvIpy@6rq.o&19:N!q-ҽe\t}~McQ/ ->mk{IJ @% M%k;<#F,b\ƥҷMs*q. h!3F&-\ADAY,3-ПG l"F;˦?lsqKA E a1*N} h HKSAz76(bT]´P(sMKoj.F]h(OA#jZb MF] Pl"F+hJ(ޣ'D@*A#_;X\V_/i6 @;M(0(bE eӒ͠l5PM3E Pl"F54PVEwC(v @(@T&۠ee Z @(@T&۠W*ӔLKީGIZ(ޠvE"EY~Dɹ) @(@T&۠˒qBɻ9 @(@T&۠[ (KLK4eQn +4P( P6(bV_/͠\ٷ5J7C2T>1@e iihGK'ZhQ*mPh3’GCPsU"ePH3E Pl"FZ,K5M9(+ _iQ*mP@v(W2[p2d81@vl"FB%MSklZZlhUTc}kQ;:1+޼ 1bP6(b$d´˒Fٺ' @嫘"w.1l"FR%hP]cg߇ %Q~"( ۠ Zb8O @K;q^?yp4>GD:~=ϟ2JቍMK K_>WY~d}+gUЂis^O#QHlHo$c 6qò1tu_(ʗv|[n`gK=mUUΨsf=Nv9|(G pR׽U颴8ݭ.;y|7wt{:E^w7Vȴ{w]KC5vs%"#hwl}1~lZROWH4PNmұg];ʪ^$#Kp战Ǐ;wVsfxa寝/:׈QU]#"F|㒘};3|c~¦{@4߁*OBT>_GM(a{]E/rvt}vk:fQИR>>cqN#N0(b^{eɾ:@S.o(~M3WZoHtdF~e',0b滈h L$9Zĸaqs?VEtQ[t§O.yӔY%A9S.<GhNMF\`1Y֘z,}iLt3᳈h1Cwnq͎>kͻ#&I=;,]-cOL>6!c,*y`墆˩o~i7>qVE:o۳!'Ed֚Z0REԴ1ŎcY #1@Taäs]TK /hNRݩC4Bc҅ǜ,q%S\匌0XXΖΑCVӭԎ1+ks?蝂il y`s˒ґPr4<"=K(b TӔ,KTKg4P(.03F8w1@ ՜Z&?Gq (~C *(JEc%Ӓ-K9|(bdo!AeE T|}(L5-[0(sz۽CqT>127$? |}(ѣ!O4P( PȨߐ$"P=_U\b2Ӊ8 hQQ!IE {@_ NS(G3E O (b%/_ ʲd`0:p:PC3E O (b%/_n)k9}hsf*EAPJ^UMK>mh]8HN|(bdoH~A(yWuu3 6mx!(bdoH~A(ys4# >[iQQ!IE {@NdigKq+T>127$? |}9˒3)4P( PȨߐ$"P= _%j(Z Kh Qu¶tE=u̚5iӦ2z~C @K=,K~ҝӄ 47H8ꨣ7oG7oG;w?yOO<ėMVss[2 -GF ;K3P:ur{>$t|3>l\RJ{@refNn(F뿾lٲϟyIYhnn>3u+"xjwy]:U4.EGȨN~ vb0"PGj8%ȡCʛm%/<̳-[/=?3fxmʔ)K<[Ig}ޗg ["Wqyo'n?o@Y5'Eg|RJ{@ee_Aٝӂg(ن'yqgΟ?ӶF`{gf52ʾq!ϻ@F֏U'WX n&t}_y9V }"{@154X-bh1ӂw(v PlÓؼy[lL^1۶nZA]g"^iwt=yK)Fl(}{U;I]dNrʊ}.btnYho@mg{UgE-?`l}?'ݳ[ưrgt FUܝ}XK3|S&"BmT۳ZV! uK=݇un;lZ]+ctr^Go8A=6ԂEG#:K)1OٳW"cW]WB|}mL5u fSXeξݜҧGC{uc81BE8i(oI^[m];wliuJ[-S6i_ ~`>MDݞ(4MdfRN%I綎оiE$\MƬq;'j{[z{&Z0>{~{hr[8mh[Γh2yl nM'{ -F`[ 똡vnq]bIfM-Ғ凗bo8aIJ)?}-c1xWq>{o wޜ'F3@y Ob;1vfssM;A"dNtO\1 M6}eUĸ{K}Za辜S,pE}w"W/f}^m3PmٮW{99VF4$sa}g|`Ic}Mt&ӓ=ɬN{/^Ig5y&̵Eu΂O= _=ȞwKx̚񴺘hNs[A]K~}ֺsVd29F/oCCGhBm#,}۽_l|PWĨKhYcNP\D3@yk_ ʼn3f̐O?48rJmUv릝 )` OսܝIn;:EN4aTg'Ni2u*b|rLvcD^vvX@;qxd^En$jH镌+M=^-FhĽOD^~H}זzgMi<6+G5WhѿhC1rS'ݽ>esd]b 'Oq斑:W:>dײ9F?/oC*?Ҍg=?zC_1ld{>|1lчZPFl#1RȓHBS@y;$ϟ/Yx+ԹT0iDuIG!xڔh,}-E ;H|&澻Kc@v2q=orvk gs4K}:E׫+>6߿5?t%>][!ufOm< t&n_6qy-E ]ʹez~?_"s]KC>q߳>F6GOׂ2bj² PPbFbytM kbqA-|1xuGӲvĴ+b4JsMp&MAR}NalA|wꤡ}wm #25R+ҽ_VE U&u#]s;pN(b S1t1u]B;E OaAsV QkqK[wg29L>_ {۵`='qC泑H (?v!mm%7Dy9ҷ7PlÓزe˷BQc͚5gq$+ݫu}oV׭0M_,*#͚VǡItM)Iu;.|W':QMb!wt~_)-yl} `{C@ ~5rʊκ~7S\"er~?_,bcٞ@mC 3-ԲVi (o yqI'ݶlٲs}x9~DWNf¨I?iio8IL>ܹy"7Q=#[1 ]YKgLg )Яsek,q>:2;-06վjغpk)OސTKZ0N'W."er~?_,bcٞ@aiںAb(H>fIׯ0o޼/žw.пظqӧOܼ85X8bnR/XBDC׫hN1[`Y2EG47H5.,Yr!9s>[fϞ3~z*"y9WVR 2]\#^i&1 Ӓ;BJ i^@a A$/ܾJ"APJ^ì%^E u4PXr\AmA"E ߎ-hJH> =A "+Y`Pv7-[i4'JH^MSEAAȕeIiӦ)_9QBG*(,E @J5M32ҷ)"AOP JFֲ Ӕh>"}{)M z"AE W2RcY3ӔiBq =A "+ۺ:9ltP4PX$ A1\I64H_3(1edRoϥ)"AOP JZ I%̢P&4PX$ A1\IZKs t(,E @m uĤPE(,E @$2C˒D(S"}{(Irؑ!KzΑNvԲi>g=ShATP)OIm (-Ki"9MVE1/y^.5C 85NVw;E ?OQ!VANB{@a}cѳgD7{|~es͢y9E ?OQֲZӒe͂ (ҷ)*"ƨ>o}ks_ΛxkTT;w#E ?OQֲd\(.4 *HKSV[י|i]~ ɽG;?y3^:$kw) .Tu7u@TRkZMwrY8Ś6yεd=Q;`ҠsxNZd'۝Q8_ݱt3{I#eǧ~1deƜEEZۣh қgFZ {߼ߜǠZYg{m\Դװ:YY1SIi! ~CEMNޯ~ݭ;/L)em/b?1qu\PybY2_ߢߢBmy ,$vDp:=ϟ2 ≍MKK_>W9<:a.Ob~VEwr= "Fì]e=˄;1ZH.Fc 62Իssҕ (b@)Jcg鷨Pk"}{(MVIl]t:]/ٶˣ9UUWZzd'8zL;ե{i[;ɰ﵇+޼ ȍX=/޷ytʧ}r:Tp؇ۜO6]ˠa'y -P2F (؇{t9D׎ꟗ8ȗy ;Ym3Fd0G#BZ*bȆi!{}ʖ%+Jy :-浖 { (u)':-ځc#};HS+=}I/}c [w:Qt͆/rvtJdnt.|Mcڽ[F0:>v݈ /v8ӧ9)sȶ 9."#`*Ӓo,u}tߢm4P>$zeDGft]Veo]w}MZ"FsF51t-?I91+s4.'FU"F7 /p]S3!}޳,콡Zq_m]}|u22뾵2;˨Z {r6H+̠\yAE{qzo)+kC1t J&sQp{饲ه\1z -FA cE9ЩL^ύmE0ʄm F,YnZ"?oΜ]i |'}Bȗx?1BcNj8 [2U(#E%/#U"@Ӣݳڇ\1t(Qӭt)$e IH[w:K>]cNմplkb 6HYߢ:?ҷkh (tcSJ-}ܲ՟^.=:wnG-t.+Bʇm F ,/om 4PxQXfX,\=n̲[]p{E ( A2EVW'=MK1MPwߢ4 @UD1ld0Jee(܎P|D3G6HHd,P|@3G6HLS.4-ho;4Px$ Pld0 &˒Bݯ)#AOPd$Q }J/-7C* z"& 0MdZei[ ;B z"& hj.%e.HPP8A29(-߇h 6H# mY/ȉC} xhZR(u[ kϒq7`d4e/˒]@N;h 6HçHPB3aA2>,9Ŵ`Po @m  XrArfld0$}Bݤ~ ݇fld0R0@MK@}Ki 6H#%6r[ (o i 6H#!9үϥ)0 82-yAog@m FӔ %Oz[`"$ H`0L ʊ4f$˒y4{QKs(~}$MvQjMS0-y^Ƹ-K m([hFD* i7MK$7` E56ү ! tv!<p&SCE%+ү !"<^%*bh4%7H>&(b"|5a#%wAuhJ_O)@"$ 8L3-"U#ː2:,-*Hޟ&(bKwӒS/,WFE%:(ү' E T%_IRee|h[Tő~M!MP@0Mߴ,KWoQVF($B򛠈 ]MK/4eA"hM_)@"$ %i"S) +鷨DDuM!MP@٫o:mT (NmU&H)@"$ 9<DQ~ ә~ݓ&(bt҃B HHSDH~1ry")OP|$ qeK/K[[(b1 ob2 L![)bPh6(b1RpC(ޥ@2ZݻSĠъmP (bPN3d(bPĠIy"˝xfɤ ֩ OrG gCt1d`C*Ő9s Ð>} YҐދ}uZ^b1ei!cҭ!;R_oĉs!o~-O"2mZR[kȊdž\v!{mHϞtdȰa|!oY0dʔeȢEFs|2ؑ# אm);;lܹcZ^AP(b{xfdTЄ&駇5q15 }"ƨQBkhw[q㒿"iR]csmW |į(}AQ~ xfdTC.|w{~+=6v&.bpBEO>iI.Xh'0[ Yؐ? 首Ր_% fr=lhWjٮ#{,<`͚:OCcɒ1x!UU-z:"C :RCGL+y{l ﷿m^{žE "FA-PL4H&"N/;$6}_|C:P[E?oy]iѿx$mbGJ6-J?Z|"{ߋ/f:=T|~!&opCr}GsqG_{-< AQ~ ӣ9ɨ1sfv`x5t wfHTظ1֮m= Qt:r]8tk~BB %ԠБBGl PB&t." $WUT 6Uz \"%ٙٝdgv?>3gygvxo霊S|=N;>6Ǯs:ݱc}{tۋ/^_*MBOM PN {_'E=pG]~:5]  HZ/>K-U>wYŏ?vٶsK`r˺0yw^&&#.}a&ƙgXW~I¾[nYNDR&FMV~:8U{ǍMj^&F\<)+non餚+_9ezz]vwql>)>^IQ^41*ϧ0H#Yg%ᢋJ^HZ51ƌ.xl^J”) K \gƌ$t͈ﱈtw#f7Mj{֬2${nbL4߼Yfgވ&561uIhjJbe;ĦA۠SqzS' -<~IxĈ:tR .#F$[ΓĨ废}6{I61<o>;wnD߿Ǝѣ'=UR匛[uE±?6|ƗBү_~׼} 6|U'["7L5S9_90'g1Āq(bH=&|g c=>֞}^+Z#q|-TK{I״g<>xυ|ߨMn{6';D41K>S P&ywԩSC-;'&*-\oˑ-/l8_˄;-޾r0hj#ۺiwpQ-6s[}=-\fu"K,/4IaO 1fA{HF1=faK Kn^'o:?ն5,0p%[&t(G[|㨣z+.]{ɦ*-\ ~7PH\H_:fU7q51v:g].&1wVXcd/q/wîkssb1M (G[|cΙ3&ٳ_5jԫjCVKxx@~е7n1KJn4|ʧ?>`Тaŏ$?Tqc*7ǎ͗8}* hG횚~o,=/sspx'>|z;NlOV-]jqoMiyib@Ն>G omM r^nG5v'4':&q{yJ+TM:6.rl> /RX0L*àRI#fiv8[z՛ vBkOybS&C~c'&N?{w%w|M-9WJQD勹(G[z[ wbY_Я_M=ЌXxPH61>6^MS*jbly9.&WCZ;Nx߯bkǩibT&뢗Lc(r/މqun?kĉպ&}Zgc 7q|Xg^ا41zqt養ׅsnqT~aGnߣMA/U6qʭ:hb4U^K(G['&EN:4551&NMMMOw>Ƣon.{?Q1׮Hho;e5٫ȋqA~ '?9kl=ǚ:?{8TGYIA_}󿽡Q>8󗍋{Ϛ5ƟWGzSSS~? \l6S->dhĈdvg ?Ҽu~XO[V. Y&Fxc/ _|s~zlپܪ6Oqqϻ;ذuVY{Yoe(r71>ﳹsVHhvM O6MM>}hE>,lISu"&.6^Pwk޿ݵVXcmbsǜEK0Yaێ!%kb@}G o9餓nouvGs=;ƨQlTK{ŏ)|3Xx61N}~mÀ4 GrمMyRXba͋/ڈ˙QGGM{> yqXi׿SCŻ\V\s^'}?Tqٰ-fvnhb@}0@9ߢQdҤInN2%žgϞܸLSN91c5j>=RĖDkr9C-5.&Lp{̸qM;No;'IQ^41b>K oƿ'EyĀ/l(rEE*6=GHe&hb@ϻ<\41Dzui^1 PJC41ݑfaR=i~g&hb@{(Jib&_o41Dzޟe&hb@{) Jib&w\a&hb@8EJib&A9Jͷ&HfgdCWʬ>P@)M ~{C41z֖ Ycrã YsÛ ATBg}5=DDDQ(&@qF{xIC@#PM 474X;]@PM 4/hb?IaQ(&@42 4 X;JaQ(&@1+F+"}i1 4 X;>HsaQ(&@߰p;F0EҩS[ a};.SIMibs' &FWpYMst5s &F' ׳CCnY;xC@Pw}wpQX 묓eI€Y{pI3MK'aM0xpLI8$g sOm㫉@}od(h=Vᆎ ݱx71b7Р$̜Y# n߿__m1 ^_S;;\[G|y'kwذMj?.=?_y3Y8ʚՌ&5:"QX7I5d~^KSO%a o&/|* wߝc ۗ\2 oNޛm;B#d'L(lB#$6@\a<6:c3d};8:$EM£ᄄK{c;x" [&N,l=F%MNl,,nbpBeMjW^P(zyGow?-W(fA-M3) ǖ^Wg/3J n̶Z7mqJ-N_3 GݻD-^9tS?6Cb"=kbT(~V(u,nbĩ:kb2474+zsNS; jibljwvSS"s}~[of b;&/)Ǎk/֭O[ԕ&F-V.o(|W~6%6In#kR䏱3Y8Λ&5<;F#ϹsE\aK S ]xaa/&F,mTBcA˯w5eI5|.o\jή51jlt)tڦX3y姓s$41*_M jpsIwuΞ/.喕%tR^[%)w;NkTz(qms٦pgƲ˖N՝3,l?xwU245 4)xNi17 ٺuzC\9PIs#iu7ƒftPB~ujᇳE_z) S._gƌlAXʵj !>w5?m)?`=+llM'+^9/p^.nj>KxL~ۆEJ+.^Ĩe|51Ҭ4I=:'aòIlAט6-+BubSb啓p!Yâ*.>oUasϕO;sOO¸qYyWhx-%)rm]=?̦g9thx\㡇lt*ig2?ֶ&aE)/޵&Uz;FREQi$yi hb~O64X}b(h$ "mPH`EM?M 0.%C@#QjbTM  DVDv @74(NZj6٤p^{SK*_))4<Xpfhbtj=7˴iсD'\fahbt>d8/!ݗ< 51ٝilh4R3#@CPIw/'aȐ$,pVZ) ;G)fuI2$azI8$̙Sy<1&O.=k_z1~W0}z6, K,I8$V󟓰I<8 K.uSI~p@6%V~+ϭX}Ot^wl7wEs:[mO>i?{O[kk9=}sFz$}v_?;NWL[$cFl~yx~Ç'C쳝_^{%41zn h()\P:.Bv^5ߕ&F5_iw0rd K /(oRccuu㮺j~0滛8=2>{QؾrI$zk6ݴ\m#f%?#qwjqGa'[vac_zW|.]gdw2K3Ph**Po/\;N'Nk龜EWϊIQBmYh=RMjϿ51b##N,.+6>Iһn+d[%561b;N,6"_uWaN;ΈΦFvIN#b3"6A~{Vx~wTrnln>j{[3~>1*Ǯ@&-kbBCs߽ MExARTL~?眎ˉP[k71]ibT{51ΏW|~q-m/Xz8RRc#%RXP1yiV;^z)!.t^wsﻯʚ;]n>V|Vm ֏FO\"޵Wo{z͏ 7bJM ^hwニFMsKT8]Qw61>tRkKtĨmb,T)ꡉ3mZaye'euu?xm$75R][Q|GMW;ޡqeٺwB$R;xǮB>ݻ4%'?O\:)uyǏQkƜyfa߸pr~{\!I*obrqmhMΦU7tR+PtR1gރ8US\ ;{l +םϯ]k }IkU܄7;NuA+zhclemV2TP܇ )<}n`(h4)I:{~?iRaw)m_ǻ(/Qkb~R|85[Yk*?gƌ$)0yr埛j?KT wwtmcd~]Q|m51j9NxG'N5eJ[3h{Rtʜ9$'{74q8gRfm)p xռf~{A_/]ɚ՞u7Yv㱘I+ ^֬הĈSQRwq~>T埛jypٶXϏE\ۿu-,ѵUĨ{Ѿ́j-wu)ϜY5kbz8MC@PkDluVTS ?xJN#p-ٝIMj^3?Ӷ&aEb,r {W{1<~1q8mV[e)%6j=T&aܸ1q=Sm#^/ N4% if\S 6ͽ*הhb,惡0@M mw*הhbo[kw1PD`~;8]}ԔJ4%4h(zTDܑ/wRhdϤiz'IM FP\h(z'IM FV{PC;)Hhb4g0&@:=gi2&@6ͫRhTN3{)Hhb4ҼbC{)Hhb4s߱ KARDDi(z/IM F45'i ޫ[ zlR ^3ujax[OPfy0nkWWpYM]yB|&Fc61ߑ&@y>͆wĨ䓅]|ٕ*wI8,7ިQoޟwPNY3 @黎Ѱy(hw5hbT7.}vױwp?'a]0xp\2 'o矟7p`O¡&g۾Ƈ&a$lYX"5pIx}? gOb%aᅓIb$sL^|0ztx-{필^뼉օ㴗U6;uI2$aPzI8$̙SY'_$|s͓Ϯ1!I8lڟt/g1bDޏƮKjyOZ~c9 ?LaV7.?ռ9K'ٝ ޭ_KV46"h,"kR8hPf,;YQub4,$'3㯵+RoQm(\\^MnxX}ݎ ?㶅*}_JCfNJE.<+oibTy5ڻZޓjDZr/׿3f~xuҕ&Fw}51ijzv{Q(-\.< ޚM7-- A߿}pUI$[/r|pa{Od8=>_Had}~^4f„l}w<O?k)IxB:_"kSO%a EXf+-ǜqFv'a O8\[*d;L¥vѕK%P{R8Qvک{ͶqDy$.曫ޟJkbԥ`cMe*).ǖ^Wg8K~XX?'a޼TIq>꼐*qcnc>׵qj8'aY9)*wmZ9Ivzjƹ|,@YذJdk/~T祳k=tk$~zg]Tڼ9pf=ޥ7{o˽?9TANH41rI6ԩ`s~T0I"Pk#45ӷ޿ G8uR? =6:+_6ʞobsNcf)|[q8n_+_kSw41yjyOZ~cӦc{nx#-Nٿ~;&/)ՍKW9QM&RKOn&.&(# .+z1wn/:q.}ۿ{f륗:.V2Εwء&GҪk+CcA61y}OĈ ¸x{Yw=ƥ;]wN|nsNO,a(z6T;/ĩZO*p=; _\8Ɩ[Οt\ )ggύ'E IƏ;efNEivj^7."&N%N}cA71ڻjߓjDZjhFXl%/uWu>9t0 v߽qJV-qha9s<=ΫY5.?جX?1/>)SJE8w5Ϙ-bPsyxgk/(Q??I:oUؿqt~qGvk|][wĨ=ek,uVwj<`An?9+Ӽf*.͚MK"h3bu9lf. .,J/b\x>Oq6޸B,ȑ"r\Iq>>zTum~KqﷳueD;C7TU{$s-.)i]MˊqX\y$rHVmq.8%V[e^#C}O'"ĩuc6ʼbh-Mx΃IMΞ줓0lX1" ܒ-VTQHZZqjǹ|\9W|1c=;ޏzibTT;vxU˭siĨ=Ϲ&@]96{$ED41Ӽf{$ED41U4PM ""jwoRWq1︨҆o ""RSOsaI~* GM%E4/i(z2*ɦP4s,j(zD\a(zrxM%ci1 @=08PifiCԋ|fɿ, P/y%}'G&قc P/{_ dwal(zi~m(zrT݅iL?'秙fcCԋ|懆'3Ӽ {'b' ^,4OYpi>IE~M4 Pb$kbb8zqYM#5P$k`|Pb4$[4KZ ",Hy+|ޟ! 3)Ilc4y9*(G_dc4y#Z",&4&Rk3 WߧJ(lb HsiYʟRė\G|fZ"̏&F>4f?3j/X!͏syP+E|&iNJ37{i& Pė61ƥy.w[w_M&Ʈi7ߥ֟I_ib L_tSGP^zO4齽{^SSSs=?Wĉ=S`V_k| fZ7r>fDrBnk”)S?fϞƍ;_{}3pŠ+/>|+\PG99}XMkb,44.ͦPa3fìYBG{cƌztQ~;./-Ȣ ZiHQn#iN:*rM7460Ν*}oSs. ~pi!,۲}/'h]41*92MiӜf4Oovt̘1Ȩqlyl&l_vZ-wO6|ׅu߹?&.X0`5 ?yUE~mRsaE鱶in/Ǵ6wӾsUXwÑaРEªղߌ(l՗b-<:|61Jϣ&f0jve=8uԲ͊:c'OsSSܡĸ۰e&[l]Ro_j!?{ -}a*mbt/jklIDAT]vVX1\}=^Sl.wΚvI̓`Xh|烈K,XKYv^O% {@e&Oϸhw9MMMe{_HYPݺ& WxOƷZeZr.a]X4|Рpe״$?UƕL_w:+-7i״;4Y&rV4YmEx'El2nĬ0sgL(qܩlMSK.tq5^MjG瞟͙3&[oQF;T61V_kZ?̩ƛe+ mֿpݏxMȈS-<􈍏cW|wi};DcOzAN7|/q'F޷X]|}Zsڱ-pQa_mwZ->lJ󫉱MĨz:;M zaRkbG?;訉q<~ּHv[aŕ>rƝ?Uɾ\ucɚM'o|d:ν5keiޡ1c:JyL:rSH7~{&Us#.}.k^Kb[<6+Xq`G];z41[MMMwug͚O(}kCuIn6ϿhƵm{7Z6;K,T㱩 +5;D0|ղLjw5풪WEB,\MjG뮻{8w܊geSSG=0]nbźc>.0}q8N󆯶FÈM6,XM橛exImؼ^Di26F\bwtNq͎b8˯04lQo̓^w{tm?f-Լ^&uoرgΚ5k^yO<ĝ1jԨ3[jR\|ڢ6jԨ}ƌq~_2{OB|SHy|/n^D;.X/-=p`'?@k\4jԨ&{RW^f _=\@:q팸zך!E|.)&uI_41K@]RM "hbPE/%E|.)&uI_41KA$`"IENDB`java-binary-block-parser-2.0.6/docs/jmh_results.ods000066400000000000000000000714221445000440600223050ustar00rootroot00000000000000PK@QUl9..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPK@QU settings.xmlZr8}H:@BTBB6^d[&-ېIh;Cf,E2?>y#J oJ̯ 9^Qfȁ5:8P9JfH.QA^Nlή’%F骰¯a~ +(K㻛׉vY<(guv|bk+cֿ߸Ro1~%:EzoNÑT`Bںk#CA`R)V(ԕ 3b]^]TKyuWSp mY0LJEc޸[jr$ OiniRc(f8e}ʑ0NvyGzJ}Zszm/(h|Y> dڠBPSJ=S?@ۨ }}0ѫ{ ^38lJ1P,{q3k¥xmixYeɚ3 MY,k-#&l?Gt)C/ Qm2KVٞ7&t$!KSA]L! :PS^'kN.5<Ӱ +;\ĉDfoc +ϦpEHpA&Q̤Av | McHMIrIZۋ=6|j{{(.zFsaiw_vKM@s!krmd?|sp$)޲=Gn#;M,&e>> ;_GS77[rlQk}j(̩[ġ#8"K*wyShB-E6A/!H6'F=4Q=*Q?1@>CJIGXYLXWJ@8UA-XF5McWj]saLfVs^bM8gR>gcdwydwxGGFGMTIQXRMHWPJYXXEWiH]rU\eMbwZaiUgygVFc]Wq\Hc\aibZwbNzgUjjjfksir{vldrlpyqiwwvWl\rgxg|u{kv~kxvw|lrmXq\we{t}h{lvy|ƸԿťɧѳŽԺʷع­ƱȸļӾпº­ȶѿιҾ p IDATx \׽هy6Dil4 F +FrsEL`|Ҧ66A( 1>ҽ+es9 &'+}̙AC hB~&_ß~kTnrW7f{k<ξtZǥ&$hS߰>z?xtlSgH?ORg~xYs,^hv. t/HZ)KS^7Uq`bMlvޥs?ߵ/sCg/u8&x}LO͐f^+CFw%$wUޓZ*U/Y&(YlgO7Cj,'5q` 3eO\){gИ_=#NXˬMl'lv"3R4OZh 0:dLe} ;uT~8ho }v }a^:7܍$̖]|,mx?)KxvժS>te֡s^2WA7e>\AO5A,~ bB;3u*U'IȺMlJ8 f~|߾'͢xw^{2'cqOfM/w{)z]w/NƮ]zݬN/{{2٬K^ށo)VWUԊau-U~r;m>Yu k9ufee_9gY|%)K?|K>=|^[-]ypo~TKYJY|>/Tu_*h`ib.V0ԃ8A|&I  Eu=AEIGOjM 9f` rGbKt/It믎Ц>L&MlrXR:(!b_eF HS4 Zz<65iYRWFes :X[su2 c L:Aѣ+`Vw:{5lyX5l ۫{/HpsGc)'joc|B>6α6C |GZWn-R{t߅ ;snƇ&(ਊĺeׇU61O/Nbݓ1nIEn 1غ-lIE;Mm]|q1MqZ#XT%)\y&<4mDv->GMFISt{!SQ2` )U'RuNFAz> XKA$ l!묽B[Hr~<ٴ<-̫ z@7/V mR-.)+l-]IJm.YnWQ2HwbG8iWC@wKt<󅬣89Bb;;Ņ'YILW#v/ &z2oL4-D36߸{)2bs1Ă1,:I$A{l[6.(𱯜H4㲓dĉ ;&i-N6vc:=9D&ɆN"ʂYP 9}61$? jhƧ#ײ{-4Î Jp++K+4ObcAgQfT:?H g4 `Gĭ z.K:ZO;hv#B: ,c*aK2[zEAݒuR7DFm秚R~X3u;D 8:*ȯ0ُ6{ԕț86& ["A7ĀZMds^)+.Ԛo&(u`9H_VH(h'`u/+()Mh guh #}TQI; >+sQ+ٳrTRXB{{_]BC)U&N'K*,)YI=lIJo"Э7Зs80 :7o [']{ZE8Iznt@ DU3B#^D/[io{$7tN2H$ Qˌ3rx#!4HB Ű5PO&X­I,1#@Y:0rA+/;,,wd-:Punu(PrtŅ.9wCg%n`*ltx--*4Z/-Hc֑@62kI(+Is׵OmUG6͖ol2AfI2xN&;ciǧ7z 1P|SN0O6V]aa 9*nHtI^%+Ԗ(?od˧Ųޒr>U./u82zGP0IhS6XK {sZi]\;;w'D\x{kDX6V焹r"#1vV9tr8ź3 U9)֝ʸPE̓evvIL2D:*Hܰ-Y:FYQ-lNtEzc 'ؾ'!U%=㨠.ztu/z |N MHgdC[Xu4?/Wb&z2 oFPY N&s%8#:"&SanRm֘ =W'ݕ_?ng^o*$]̪vTlg.' ԃoᶃY6IɨڣA+O˳Oh1/di &N'4FG5kJxc+z\5hʨ7L'_:2%쨊7n$ ACvc89KXDnjfg6FЉA{ ,?0 "sgq]])5&zC/=؞Y&H}.EII]=vboȶzQAyiEJUˠ%'$% +=Ic>n7::8擏mhGIJ:Wy̯HW^qX[1M *XV'e ϭ;JΩV^"bZ'| ō^' l|ĺ6ZUNtRAtF6-)>n6<M8ŭY-Mt|rBKT*ӯvҳ/YT9wH%/oXmqR*7J*ć1䆩[ٍkagv[U;O[1æOM@虛P[;7ʢGtQgwAš^ѳNBo/1x|CN?ٺ:v!ATt\Öy>g4>wOB(@S]q=Dϳ? BK\#*,JHWAW//aIguۊkH~ztv|FnL*?Enwc•"y]Xon%J >MgU1\)oo*"I" ]l ̫%N.JqwmV.p]=] yWcZ+~p:{j4ab y} !dXxTsoD}O.V#o+WI&(w6@,;z \#KUg'ﻥ54c  Ј4aWFb:GZӕů?5 nMkN(rqi6D[k'o_Y[^A+ӬtʜyN*#{1l:]NRc]*Oz)"o7Xu-;ITg0?|CÍ#̵N_LxGU~\ svfAX8Jƫȅ'AQ2|\! GUg8gOVr%>KeۤOCJ_IɜX,-Ō/}(Jo| 5qAt5{X:2\n"ۛt-zqi3{y//6Z?P06vNzEPU@5F"[u dOpˇgĎ j~z=+u2F hu_e !%hT{&oiܸtI@eL)3:++AL^'>C}D w 2R_W azm m낇 ebFG^N{ʛXuHw4|) Kּ4ag 1v{&d$VgZ&!OѢ taj?18MNKN"cWX>;BÍ C 6twxWPW.ᴥTPrkz I U-`gKvS@1Sm py$(e=JU_k@D-$D/h+u*7KEhl 79n[Xt :eBcN\RsRCIfy\Iu{OZlu=崍B\v{s"\(sşhK56Ж1=*1hzN9Ş6!inI9=5~|He+LO ?6Q">!1qlF鐌D I_pMX󅌭>Hb}} l!'5 k_;Ojx|$UrN*[b%f^7:lg4 ݊8  A @AA  A A @ A @AA  A A @ A @ AA   A @ A @ AA   A @ A @ AA   A @ A @ AA A  A @A @ AA A  A @A @ AA A  A @A @ AA A  A @A @  A A  A @A @  A A  A @A @  A A  A @AA  A A @ A @AA  A A @ A @AA  A A @ A @A4hPIENDB`PK@QUObject 1/meta.xmln0E D y캊M+u9@܂ 'EUw\Ͻ3.6`U(TM:C5U^:%15a1zLbƖ)D`[@K8`r%4[OY#HYo`sz% :Ycid(jUY %iuYwf&.!;#Z:^)Bt,4d׼1uq_5YeŮm^࿡Wyj7 :jVRhZ'Qpmv_-:m-IA>۞y!C&5VƃWvJJ`Q~̗^f'HU)*L ]CtPK(PK@QUObject 1/content.xmlm6=IdӭN{=_0]M&{lc ɐn[v# ~}X jrfJyFb+<<)Yg<ݗjܔW u%sqvMS}פ2$ɱRFM~8CQW0!Ʒ؅26"/]SRIjQs.ًjݫpIk"5@,w)/xZْ5tGJ Lp!*+!-ezDZs?,mNL6W!_:uǢSe)ܳct;B9» [?MYD􄉲lACj_n,:u +T>Eѵۜ+-z/)+o^kag J!p8 ?/oPՄɋr]S²ilߊgﮈqQ+?,2 aa[3k7X "nxpN{d s:3%8AҬ}*icQ]F ڀn??#< k.pڃԑ5,I!pk: %rX T"Z@=q}qi(T96ګGFB.1 5Άri 1STݝc ~MV7;ADSefrUqw4H妄1H3١m27%eݜ,GF q \[A]cH9 Q2%̵QYNT@rv݉t&?x8C-An2*kOFso0^`X]v%M/,D `AR0Cp|Mo >znNta܁*|/ҋ`#HÆV=﶑L.PՔ=;E".Ct ceSĝCPD kϛdIX= N9~x T?Eu}+WLɗDAUR݀wCz29! Pw>p#5@oj3ͩVL[N]zԑTUc)RYoÖXU(wT 'h1!Ӷ~0p 3r{"O~Z%j zT̼TWUWJ}.@qGHx}w~~6qwXپ`xw6f%!K<ԡn/8xͽe8W,@axU6g|̽2J*o^y`^Ar Cp1U.8NjREh,8v+ Iq3 b%J@r0j a-75ƙ{agyS\FB\ɰ%πKrKiR2sJLt%ƹAmhC/Byh 0+/Rzy}Y! TRκEĸ&aњ兙BVrw웴o_+ctmc7âp;,;8g&q(PF:)\Ezm oiܘӸ1b1]Kx5—1]/C7ޫ>-en+);![6)+R5U3[^PhKyq5 Yo 8HM8gXbhdaҫ氖y/ڿpLxKoP`}s?L7ίck­kT)Q +~n?PKğd8(PK@QU manifest.rdf͓n0\7.t'L}Nx)j!\15JnV޵Z:L#WףhQx:]ڍxbλH\oZ{ZSb#Ӄ*&4C OE(J%"cDrD螎B:Wnd%r~£il5_oUBR\%+bX ,q&qEG"5>ڑ%TiM+Ai9 GV ϣB1yV 1l#Zʿ~ "(VZF&Kz<3XM~Jzd(ܡ dޫ)jChtgd>C[pJ å&iBcŬbdQX6֬n]`Z{r6`h~1=VP._:h*>ضږSqLhWC9mU41kU2 5W:xr\궒[J_Wu`ڨ#r3=%[UB_hg|k5Nы=Ճenp4ɫT3*Ustٖ}#wg~DzE]jPohƑ4EJp >6{0D:~Y WAḼpǼT7.2Yq3#4ۂ6Ƀ}>A_}:Q{MA] wю WQv,X<%=`_! mNJpX6T'rlԸ ?'4/Lpn6 ii䀦\ӌT!s}^&7u>ӧp[!4 bPy]?ރfrF֒ gu$Fˎ&N؜=8:PKT!dWjqls|J*FѫW=)S=)lB@ PVgo٣18uc{m(Ow)|O_$IcPn1Y8wa*uZjvU |8л!ǪQ4.}^ aQU[Be?X{`-5T/:kE1fW/Bw3~=:m9mx{~ {_PK gr  ,PK@QU content.xmlYMs6Wp^: EQr$vOv4+Db%wA)*[i,.aPo9sWnM\1Oh]}|.yҘD  żPBF T8TFΉT6zlq<2TWZ2fmBcHv&ӞN& V=Pye-k˥_ZՔ| 450ImI`q')PhVۻxMr >EL(㰖s_ eU.+j$ #/hPc'Vf# K#]'^.k0\vm9MVV>9j#(ǪDᱱnc֣{~ |` !V~Ys=„x„I-x5~kuQ+"Fdžn.T`"kqʫ"1=H_fQãj_+uI`kWn7<l ZS’q\omɿvcX = oXvn,2v/1P6Ͱ4Bڞ W鷂(1A 4ˡ;Y#rogrpq`j9e+G\rsG\R뢌0N*TJb,hMk4w@s"qr2zcR> _Sܐ\ l(~+^yoAt :*hCkJqhh4Fv1ןb= ZvKD:)V{"um=6xu(<OL z!nH'kCr>x*)(zvs}OBѬ@WBSp$}CNd8\D;)e9-#fSK&em .}:g߃ZRzњ C!ރr$z0 ²$1|UFGi@u̡z:RFp iV I^E}0Ѫ? S;jddek\j,M=!![ k wPFoֳ|C̽d>]Z my2 tpZL ջ!3p,AXS$pi~Fiݴ Kh-V=>r+!I27܏+±ˋ[v&HwlB9] n^C=ПmCf2tcMsj} D_#!;MoYEPS|%`E}pd S~x$鳡7&Og'#yfic՟+褯g,#13Ͻp螫"@rdEjH?,a< 't$y`,g<G0qq'Q0wB;e Ky[d%H:J|77'+05Dj}te߻ا̯PK4+qPK@QUMETA-INF/manifest.xmlUn!+Vzsh(R#k*( uvU ޼ίV!DlCf Jj5 Wy/V*gڐ,w"ȭ!ry|>0mo{qqCG m7"Xy< ÝRZCgծ 9=wT2 auLV"7Zسm/F@q Mr $ށ7BBlfGYEƑkE( ^rX\l>N/|ys&0Mn<7fV'/Sd6xvHt/:`nW3V>ޣ-tԝ "L\1FC Y'Wv~sV.AWʄ` _]`2ב 4i*&niy_+PKEPK@QUObjectReplacements/Object 1 |ǟ !$@ppK (K"G@"D"jVEE#ڪ9$jOjbVm=іjVm=3dvwflޝwߗg}v3|2 f^0Gcdf$J]nÈi-=dzY%fXzh{@ުM3^ZbOӃw_eQ+=jh|Ot.eS!f45BL?t7Mw[0+}{3h[nO NiCo dƍQmۯ @ <K7l,(&ろKwVVƒ7*w)G;w>""QdwqQ107m "o7RN~~>3j՞?q}!DD,ݰ<;rQaTQcӥK=9SZu-Yص:~q[pΒ {v-(0.ڼ~庝ڝ+/Bq*r1j /[o;HNG>|.h7>Oz݄gqA%ծƲy+/ӚW%KMZ,-{(fFߥnʭ[Q*^KO=E}D|t@}Euo-'׾S8= ]dv^j{Y#F/ר7.{6K7|-_\9]g*}嬨ʉa+ũcuAqqpݹy>}1&_~IoMO>I,-_]%V۟.ێ)wi}Y#1.v3Xm+K+ߧ醿ҊFZt4ЂZFMإfZQũs)ۧ3j]JɌz.E^etX;\KMeCnfcũSեgK)NKF;hǂrQSեhdGcF:v\]!.ZRJm,z3VI`rj1|W3Vns}VǎvZ,[]ҁ@r n9rvt.-v]W7"\5|'n̨&ZҬ[Yt_=c\KuJVUzK9TfTsq .5\J55,zWUui^~c~n3'>G(xvY#5]͸4Ӛ̡]W-(Kt-U嬨b-r TF.mr)iu5UU5@ڿdGY#f8z#K[K8]%. .uѥAVӬ*'<,Ra ?piK \=|ʍm^]JqiphK304K]bI8W.Ny.Mh@)Ō jMܣ5Kpibj[z֥!n6qwh9/ a͇Kč]Kbٵ5ySvy҅¥DD.KORWXy稙{B;ɉI}tW\Fty׭χK-[6z/c]G;иtx.wUh.wxAĠ?p[&R+ʕn/>lE$8ƥiDpiKzi0v"&Ri]jyz-RKd Cp)\ p)\lҲ2d$y.KKRgHwEfpt']Tff\ &\. ޽u6\fR41v)H.M.pi"p).K.KA8p)\p)\ KDKR\ &:.:KAsDLp).ť>`mmmqqq.].KA$p,.eK1lT.?՝RJR)3*`T.ÍZS\\\Np)\ KvaT'¥p).M rH*Np).M%rrSp)\ KSϥf$RK9F_pq .KA8pijÌ*8¥ Tp (Np).UʥTʸ.BHK('RKR\K9F؊Sp)\ Kw)'T.pQo8¥ .0mq .KA8p)\jG\ pR46QÊSO{'ЧJ~HoA>J@p)\)S6:=rDX<rf?6.e:KR4~ S˖QU{/>ĸ>ޖ}P.-.K 8Ōz)`fs>iNRppiFՊSg 7hRbj,/NR7p)\Bũ[++5#1/1;p)hp)\*0rK11#tӸ]eF*6å.]\iB۾ls6]KwީIt KNR`\ :ϙ3;]D>>+ kcd0߼(RT 9p ]}f$\Pϙ.!RT ɌzY.\a.&RT ǖgN-Z{I0D .J"y2NFEZUEyyə@RT -sTm\ǂII`_g.K"K.K"K.K"K.K"K'#\j\ KRȒ'KRȒ'KeqiK]N,R,p,.-//׭c#VR,p,.2]b{FKrc6w1R,p.5We' \ .ڥ.K9],å@YҔq)Ǹ+FK)RaTOK)R(?߱R,pi Tp)P4]1r?\ .UĥfT T‹Sp)PT)$*( \K9 eKUv)Z^Kp)8%%FKpQ(å@YR4OKpF8eKFeK8*/ +p1aղ<p.MfT'¥ }7RF2ųyTRTߝ:@5Vm.K]"%ӄa'}~sFBrh"H$ CKgVs ̡ CR1A*4r Z')3jq4n%MݬyI` /ђr)FZ`ArOf@v)0*)8w)\5 ;1g B.J'@A HȒ'@ARh9p)Z\ -.@ˁKrRh9p)ZTmmm5A\ ,\PL_&p)/]/XnV-(è^aRgZQRg-(ul׋Sp)#(]4*\ , }.xcaTp)$&(nTS'Sp)PP*g <AFMjq .x2NIMQRg.(*)Y$d.x 8JHq .x%ʨp)8&()Rgq^P~/K-AF8<2NIF}(.x/8*vq %TQSI"'PE*xSPF~o mA1 Ϧ P/lԍ=5ؗ)MUN4?#:|ny?|>QV(==ZnMԦM̤,j۶-k׎ڷoOԡCر#uԉrrrs`vJݺuݻqG=z={R^wԧOͥ}R~4`ˣҠAh4dECaÆiĈ4rH:iԨQt 'ѣi̘14vX7n?&L@4qD4i҉'E't|t)ЩJwh4e***SҴih4c 9s&vitgAf͢ٳgӜ9s :3iܹ4o-XJJJh…h":묳ϦŋӒ%KsΡ~tR:siٲe|r:󨴴?|Zb\. 颋./VZEWK.֬YC^z)]֭[Geee~zhÆ tk?tWЕW^IW]u}{ߣO^{-?뮻zhӦMt7M7D7|3m޼n[n6ڲe UTTC;;3~яc뮻{*++nJ۶m۷[iǎsNڵk޽C{}8@CѽK~zbC=D?OGy~ӟ~3N/藿%WG}{1z'_OSO=Eo駟Fzgg#Gs=Go矧^x~ы/H饗^_~^y@W_^{^uz7O[ooM/׿=Jwߥ{O|>C裏7}'Ч~J/}g_|A_~%}W_??oo@ @_c d(-7@W1=@oUL`-V_OoVeGi:k|e6_c8Jo k 6)찶z[:ZKOvm]ا ۺ7eXk觷hʆ}Jhc߯([Mz[_s[cw9G]ց|:mgwggÂnhjc.Ko;^ˇOn JT/֦OmejSkaj9=/(^9W=UMa/)k`ߦ'-lB[Ό1_bz)NֿSO18fJ?ZVR觴_T^hNV}%Tvt'uc 4RleTHmׂ4> mZh)ZݺBKLeZefzl%zLE.\L]5W˵\slſ ˵}afL7@9fz6S ߅ea3`#t),4ZM1,@mxĢ_(̗{@Ze,zGŠ^ԊeDŽ:ߢw|0B?tpq:ͱO~fYFw6pMo;lM/jӫZ¬hկnUJ,DҶ ˴vltOhOhYzjE_foE_[:z[v06Т36,z;ߦEoWvMeN=,z{-zsX]-zFl; [A,zߩE`È,zFQYÍƢw$eYXo+^Pw ->ftsUW\r}Ze#q~a{8Zoj{9VhmlVhclV2FUC-z{+hEo1_Yv[1B-z{[fO^Fq}q/7|`ȥ/B60Mb,/rcBg|/T-4˳_vڦ |;_T"[>yY#ǔ3F;gP*2M~$衿F;o<Ҫ+ %'5w̰qT$VqZC4[#,b~'I>}T(B_ylVlNMcm%c*&6--|LDkFmZ|uL;kgN>m΂3'9)f6'tϭiTGuy+ʖ_vѥk_f=h(@eZ'mfOƛٍe^ؓڇW'PO+i -K"*~S؇d6{]a6EkطzHVN+.2wwJjmߕa5>3]}]?Sw/iحv}x5LUZ[Ȣ6'ˍ:4[Bkb3!'1f}f}2fn|O1'a֋?!}>{GYGwӴVc WGlj-3٥/2Np\Z:]:h˷t>0l_S_~0p,Mds~[cB=.b!u j1D&QX >DΌ6/ ؿQ4- dV#i+AF.~u#ÉXCؒ+Zچ~[yMe,j?'v{A6hY_?rysIsOwwjDDDDUm$"""* 7DDDdUnȪ0Ua!"""pCDDDVᆈ Y"""* 7DDDdUnȪ0Ua!"""pCDDDVᆈ Y"""* 7DDDdUnȪ0P7 C %lԫ]^jg`Ҭ84d&Zܡ5~0IQ{Az,)YYt!蟠F]%ډE+oar2N-ƒ Т%1z2+;T°)ҒPM=f##aXخ:>G&# ĬʯE-._Uctt\o_*Spx2\=OPqh.O_Ah.hoO~Cv;oXkCM2wsDLiވޡyMAP`B3X/RĻsO;5w(rݶ&33M|#^W@\6eO֬bN қ[{kri}?.9[p!7xmL{"lLY&vMiDJxؗm^j@ΐ =0\wi?Sqnh?%m}`{gf Dc/\:X59 ^}<`ޯȁqлFލ#PJD>X^~Y /LD@z1Fa˲M8>cgZluhEx'JkKkE0Mv Iqq>:#ɹaŬ/®3Q9q#!\[PHđm0ei Q{s6;l^fv]Q6zwZ#Q;¼]P隰muJ_ Ywu?kvU+?g'+No[zr{N@M]1"dX|9`6V\\0_oޏMY&vDN)u>>?>X~X3 Lzw8j=k5=>vV[(_nS==9<>>ޡ@O71p7);.=McЬ#V8WpZ끪2Q*0pGmШC46mK$\Y qx{"Eg!) Zc=$$>Ǒ%WwVܤZ-<7.քzзGaRţKyfV}4ƳvQ:$/VboVo6|V-%bxyOUNx^;zv^mՀO@s-pblPqLG~^<12w`)5F2z,8~ɨ&O ,cʶ5ȽzfvS4tx½_BL,esʭ۹6Zq6(=HFBDa 3MóiEx՟`_X6Na؆(͒yFz@GMq|֭Ôg2pv_E񉦷Q.v[UXunj׊eBlTU}xp# ~jm?=캅T%<š7=ȢvM}|T~6~]%OIвXLm4vW>w6F#VZ-p$Np\LڶTO,}vqC) }b/hLzN13vZg] .ci'760(\kB$]Usx. vnpQ$"*: 7k2Ѣ1ѱFhټ$ni!O֬}EbG5!FzLV)9ٞʽJ^kSkzI|?>ѣn9ᙏf㍵3}$|ݾ XN(ܼR.P>r!.ҷ.v!I'h P̣R\Z˥Ǻ>QwKeפ=R-~݅xRx6:uyT*V 5YLx-?xmsx 7c*4{JIͳG!`< v5}O4]ػuJ+(ČFOCow!iEkq X:J,>6mLh^'hkV>Ԟ܇1A Nd~L2aǦ<'smڔڄ 5n0PSǧcKݱ[ O9woaHӵ:\cx^ y5xx=6|l]뼂ެ+#7QNæ\|j퉲?-Ag2}M=ϼ6&1PZ|%x^.u/t3?j|%77V`\}hN:[6D'=t튑ĚG6ވ>3&~&, 3F 53FaXԭ2Ta3pd,_Q gn vR 45 &}R U)[0uOpp {mbFop„e9Mh狝rM9dDᆞ<lF O՝<>? @Q!HD1?tŻ/f|o*SŦ|^Wo?}b r_}~kEJYo9\kB`Sg<֬1b8W@>?⧯zx g7 xNAO^ɓg?0adMDZ&6}rtFw ijccVۣGu>2 _&vo[5Li3}wr]I8y_j0nُ!Y;em \_kS%zWi1-&ھ,PÇX.ݲ5.xG%qirpBՉcQX%r*vI)]d7>:5Q'ۚc>3f^h9z1~m9.Sr{N& :6>LB{Mᆈ Y"ؐj>!|a!"""pCDDDVᆈ Y"""* 7DDDdUnȪ0Ua!"""pCDDDVᆈ Y"""* 7DDDdUn(ORRRp)\r0 HLLN3wӈ@W_n$";v ׯ_ ^J7Qn/^1Pbcc}vw JRhj6m2wpC1AAA8yFh}30c+pCD̍7pQ8::=4ɨY&jԨaᦘ=24"؈"aC#ln"Q0S"Ԉ:={K43w󈈈ᦘ54 ضm8Nxb)D/E¢ Bz""6 7VLQ|i\v tqBgy>>>fn!QcBbIq&aq=qS/GDDTn/"Ԉ"4bz*U'""ᦈK?͛ܢF񯟟C ; 7E`z}jD=:O 4`= [ 7E(=5HXӈP~1"$%%4Ur艈( &‹5HxǎrE9DDD ~(9~8WWWƂ3s{L¢pS%^j1!QK臱GDѰveƈCb?l4OG'G(f||\TNpvIŢXp+ޅ7K]_UmY\Mmx\d\KUCÎIѱ|.O%> {N!\kU1eDyCO:  }ۘb 9-iͿVޘ6-a7cؾ,Vn^<7&CϏ!`jx lYx7OGq.wz>?ރWhNᆈh,{#_oz,zYfjj)H;borxfz|1~Ǘƣz įKD Οao{M3#j|:^_.#"|4K_z#d4tzaӸh|FGLoCỎᆈ# N,x.?mzIý0lNUõ7C%:+ݫjжD!P85 6&NFYɃ8hh'x4=e`E^yӚ4ĠᆈWeۈ]rXRY~ NOiﴣWmm2M#16^S:Yj9 i=B ՙ0306̆ᆈll^ &x_pC_5/To7oO RY4a_r"=tF.1816ֆCX¤}xp0ŋ?3|q]I\xR)߼3jqF ΞgJ3U++ A3MKN:щpum1%Zcᆈ,WH8o ;0u)8:qa{h ?WGm_'lU scy1t1R 5  R6:CRfpC&%+5DThv?c fx4pk9t㓟u]@|i3J(Y6nU~G迶~[#*+ {L㚾1(b 7gZQQQr J|۲eEDEAIw]0ٱNC әqK 6~{ؚMN5"dwjD9xZnP Swг)}]*c| C/-8@]{ 6@6(ylll}N^1ܐɒ[nի=RrFiﯔ>CT&NcO[p;s}~ )> =l;d;( DA!z]Spsj콟PJj! jQql[}Ǿjص`6aS.ᅡkBgxYIU6=7T(+>>pn*Y:u}->;M<%:2E:l;⻓x+Ԑ:?8~.~ydu6 >éC0}}z۝.ǬqZҳ-gBwe`Qgn%=dKVAv1bx/hD|]$=P~G 3t`cn!bl3=?~\9 BիW~~~n"U{0cMYHmS;`@rr2 ҿ)LFrXṬZ'-TOAKtKvۣbBBcj \ikk8*Ϝ"Bz~:]&qT^UV5w$Ll\, ©@ĥ$rL@Qg=Uzր-ꗫFM1'qHXӈ '''4k ^^^n"QӻKEZ1oq9!Sƨ`/l)9J,efM1+=Eݗh׮[GDDFEԕXkR!\hD)>@*=ԈM~3N8õA)],fe)F08?5gϞE\\jD=Mݺu9DDM|fՋwn#+!V5yRy E~QŃ炯A:);b {ĉ ԩPCDMH; kBb5Ȩ"jL~7fEX=Ɗ aQ(4Ddz\O'ⷐ}8 G]>utU,i '쵽'< bГ;ōᦈF =Z7nOFPCDJ GD#Jm:EY$92v;v/=1"B ݻrI5<YPsV^}tj=&,%d(9Ea)H 7N\R\|4g= Y4Q${w!?[iEB|FE2 6 %(>|+q!KQOSZ5"*=ف-%i|r-"<*En0>^cCƂӈ"3gEh( QQ#^ނu‘ب\Tn@$I&* 4O ÍFF ߼yS55>>>!"T9\DK( h $}hujay*nH޽{֭[4SƍuDDOH>PI7uq 7ު=5 7f-\rEWӈs׮][!""pc0hRx}~ۇpo>v.k> ?m/1Ga3j,k|Si^Dp``|+jz%Jܶ ""*Ƭ#Hfg1)%W#0,C7} Vll\f0 $} A@{|:^_Ċ`f4"ԈCġI uiT{cq 7P~A>~pC=нds%IeXk'fa,ycjD E=(=5,&""*\nNޠ(?6KY z{lGۖ^q 1/c+7N"#yEFlCШQ#)pc"ݝ s]Q5ZCSJ)`}B<ⳛd~*F# @"a""#€e:\S '8:9(Di ۴rr[L*(NJJB߾} Q֊A1"x~h.|+JWۜڈix*oCcgCDDD9pc܈ Xz Cg4鿼?TEҾfW }W OEEDDTdXG_>ebM`Х@ D6d߱E躔;&\ SNƈw:ܸK60Oė9#"""Ke,z%m6u׃C<"""Ln0Ua!"""pCDDDVᆈ Y"""* 7DDDdUn(ߒ .]d% (uY*1r l5\2|ݏDѢ+W@RAPȑ#nY c݊vb8<Eѽl ZmpC&&** AAAy&`#n:vvvn"Yd)py@{Y]M~pC9sݻ`=5ၐ9M%" r, p9U*;<{{{s7%/^1Pp]yUVE:uM6DDDc 47F%JCM H""\0ܐ^߿/;ݾ}[IzDFnfp0#D(AGTyn"Q1C&HXh 6i 7D"ԈNFp艈ÍK֭[]|y=aRGFQ$,M?RCÍ\O#=7"899Ɂ4DDdnDddfDQ*t///H4DDT\0aW&4BٲeѢE 3c)DL?鞸p%&""JpS$&&'K?5>>>z"""JpSzC#jj*E55P5k2epceҋiГ&""Íq<$n4gggԮ]4DDD`F MӈYOCDD 7f&iD~~4"ԈMDDw 7f"BCCP#*,"lgg bŊnQe= GaИ E9z9#Wc^ScfCOO'=7R4YLz -%"""ʑ@Ӽz:RIf]5j<e:Mr)3k.U?%J߷ԥ1(7.M0h" e>p FlM!,9sY&7DDDDinȪ0Ua!"""pCDDDVᆈ Y"""* 7/FQ깠qa3,ɕAHQ Gq7&Ož&?3&0  ŭ[wes7,-cnFWFQt/$1Z6c!Ps}"$$D~) h4$''M$" ̀c.J[AS{nutr6"ԈEO<==qT*tM%" r3w3Jѩ}ۛ)x|ݏ#Kll,n߾7o"<<鞨ąJ.z""*n Q;#$D/p%iR1!";wN6jXOCDD0" =ԤtOWipcD=8ppp0䐓^OSvmdžpciđOb)%%E&iʗ/z""\0XWg4FDLz8DDDd" pFF zrG>1 ÍF҈3 ^HX4DDDp:q½ FE4DDDnoBF   #bfbX(=j٧*lӼEgϞ/b)擫+֭ˡ'""dx1 \bmKWoލbr>L3InCР*1D=證{jD=3!""*Vn.Ⲳ4vB]]5Zc]`t˰ֶ/V4Ͼ0 SVfPۤ aDGG?zӰX}QG;X:XzM{ .!`el;@{d7=܈퉢ajđO!""z:> >ytys.ԀWaX w{fR)0b}B<ⳛd7,*3QSӲeK==eVng[߽އaJ\jq P99-yN&Cl7W- %8T /F߽+{B%MQny x 9gqQ 4nr-g7:4j"""ʿ 7,{#,3v7bзlV B7 x֨6!xƘذ^ ~sHU$훁ov@O޷_Td"""z"nR`[?6c`6[)[~Nb'`KSèCQ5.tFq1PlA a/-sGDDD*@u3_tBp =òTw~=8$󈈈2n4FZas=xM eaZ?>)G'""bpÍ^y;|A  ]yo? Be>:C?,P{W!)CQ7B7k0i8,qq!h& z'""pÍ:3ci5~DnўƑN MTsh<Wt`!""UFU% G'0M>~x Xu# PǪ~.nEleĵU^;vEDdID#Q#fJsy{we_'ǦmGpU 켪h^Ρp 58wh`0p]s7,H1ZGsx'n}(0 ZmW&q;&rϭo(ԇ%zgn޼)c# t5s Ȓ$F(> eJܹs'_+pc[ =gaٺbe8}E.~:bbbz*T*Znm%ќ8nFgo/ 7qU*j|c+ gRɓrjNHHBCJ. DDDᆈȒniw.2+i )D,b)44IIItZ oooԬYSȒPoNvePYPsYH8˗Grj(NMƊsثKkw_cR$B(>s 2BAjh)p~ݷ.m0&G|{.NtE\vM>I܈'Q ,zjD= QQUq6;GRؗB7GsqF TbS#|:~8nݺQO#!PD (p$iѤAGqkSDLdd\O#iTRR C YB>Z1vMkbyFAJew فEBE(>}|`QO#vvvOWjUU*b*Z-vaöMNxnh]ٙW)W&(((HX.n4zn"Q*KWb0l4:5x+|FS?z!zf.\ T/=Ԥӈ'""*. 7C⭮8:I o#%}DđOǎ(CO,&"pM!?ߜ&iPx6߾Z+q0'R"E=MXXXFX"iX+䣥iwA!zK']u[Oh>^G.7"""ʻBRC-B<tFE-(>w\Fqpp@ɒ%Qre= 76аH,Zz -EQ7(-u-)16]C9 ?Ej1Ga3j,k|Si^>|X.NIICE¬!""*8/0ˡܫ<0\XfEx8fHx}>Ɛ1 OcP2a^L3InCР*1٢q HXOF\[0B;ql]{Iɀ:cʩF_|TVc:s;j .Z۾XG`dY!QK@}Gҥj"7 [;عwׂ(#{~[ö#RXI& 3ܜdRASNa=-"""ʆՇ_ TI>MD#h9gqQ 4nr-f;zjX}QUW|nų_v[|*mg5cs;D?Lu9*7j`/yDDDÍn0q푨}: h;b %.tFq1PlA a/9#"""KeF._lnYs;rGDDDX"""*>nȪ0Ua!"""pCDDDVᆈ Y"""* 7/QQQz֭3oȢHEQIa[W`-Q3+ BypCy"̵kpDDD:111nYXc"ØURE 7666[L"4gΜAJJ-))IFn"YK ܘ+C# (99ݻw^AѠL2prrÎRDzR"$IA4w35ogԮU nJ]pRT0)$$'N@ddd([,|||a)n, exFCNX"J, WWW3(g 7$ӜHwZ*UC5z"""2 Íz3gd8;;ˇq5敌Ŕ&Zwy c'E 5m.5vC#_ӈsK0u,,{jxTl.o|i#ۡM^[/O!(=Nq{`x)L{Ke0F__ytµv(Q>SFA<̅Lvڅˇu'q91TB z2_k+qGoLDZq lzo"_k]cK!c8tk=nVIS7`|tPOnNC\ƺ|v;~/vpc؈! _ĆNqcn{h`D1|Ll G-:3Tm'<<\>Ν;rmv\\ScQCOIդR($_58lb rcǍ}$`7NbM#ti O9`gSM 6JvbR Hpq$ rocn|4d6ΩQN_~Ȅ~%"|4lp7KVLC'X?;+ot&DDTXO1c㸱ة2}ZL3InCР*1mPsСz{{{Pnq' 5i X=:UWiX[nGo7݂&Rç#Ch033o}~h7[~)0 FbO p xcƿJԝ^/9\|:TAKҙWvtF/&a>X}WǴ&M)""VLabJIo~~?#;”1Y6iÉzq8zpvJd Æ~MO8WKŽ8- MMvjuxoH1A2z%kS=wux)mޙ{z`|=S[~рA2Ll OSgalV7DDDXc:VA+Ӧx 1/c+7N"#yoFƈޚCEq8?ej7'IYM{T݅)z :d2G"pswEFNPM H&}D-M0o¸wM|`ڇ`>ÛjFQji¾D;{2\bpbl 1=aҏ>&X1b XPC˫3f >> {g&'#*y H~Gĉ.Kzʋs?aٕ]㋗_*(£KpqXpE8#sHII¡̓ph{IH2ʋ]C G CE/c®~Npųp+ޅ7K]_UmY\vM^x\yq 9(Qabl~7p jTL+}DDE eɡH2شiSpW?ϤUz/n#߻÷"99oMX7u~ܪ)XJ3?¶SҦ/k-N.ڸߘ7ЩbaMv\J \\|Y(WJ>/0ܐ٥̙#8ygG:(n39cF~A :P;x yDDV&..{ݻ|*UllCrĿC0nᆞ*1|Ooǝ;tgTֶx́FT")fw&",1S!B͝;7qRDFvPI;s7.*~'ԚuDDE *QOs =;R݁}s7,Dx9QA`BÇտa0B]K#0>  6G*H;)JJj2mW%(%׋#zkY_Te ypCypE\|YL=TG vʶWOS(shy4䷧F!999"2dzUFb ="nlllnPs9&Q*HL ).M6wS API.h޼L_C5s7X+XM4o>n(Gػw/2`6Rǥ@)͋'Mf +V,=E )F ʗ{؋Hy`eLj^{Z6qssFt)+po[t!"pCD ѣG1R)l# .=9:>Ptv,#<6"p :Zn='"a);wiTCQrexxx11Ѹ|GIpp0sD}ȧ84;5ec)&D9rHz|4N2ePE1 '=7Mzύ"XBʉz@\tI545jԐ (ADT40X)" Ϝ9qj1/=5b2HxǎIİ)Q\O#ziD""BpcD{.>xZ"TTI4E DDDypSq88Jnggˡ&z"""k=_$iΟ?/_!"bI ""E۷o8?Mz=MժUz"""0 F øO+԰ &}A$%%CON:puuC iǽCM.\O#t:*UJ>I =eBP~NB^^^r=WQnL[E‚8IpB jL߿ !!AE @#XOcyą44O{pN,Ѹۘ<K7}P wswn N{!8^ 1md;zpӈXzeʽ4HX܈cg|z8y}<}vX1:DGSh\n<=[ǖ]mm7EKDmſWpgDZ0">l&6_ңzM}9;jN8!_Ai8DC3cӥ"z7f._s"rp E͂19ض ^=>mRQ[bd@팗8'a;m0_%pcCvv*0crKl&!hPhO ]XqwLl[lɨ4 5d}tH6*4y%y>VW VqO +yY6+hDE^Tnp)$M!W%_#y_R FbO p@Ep?^G!kN?W"dX}i0eylspf毿 Eiܸ1'rbM pN ;gz0PW3/TY{X#hB`lש{%DPxT *#38)kV}UO%sB 7yX/Y&7ʒMл߃ `s?ڶBK<~[~q:ͻ$}nDqqqoDNt 7J'֜́=0wI'9l.xkʗmi'/fɐ:|$v93qfwxc{;׵!>BR3<ܔn߇4vyX/Y&7l#ejDк( 88C% Y- I&QPDŅ1>q>x &j12 W[N"b|1Ofɐ$ٶ{9Be-N^xǗSV`prۃ^hҎ2&'!ɘz"pcDY_kJG'V +@Z7q P99-yN&MTHA $Ux[6OG{cѩ8ts]-!˺eXv/⏐X~6؃Z Ǵvr1Bզ %/EJj"}< 'H }Siu9҇[SS6BZv^u(k>(!Fy j6~ p/ eJU+hB1.1>#=%1%}*}"!p,U*,"d'5'a/!UoU?{(k* PMa߇US^)HNNVVe|/LmCQk\jVKWSw(KAʹHB8H wΦ+lQA-9eDd>$;\]1Uw7¬Vt2F@] -X~#e(Kmj2k->v [9iPT樬㲈qcھ(:u68.(D/sG`1|_@C94Eb7f{1WsqHZT++O%pcs9Ϸ;<QV~u(iP~ƴN[˲:\8}p1gBгwڡ[6r8ڱո/ʚXzk&4ExxBy^"DVnRz nE)p9$6ѠR^epųwbo|j9V/ Lo&}ENV%6KC?+y\/Y"*pR1rt+e0Dt A@DnXeAWp £|ehQF-+gY6"xϝ?! &  00lR*Dm`T E߅Edqn"#)YAq6)IR1~g+?6ڴq 8WFQtO>R|?" V'@H6w3 ^X( 'xhtZ( /3PPiZ{Dk Í9%$Kd=R4ÍNoAn|_"3mg#&iLaj Pݹ%ƭ6w ϣ9:K>qwmWS'B.w3撘D=4E,`JyTDA9໎bT㒐Um\ M)O.GwŨ&1OGDیy펢e4>1vMנlZhޢ5`oou1P $ixs7(EbW.Cs,TQRI 5|[Uxh l肚MPV]9>:n(47ođ#G9M"Fͥ\$aCZ'!4DS۔DmPl9F.HpCP0`Ї#9)M+Ƥo.Fws7dʈp؞=͍P&Cgy(Ȥ#ݪmv*{\`ca:Ю}78::=5Je1c'O"00P~)`hqP([Q?ZnJδZ1<5I")C3y"6~uQқ٨FW]BNgytKcggWpS `޽wj줷~"#O R1%I@ H)cCm3Z=jjF:uakkW&ezRJJJ–-[7#P NɊq/jC&6a#/*>uZ;@D#p|anh*#}1/F'J5TY-+ԨrgrP[nСCriCKPc6F5i /N(*&Bzo)ROjS^Y z@J[95LYksųz"mߡ9Cw𑋄+T(F=)nTz9x\. VPcV* >HIE<5aeUF[/D7A0R k]Q_"qԺ}Q$^O#6WIpceD(x\OF >.J>zzڌP}U<8J|ԅ^ۢ uW"B-jn:uI^>`ȧ={ݻoXH4ffHA)\6RC @3UGj/ i(M-xтiJZ:IO 7EVŦMFC"CN{Pc>RA,BMz0zOOkB_p8D5ѹM;JR]O#PӼE{FҘ{)' 7E.w:$&FJ#OuDW6'3Г|KJ899FjUx2!CFc!^ 5MI`1QO)5H 5_Om:-WWj >g R\6NF ۢllVz(a)DLz@Lt|=^I%}FrR#5s"5d:3U^zJ#&y_[2׼jjjzgzS0X0jvލr?$^O!ȏvB !5X3=}zOHF[J)$}q38F-t| f=?MAa@p 6gV)4axF  !*bcfY$'kMĦϢHxj"ŠEP$5K1JFRK)COuv(dh-~.cqHΉ$9u+l3d 4 6C(. 4` f߾}EߓOco=*$[U&Qwh|ׁԦS {Uaj%.; #64&)_0]43 B+i$KGtpBd\E6qN4|u5iR/bٹˋpvv{i%ԤcFQDB=ҷv*n{DF6"Ӎk!z{q2o,۠d ǥmby̟f 6j=)nԩS8{,m|1ёRI6w2oԁrΜozc,jTQu֗&+ 7fk.VА{_=QOcg- WTȜ0dˈ?b|5JZ5u>f |ARR|ҽ hxK/e899&;;0 $} A@{|:^_7޽{H$&F@+VQ1$iʕv$\zip}˰ֶ/V}a<Mp˗/Z!(M;Q0 4*64`ɒ!Sy02}q%h{qqqAX=D%k( DDTlh4hּ-*WGqdɈ(^T 쳿k׮B""*K=,)dR I|M9gqQ 4/nrИyDDD-lhCO_^ǟC"i |Z {NG""&;-1udx3ʍd Zm,MDDD 7DDDdUnȪ0Ua!"""pCDDDVᆈ MqC+v`C v.FzEN(rؚ`[^4fzm`߿?}gg{iTm~mJimc)VpN p!|o?(WJwnlQܭ#VHnd ZO>x kv:>}aLo /m6 kbz vSc+ۊ"IOJ6ӏܥ]˷ėNmV}Шkۑ/|-23َ}Aѹ;MRߏyQƆwʧajEN|ܢp $Oⶢ"R¹bn? /*WWlqg`E(%ŕ_=~Ww.C#pX M~gB+a nA\KC:f|G@N O[ׄm,bկauʶ ~VPdls&yqZ~hcY`_ Ýmӯ2-f7mIuFTn9e0lO(r"~|J_|3u#۠D#7"®"Z>mK 㭧GXPhYF>WP j%|"i{GUj\W)g/Covq\m9S$n'λ4Ӧi5e2bN wq}~郷|VWbwBt{n[ 2xvpI)cb6Za1Z#lZJ-6] 0r ;waU]ƺ6y(U_Ʀ>6Bò%p=>< }gzuFO"ٽ6途`o[;EVS-lE #CaԨjc@jo{"F\ѩKh61)K1M)jX6 BmL.)Ѱhwo! ʴ~ =6A m7<*IC ńLIA@0ڥ%v'W)Qɘ%jgbŶ=35#?b@ٰu:p~S,@'~ɯ^26a2/zB|sۦ4 rWEB8njT*]iwVhТ(+o|?C G5HNt)qNҺv/Y^j)p :V6j:(p<. QǞ7Ba ZqK. Wk7{-Gb%4ߦh︻jjEH Z4Giaܸug#hx$ΥHJ];xn+gRlVZPpn:_ƶϖؽ0.1z^l” ,?&GȝLhUK ( k:AmsZw)ːY1{ɸ|m[ ٗ`U/ {ނަț.i_ hl5F1^#7b4fGl#]ҿN6n8# OxQ>` WKVz-.eQfM$N+aErn/?s݃v9:Qp0=*X;7iZ HIwt9c׌;٬ynMimkL]<|ϋ[Us2n8MpSo#aPؠvV(-KY^oۼkljz6z}U9> 1iS?;`4nnڟd1AL#!" 1)gk iaߪka&)/;<5[|Ĭgԯh\/MO pDr"2-$%%E#:I ŀd TCؾ%[CZS~v(ٰyFP-rz߮IydgZ{po?,?;ᗎbسq~Yy+>x-:{&-CpS%m篍š0%èת=KM˰ӹ١嫽P)OS=iPtBUe/%}Iش^ 4qb(~ ڀw^0e~kQ\)ؿz= RDݴ!뢶2qp:zYApW_.Pj%[Ĝġ Xsjhٔ::ɷ@tg6v}s/5jJ߲Fu`ע*lp-]U)P_>dy[.z`P)ط>kBujİTxltS;oMԈ}aTxh&w9D {SFnTBܯ;OjP;~}L{_|Z8}=cW:vDZ6Pu2/bEt:ޗ>6p(Q ڿaFWMA׶n{\: >yzWDﯱp4&qž郰:ⵟ}~;z~h=}+6L' 7bC_`ԷPǢpZdn<y}6h5g4:_;h7t2FUÝc.GJ_ in=s^jy0/Oڦ?Uʃ(nvxgs/ڡѭrs@}|MbǮ6NηcsΓ{fqv4$?&ibXQI,@vPGxtp=$eȬnM[xGx7ݵsk$ F46M%z |{4IY|S=O#2S=..HX1!6Hmm;w閇'4țI1jbpk96&k1wCЙ6#RJB;KB OrcW̗n/Po$|x )duxv^lm+2dN 7õ0j_8td'5µxȯ"ע0F`1 vU:jCs} ³  5QocL۷X+uY EBHkQ (bǘ~_\ʠF0bT 4.0.0 com.igormaznitsa jbbp-gradle-tests 0.0.0-SNAPSHOT jbbp-gradle5-plugin-test pom gradle5 maven-clean-plugin ${project.basedir}/gradle false ${project.basedir} gradlew gradlew.bat false ${project.basedir}/downloaded false ${project.basedir}/build false ${project.basedir}/out false org.codehaus.mojo exec-maven-plugin 3.1.0 gradle compile ${gradle.executable} clean test --info --scan --no-daemon -Pjbbp_plugin_version=${jbbp.test.version} exec settings.gradle000066400000000000000000000002201445000440600336500ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-testpluginManagement { repositories { gradlePluginPortal() mavenLocal() } } rootProject.name = 'jbbp-gradle5-plugin-test' java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/000077500000000000000000000000001445000440600315045ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/jbbp/000077500000000000000000000000001445000440600324215ustar00rootroot00000000000000com.igormaznitsa.mvn.test.jbbp.GenAnnotations.jbbp000066400000000000000000000001731445000440600441050ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/jbbpubyte len; uint uintField; uint [1] uintArr; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }com.igormaznitsa.mvn.test.jbbp.GenAnnotationsNonStatic.jbbp000066400000000000000000000001311445000440600457220ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/jbbpubyte len; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }com.igormaznitsa.mvn.test.jbbp.VarCustom.jbbp000066400000000000000000000003001445000440600430710ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/jbbp// custom and var fields byte bytea; some field1; readVarArray(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException { if (readWholeStream) { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(-1, byteOrder)); } else { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(arraySize, byteOrder)); } } @Override public void run() { } @Override public void writeVarField(Object sourceStruct, JBBPAbstractField value, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue) throws IOException { outStream.writeLong(((JBBPFieldLong) value).getAsLong(), byteOrder); } @Override public void writeVarArray(Object sourceStruct, JBBPAbstractArrayField array, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, int arraySizeToWrite) throws IOException { final JBBPFieldArrayLong a = (JBBPFieldArrayLong) array; if (arraySizeToWrite < 0) { for (final long l : a.getArray()) { outStream.writeLong(l, byteOrder); } } else { final long[] larr = a.getArray(); for (int i = 0; i < arraySizeToWrite; i++) { outStream.writeLong(larr[i], byteOrder); } } } } org/000077500000000000000000000000001445000440600340615ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/main/javaexample/000077500000000000000000000000001445000440600355145ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/main/java/orgMain.java000066400000000000000000000002121445000440600372360ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/main/java/org/examplepackage org.example; public class Main { public static void main(String[] args) { System.out.println("Hello world!"); } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/test/000077500000000000000000000000001445000440600324635ustar00rootroot00000000000000java/000077500000000000000000000000001445000440600333255ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/testorg/000077500000000000000000000000001445000440600341145ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/test/javaexample/000077500000000000000000000000001445000440600355475ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/test/java/orgMainTest.java000066400000000000000000000106241445000440600401410ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle5-plugin-test/src/test/java/org/examplepackage org.example; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.mvn.tst.VarCustomImpl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Random; import java.io.IOException; import org.junit.jupiter.api.Test; import com.igormaznitsa.mvn.test.jbbp.*; public class MainTest { @Test void testReadWrite_Annotations_Static() throws IOException { final byte[] testData = new byte[] { 4, (byte)0xFF, (byte)0x1A, (byte)0x1B, (byte)0x1C, (byte)0xFF, (byte)0x2A, (byte)0x2B, (byte)0x2C, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7 }; final GenAnnotations result = new GenAnnotations().read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + " uint uintField;" + " uint [1] uintArr;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotations instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotations()); assertEquals(result.getUINTFIELD(), instance.getUINTFIELD()); assertArrayEquals(result.getUINTARR(), instance.getUINTARR()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } @Test void testReadWrite_Annotations_NonStatic() throws IOException { final byte[] testData = new byte[] {4, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7}; final GenAnnotationsNonStatic result = new GenAnnotationsNonStatic() .read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotationsNonStatic instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotationsNonStatic()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } private static final Random RND = new Random(12345); @Test public void testReadWrite_VarCustom() throws Exception { final VarCustomImpl impl = new VarCustomImpl(); final byte[] etalonArray = new byte[319044]; RND.nextBytes(etalonArray); etalonArray[0] = 1; impl.read(new JBBPBitInputStream(new ByteArrayInputStream(etalonArray))); assertEquals(1, impl.getBYTEA()); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final JBBPBitOutputStream bios = new JBBPBitOutputStream(bos); impl.write(bios); bios.close(); assertArrayEquals(etalonArray, bos.toByteArray()); } @Test public void testRead_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray().read(new JBBPBitInputStream( new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, struct.getARRAY()); } @Test public void testWrite_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray(); struct.setARRAY(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); final ByteArrayOutputStream out = new ByteArrayOutputStream(); final JBBPBitOutputStream bitOut = new JBBPBitOutputStream(out); struct.write(bitOut); bitOut.close(); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, out.toByteArray()); } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/000077500000000000000000000000001445000440600307165ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/build.gradle000066400000000000000000000014771445000440600332060ustar00rootroot00000000000000plugins { id 'java' id 'com.igormaznitsa.gradle.jbbp' version "${jbbp_plugin_version}" } group = 'com.igormaznitsa.jbbp.gradle.test' version = '0.0.0-SNAPSHOT' repositories { mavenLocal() mavenCentral() } dependencies { implementation "com.igormaznitsa:jbbp:${jbbp_plugin_version}" testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' } test { useJUnitPlatform() } import com.igormaznitsa.jbbp.plugin.common.converters.ParserFlags jbbp { headComment = 'Free license' addBinAnnotations = true addGettersSetters = true customTypes = ['some'] interfaces = ['java.lang.Runnable'] customText = 'public void run () {}' parserFlags = [ParserFlags.SKIP_REMAINING_FIELDS_IF_EOF] } compileJava.dependsOn(jbbpGenerate)java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/pom.xml000066400000000000000000000064271445000440600322440ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-gradle-tests 0.0.0-SNAPSHOT jbbp-gradle6-plugin-test pom gradle6 maven-clean-plugin ${project.basedir}/gradle false ${project.basedir} gradlew gradlew.bat false ${project.basedir}/downloaded false ${project.basedir}/build false ${project.basedir}/out false org.codehaus.mojo exec-maven-plugin 3.1.0 gradle compile ${gradle.executable} clean test --info --scan --no-daemon -Pjbbp_plugin_version=${jbbp.test.version} exec settings.gradle000066400000000000000000000002201445000440600336510ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-testpluginManagement { repositories { gradlePluginPortal() mavenLocal() } } rootProject.name = 'jbbp-gradle6-plugin-test' java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/000077500000000000000000000000001445000440600315055ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/jbbp/000077500000000000000000000000001445000440600324225ustar00rootroot00000000000000com.igormaznitsa.mvn.test.jbbp.GenAnnotations.jbbp000066400000000000000000000001731445000440600441060ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/jbbpubyte len; uint uintField; uint [1] uintArr; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }com.igormaznitsa.mvn.test.jbbp.GenAnnotationsNonStatic.jbbp000066400000000000000000000001311445000440600457230ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/jbbpubyte len; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }com.igormaznitsa.mvn.test.jbbp.VarCustom.jbbp000066400000000000000000000003001445000440600430720ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/jbbp// custom and var fields byte bytea; some field1; readVarArray(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException { if (readWholeStream) { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(-1, byteOrder)); } else { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(arraySize, byteOrder)); } } @Override public void run() { } @Override public void writeVarField(Object sourceStruct, JBBPAbstractField value, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue) throws IOException { outStream.writeLong(((JBBPFieldLong) value).getAsLong(), byteOrder); } @Override public void writeVarArray(Object sourceStruct, JBBPAbstractArrayField array, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, int arraySizeToWrite) throws IOException { final JBBPFieldArrayLong a = (JBBPFieldArrayLong) array; if (arraySizeToWrite < 0) { for (final long l : a.getArray()) { outStream.writeLong(l, byteOrder); } } else { final long[] larr = a.getArray(); for (int i = 0; i < arraySizeToWrite; i++) { outStream.writeLong(larr[i], byteOrder); } } } } org/000077500000000000000000000000001445000440600340625ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/main/javaexample/000077500000000000000000000000001445000440600355155ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/main/java/orgMain.java000066400000000000000000000002121445000440600372370ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/main/java/org/examplepackage org.example; public class Main { public static void main(String[] args) { System.out.println("Hello world!"); } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/test/000077500000000000000000000000001445000440600324645ustar00rootroot00000000000000java/000077500000000000000000000000001445000440600333265ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/testorg/000077500000000000000000000000001445000440600341155ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/test/javaexample/000077500000000000000000000000001445000440600355505ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/test/java/orgMainTest.java000066400000000000000000000106241445000440600401420ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle6-plugin-test/src/test/java/org/examplepackage org.example; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.mvn.tst.VarCustomImpl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Random; import java.io.IOException; import org.junit.jupiter.api.Test; import com.igormaznitsa.mvn.test.jbbp.*; public class MainTest { @Test void testReadWrite_Annotations_Static() throws IOException { final byte[] testData = new byte[] { 4, (byte)0xFF, (byte)0x1A, (byte)0x1B, (byte)0x1C, (byte)0xFF, (byte)0x2A, (byte)0x2B, (byte)0x2C, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7 }; final GenAnnotations result = new GenAnnotations().read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + " uint uintField;" + " uint [1] uintArr;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotations instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotations()); assertEquals(result.getUINTFIELD(), instance.getUINTFIELD()); assertArrayEquals(result.getUINTARR(), instance.getUINTARR()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } @Test void testReadWrite_Annotations_NonStatic() throws IOException { final byte[] testData = new byte[] {4, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7}; final GenAnnotationsNonStatic result = new GenAnnotationsNonStatic() .read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotationsNonStatic instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotationsNonStatic()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } private static final Random RND = new Random(12345); @Test public void testReadWrite_VarCustom() throws Exception { final VarCustomImpl impl = new VarCustomImpl(); final byte[] etalonArray = new byte[319044]; RND.nextBytes(etalonArray); etalonArray[0] = 1; impl.read(new JBBPBitInputStream(new ByteArrayInputStream(etalonArray))); assertEquals(1, impl.getBYTEA()); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final JBBPBitOutputStream bios = new JBBPBitOutputStream(bos); impl.write(bios); bios.close(); assertArrayEquals(etalonArray, bos.toByteArray()); } @Test public void testRead_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray().read(new JBBPBitInputStream( new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, struct.getARRAY()); } @Test public void testWrite_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray(); struct.setARRAY(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); final ByteArrayOutputStream out = new ByteArrayOutputStream(); final JBBPBitOutputStream bitOut = new JBBPBitOutputStream(out); struct.write(bitOut); bitOut.close(); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, out.toByteArray()); } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/000077500000000000000000000000001445000440600307175ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/build.gradle000066400000000000000000000014771445000440600332070ustar00rootroot00000000000000plugins { id 'java' id 'com.igormaznitsa.gradle.jbbp' version "${jbbp_plugin_version}" } group = 'com.igormaznitsa.jbbp.gradle.test' version = '0.0.0-SNAPSHOT' repositories { mavenLocal() mavenCentral() } dependencies { implementation "com.igormaznitsa:jbbp:${jbbp_plugin_version}" testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' } test { useJUnitPlatform() } import com.igormaznitsa.jbbp.plugin.common.converters.ParserFlags jbbp { headComment = 'Free license' addBinAnnotations = true addGettersSetters = true customTypes = ['some'] interfaces = ['java.lang.Runnable'] customText = 'public void run () {}' parserFlags = [ParserFlags.SKIP_REMAINING_FIELDS_IF_EOF] } compileJava.dependsOn(jbbpGenerate)java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/pom.xml000066400000000000000000000064271445000440600322450ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-gradle-tests 0.0.0-SNAPSHOT jbbp-gradle7-plugin-test pom gradle7 maven-clean-plugin ${project.basedir}/gradle false ${project.basedir} gradlew gradlew.bat false ${project.basedir}/downloaded false ${project.basedir}/build false ${project.basedir}/out false org.codehaus.mojo exec-maven-plugin 3.1.0 gradle compile ${gradle.executable} clean test --info --scan --no-daemon -Pjbbp_plugin_version=${jbbp.test.version} exec settings.gradle000066400000000000000000000002201445000440600336520ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-testpluginManagement { repositories { gradlePluginPortal() mavenLocal() } } rootProject.name = 'jbbp-gradle7-plugin-test' java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/000077500000000000000000000000001445000440600315065ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/jbbp/000077500000000000000000000000001445000440600324235ustar00rootroot00000000000000com.igormaznitsa.mvn.test.jbbp.GenAnnotations.jbbp000066400000000000000000000001731445000440600441070ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/jbbpubyte len; uint uintField; uint [1] uintArr; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }com.igormaznitsa.mvn.test.jbbp.GenAnnotationsNonStatic.jbbp000066400000000000000000000001311445000440600457240ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/jbbpubyte len; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }com.igormaznitsa.mvn.test.jbbp.VarCustom.jbbp000066400000000000000000000003001445000440600430730ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/jbbp// custom and var fields byte bytea; some field1; readVarArray(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException { if (readWholeStream) { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(-1, byteOrder)); } else { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(arraySize, byteOrder)); } } @Override public void run() { } @Override public void writeVarField(Object sourceStruct, JBBPAbstractField value, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue) throws IOException { outStream.writeLong(((JBBPFieldLong) value).getAsLong(), byteOrder); } @Override public void writeVarArray(Object sourceStruct, JBBPAbstractArrayField array, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, int arraySizeToWrite) throws IOException { final JBBPFieldArrayLong a = (JBBPFieldArrayLong) array; if (arraySizeToWrite < 0) { for (final long l : a.getArray()) { outStream.writeLong(l, byteOrder); } } else { final long[] larr = a.getArray(); for (int i = 0; i < arraySizeToWrite; i++) { outStream.writeLong(larr[i], byteOrder); } } } } org/000077500000000000000000000000001445000440600340635ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/main/javaexample/000077500000000000000000000000001445000440600355165ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/main/java/orgMain.java000066400000000000000000000002121445000440600372400ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/main/java/org/examplepackage org.example; public class Main { public static void main(String[] args) { System.out.println("Hello world!"); } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/test/000077500000000000000000000000001445000440600324655ustar00rootroot00000000000000java/000077500000000000000000000000001445000440600333275ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/testorg/000077500000000000000000000000001445000440600341165ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/test/javaexample/000077500000000000000000000000001445000440600355515ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/test/java/orgMainTest.java000066400000000000000000000106241445000440600401430ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle7-plugin-test/src/test/java/org/examplepackage org.example; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.mvn.tst.VarCustomImpl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Random; import java.io.IOException; import org.junit.jupiter.api.Test; import com.igormaznitsa.mvn.test.jbbp.*; public class MainTest { @Test void testReadWrite_Annotations_Static() throws IOException { final byte[] testData = new byte[] { 4, (byte)0xFF, (byte)0x1A, (byte)0x1B, (byte)0x1C, (byte)0xFF, (byte)0x2A, (byte)0x2B, (byte)0x2C, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7 }; final GenAnnotations result = new GenAnnotations().read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + " uint uintField;" + " uint [1] uintArr;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotations instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotations()); assertEquals(result.getUINTFIELD(), instance.getUINTFIELD()); assertArrayEquals(result.getUINTARR(), instance.getUINTARR()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } @Test void testReadWrite_Annotations_NonStatic() throws IOException { final byte[] testData = new byte[] {4, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7}; final GenAnnotationsNonStatic result = new GenAnnotationsNonStatic() .read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotationsNonStatic instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotationsNonStatic()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } private static final Random RND = new Random(12345); @Test public void testReadWrite_VarCustom() throws Exception { final VarCustomImpl impl = new VarCustomImpl(); final byte[] etalonArray = new byte[319044]; RND.nextBytes(etalonArray); etalonArray[0] = 1; impl.read(new JBBPBitInputStream(new ByteArrayInputStream(etalonArray))); assertEquals(1, impl.getBYTEA()); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final JBBPBitOutputStream bios = new JBBPBitOutputStream(bos); impl.write(bios); bios.close(); assertArrayEquals(etalonArray, bos.toByteArray()); } @Test public void testRead_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray().read(new JBBPBitInputStream( new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, struct.getARRAY()); } @Test public void testWrite_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray(); struct.setARRAY(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); final ByteArrayOutputStream out = new ByteArrayOutputStream(); final JBBPBitOutputStream bitOut = new JBBPBitOutputStream(out); struct.write(bitOut); bitOut.close(); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, out.toByteArray()); } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/000077500000000000000000000000001445000440600307205ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/build.gradle000066400000000000000000000014771445000440600332100ustar00rootroot00000000000000plugins { id 'java' id 'com.igormaznitsa.gradle.jbbp' version "${jbbp_plugin_version}" } group = 'com.igormaznitsa.jbbp.gradle.test' version = '0.0.0-SNAPSHOT' repositories { mavenLocal() mavenCentral() } dependencies { implementation "com.igormaznitsa:jbbp:${jbbp_plugin_version}" testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' } test { useJUnitPlatform() } import com.igormaznitsa.jbbp.plugin.common.converters.ParserFlags jbbp { headComment = 'Free license' addBinAnnotations = true addGettersSetters = true customTypes = ['some'] interfaces = ['java.lang.Runnable'] customText = 'public void run () {}' parserFlags = [ParserFlags.SKIP_REMAINING_FIELDS_IF_EOF] } compileJava.dependsOn(jbbpGenerate)java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/pom.xml000066400000000000000000000064271445000440600322460ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-gradle-tests 0.0.0-SNAPSHOT jbbp-gradle8-plugin-test pom gradle8 maven-clean-plugin ${project.basedir}/gradle false ${project.basedir} gradlew gradlew.bat false ${project.basedir}/downloaded false ${project.basedir}/build false ${project.basedir}/out false org.codehaus.mojo exec-maven-plugin 3.1.0 gradle compile ${gradle.executable} clean test --info --scan --no-daemon -Pjbbp_plugin_version=${jbbp.test.version} exec settings.gradle000066400000000000000000000002201445000440600336530ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-testpluginManagement { repositories { gradlePluginPortal() mavenLocal() } } rootProject.name = 'jbbp-gradle8-plugin-test' java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/000077500000000000000000000000001445000440600315075ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/jbbp/000077500000000000000000000000001445000440600324245ustar00rootroot00000000000000com.igormaznitsa.mvn.test.jbbp.GenAnnotations.jbbp000066400000000000000000000001731445000440600441100ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/jbbpubyte len; uint uintField; uint [1] uintArr; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }com.igormaznitsa.mvn.test.jbbp.GenAnnotationsNonStatic.jbbp000066400000000000000000000001311445000440600457250ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/jbbpubyte len; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }com.igormaznitsa.mvn.test.jbbp.VarCustom.jbbp000066400000000000000000000003001445000440600430740ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/jbbp// custom and var fields byte bytea; some field1; readVarArray(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException { if (readWholeStream) { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(-1, byteOrder)); } else { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(arraySize, byteOrder)); } } @Override public void run() { } @Override public void writeVarField(Object sourceStruct, JBBPAbstractField value, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue) throws IOException { outStream.writeLong(((JBBPFieldLong) value).getAsLong(), byteOrder); } @Override public void writeVarArray(Object sourceStruct, JBBPAbstractArrayField array, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, int arraySizeToWrite) throws IOException { final JBBPFieldArrayLong a = (JBBPFieldArrayLong) array; if (arraySizeToWrite < 0) { for (final long l : a.getArray()) { outStream.writeLong(l, byteOrder); } } else { final long[] larr = a.getArray(); for (int i = 0; i < arraySizeToWrite; i++) { outStream.writeLong(larr[i], byteOrder); } } } } org/000077500000000000000000000000001445000440600340645ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/main/javaexample/000077500000000000000000000000001445000440600355175ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/main/java/orgMain.java000066400000000000000000000002121445000440600372410ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/main/java/org/examplepackage org.example; public class Main { public static void main(String[] args) { System.out.println("Hello world!"); } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/test/000077500000000000000000000000001445000440600324665ustar00rootroot00000000000000java/000077500000000000000000000000001445000440600333305ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/testorg/000077500000000000000000000000001445000440600341175ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/test/javaexample/000077500000000000000000000000001445000440600355525ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/test/java/orgMainTest.java000066400000000000000000000106241445000440600401440ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/jbbp-gradle8-plugin-test/src/test/java/org/examplepackage org.example; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.mvn.tst.VarCustomImpl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Random; import java.io.IOException; import org.junit.jupiter.api.Test; import com.igormaznitsa.mvn.test.jbbp.*; public class MainTest { @Test void testReadWrite_Annotations_Static() throws IOException { final byte[] testData = new byte[] { 4, (byte)0xFF, (byte)0x1A, (byte)0x1B, (byte)0x1C, (byte)0xFF, (byte)0x2A, (byte)0x2B, (byte)0x2C, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7 }; final GenAnnotations result = new GenAnnotations().read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + " uint uintField;" + " uint [1] uintArr;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotations instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotations()); assertEquals(result.getUINTFIELD(), instance.getUINTFIELD()); assertArrayEquals(result.getUINTARR(), instance.getUINTARR()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } @Test void testReadWrite_Annotations_NonStatic() throws IOException { final byte[] testData = new byte[] {4, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7}; final GenAnnotationsNonStatic result = new GenAnnotationsNonStatic() .read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotationsNonStatic instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotationsNonStatic()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } private static final Random RND = new Random(12345); @Test public void testReadWrite_VarCustom() throws Exception { final VarCustomImpl impl = new VarCustomImpl(); final byte[] etalonArray = new byte[319044]; RND.nextBytes(etalonArray); etalonArray[0] = 1; impl.read(new JBBPBitInputStream(new ByteArrayInputStream(etalonArray))); assertEquals(1, impl.getBYTEA()); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final JBBPBitOutputStream bios = new JBBPBitOutputStream(bos); impl.write(bios); bios.close(); assertArrayEquals(etalonArray, bos.toByteArray()); } @Test public void testRead_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray().read(new JBBPBitInputStream( new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, struct.getARRAY()); } @Test public void testWrite_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray(); struct.setARRAY(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); final ByteArrayOutputStream out = new ByteArrayOutputStream(); final JBBPBitOutputStream bitOut = new JBBPBitOutputStream(out); struct.write(bitOut); bitOut.close(); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, out.toByteArray()); } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle-tests/pom.xml000066400000000000000000000015461445000440600255310ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-plugin-pom 2.0.6 jbbp-gradle-tests 0.0.0-SNAPSHOT pom ${jbbp.version} jbbp-gradle5-plugin-test jbbp-gradle6-plugin-test jbbp-gradle7-plugin-test jbbp-gradle8-plugin-test java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/000077500000000000000000000000001445000440600230465ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/build.gradle000066400000000000000000000041641445000440600253320ustar00rootroot00000000000000def getProp(name) { String found = project.findProperty(name) if (found == null) { GradleException('Property not found: ' + name) } else { return project.getProperty(name) } } def jbbpVersion = getProp('jbbp_plugin_version') def metaLibVersion = getProp('meta_lib_version') group = 'com.igormaznitsa' version = jbbpVersion apply plugin: 'groovy' apply plugin: 'maven-publish' apply plugin: "com.gradle.plugin-publish" sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { implementation gradleApi() implementation localGroovy() implementation 'commons-io:commons-io:2.13.0' implementation "com.igormaznitsa:meta-annotations:" + metaLibVersion implementation "com.igormaznitsa:meta-utils:" + metaLibVersion testImplementation 'junit:junit:4.13.2' } repositories { mavenCentral() mavenLocal() } buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath "com.gradle.publish:plugin-publish-plugin:1.1.0" } } task sourcesJar(type: Jar) { duplicatesStrategy = 'include' classifier = 'sources' from sourceSets.main.allSource } sourceSets { main { java { srcDirs '../../jbbp/src/main/java' srcDirs 'src/main/java' srcDirs '../jbbp-plugin-common/src/main/java' } } } pluginBundle { website = 'https://github.com/raydac/java-binary-block-parser' vcsUrl = 'https://github.com/raydac/java-binary-block-parser' description = 'The plug-in processes JBBP scripts and generate Java classes from them' tags = ['jbbp', 'converter'] } gradlePlugin { plugins { jbbpPlugin { id = 'com.igormaznitsa.gradle.jbbp' displayName = 'JBBP Sources Generator plugin' implementationClass = 'com.igormaznitsa.jbbp.plugin.gradle.JBBPPlugin' description = 'The plug-in processes JBBP scripts and generate Java classes from them' } } } publishing { publications { maven(MavenPublication) { from components.java } } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/pom.xml000066400000000000000000000121211445000440600243600ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-plugin-pom 2.0.6 jbbp-gradle-plugin pom ${project.groupId} jbbp-plugin-common ${project.version} provided 1.8 1.8 gradle publishToMavenLocal publish-gradle publishPlugins maven-clean-plugin ${project.basedir}/gradle false ${project.basedir} gradlew gradlew.bat false ${project.basedir}/downloaded false ${project.basedir}/build false ${project.basedir}/out false org.codehaus.mojo properties-maven-plugin set-system-properties gradle.prop.jbbp_plugin_version ${jbbp.plugin.version} gradle.prop.meta_lib_version ${meta.version} org.codehaus.mojo exec-maven-plugin 3.1.0 gradle compile ${gradle.executable} clean build ${gradleGoal} --info --scan --no-daemon -Pjbbp_plugin_version=${project.version} -Pmeta_lib_version=${meta.version} exec java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/settings.gradle000066400000000000000000000000471445000440600260670ustar00rootroot00000000000000rootProject.name = 'jbbp-gradle-plugin'java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/000077500000000000000000000000001445000440600236355ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/000077500000000000000000000000001445000440600245615ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/000077500000000000000000000000001445000440600255025ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/000077500000000000000000000000001445000440600262605ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/000077500000000000000000000000001445000440600307675ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/000077500000000000000000000000001445000440600317045ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/plugin/000077500000000000000000000000001445000440600332025ustar00rootroot00000000000000gradle/000077500000000000000000000000001445000440600343615ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/pluginAbstractJBBPTask.java000066400000000000000000000037411445000440600402550ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/plugin/gradlepackage com.igormaznitsa.jbbp.plugin.gradle; import com.igormaznitsa.meta.common.utils.GetUtils; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.io.FileUtils; import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.file.FileVisitDetails; import org.gradle.api.file.FileVisitor; import org.gradle.api.tasks.TaskAction; public abstract class AbstractJBBPTask extends DefaultTask { public AbstractJBBPTask() { super(); } @Nullable public static String getTextOrFileContent(@Nonnull final JBBPExtension extension, @Nullable final String text, @Nullable final File file) { String result = null; if (text != null) { result = text; } else if (file != null) { try { result = FileUtils.readFileToString(file, GetUtils.ensureNonNull(extension.inEncoding, "UTF-8")); } catch (IOException ex) { throw new GradleException("Can't read file " + file, ex); } } return result; } @Nonnull protected static Set findScripts(@Nonnull final JBBPExtension ext) { final Set result = new HashSet(); ext.source.visit(new FileVisitor() { @Override public void visitDir(final FileVisitDetails fileVisitDetails) { } @Override public void visitFile(final FileVisitDetails fileVisitDetails) { result.add(fileVisitDetails.getFile()); } }); return result; } @TaskAction public final void doAction() { JBBPExtension ext = getProject().getExtensions().findByType(JBBPExtension.class); if (ext == null) { ext = new JBBPExtension(getProject()); } ext.prepare(getProject()); doTaskAction(ext); } protected abstract void doTaskAction(@Nonnull JBBPExtension extension); } JBBPCleanTask.java000066400000000000000000000023531445000440600375320ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/plugin/gradlepackage com.igormaznitsa.jbbp.plugin.gradle; import com.igormaznitsa.jbbp.plugin.common.converters.JBBPScriptTranslator; import java.io.File; import java.io.IOException; import javax.annotation.Nonnull; import org.gradle.api.GradleException; /** * Task allows to delete generated files. * * @since 1.3.0 */ public class JBBPCleanTask extends AbstractJBBPTask { @Override protected void doTaskAction(@Nonnull final JBBPExtension ext) { final JBBPScriptTranslator.Parameters parameters = new JBBPScriptTranslator.Parameters(); parameters .setPackageName(ext.packageName) .setOutputDir(ext.output); for (final File aScript : findScripts(ext)) { getLogger().debug("Script file : " + aScript); parameters.setScriptFile(aScript); try { for (final File f : ext.target.getTranslator().translate(parameters, true)) { if (f.isFile()) { if (f.delete()) { getLogger().info("File " + f + " has been deleted"); } else { getLogger().error("Can't delete file " + f); } } } } catch (IOException ex) { throw new GradleException("Error during processing JBBP script " + aScript, ex); } } } } JBBPExtension.java000066400000000000000000000071441445000440600376440ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/plugin/gradlepackage com.igormaznitsa.jbbp.plugin.gradle; import com.igormaznitsa.jbbp.plugin.common.converters.ParserFlags; import com.igormaznitsa.jbbp.plugin.common.converters.Target; import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileTree; /** * JBBP extension settings. * * @since 1.3.0 */ public class JBBPExtension { /** * The Registered name of the extension. */ public static final String EXT_NAME = "jbbp"; /** * Target of translation. */ public Target target = Target.JAVA; /** * Flag to generate getters and setters in result class, all fields will be * protected ones. */ public boolean addGettersSetters = false; /** * Make the result class as abstract one even if it doesn't have abstract * methods. */ public boolean doAbstract = false; /** * Interfaces to be implemented by the generated class. */ public Set interfaces = new HashSet(); /** * Set of parser flags. */ public Set parserFlags = new HashSet(); /** * Allowed custom types for JBBP scripts. */ public Set customTypes = new HashSet(); /** * Interface names mapped to generated sub-classes, mapped interface will be * used in getters instead of the sub-class type. */ public Map mapSubClassInterfaces = new HashMap(); /** * Superclasses to be mapped to generated sub-classes * * @since 1.4.0 */ public Map mapSubClassSuperclasses = new HashMap(); /** * Encoding for text input. */ public String inEncoding = "UTF-8"; /** * Encoding for text output. */ public String outEncoding = "UTF-8"; /** * Text to be placed in the head of generated class as comments. */ public String headComment = null; /** * Text file which content will be added into head of generated class as * comments. */ public File headCommentFile = null; /** * Text to be inserted into body of generated class. */ public String customText = null; /** * Text file which content will be injected into body of generated class. */ public File customTextFile = null; /** * Super-class for the generated class. */ public String superClass = null; /** * Package name to override the package name extracted from JBBP script name. */ public String packageName = null; /** * Disable generate fields for target class. * * @since 1.4.0 */ public boolean disableGenerateFields = false; /** * Make inner clases non-static ones * * @since 1.4.0 */ public boolean doInternalClassesNonStatic = false; /** * Output folder for result classes. */ public File output; /** * Source folder to find JBBP scripts. */ public ConfigurableFileTree source; /** * Generate methods newInstance in generated classes. */ public boolean addNewInstanceMethods; /** * Generate Bin annotations for fields */ public boolean addBinAnnotations; public JBBPExtension(@Nonnull final Project project) { this.source = project.fileTree("src/jbbp"); } public void prepare(@Nonnull final Project project) { if (this.source == null) { this.source = project.fileTree("src/jbbp"); } if (this.source.getIncludes().isEmpty()) { this.source.include("**/*.jbbp"); } if (this.output == null) { this.output = new File(project.getBuildDir(), "generated-jbbp-dir"); } } } JBBPGenerateTask.java000066400000000000000000000152071445000440600402440ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/plugin/gradlepackage com.igormaznitsa.jbbp.plugin.gradle; import static com.igormaznitsa.jbbp.utils.JBBPUtils.ARRAY_STRING_EMPTY; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.plugin.common.converters.JBBPScriptTranslator; import com.igormaznitsa.jbbp.plugin.common.converters.ParserFlags; import com.igormaznitsa.jbbp.plugin.common.converters.Target; import com.igormaznitsa.jbbp.plugin.common.utils.CommonUtils; import com.igormaznitsa.meta.annotation.MustNotContainNull; import com.igormaznitsa.meta.common.utils.Assertions; import com.igormaznitsa.meta.common.utils.GetUtils; import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Locale; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.gradle.api.GradleException; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.SourceSet; /** * Task to translate found JBBP scripts in source files. * * @since 1.3.0 */ public class JBBPGenerateTask extends AbstractJBBPTask { /** * Flag to register the output folder in Java source folders at the end of process. */ @Input protected boolean addSource = true; public boolean isAddSource() { return this.addSource; } @Override protected void doTaskAction(@Nonnull final JBBPExtension ext) { final Target target = GetUtils.ensureNonNull(ext.target, Target.JAVA); final Set normalizedCustomTypeNames = new HashSet(); if (ext.customTypes != null) { for (final String s : ext.customTypes) { final String trimmed = s.trim(); final String normalized = trimmed.toLowerCase(Locale.ENGLISH); if (!normalized.equals(trimmed)) { getLogger().warn(String .format("Custom type name '%s' in JBBP normal form is '%s' ", trimmed, normalized)); } normalizedCustomTypeNames.add(normalized); } getLogger().debug("Defined normalized custom types : " + normalizedCustomTypeNames); } final String[] customTypesArray = normalizedCustomTypeNames.toArray(ARRAY_STRING_EMPTY); final JBBPCustomFieldTypeProcessor customFieldProcessor = new JBBPCustomFieldTypeProcessor() { @Override @Nonnull @MustNotContainNull public String[] getCustomFieldTypes() { return customTypesArray; } @Override public boolean isAllowed(@Nonnull final JBBPFieldTypeParameterContainer fieldType, @Nullable final String fieldName, final int extraData, final boolean isArray) { final boolean result = normalizedCustomTypeNames.contains(fieldType.getTypeName()); if (!result) { getLogger().warn("Detected not allowed custom type name : " + fieldType.getTypeName()); } return result; } @Override @Nonnull public JBBPAbstractField readCustomFieldType(@Nonnull final JBBPBitInputStream in, @Nonnull final JBBPBitOrder bitOrder, final int parserFlags, @Nonnull final JBBPFieldTypeParameterContainer customTypeFieldInfo, @Nullable final JBBPNamedFieldInfo fieldName, final int extraData, final boolean readWholeStream, final int arrayLength) throws IOException { throw new Error("Must not be called"); } }; final JBBPScriptTranslator.Parameters parameters = new JBBPScriptTranslator.Parameters(); parameters .setParserFlags(ParserFlags.makeFromSet(ext.parserFlags)) .setPackageName(ext.packageName) .setOutputDir(ext.output) .setHeadComment(getTextOrFileContent(ext, ext.headComment, ext.headCommentFile)) .setCustomText(getTextOrFileContent(ext, ext.customText, ext.customTextFile)) .setEncodingIn(CommonUtils.ensureEncodingName(ext.inEncoding)) .setEncodingOut(CommonUtils.ensureEncodingName(ext.outEncoding)) .setCustomFieldTypeProcessor(customFieldProcessor) .setSuperClass(ext.superClass) .setAddNewInstanceMethods(ext.addNewInstanceMethods) .setAddBinAnnotations(ext.addBinAnnotations) .setClassImplements(ext.interfaces) .setSubClassInterfaces(ext.mapSubClassInterfaces) .setSubClassSuperclasses(ext.mapSubClassSuperclasses) .setDoInternalClassesNonStatic(ext.doInternalClassesNonStatic) .setAddGettersSetters(ext.addGettersSetters) .setDoAbstract(ext.doAbstract) .setDisableGenerateFields(ext.disableGenerateFields); for (final File aScript : findScripts(ext)) { parameters.setScriptFile(aScript).assertAllOk(); getLogger().info("Detected JBBP script file : " + aScript); try { final Set files = target.getTranslator().translate(parameters, false); getLogger().debug("Converted " + aScript + " into " + files); for (final File f : files) { getLogger().info(String .format("JBBP script '%s' has been converted into '%s'", aScript.getName(), f.getName())); } } catch (IOException ex) { throw new GradleException( "Error during JBBP script translation : " + aScript.getAbsolutePath(), ex); } } if (this.addSource) { getLogger().debug("Registering path to java sources : " + Assertions.assertNotNull("Output must not be null", ext.output)); if (getProject().getPlugins().hasPlugin(JavaPlugin.class)) { final JavaPluginConvention javaPluginConvention = getProject().getConvention().getPlugin(JavaPluginConvention.class); final SourceSet main = javaPluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME); main.getJava().srcDir(ext.output); getLogger().info("Source folder has been added into Java task : " + ext.output); } else { getLogger().info("Java plugin not found"); } } else { getLogger().info("Source folder registration has been disabled"); } } } JBBPPlugin.java000066400000000000000000000012611445000440600371200ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/plugin/gradlepackage com.igormaznitsa.jbbp.plugin.gradle; import javax.annotation.Nonnull; import org.gradle.api.Plugin; import org.gradle.api.Project; public class JBBPPlugin implements Plugin { @Override public void apply(@Nonnull final Project project) { JBBPExtension extension = project.getExtensions() .create(JBBPExtension.class, JBBPExtension.EXT_NAME, JBBPExtension.class, project); project.getTasks().register("jbbpGenerate", JBBPGenerateTask.class, task -> { task.setDescription("Generate JBBP stuff."); }); project.getTasks().register("jbbpClean", JBBPCleanTask.class, task -> { task.setDescription("Clean all JBBP stuff."); }); } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/test/000077500000000000000000000000001445000440600246145ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/test/groovy/000077500000000000000000000000001445000440600261415ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/test/groovy/com/000077500000000000000000000000001445000440600267175ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/test/groovy/com/igormaznitsa/000077500000000000000000000000001445000440600314265ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/test/groovy/com/igormaznitsa/jbbp/000077500000000000000000000000001445000440600323435ustar00rootroot00000000000000plugin/000077500000000000000000000000001445000440600335625ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/test/groovy/com/igormaznitsa/jbbpgradle/000077500000000000000000000000001445000440600350205ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/test/groovy/com/igormaznitsa/jbbp/pluginJBBPPluginTest.groovy000066400000000000000000000007751445000440600410340ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-gradle/src/test/groovy/com/igormaznitsa/jbbp/plugin/gradleimport org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.junit.Test import static org.junit.Assert.assertNotNull class JBBPPluginTest { @Test void demo_plugin_should_add_task_to_project() { Project project = ProjectBuilder.builder().build() project.tasks.create("jbbpGenerate") project.tasks.create("jbbpClean") assertNotNull(project.tasks.getByName("jbbpGenerate")) assertNotNull(project.tasks.getByName("jbbpClean")) } }java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/000077500000000000000000000000001445000440600227165ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/000077500000000000000000000000001445000440600273735ustar00rootroot00000000000000jbbp-mvn-test-customvars/000077500000000000000000000000001445000440600342105ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000024701445000440600355300ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars 4.0.0 com.igormaznitsa jbbp-maven-plugin-tests 0.0.0-SNAPSHOT jbbp-mvn-test-customvars jar ${project.groupId} jbbp-maven-plugin ${jbbp.plugin.version} generate Parser for custom and var fields some src/000077500000000000000000000000001445000440600347775ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvarsjbbp/000077500000000000000000000000001445000440600357145ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/srccom.igormaznitsa.mvn.test.jbbp.VarCustom.jbbp000066400000000000000000000002331445000440600464500ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/jbbp// custom and var fields some field1; readVarArray(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException { if (readWholeStream) { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(-1, byteOrder)); } else { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(arraySize, byteOrder)); } } @Override public void writeVarField(Object sourceStruct, JBBPAbstractField value, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue) throws IOException { outStream.writeLong(((JBBPFieldLong) value).getAsLong(), byteOrder); } @Override public void writeVarArray(Object sourceStruct, JBBPAbstractArrayField array, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, int arraySizeToWrite) throws IOException { final JBBPFieldArrayLong a = (JBBPFieldArrayLong) array; if (arraySizeToWrite < 0) { for (final long l : a.getArray()) { outStream.writeLong(l, byteOrder); } } else { final long[] larr = a.getArray(); for (int i = 0; i < arraySizeToWrite; i++) { outStream.writeLong(larr[i], byteOrder); } } } } test/000077500000000000000000000000001445000440600357565ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/srcjava/000077500000000000000000000000001445000440600366775ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/testcom/000077500000000000000000000000001445000440600374555ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/javaigormaznitsa/000077500000000000000000000000001445000440600421645ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/java/commvn/000077500000000000000000000000001445000440600427645ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/java/com/igormaznitsatest/000077500000000000000000000000001445000440600437435ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/java/com/igormaznitsa/mvnjbbp/000077500000000000000000000000001445000440600446605ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/java/com/igormaznitsa/mvn/testVarCustomTest.java000066400000000000000000000030651445000440600503120ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/java/com/igormaznitsa/mvn/test/jbbp/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.mvn.test.jbbp; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.mvn.tst.VarCustomImpl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Random; import org.junit.jupiter.api.Test; public class VarCustomTest { private static final Random RND = new Random(12345); @Test public void testReadWrite() throws Exception { final VarCustomImpl impl = new VarCustomImpl(); final byte[] etalonArray = new byte[319040]; RND.nextBytes(etalonArray); impl.read(new JBBPBitInputStream(new ByteArrayInputStream(etalonArray))); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final JBBPBitOutputStream bios = new JBBPBitOutputStream(bos); impl.write(bios); bios.close(); assertArrayEquals(etalonArray, bos.toByteArray()); } } jbbp-mvn-test-genannotations-nonstatic/000077500000000000000000000000001445000440600370315ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000027371445000440600403570ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic 4.0.0 com.igormaznitsa jbbp-maven-plugin-tests 0.0.0-SNAPSHOT jbbp-mvn-test-genannotations-nonstatic jar ${project.groupId} jbbp-maven-plugin ${jbbp.plugin.version} generate Generated with annotations true true true true src/000077500000000000000000000000001445000440600376205ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstaticjbbp/000077500000000000000000000000001445000440600405355ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/srccom.igormaznitsa.mvn.test.jbbp.GenAnnotationsNonStatic.jbbp000066400000000000000000000001311445000440600541150ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/jbbpubyte len; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }test/000077500000000000000000000000001445000440600405775ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/srcjava/000077500000000000000000000000001445000440600415205ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/testcom/000077500000000000000000000000001445000440600422765ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/javaigormaznitsa/000077500000000000000000000000001445000440600450055ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/java/commvn/000077500000000000000000000000001445000440600456055ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/java/com/igormaznitsatest/000077500000000000000000000000001445000440600465645ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/java/com/igormaznitsa/mvntest/jbbp/000077500000000000000000000000001445000440600475015ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/java/com/igormaznitsa/mvntest/jbbp/GenAnnotationsNonStaticTest.java000066400000000000000000000040321445000440600557550ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/java/com/igormaznitsa/mvn/* * Copyright 2019 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.mvn.test.jbbp; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import org.junit.jupiter.api.Test; class GenAnnotationsNonStaticTest { @Test void testReadWrite() throws IOException { final byte[] testData = new byte[] {4, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7}; final GenAnnotationsNonStatic result = new GenAnnotationsNonStatic() .read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotationsNonStatic instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotationsNonStatic()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } } jbbp-mvn-test-genannotations/000077500000000000000000000000001445000440600350315ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000026111445000440600363460ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations 4.0.0 com.igormaznitsa jbbp-maven-plugin-tests 0.0.0-SNAPSHOT jbbp-mvn-test-genannotations jar ${project.groupId} jbbp-maven-plugin ${jbbp.plugin.version} generate Generated with annotations true true true src/000077500000000000000000000000001445000440600356205ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotationsjbbp/000077500000000000000000000000001445000440600365355ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/srccom.igormaznitsa.mvn.test.jbbp.GenAnnotations.jbbp000066400000000000000000000001731445000440600503000ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/jbbpubyte len; uint uintField; uint [1] uintArr; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }test/000077500000000000000000000000001445000440600365775ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/srcjava/000077500000000000000000000000001445000440600375205ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/testcom/000077500000000000000000000000001445000440600402765ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/javaigormaznitsa/000077500000000000000000000000001445000440600430055ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/java/commvn/000077500000000000000000000000001445000440600436055ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/java/com/igormaznitsatest/000077500000000000000000000000001445000440600445645ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/java/com/igormaznitsa/mvnjbbp/000077500000000000000000000000001445000440600455015ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/java/com/igormaznitsa/mvn/testGenAnnotationsTest.java000066400000000000000000000044561445000440600521440ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/java/com/igormaznitsa/mvn/test/jbbp/* * Copyright 2019 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.mvn.test.jbbp; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import org.junit.jupiter.api.Test; class GenAnnotationsTest { @Test void testReadWrite() throws IOException { final byte[] testData = new byte[] { 4, (byte)0xFF, (byte)0x1A, (byte)0x1B, (byte)0x1C, (byte)0xFF, (byte)0x2A, (byte)0x2B, (byte)0x2C, (byte) 0x12, (byte) 0x34, 3, 5, 6, 7}; final GenAnnotations result = new GenAnnotations().read(new JBBPBitInputStream(new ByteArrayInputStream(testData))); assertEquals(4, result.getLEN()); assertEquals(3, result.getSOME1().getSOME2().getFIELD().length); final String script = "ubyte len;" + " uint uintField;" + " uint [1] uintArr;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotations instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotations()); assertEquals(result.getUINTFIELD(), instance.getUINTFIELD()); assertArrayEquals(result.getUINTARR(), instance.getUINTARR()); assertEquals(result.getLEN(), instance.getLEN()); assertEquals(result.getSOME1().getLEN(), instance.getSOME1().getLEN()); assertArrayEquals(result.getSOME1().getSOMEFIELD(), instance.getSOME1().getSOMEFIELD()); assertArrayEquals(result.getSOME1().getSOME2().getFIELD(), instance.getSOME1().getSOME2().getFIELD()); } } jbbp-mvn-test-getterssetters/000077500000000000000000000000001445000440600350715ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000030761445000440600364140ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters 4.0.0 com.igormaznitsa jbbp-maven-plugin-tests 0.0.0-SNAPSHOT jbbp-mvn-test-getterssetters jar ${project.groupId} jbbp-maven-plugin ${jbbp.plugin.version} generate java.lang.Runnable java.lang.Cloneable true Parser for custom and var fields some src/000077500000000000000000000000001445000440600356605ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssettersjbbp/000077500000000000000000000000001445000440600365755ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/srccom.igormaznitsa.mvn.test.jbbp.VarCustom.jbbp000066400000000000000000000003001445000440600473240ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/jbbp// custom and var fields byte bytea; some field1; readVarArray(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException { if (readWholeStream) { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(-1, byteOrder)); } else { return new JBBPFieldArrayLong(nullableNamedFieldInfo, inStream.readLongArray(arraySize, byteOrder)); } } @Override public void run() { } @Override public void writeVarField(Object sourceStruct, JBBPAbstractField value, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue) throws IOException { outStream.writeLong(((JBBPFieldLong) value).getAsLong(), byteOrder); } @Override public void writeVarArray(Object sourceStruct, JBBPAbstractArrayField array, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, int arraySizeToWrite) throws IOException { final JBBPFieldArrayLong a = (JBBPFieldArrayLong) array; if (arraySizeToWrite < 0) { for (final long l : a.getArray()) { outStream.writeLong(l, byteOrder); } } else { final long[] larr = a.getArray(); for (int i = 0; i < arraySizeToWrite; i++) { outStream.writeLong(larr[i], byteOrder); } } } } test/000077500000000000000000000000001445000440600366375ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/srcjava/000077500000000000000000000000001445000440600375605ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/testcom/000077500000000000000000000000001445000440600403365ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/javaigormaznitsa/000077500000000000000000000000001445000440600430455ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/java/commvn/000077500000000000000000000000001445000440600436455ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/java/com/igormaznitsatest/000077500000000000000000000000001445000440600446245ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/java/com/igormaznitsa/mvnjbbp/000077500000000000000000000000001445000440600455415ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/java/com/igormaznitsa/mvn/testVarCustomTest.java000066400000000000000000000032641445000440600511740ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/java/com/igormaznitsa/mvn/test/jbbp/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.mvn.test.jbbp; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.mvn.tst.VarCustomImpl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Random; import org.junit.jupiter.api.Test; public class VarCustomTest { private static final Random RND = new Random(12345); @Test public void testReadWrite() throws Exception { final VarCustomImpl impl = new VarCustomImpl(); final byte[] etalonArray = new byte[319044]; RND.nextBytes(etalonArray); etalonArray[0] = 1; impl.read(new JBBPBitInputStream(new ByteArrayInputStream(etalonArray))); assertEquals(1, impl.getBYTEA()); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final JBBPBitOutputStream bios = new JBBPBitOutputStream(bos); impl.write(bios); bios.close(); assertArrayEquals(etalonArray, bos.toByteArray()); } } jbbp-mvn-test-primitives/000077500000000000000000000000001445000440600341755ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000025121445000440600355120ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives 4.0.0 com.igormaznitsa jbbp-maven-plugin-tests 0.0.0-SNAPSHOT jbbp-mvn-test-primitives jar ${project.groupId} jbbp-maven-plugin ${jbbp.plugin.version} generate Easy parser for byte array SKIP_REMAINING_FIELDS_IF_EOF src/000077500000000000000000000000001445000440600347645ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitivesjbbp/000077500000000000000000000000001445000440600357015ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/srccom.igormaznitsa.mvn.test.jbbp.WholeStreamByteArray.jbbp000066400000000000000000000000601445000440600505650ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/jbbp// some easy JBBP script to test byte [_] array;test/000077500000000000000000000000001445000440600357435ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/srcjava/000077500000000000000000000000001445000440600366645ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/testcom/000077500000000000000000000000001445000440600374425ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/javaigormaznitsa/000077500000000000000000000000001445000440600421515ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/java/commvn/000077500000000000000000000000001445000440600427515ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/java/com/igormaznitsatest/000077500000000000000000000000001445000440600437305ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/java/com/igormaznitsa/mvnjbbp/000077500000000000000000000000001445000440600446455ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/java/com/igormaznitsa/mvn/testWholeStreamByteArrayTest.java000066400000000000000000000034151445000440600524300ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/java/com/igormaznitsa/mvn/test/jbbp/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.mvn.test.jbbp; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import org.junit.jupiter.api.Test; public class WholeStreamByteArrayTest { @Test public void testRead_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray().read(new JBBPBitInputStream( new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}))); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, struct.array); } @Test public void testWrite_DefaultBitOrder() throws IOException { final WholeStreamByteArray struct = new WholeStreamByteArray(); struct.array = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; final ByteArrayOutputStream out = new ByteArrayOutputStream(); final JBBPBitOutputStream bitOut = new JBBPBitOutputStream(out); struct.write(bitOut); bitOut.close(); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, out.toByteArray()); } } java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/pom.xml000066400000000000000000000025641445000440600307170ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-maven-plugin-pom 2.0.6 jbbp-maven-plugin-tests pom 0.0.0-SNAPSHOT Integration tests for JBBP Maven plugin jbbp-mvn-test-primitives jbbp-mvn-test-customvars jbbp-mvn-test-getterssetters jbbp-mvn-test-genannotations jbbp-mvn-test-genannotations-nonstatic com.igormaznitsa jbbp ${jbbp.version} org.apache.maven.shared maven-verifier 1.6 test java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/000077500000000000000000000000001445000440600262335ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/pom.xml000066400000000000000000000220441445000440600275520ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-maven-plugin-pom 2.0.6 jbbp-maven-plugin maven-plugin JBBP Maven plugin Generator of sources from JBBP scripts 1.8 1.8 publish com.igormaznitsa uber-pom

parent
modules
profiles/profile/modules
true upom org.apache.maven.plugins maven-source-plugin generate-sources package jar org.apache.maven.plugins maven-javadoc-plugin generate-javadoc package jar 1.8 org.apache.maven.plugins maven-gpg-plugin sign-artifacts verify sign org.codehaus.plexus plexus-compiler-api 2.7 org.apache.maven maven-compat ${mvn.version} test org.apache.maven.shared maven-verifier 1.6 test org.apache.maven.plugin-testing maven-plugin-testing-harness 2.1 test org.apache.maven.plugin-tools maven-plugin-annotations 3.0 org.apache.maven maven-plugin-api ${mvn.version} provided org.apache.maven maven-core ${mvn.version} provided ${project.groupId} jbbp ${project.version} org.apache.maven maven-artifact ${mvn.version} provided org.codehaus.mojo build-helper-maven-plugin 1.12 add-common-sources generate-sources add-source ../../jbbp-plugin-common/src/main/java com.igormaznitsa uber-pom 1.0.3
parent
modules
profiles/profile/modules
true
upom
org.codehaus.mojo animal-sniffer-maven-plugin 1.19 org.codehaus.mojo.signature java16-sun 1.10 animal-sniffer test check org.apache.maven.plugins maven-plugin-plugin 3.5 true jbbp mojo-descriptor process-classes descriptor generated-helpmojo helpmojo
java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/000077500000000000000000000000001445000440600270225ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/assemble/000077500000000000000000000000001445000440600306155ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/assemble/bundle.xml000066400000000000000000000020111445000440600326020ustar00rootroot00000000000000 bundle false false jar ${project.build.directory} / *.jar.asc *.jar *.pom *.pom.asc original*.* *.zip distribution.xml000066400000000000000000000032051445000440600337770ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/assemble distr false false tar.gz ${project.basedir} /project target/ texts/ nbactions.xml catalog.xml **/.* /lib ${project.basedir}/target ${project.build.finalName}.jar /lib ${project.basedir} pom.xml / ${basedir}/texts *.* java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/000077500000000000000000000000001445000440600277465ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/000077500000000000000000000000001445000440600306675ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/000077500000000000000000000000001445000440600314455ustar00rootroot00000000000000igormaznitsa/000077500000000000000000000000001445000440600340755ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/comjbbp/000077500000000000000000000000001445000440600350125ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/igormaznitsaplugin/000077500000000000000000000000001445000440600363105ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/igormaznitsa/jbbpmvn/000077500000000000000000000000001445000440600371105ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/igormaznitsa/jbbp/pluginAbstractJBBPMojo.java000066400000000000000000000146561445000440600430150ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/igormaznitsa/jbbp/plugin/mvnpackage com.igormaznitsa.jbbp.plugin.mvn; import com.igormaznitsa.jbbp.plugin.common.converters.Target; import com.igormaznitsa.meta.annotation.MustNotContainNull; import com.igormaznitsa.meta.common.utils.Assertions; import java.io.File; import java.util.HashSet; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.compiler.util.scan.InclusionScanException; import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner; import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner; import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping; public abstract class AbstractJBBPMojo extends AbstractMojo { /** * Provides an explicit list of all the JBBP scripts that should be included * in the generate phase of the plug-in. *

* A set of Ant-like inclusion patterns used to select files from the source * directory for processing. By default, the pattern * **/*.jbbp is used to select JBBP script files. *

*/ @Parameter(alias = "includes") protected final Set includes = new HashSet(); /** * A set of Ant-like exclusion patterns used to prevent certain files from * being processed. By default, this set is empty such that no files are * excluded. */ @Parameter(alias = "excludes") protected final Set excludes = new HashSet(); /** * Current maven project. */ @Parameter(property = "project", required = true, readonly = true) protected MavenProject project; /** * Specifies whether sources are added to the {@code compile} or {@code test} * scope. */ @Parameter(alias = "generateTestSources", defaultValue = "false") protected boolean generateTestSources; /** * Package to override package name extracted from script file name, it will be * common for all processed classes. */ @Parameter(alias = "packageName") protected String packageName; /** * Target for translation. * * @see Target */ @Parameter(alias = "target", defaultValue = "JAVA") protected String target; /** * Flag to skip processing of the plug-in. */ @Parameter(alias = "skip", defaultValue = "false") protected boolean skip; /** * Destination directory for generated Java classes, the default value is * "${project.build.directory}/generated-sources/jbbp" */ @Parameter(alias = "output", defaultValue = "${project.build.directory}/generated-sources/jbbp") protected File output; /** * Flag to make plugin verbose. */ @Parameter(alias = "verbose", defaultValue = "false") protected boolean verbose; /** * Source directory for JBBP scripts, the default value is * "${project.basedir}/src/jbbp" */ @Parameter(alias = "source", defaultValue = "${project.basedir}/src/jbbp") protected File source; @Nullable public String getTarget() { return this.target; } public void setTarget(@Nullable final String value) { this.target = value; } @MustNotContainNull @Nonnull public Set getExcludes() { return this.excludes; } @Nullable public String getPackageName() { return this.packageName; } public void setPackageName(@Nullable final String text) { this.packageName = text; } @MustNotContainNull @Nonnull public Set getIncludes() { return this.includes; } @Nonnull public File getSource() { return this.source; } public void setSource(@Nonnull final File file) { this.source = Assertions.assertNotNull("Source must not be null", file); } public boolean getSkip() { return this.skip; } public void setSkip(final boolean value) { this.skip = value; } @Nonnull public File getOutput() { return this.output; } public void setOutput(@Nullable final File file) { this.output = file; } public boolean getVerbose() { return this.verbose; } public void setVerbose(final boolean flag) { this.verbose = flag; } public boolean getGenerateTestSources() { return this.generateTestSources; } public void setGenerateTestSources(final boolean flag) { this.generateTestSources = flag; } protected void registerSourceRoot(@Nonnull final File outputDir) { if (this.generateTestSources) { getLog().debug("Registering TEST source root : " + outputDir.getPath()); this.project.addTestCompileSourceRoot(outputDir.getPath()); } else { getLog().debug("Registering source root : " + outputDir.getPath()); this.project.addCompileSourceRoot(outputDir.getPath()); } } @Nonnull public Set findSources(@Nonnull final File targetDirectory) throws MojoExecutionException { try { final SourceInclusionScanner scanner = new SimpleSourceInclusionScanner(this.includes, this.excludes); scanner.addSourceMapping(new SuffixMapping("JBBP", "jbbp")); return scanner.getIncludedSources(this.source, targetDirectory); } catch (InclusionScanException ex) { throw new MojoExecutionException("Error during sources scanning", ex); } } public void logInfo(@Nullable final String text, final boolean onlyIfVerbose) { if (text != null) { if (this.verbose) { getLog().info(text); } else { if (!onlyIfVerbose) { getLog().info(text); } } } } public boolean checkSetNonEmptyWithLogging(@Nonnull final Set files) { boolean result = true; if (files.isEmpty()) { getLog().warn("There is not any detected JBBP script"); result = false; } return result; } @Override public final void execute() throws MojoExecutionException, MojoFailureException { if (this.skip) { logInfo("Skip processing", false); return; } if (this.includes.isEmpty()) { this.includes.add("**/*.jbbp"); } executeMojo(); } @Nonnull public Target findTarget() throws MojoExecutionException { final Target theTarget; try { return Target.valueOf(this.target); } catch (IllegalArgumentException ex) { throw new MojoExecutionException("Unsupported target : " + this.target, ex); } } protected abstract void executeMojo() throws MojoExecutionException, MojoFailureException; } JBBPCleanMojo.java000066400000000000000000000054721445000440600422700ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/igormaznitsa/jbbp/plugin/mvnpackage com.igormaznitsa.jbbp.plugin.mvn; import com.igormaznitsa.jbbp.plugin.common.converters.JBBPScriptTranslator; import com.igormaznitsa.jbbp.plugin.common.converters.Target; import java.io.File; import java.io.IOException; import java.util.Set; import org.apache.commons.io.FileUtils; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; /** * The Mojo looks for all java files generated for JBBP scripts and delete them. * * @author Igor Maznitsa */ @Mojo(name = "clean", defaultPhase = LifecyclePhase.CLEAN, threadSafe = true) public class JBBPCleanMojo extends AbstractJBBPMojo { /** * Clean whole target folder. */ @Parameter(alias = "cleanAll", defaultValue = "false") private boolean cleanAll; @Override public void executeMojo() throws MojoExecutionException, MojoFailureException { if (this.cleanAll) { getLog().debug("Clean whole folder : " + this.output); if (this.output.isDirectory()) { try { FileUtils.cleanDirectory(this.output); } catch (IOException ex) { throw new MojoExecutionException("Can't clean folder : " + this.output, ex); } } else { getLog().info("Can't find output folder : " + this.output); } return; } final Set scripts = findSources(this.output); if (checkSetNonEmptyWithLogging(scripts)) { int counter = 0; final Target target = findTarget(); final JBBPScriptTranslator.Parameters parameters = new JBBPScriptTranslator.Parameters(); parameters.setOutputDir(this.output).setPackageName(this.packageName); for (final File aScript : scripts) { getLog().debug("Processing JBBP script : " + aScript); parameters.setScriptFile(aScript); final Set files; try { files = target.getTranslator().translate(parameters, true); } catch (IOException ex) { throw new MojoExecutionException("Error during form file set", ex); } for (final File f : files) { if (f.isFile()) { if (f.delete()) { counter++; logInfo("Deleted file '" + f.getAbsolutePath() + "' for script '" + aScript + "'", true); } else { getLog().error( "Can't delete file '" + f.getAbsolutePath() + "' for script '" + aScript + "'"); } } else { getLog().debug("File '" + f.getAbsolutePath() + "' generated for script '" + aScript + "' is not found"); } } } getLog().info(String.format("Deleted %d file(s)", counter)); } } } JBBPGenerateMojo.java000066400000000000000000000354741445000440600430050ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/igormaznitsa/jbbp/plugin/mvn/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.plugin.mvn; import static com.igormaznitsa.jbbp.plugin.common.utils.CommonUtils.ensureEncodingName; import static com.igormaznitsa.jbbp.utils.JBBPUtils.ARRAY_STRING_EMPTY; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.plugin.common.converters.JBBPScriptTranslator; import com.igormaznitsa.jbbp.plugin.common.converters.ParserFlags; import com.igormaznitsa.jbbp.plugin.common.converters.Target; import com.igormaznitsa.meta.annotation.MustNotContainNull; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.io.FileUtils; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; /** * The Mojo looks for all JBBP scripts in source and generate sources. * * @author Igor Maznitsa */ @Mojo(name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true) public class JBBPGenerateMojo extends AbstractJBBPMojo { /** * Interfaces for section 'implements' in generated classes. */ @Parameter(alias = "interfaces") private final Set interfaces = new HashSet(); /** * List of parser flags. *
    *
  • SKIP_REMAINING_FIELDS_IF_EOF
  • *
*/ @Parameter(alias = "parserFlags") private final Set parserFlags = new HashSet(); /** * List of names of allowed custom value types. */ @Parameter(alias = "customTypes") private final Set customTypes = new HashSet(); /** * Interfaces for structures, structure will be implementing the interface and getter will return interface instead of structure type. */ @Parameter(alias = "mapStructToInterfaces") private final Map mapStructToInterfaces = new HashMap(); /** * Superclasses for structures, structure will be extending mapped superclass. * * @since 1.4.0 */ @Parameter(alias = "mapStructToSuperclasses") private final Map mapStructToSuperclasses = new HashMap(); /** * Do not make generated inner classes as static ones. * * @since 1.4.0 */ @Parameter(alias = "doInnerClassesNonStatic") private boolean doInnerClassesNonStatic; /** * Specify output file encoding; defaults to source encoding. */ @Parameter(alias = "outputEncoding", defaultValue = "${project.build.sourceEncoding}") private String outputEncoding; /** * Specify grammar file encoding; defaults to source encoding. */ @Parameter(alias = "inputEncoding", defaultValue = "${project.build.sourceEncoding}") private String inputEncoding; /** * File contains text of cap comment for each generated class file. The Cap * text will be placed before package name and usually it can be used to * provide license information. */ @Parameter(alias = "headCommentFile") private File headCommentFile; /** * Plain text of cap comment for each generated class file. The Cap text will * be placed before package name and usually it can be used to provide license * information. */ @Parameter(alias = "headComment") private String headComment; /** * File contains text of custom section to be added into class body. */ @Parameter(alias = "customTextFile") private File customTextFile; /** * Plain text of custom section to be added into class body. */ @Parameter(alias = "customText") private String customText; /** * Disable generate fields in result class. */ @Parameter(alias = "disableGenerateFields", defaultValue = "false") private boolean disableGenerateFields; /** * Generate getters and setters for class fields (class fields will be private * ones). */ @Parameter(alias = "addGettersSetters") private boolean addGettersSetters; /** * Super class for generated classes. */ @Parameter(alias = "superClass") private String superClass; /** * Force abstract modifier for generated classes even if they don't have abstract methods. */ @Parameter(alias = "doAbstract") private boolean doAbstract; /** * Add generated output folder to list of source folders. * * @since 1.4.0 */ @Parameter(alias = "addToSourceFolders") private boolean addToSourceFolders; /** * Add generated output folder to list of test source folders. * * @since 1.4.0 */ @Parameter(alias = "addToTestSourceFolders") private boolean addToTestSourceFolders; /** * Turn on generate of newInstance methods inside generated classes. * * @since 2.0.0 */ @Parameter(alias = "addNewInstanceMethods", defaultValue = "false") private boolean addNewInstanceMethods; /** * Add Bin annotations * * @see Bin * @since 2.0.0 */ @Parameter(alias = "addBinAnnotations", defaultValue = "false") private boolean addBinAnnotations; public boolean isAddToSourceFolders() { return this.addToSourceFolders; } public void setAddToSourceFolders(final boolean value) { this.addToSourceFolders = value; } public boolean isAddToTestSourceFolders() { return this.addToTestSourceFolders; } public void setAddToTestSourceFolders(final boolean value) { this.addToTestSourceFolders = value; } @Nullable public String getSuperClass() { return this.superClass; } public boolean isDoAbstract() { return this.doAbstract; } public void setDoAbstract(final boolean value) { this.doAbstract = value; } @Nonnull public Map getMapStructToInterfaces() { return this.mapStructToInterfaces; } @Nonnull public Map getMapStructToSuperclasses() { return this.mapStructToSuperclasses; } @Nonnull @MustNotContainNull public Set getInterfaces() { return this.interfaces; } @Nonnull @MustNotContainNull public Set getCustomTypes() { return this.customTypes; } public boolean getAddGettersSetters() { return this.addGettersSetters; } @Nullable public File getCustomTextFile() { return this.customTextFile; } @Nullable public String getCustomText() { return this.customText; } @Nullable public File getHeadCommentFile() { return this.headCommentFile; } @Nullable public String getHeadComment() { return this.headComment; } @Nullable public String getInputEncoding() { return this.inputEncoding; } @Nullable public String getOutputEncoding() { return this.outputEncoding; } @MustNotContainNull @Nonnull public Set getParserFlags() { return this.parserFlags; } public boolean isDisableGenerateFields() { return this.disableGenerateFields; } public void setDisableGenerateFields(final boolean value) { this.disableGenerateFields = value; } public boolean isDoInnerClassesNonStatic() { return this.doInnerClassesNonStatic; } public void setDoInnerClassesNonStatic(final boolean value) { this.doInnerClassesNonStatic = value; } @Nullable private String makeCapText(@Nonnull final String inEncoding) throws IOException { String result = null; if (this.headComment != null) { result = this.headComment; } else if (this.headCommentFile != null) { getLog().debug("Provided CAP comment file: " + this.headCommentFile.getPath()); result = FileUtils.readFileToString(this.headCommentFile, inEncoding); } return result; } @Nullable private String makeCustomText(@Nonnull final String inEncoding) throws IOException { String result = null; if (this.customText != null) { result = this.customText; } else if (this.customTextFile != null) { getLog().debug("Provided custom text file: " + this.customTextFile.getPath()); result = FileUtils.readFileToString(this.customTextFile, inEncoding); } return result; } @Override protected void executeMojo() throws MojoExecutionException, MojoFailureException { final String inEncoding = ensureEncodingName(this.inputEncoding); final String outEncoding = ensureEncodingName(this.outputEncoding); getLog().debug("Encoding In: " + inEncoding); getLog().debug("Encoding Out: " + outEncoding); final String capText; try { capText = makeCapText(inEncoding); } catch (IOException ex) { throw new MojoExecutionException("Can't read cap comment file", ex); } final String customTextForClass; try { customTextForClass = makeCustomText(inEncoding); } catch (IOException ex) { throw new MojoExecutionException("Can't read custom text file", ex); } if (this.includes.isEmpty()) { this.includes.add("**/*.jbbp"); } final Set normalizedCustomTypeNames = new HashSet(); for (final String s : this.customTypes) { final String trimmed = s.trim(); final String normalized = trimmed.toLowerCase(Locale.ENGLISH); if (!normalized.equals(trimmed)) { getLog().warn(String .format("Custom type name '%s' in JBBP normal form is '%s' ", trimmed, normalized)); } normalizedCustomTypeNames.add(normalized); } getLog().debug("Defined normalized custom types : " + normalizedCustomTypeNames); final String[] customTypesArray = normalizedCustomTypeNames.toArray(ARRAY_STRING_EMPTY); final JBBPCustomFieldTypeProcessor customFieldProcessor = new JBBPCustomFieldTypeProcessor() { @Override @Nonnull @MustNotContainNull public String[] getCustomFieldTypes() { return customTypesArray; } @Override public boolean isAllowed(@Nonnull final JBBPFieldTypeParameterContainer fieldType, @Nullable final String fieldName, final int extraData, final boolean isArray) { final boolean result = normalizedCustomTypeNames.contains(fieldType.getTypeName()); if (!result) { getLog().warn("Detected not allowed custom type name : " + fieldType.getTypeName()); } return result; } @Override @Nonnull public JBBPAbstractField readCustomFieldType(@Nonnull final JBBPBitInputStream in, @Nonnull final JBBPBitOrder bitOrder, final int parserFlags, @Nonnull final JBBPFieldTypeParameterContainer customTypeFieldInfo, @Nullable final JBBPNamedFieldInfo fieldName, final int extraData, final boolean readWholeStream, final int arrayLength) throws IOException { throw new Error("Must not be called"); } }; final Set foundJBBPScripts = findSources(this.source); if (checkSetNonEmptyWithLogging(foundJBBPScripts)) { final Target theTarget = findTarget(); final JBBPScriptTranslator.Parameters parameters = new JBBPScriptTranslator.Parameters(); parameters .setPackageName(this.packageName) .setParserFlags(ParserFlags.makeFromSet(this.parserFlags)) .setOutputDir(this.output) .setHeadComment(capText) .setCustomText(customTextForClass) .setEncodingIn(inEncoding) .setEncodingOut(outEncoding) .setCustomFieldTypeProcessor(customFieldProcessor) .setSuperClass(this.getSuperClass()) .setClassImplements(this.getInterfaces()) .setSubClassInterfaces(this.getMapStructToInterfaces()) .setDoInternalClassesNonStatic(this.isDoInnerClassesNonStatic()) .setSubClassSuperclasses(this.getMapStructToSuperclasses()) .setAddGettersSetters(this.getAddGettersSetters()) .setDoAbstract(this.isDoAbstract()) .setDisableGenerateFields(this.isDisableGenerateFields()) .setAddBinAnnotations(this.isAddBinAnnotations()) .setAddNewInstanceMethods(this.isAddNewInstanceMethods()); for (final File aScript : foundJBBPScripts) { parameters.setScriptFile(aScript).assertAllOk(); getLog().debug("Processing JBBP script file : " + aScript); try { final Set files = theTarget.getTranslator().translate(parameters, false); getLog().debug("Converted " + aScript + " into " + files); for (final File f : files) { logInfo(String .format("JBBP script '%s' has been converted into '%s'", aScript.getName(), f.getName()), false); } } catch (IOException ex) { throw new MojoExecutionException( "Error during JBBP script translation : " + aScript.getAbsolutePath(), ex); } } if (this.isAddToSourceFolders()) { getLog().info("Add folder to compile source root: " + this.getOutput().getAbsolutePath()); this.project.addCompileSourceRoot(this.getOutput().getAbsolutePath()); } if (this.isAddToTestSourceFolders()) { getLog() .info("Add folder to test compile source root: " + this.getOutput().getAbsolutePath()); this.project.addTestCompileSourceRoot(this.getOutput().getAbsolutePath()); } } registerSourceRoot(this.output); } public boolean isAddNewInstanceMethods() { return this.addNewInstanceMethods; } public void setAddNewInstanceMethods(final boolean addNewInstanceMethods) { this.addNewInstanceMethods = addNewInstanceMethods; } public boolean isAddBinAnnotations() { return addBinAnnotations; } public void setAddBinAnnotations(boolean addBinAnnotations) { this.addBinAnnotations = addBinAnnotations; } } java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/000077500000000000000000000000001445000440600300015ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/000077500000000000000000000000001445000440600307225ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/000077500000000000000000000000001445000440600315005ustar00rootroot00000000000000igormaznitsa/000077500000000000000000000000001445000440600341305ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/comjbbp/000077500000000000000000000000001445000440600350455ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsaplugin/000077500000000000000000000000001445000440600363435ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbpcommon/000077500000000000000000000000001445000440600376335ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbp/pluginconverters/000077500000000000000000000000001445000440600420255ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbp/plugin/commonJavaConverterTest.java000066400000000000000000000043061445000440600463040ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbp/plugin/common/converterspackage com.igormaznitsa.jbbp.plugin.common.converters; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import org.junit.jupiter.api.Test; class JavaConverterTest { @Test void testTranslateParameters() { final JBBPScriptTranslator.Parameters parameters = new JBBPScriptTranslator.Parameters(); assertTrue(parameters.setAddNewInstanceMethods(true).isAddNewInstanceMethods()); assertFalse(parameters.setAddNewInstanceMethods(false).isAddNewInstanceMethods()); assertTrue(parameters.setAddBinAnnotations(true).isAddBinAnnotations()); assertFalse(parameters.setAddBinAnnotations(false).isAddBinAnnotations()); assertTrue(parameters.setAddGettersSetters(true).isAddGettersSetters()); assertFalse(parameters.setAddGettersSetters(false).isAddGettersSetters()); assertTrue(parameters.setDisableGenerateFields(true).isDisableGenerateFields()); assertFalse(parameters.setDisableGenerateFields(false).isDisableGenerateFields()); assertTrue(parameters.setDoAbstract(true).isDoAbstract()); assertFalse(parameters.setDoAbstract(false).isDoAbstract()); assertTrue(parameters.setDoInternalClassesNonStatic(true).isDoInternalClassesNonStatic()); assertFalse(parameters.setDoInternalClassesNonStatic(false).isDoInternalClassesNonStatic()); assertEquals(new File("some_test"), parameters.setOutputDir(new File("some_test")).getOutputDir()); assertEquals(new File("some_test.script"), parameters.setScriptFile(new File("some_test.script")).getScriptFile()); assertEquals("some.test.package", parameters.setPackageName("some.test.package").getPackageName()); assertEquals("bit a;", parameters.setScriptText("bit a;").getScriptText()); assertEquals("SomeSuperClass", parameters.setSuperClass("SomeSuperClass").getSuperClass()); assertEquals(1234, parameters.setParserFlags(1234).getParserFlags()); assertEquals("UTF-8", parameters.setEncodingIn("UTF-8").getEncodingIn()); assertEquals("UTF-8", parameters.setEncodingOut("UTF-8").getEncodingOut()); parameters.setScriptFile(null); } }mvn/000077500000000000000000000000001445000440600371435ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbp/pluginJBBPGenerateMojoTest.java000066400000000000000000000115731445000440600436720ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbp/plugin/mvn/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.plugin.mvn; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.io.File; import java.util.Arrays; import java.util.Map; import java.util.Set; import org.apache.maven.execution.DefaultMavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.plugin.testing.AbstractMojoTestCase; import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuilder; import org.apache.maven.project.ProjectBuildingRequest; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class JBBPGenerateMojoTest extends AbstractMojoTestCase { private JBBPGenerateMojo findMojo(final String pomName, final String goal) throws Exception { final File pomFile = new File(this.getClass().getResource(pomName).toURI()); final MavenExecutionRequest executionRequest = new DefaultMavenExecutionRequest(); final ProjectBuildingRequest buildingRequest = executionRequest.getProjectBuildingRequest(); final ProjectBuilder projectBuilder = this.lookup(ProjectBuilder.class); final MavenProject project = projectBuilder.build(pomFile, buildingRequest).getProject(); return (JBBPGenerateMojo) this.lookupConfiguredMojo(project, goal); } @BeforeEach @Override protected void setUp() throws Exception { super.setUp(); } @AfterEach @Override protected void tearDown() throws Exception { super.tearDown(); } private String[] set2array(final Set set) { final String[] arr = set.toArray(new String[set.size()]); Arrays.sort(arr); return arr; } private void assertPath(final String expected, final String check) { final String normalized = expected.replace('/', File.separatorChar); if (check.endsWith(normalized)) { final String start = check.substring(0, check.length() - expected.length()); if (start.length() == 0 || start.endsWith(":")) { return; } } fail(String.format("Expected %s but detected %s", expected, check)); } @Test public void testConfig() throws Exception { final JBBPGenerateMojo mojo = findMojo("mojoConfig.xml", "generate"); assertNotNull(mojo); assertTrue(mojo.getGenerateTestSources()); assertTrue(mojo.getSkip()); assertTrue(mojo.getVerbose()); assertPath("/some/custom/file", mojo.getCustomTextFile().getPath()); assertEquals("public void test(){}", mojo.getCustomText()); assertEquals("uber.package", mojo.getPackageName()); assertPath("/some/cap/file", mojo.getHeadCommentFile().getPath()); assertEquals("some cap text", mojo.getHeadComment()); assertPath("/some/source", mojo.getSource().getPath()); assertPath("/some/output", mojo.getOutput().getPath()); assertEquals("IN-8", mojo.getInputEncoding()); assertEquals("OUT-8", mojo.getOutputEncoding()); assertEquals("com.igormaznitsa.Super", mojo.getSuperClass()); assertEquals("SOME_TARGET", mojo.getTarget()); assertTrue(mojo.isDisableGenerateFields()); assertTrue(mojo.isDoAbstract()); assertTrue(mojo.isAddToSourceFolders()); assertTrue(mojo.isAddToTestSourceFolders()); assertTrue(mojo.getAddGettersSetters()); assertArrayEquals(new String[] {"abc", "def"}, set2array(mojo.getCustomTypes())); assertArrayEquals(new String[] {"com.igormaznitsa.InterfaceA", "com.igormaznitsa.InterfaceB"}, set2array(mojo.getInterfaces())); assertArrayEquals(new String[] {"path1/**/*.jbbp", "path2/**/*.jbbp"}, set2array(mojo.getIncludes())); assertArrayEquals(new String[] {"path3/**/*.jbbp", "path4/**/*.jbbp"}, set2array(mojo.getExcludes())); assertTrue(mojo.isDoInnerClassesNonStatic()); assertTrue(mojo.isDisableGenerateFields()); final Map mapStructToInterfaces = mojo.getMapStructToInterfaces(); assertEquals(2, mapStructToInterfaces.size()); assertEquals("com.test.C", mapStructToInterfaces.get("a.b.c")); assertEquals("com.test.D", mapStructToInterfaces.get("a.b.d")); final Map mapStructToSuperclasses = mojo.getMapStructToSuperclasses(); assertEquals(2, mapStructToSuperclasses.size()); assertEquals("com.test.CC", mapStructToSuperclasses.get("a.b.c")); assertEquals("com.test.DD", mapStructToSuperclasses.get("a.b.d")); } } java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/000077500000000000000000000000001445000440600320135ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/com/000077500000000000000000000000001445000440600325715ustar00rootroot00000000000000igormaznitsa/000077500000000000000000000000001445000440600352215ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/comjbbp/000077500000000000000000000000001445000440600361365ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/com/igormaznitsaplugin/000077500000000000000000000000001445000440600374345ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/com/igormaznitsa/jbbpmvn/000077500000000000000000000000001445000440600402345ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/com/igormaznitsa/jbbp/pluginmojoConfig.xml000066400000000000000000000065301445000440600430540ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/com/igormaznitsa/jbbp/plugin/mvn 4.0.0 com.igormaznitsa mvngolang-tests 1.0.0-SNAPSHOT jar com.igormaznitsa jbbp-maven-plugin 2.0.4-SNAPSHOT generate true true true uber.package OUT-8 IN-8 /some/cap/file /some/custom/file some cap text public void test(){} /some/source /some/output true com.igormaznitsa.Super true SOME_TARGET true com.test.C com.test.D com.test.CC com.test.DD true true true true abc def com.igormaznitsa.InterfaceA com.igormaznitsa.InterfaceB path1/**/*.jbbp path2/**/*.jbbp path3/**/*.jbbp path4/**/*.jbbp SKIP_REMAINING_FIELDS_IF_EOF java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-maven/pom.xml000066400000000000000000000021451445000440600242350ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-plugin-pom 2.0.6 jbbp-maven-plugin-pom pom Common POM for JBBP Maven plugin project jbbp-maven-plugin jbbp-maven-plugin-tests org.apache.maven.plugins maven-jar-plugin **/.netbeans_automatic_build java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/000077500000000000000000000000001445000440600243745ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/pom.xml000066400000000000000000000014761445000440600257210ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-plugin-pom 2.0.6 jbbp-plugin-common jar Contains common code for plugins ${project.groupId} jbbp ${jbbp.version} java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/000077500000000000000000000000001445000440600251635ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/000077500000000000000000000000001445000440600261075ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/000077500000000000000000000000001445000440600270305ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/000077500000000000000000000000001445000440600276065ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/000077500000000000000000000000001445000440600323155ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/000077500000000000000000000000001445000440600332325ustar00rootroot00000000000000plugin/000077500000000000000000000000001445000440600344515ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbpcommon/000077500000000000000000000000001445000440600357415ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/pluginconverters/000077500000000000000000000000001445000440600401335ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/plugin/commonJBBPScriptTranslator.java000066400000000000000000000220741445000440600447570ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/plugin/common/converterspackage com.igormaznitsa.jbbp.plugin.common.converters; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.meta.common.utils.Assertions; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * Interface for auxiliary class to process found JBBP script and translate them into set of files. * * @since 1.3.0 */ public interface JBBPScriptTranslator { /** * Do translation. * * @param parameters parameter block, must not be null * @param dryRun if true then only set of files will be returned without any creation. * @return set of result files created during operation. * @throws IOException if any transport error */ @Nonnull Set translate(@Nonnull Parameters parameters, boolean dryRun) throws IOException; /** * Class to be used as parameter container. * * @since 1.3.0 */ public final class Parameters { /** * Map of interface names to be implemented by subclasses. */ @Nonnull private final Map subClassInterfaces = new HashMap(); /** * Map of superclasses to be extended by subclasses. * * @since 1.4.0 */ @Nonnull private final Map subClassSuperclasses = new HashMap(); /** * Set of interface names to be implemented by the main class. */ @Nonnull private final Set classImplements = new HashSet(); /** * Processor for custom types. */ @Nullable JBBPCustomFieldTypeProcessor customFieldTypeProcessor = null; /** * Flag to not make generated subclasses as static ones. * * @since 1.4.0 */ private boolean doInternalClassesNonStatic; /** * Super class for main class. */ @Nullable private String superClass = null; /** * Destination file name. * * @since 2.0.0 */ @Nullable private String destFileName = null; /** * Disable generate class fields. * * @since 1.4.0 */ private boolean disableGenerateFields = false; /** * Flag to force abstract main class. */ private boolean doAbstract = false; /** * Flag to generated getters and setters instead of direct access. */ private boolean addGettersSetters = false; /** * Script file. */ @Nullable private File scriptFile = null; /** * Script text, it will be used only if {@link #scriptFile} is null * * @since 2.0.0 */ private String scriptText = null; /** * Package name for main class. */ @Nullable private String packageName = null; /** * Comment to be placed in the start of generated class. */ @Nullable private String headComment = null; /** * Text to be injected into class body. */ @Nullable private String customText; /** * Encoding for input text data. */ @Nonnull private String inEncoding = "UTF-8"; /** * Encoding for output text data. */ @Nonnull private String outEncoding = "UTF-8"; /** * Output directory for result files. */ @Nullable private File outputDir = null; /** * Parser flags. */ private int parserFlags; /** * Turn on generate Bin annotations for fields. * * @since 2.0.0 */ private boolean addBinAnnotations; /** * Generate newInstance methods in classes. * * @since 2.0.0 */ private boolean addNewInstanceMethods; @Nullable public String getPackageName() { return this.packageName; } @Nonnull public Parameters setPackageName(@Nullable final String value) { this.packageName = value; return this; } public boolean isAddGettersSetters() { return this.addGettersSetters; } @Nonnull public Parameters setAddGettersSetters(final boolean value) { this.addGettersSetters = value; return this; } @Nullable public String getDestFileName() { return this.destFileName; } @Nonnull public Parameters setDestFileName(@Nullable final String name) { this.destFileName = name; return this; } public boolean isDisableGenerateFields() { return this.disableGenerateFields; } @Nonnull public Parameters setDisableGenerateFields(final boolean value) { this.disableGenerateFields = value; return this; } @Nonnull public Set getClassImplements() { return Collections.unmodifiableSet(this.classImplements); } @Nonnull public Parameters setClassImplements(@Nonnull final Set value) { this.classImplements.clear(); this.classImplements.addAll(value); return this; } @Nonnull public Map getSubClassInterfaces() { return Collections.unmodifiableMap(this.subClassInterfaces); } @Nonnull public Parameters setSubClassInterfaces(@Nonnull final Map value) { this.subClassInterfaces.clear(); this.subClassInterfaces.putAll(value); return this; } @Nonnull public Map getSubClassSuperclasses() { return Collections.unmodifiableMap(this.subClassSuperclasses); } @Nonnull public Parameters setSubClassSuperclasses(@Nonnull final Map value) { this.subClassSuperclasses.clear(); this.subClassSuperclasses.putAll(value); return this; } public boolean isDoInternalClassesNonStatic() { return this.doInternalClassesNonStatic; } @Nonnull public Parameters setDoInternalClassesNonStatic(final boolean flag) { this.doInternalClassesNonStatic = flag; return this; } public boolean isAddNewInstanceMethods() { return this.addNewInstanceMethods; } @Nonnull public Parameters setAddNewInstanceMethods(final boolean flag) { this.addNewInstanceMethods = flag; return this; } public boolean isAddBinAnnotations() { return this.addBinAnnotations; } @Nonnull public Parameters setAddBinAnnotations(final boolean flag) { this.addBinAnnotations = flag; return this; } public int getParserFlags() { return this.parserFlags; } @Nonnull public Parameters setParserFlags(final int value) { this.parserFlags = value; return this; } public boolean isDoAbstract() { return this.doAbstract; } @Nonnull public Parameters setDoAbstract(final boolean value) { this.doAbstract = value; return this; } @Nullable public File getOutputDir() { return this.outputDir; } @Nonnull public Parameters setOutputDir(@Nullable final File dir) { this.outputDir = dir; return this; } @Nullable public File getScriptFile() { return this.scriptFile; } @Nonnull public Parameters setScriptFile(@Nullable final File file) { this.scriptFile = file; return this; } @Nullable public String getScriptText() { return this.scriptText; } @Nonnull public Parameters setScriptText(@Nonnull final String text) { this.scriptText = Assertions.assertNotNull(text); return this; } @Nullable public String getHeadComment() { return this.headComment; } @Nonnull public Parameters setHeadComment(@Nullable final String text) { this.headComment = text; return this; } @Nullable public String getCustomText() { return this.customText; } @Nonnull public Parameters setCustomText(@Nullable final String text) { this.customText = text; return this; } @Nonnull public String getEncodingIn() { return this.inEncoding; } @Nonnull public Parameters setEncodingIn(@Nullable final String text) { this.inEncoding = Assertions.assertNotNull(text); return this; } @Nonnull public String getEncodingOut() { return this.outEncoding; } @Nonnull public Parameters setEncodingOut(@Nullable final String text) { this.outEncoding = Assertions.assertNotNull(text); return this; } @Nullable public JBBPCustomFieldTypeProcessor getCustomFieldTypeProcessor() { return this.customFieldTypeProcessor; } @Nonnull public Parameters setCustomFieldTypeProcessor( @Nullable final JBBPCustomFieldTypeProcessor customProcessor) { this.customFieldTypeProcessor = customProcessor; return this; } @Nonnull public Parameters assertAllOk() { if (this.scriptFile == null) { throw new NullPointerException("Script file is null"); } return this; } @Nullable public String getSuperClass() { return this.superClass; } @Nonnull public Parameters setSuperClass(@Nullable final String value) { this.superClass = value; return this; } } } JavaConverter.java000066400000000000000000000066771445000440600435670ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/plugin/common/converterspackage com.igormaznitsa.jbbp.plugin.common.converters; import static com.igormaznitsa.jbbp.utils.JBBPUtils.ARRAY_STRING_EMPTY; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.conversion.JBBPToJavaConverter; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.plugin.common.utils.CommonUtils; import com.igormaznitsa.meta.common.utils.Assertions; import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.Set; import javax.annotation.Nonnull; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; public class JavaConverter implements JBBPScriptTranslator { @Override @Nonnull public Set translate(@Nonnull final Parameters parameters, final boolean dryRun) throws IOException { final String text; final String rawFileName; if (parameters.getScriptFile() == null) { rawFileName = parameters.getDestFileName() == null ? "JbbpNoName" : parameters.getDestFileName(); text = Assertions .assertNotNull("Script file is null, expected script text", parameters.getScriptText()); } else { final File scriptToProcess = parameters.getScriptFile(); rawFileName = FilenameUtils.getBaseName(scriptToProcess.getName()); text = FileUtils.readFileToString(scriptToProcess, parameters.getEncodingIn()); } final String className = CommonUtils.extractClassName(rawFileName); final String packageName = parameters.getPackageName() == null ? CommonUtils.extractPackageName(rawFileName) : parameters.getPackageName(); final Set resultFiles = Collections.singleton(CommonUtils .scriptFileToJavaFile(parameters.getOutputDir(), parameters.getPackageName(), parameters.getScriptFile())); if (!dryRun) { final File resultJavaFile = resultFiles.iterator().next(); final JBBPParser parser = JBBPParser .prepare(text, JBBPBitOrder.LSB0, parameters.customFieldTypeProcessor, parameters.getParserFlags()); final String[] implementsSorted = parameters.getClassImplements().toArray(ARRAY_STRING_EMPTY); Arrays.sort(implementsSorted); final JBBPToJavaConverter.Builder builder = JBBPToJavaConverter.makeBuilder(parser) .setMapSubClassesInterfaces(parameters.getSubClassInterfaces()) .setMapSubClassesSuperclasses(parameters.getSubClassSuperclasses()) .setMainClassName(className) .setHeadComment(parameters.getHeadComment()) .setMainClassPackage(packageName) .setMainClassCustomText(parameters.getCustomText()) .setAddGettersSetters(parameters.isAddGettersSetters()) .setDoMainClassAbstract(parameters.isDoAbstract()) .setMainClassImplements(implementsSorted) .setParserFlags(parameters.getParserFlags()) .setSuperClass(parameters.getSuperClass()); if (parameters.isAddBinAnnotations()) { builder.addBinAnnotations(); } if (parameters.isAddNewInstanceMethods()) { builder.addNewInstanceMethods(); } if (parameters.isDoInternalClassesNonStatic()) { builder.doInternalClassesNonStatic(); } if (parameters.isDisableGenerateFields()) { builder.disableGenerateFields(); } FileUtils.write(resultJavaFile, builder.build().convert(), parameters.getEncodingOut()); } return resultFiles; } } ParserFlags.java000066400000000000000000000015501445000440600432100ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/plugin/common/converterspackage com.igormaznitsa.jbbp.plugin.common.converters; import com.igormaznitsa.jbbp.JBBPParser; import java.util.Set; import javax.annotation.Nullable; /** * Allowed parser flags. * * @since 1.3.0 */ public enum ParserFlags { /** * Do not throw exception if input stream ended unexpectedly and not all * fields have been read. */ SKIP_REMAINING_FIELDS_IF_EOF(JBBPParser.FLAG_SKIP_REMAINING_FIELDS_IF_EOF); private final int flag; ParserFlags(final int flag) { this.flag = flag; } public static int makeFromSet(@Nullable final Set set) { int result = 0; if (set != null) { for (final ParserFlags f : set) { result |= f.getFlag(); } } return result; } /** * Get the flag value. * * @return the flag value as integer */ public int getFlag() { return this.flag; } } Target.java000066400000000000000000000006351445000440600422300ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/plugin/common/converterspackage com.igormaznitsa.jbbp.plugin.common.converters; import javax.annotation.Nonnull; public enum Target { JAVA(new JavaConverter()); private final JBBPScriptTranslator JBBPScriptTranslator; Target(@Nonnull final JBBPScriptTranslator translator) { this.JBBPScriptTranslator = translator; } @Nonnull public JBBPScriptTranslator getTranslator() { return this.JBBPScriptTranslator; } } utils/000077500000000000000000000000001445000440600371015ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/plugin/commonCommonUtils.java000066400000000000000000000074661445000440600422320ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/plugin/common/utils/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.plugin.common.utils; import java.io.File; import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.io.FilenameUtils; /** * Misc auxiliary methods. * * @since 1.3.0 */ public final class CommonUtils { private CommonUtils() { } /** * Get charset name. If name is null then default charset name provided. * * @param charsetName name of charset, can be null * @return charset name, must not be null * @throws IllegalArgumentException if charset name can't be recognized */ @Nonnull public static String ensureEncodingName(@Nullable final String charsetName) { final Charset defaultCharset = Charset.defaultCharset(); try { return (charsetName == null) ? defaultCharset.name() : Charset.forName(charsetName.trim()).name(); } catch (IllegalCharsetNameException ex) { throw new IllegalArgumentException("Can't recognize charset for name '" + charsetName + '\''); } } /** * Extract class name from canonical Java class name * * @param canonicalJavaClassName canonical class name (like 'a.b.c.SomeClassName'), must not be null * @return extracted class name, must not be null but can be empty for case "a.b.c.d." */ @Nonnull public static String extractClassName(@Nonnull final String canonicalJavaClassName) { final int lastDot = canonicalJavaClassName.lastIndexOf('.'); if (lastDot < 0) { return canonicalJavaClassName.trim(); } return canonicalJavaClassName.substring(lastDot + 1).trim(); } /** * Extract package name from canonical Java class name * * @param fileNameWithoutExtension canonical class name (like 'a.b.c.SomeClassName'), must not be null * @return extracted package name, must not be null but can be empty */ @Nonnull public static String extractPackageName(@Nonnull final String fileNameWithoutExtension) { final int lastDot = fileNameWithoutExtension.lastIndexOf('.'); if (lastDot < 0) { return ""; } return fileNameWithoutExtension.substring(0, lastDot).trim(); } /** * Convert script file into path to Java class file. * * @param targetDir the target dir for generated sources, it can be null * @param classPackage class package to override extracted one from script name, it can be null * @param scriptFile the script file, must not be null * @return java source file for the script file */ @Nonnull public static File scriptFileToJavaFile(@Nullable final File targetDir, @Nullable final String classPackage, @Nonnull final File scriptFile) { final String rawFileName = FilenameUtils.getBaseName(scriptFile.getName()); final String className = CommonUtils.extractClassName(rawFileName); final String packageName = classPackage == null ? CommonUtils.extractPackageName(rawFileName) : classPackage; String fullClassName = packageName.isEmpty() ? className : packageName + '.' + className; fullClassName = fullClassName.replace('.', File.separatorChar) + ".java"; return new File(targetDir, fullClassName); } } java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/test/000077500000000000000000000000001445000440600261425ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/test/java/000077500000000000000000000000001445000440600270635ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/test/java/com/000077500000000000000000000000001445000440600276415ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/000077500000000000000000000000001445000440600323505ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/jbbp/000077500000000000000000000000001445000440600332655ustar00rootroot00000000000000plugin/000077500000000000000000000000001445000440600345045ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/jbbpcommon/000077500000000000000000000000001445000440600357745ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/jbbp/pluginutils/000077500000000000000000000000001445000440600371345ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/jbbp/plugin/commonCommonUtilsTest.java000066400000000000000000000031101445000440600431030ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/jbbp/plugin/common/utils/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.plugin.common.utils; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class CommonUtilsTest { @Test public void testExtractClassName() { assertEquals("", CommonUtils.extractClassName("")); assertEquals("", CommonUtils.extractClassName("a.b.c.")); assertEquals("hello", CommonUtils.extractClassName("hello")); assertEquals("hello", CommonUtils.extractClassName("a.b.c.hello")); assertEquals("hello", CommonUtils.extractClassName(".hello")); } @Test public void testExtractPackageName() { assertEquals("", CommonUtils.extractPackageName("")); assertEquals("", CommonUtils.extractPackageName("hello")); assertEquals("a", CommonUtils.extractPackageName("a.hello")); assertEquals("a.b.c", CommonUtils.extractPackageName("a.b.c.hello")); assertEquals("a.b.c", CommonUtils.extractPackageName("a.b.c.")); assertEquals("", CommonUtils.extractPackageName(".hello")); } } java-binary-block-parser-2.0.6/jbbp-plugins/pom.xml000066400000000000000000000051311445000440600222120ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-pom 2.0.6 jbbp-main-plugin-pom pom jbbp-plugin-common jbbp-maven jbbp-gradle com.igormaznitsa meta-annotations ${meta.version} com.igormaznitsa meta-utils ${meta.version} commons-io commons-io 2.8.0 gradle-tests jbbp-gradle-tests com.igormaznitsa meta-checker ${meta.version} 6 true true risky *.HelpMojo com.igormaznitsa.mvn.test.* com.igormaznitsa.mvn.tst.* check java-binary-block-parser-2.0.6/jbbp/000077500000000000000000000000001445000440600172165ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/pom.xml000066400000000000000000000260341445000440600205400ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-pom 2.0.6 jbbp jar jbbp JBBP (Java Binary Block Parser) is a small lightweight framework to parse binary data in Java, it has own DSL to describe parsing data format 1.8 1.8 org.openjdk.jmh jmh-core ${jmh.version} test org.openjdk.jmh jmh-generator-annprocess ${jmh.version} test org.apache.commons commons-lang3 3.12.0 test net.minidev json-smart 2.4.10 test commons-io commons-io 2.11.0 test commons-codec commons-codec 1.15 test jmh true org.codehaus.mojo build-helper-maven-plugin 3.0.0 add-jmh-test-source generate-test-sources add-test-source ${basedir}/src/test/jmh com.igormaznitsa jbbp-maven-plugin ${project.version} generate-jbbp generate-test-sources generate true ${project.basedir}/src/test/jbbp ${project.build.directory}/jbbp-test-generated org.codehaus.mojo exec-maven-plugin run-benchmarks test exec test java8 -classpath org.openjdk.jmh.Main .* -rf json -o ${project.basedir}/jmh-out.txt publish org.apache.maven.plugins maven-source-plugin generate-sources package jar org.apache.maven.plugins maven-javadoc-plugin false true protected UTF-8 generate-javadoc package jar 8 org.apache.maven.plugins maven-gpg-plugin sign-artifacts verify sign com.igormaznitsa uber-pom
parent
modules
profiles/profile/modules
true
upom
org.apache.maven.plugins maven-jar-plugin igormaznitsa.jbbp org.codehaus.mojo animal-sniffer-maven-plugin ensure-java-1.8-class-library test check org.codehaus.mojo.signature java18 1.0 ensure-android-3.0_r2-class-library test check net.sf.androidscents.signature android-api-level-11 3.0_r2 sun.misc.Unsafe
java-binary-block-parser-2.0.6/jbbp/src/000077500000000000000000000000001445000440600200055ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/assemble/000077500000000000000000000000001445000440600216005ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/assemble/bundle.xml000066400000000000000000000031231445000440600235720ustar00rootroot00000000000000 bundle false false jar ${project.build.directory} ${file.separator} *.jar.asc *.jar *.pom *.pom.asc *.zip java-binary-block-parser-2.0.6/jbbp/src/main/000077500000000000000000000000001445000440600207315ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/000077500000000000000000000000001445000440600216525ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/000077500000000000000000000000001445000440600224305ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/000077500000000000000000000000001445000440600251375ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/000077500000000000000000000000001445000440600260545ustar00rootroot00000000000000JBBPCustomFieldTypeProcessor.java000066400000000000000000000063311445000440600342610ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import java.io.IOException; /** * Allows to define and process own custom field types during parsing. * * @since 1.2.0 */ public interface JBBPCustomFieldTypeProcessor { /** * Get custom types of fields supported by the custom type field processor. * * @return array of strings where every string is custom field type in lower-case. */ String[] getCustomFieldTypes(); /** * Called by compiler to check parameters for custom field. * * @param fieldType field type info, it must not be null * @param fieldName name of the field, it can be null for anonymous fields * @param extraData number placed as extra value for field, followed by ':' if not presented then zero, if it is expression then -1 * @param isArray flag shows that the field describes an array * @return true if such configuration allowed, false otherwise */ boolean isAllowed(JBBPFieldTypeParameterContainer fieldType, String fieldName, int extraData, boolean isArray); /** * Read custom field from stream and return the data as a JBBP field. * * @param in the data source stream, must not be null * @param bitOrder the bit order defined for parsing, must not be null * @param parserFlags the flags defined for parsing * @param customTypeFieldInfo the current field type info, must not be null * @param fieldName the field name info, it can be null if the field is anonymous one * @param extraData extra numeric value for the field, followed by ':', if not presented then 0 * @param readWholeStream if true then the field is array which should contain parse data for whole stream till the end * @param arrayLength -1 if it is not array else length of the array to be read. * @return parsed data as JBBP field, must not be null * @throws IOException it can be thrown for transport errors */ JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, int parserFlags, JBBPFieldTypeParameterContainer customTypeFieldInfo, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException; } JBBPExternalValueProvider.java000066400000000000000000000024771445000440600336020ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; /** * The Interface describes a provider which can provide numeric size for decoded arrays by their names. * * @since 1.0 */ public interface JBBPExternalValueProvider { /** * Get an array size. * * @param fieldName the field name of the array * @param numericFieldMap the numeric field map contains information about already read fields * @param compiledBlock the compiled block for the script to provide extra information * @return the size of an array */ int provideArraySize(final String fieldName, final JBBPNamedNumericFieldMap numericFieldMap, final JBBPCompiledBlock compiledBlock); } JBBPNamedNumericFieldMap.java000066400000000000000000000234141445000440600332530ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.varlen.JBBPIntegerValueEvaluator; import com.igormaznitsa.jbbp.exceptions.JBBPEvalException; import com.igormaznitsa.jbbp.exceptions.JBBPException; import com.igormaznitsa.jbbp.exceptions.JBBPTooManyFieldsFoundException; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPNumericField; import com.igormaznitsa.jbbp.model.finder.JBBPFieldFinder; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.util.LinkedHashMap; import java.util.Map; /** * Implements a storage to keep named fields. it is not a thread-safe one * * @since 1.0 */ public final class JBBPNamedNumericFieldMap implements JBBPFieldFinder { /** * Inside map to get numeric fields for their name field descriptors. */ private final Map fieldMap; /** * Defined external value provider, it can be null. */ private final JBBPExternalValueProvider externalValueProvider; /** * Empty constructor which makes a map with null provider. */ public JBBPNamedNumericFieldMap() { this(null); } /** * A Constructor. * * @param externalValueProvider an external value provider, it can be null */ public JBBPNamedNumericFieldMap(final JBBPExternalValueProvider externalValueProvider) { this.fieldMap = new LinkedHashMap<>(); this.externalValueProvider = externalValueProvider; } /** * Get the external value provider. * * @return the external value provider or null if it is undefined */ public JBBPExternalValueProvider getExternalValueProvider() { return this.externalValueProvider; } /** * Get a numeric field for its field name info. * * @param namedField a field name info, it must not be null * @return the found field or null if it is not found */ public JBBPNumericField get(final JBBPNamedFieldInfo namedField) { return this.fieldMap.get(namedField); } /** * Put a numeric field into map. * * @param field a field to be added into map or replace already exists one, it * must not be null * @throws NullPointerException if the field is null or if it is an anonymous * field */ public void putField(final JBBPNumericField field) { JBBPUtils.assertNotNull(field, "Field must not be null"); final JBBPNamedFieldInfo fieldName = field.getNameInfo(); JBBPUtils.assertNotNull(fieldName, "Field name info must not be null"); this.fieldMap.put(fieldName, field); } /** * Remove a field for its field name info descriptor. * * @param nameInfo the field name info, it must not be null * @return removed numeric field or null if there was not any field for the info */ public JBBPNumericField remove(final JBBPNamedFieldInfo nameInfo) { JBBPUtils.assertNotNull(nameInfo, "Name info must not be null"); return this.fieldMap.remove(nameInfo); } /** * Find a registered field for its field offset in compiled script. * * @param offset the field offset * @return found field or null if there is not any found for the offset */ public JBBPNumericField findForFieldOffset(final int offset) { JBBPNumericField result = null; for (final Map.Entry f : fieldMap.entrySet()) { if (f.getKey().getFieldOffsetInCompiledBlock() == offset) { result = f.getValue(); break; } } return result; } @Override public T findFirstFieldForType(final Class fieldType) { JBBPUtils.assertNotNull(fieldType, "Type must not be null"); T result = null; for (final JBBPNumericField f : fieldMap.values()) { if (fieldType.isAssignableFrom(f.getClass())) { result = fieldType.cast(f); break; } } return result; } @Override public T findLastFieldForType(final Class fieldType) { JBBPUtils.assertNotNull(fieldType, "Type must not be null"); T result = null; for (final JBBPNumericField f : fieldMap.values()) { if (fieldType == f.getClass()) { result = fieldType.cast(f); } } return result; } @Override public T findFieldForType(final Class fieldType) { JBBPUtils.assertNotNull(fieldType, "Type must not be null"); int count = 0; T result = null; for (final JBBPNumericField f : fieldMap.values()) { if (fieldType == f.getClass()) { result = fieldType.cast(f); count++; } } if (count > 1) { throw new JBBPTooManyFieldsFoundException(count, "Too many fields detected", null, fieldType); } return result; } @Override public T findFieldForNameAndType(final String fieldName, final Class fieldType) { final String normalizedName = JBBPUtils.normalizeFieldNameOrPath(fieldName); JBBPUtils.assertNotNull(fieldType, "Field type must not be null"); T result = null; for (final Map.Entry f : fieldMap.entrySet()) { if (normalizedName.equals(f.getKey().getFieldName()) && fieldType.isAssignableFrom(f.getValue().getClass())) { result = fieldType.cast(f.getValue()); break; } } return result; } @Override public T findFieldForPathAndType(final String fieldPath, final Class fieldType) { final String normalizedPath = JBBPUtils.normalizeFieldNameOrPath(fieldPath); JBBPUtils.assertNotNull(fieldType, "Field type must not be null"); T result = null; for (final Map.Entry f : fieldMap.entrySet()) { if (normalizedPath.equals(f.getKey().getFieldPath()) && fieldType.isAssignableFrom(f.getValue().getClass())) { result = fieldType.cast(f.getValue()); break; } } return result; } @Override public JBBPAbstractField findFieldForName(final String fieldName) { final String normalizedName = JBBPUtils.normalizeFieldNameOrPath(fieldName); JBBPAbstractField result = null; for (final Map.Entry f : fieldMap.entrySet()) { if (normalizedName.equals(f.getKey().getFieldName())) { result = (JBBPAbstractField) f.getValue(); break; } } return result; } @Override public JBBPAbstractField findFieldForPath(final String fieldPath) { final String normalizedPath = JBBPUtils.normalizeFieldNameOrPath(fieldPath); JBBPAbstractField result = null; for (final Map.Entry f : fieldMap.entrySet()) { if (normalizedPath.equals(f.getKey().getFieldPath())) { result = (JBBPAbstractField) f.getValue(); break; } } return result; } @Override public boolean nameExists(final String fieldName) { final String normalizedName = JBBPUtils.normalizeFieldNameOrPath(fieldName); boolean result = false; for (final JBBPNamedFieldInfo f : fieldMap.keySet()) { if (normalizedName.equals(f.getFieldName())) { result = true; break; } } return result; } @Override public boolean pathExists(final String fieldPath) { final String normalizedPath = JBBPUtils.normalizeFieldNameOrPath(fieldPath); boolean result = false; for (final JBBPNamedFieldInfo f : fieldMap.keySet()) { if (normalizedPath.equals(f.getFieldPath())) { result = true; break; } } return result; } /** * Clear the map. */ public void clear() { this.fieldMap.clear(); } /** * Check that the map is empty. * * @return true if the map is empty, false otherwise */ public boolean isEmpty() { return this.fieldMap.isEmpty(); } /** * Get number of registered fields in the map. * * @return number of registered fields as integer */ public int size() { return this.fieldMap.size(); } /** * Ask the registered external value provider for a field value. * * @param externalFieldName the name of a field, it must not be null * @param compiledBlock the compiled block, it must not be null * @param evaluator an evaluator which is calling the method, it can be null * @return integer value for the field * @throws JBBPException if there is not any external value provider */ public int getExternalFieldValue(final String externalFieldName, final JBBPCompiledBlock compiledBlock, final JBBPIntegerValueEvaluator evaluator) { final String normalizedName = JBBPUtils.normalizeFieldNameOrPath(externalFieldName); if (this.externalValueProvider == null) { throw new JBBPEvalException( "Request for '" + externalFieldName + "' but there is not any value provider", evaluator); } else { return this.externalValueProvider.provideArraySize(normalizedName, this, compiledBlock); } } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/JBBPParser.java000066400000000000000000001107341445000440600306170ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp; import static com.igormaznitsa.jbbp.utils.JBBPUtils.ARRAY_FIELD_EMPTY; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.JBBPCompiler; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.conversion.JBBPToJavaConverter; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.compiler.varlen.JBBPIntegerValueEvaluator; import com.igormaznitsa.jbbp.exceptions.JBBPParsingException; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractArrayField; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBit; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayDouble; import com.igormaznitsa.jbbp.model.JBBPFieldArrayFloat; import com.igormaznitsa.jbbp.model.JBBPFieldArrayInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong; import com.igormaznitsa.jbbp.model.JBBPFieldArrayShort; import com.igormaznitsa.jbbp.model.JBBPFieldArrayString; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUShort; import com.igormaznitsa.jbbp.model.JBBPFieldBit; import com.igormaznitsa.jbbp.model.JBBPFieldBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldByte; import com.igormaznitsa.jbbp.model.JBBPFieldDouble; import com.igormaznitsa.jbbp.model.JBBPFieldFloat; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldShort; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.model.JBBPFieldUByte; import com.igormaznitsa.jbbp.model.JBBPFieldUInt; import com.igormaznitsa.jbbp.model.JBBPFieldUShort; import com.igormaznitsa.jbbp.model.JBBPNumericField; import com.igormaznitsa.jbbp.utils.JBBPIntCounter; import com.igormaznitsa.jbbp.utils.JBBPUtils; import com.igormaznitsa.jbbp.utils.TargetSources; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; /** * the Main class allows a user to parse a binary stream or block for predefined * and precompiled script. * * @since 1.0 */ @SuppressWarnings({"WeakerAccess"}) public final class JBBPParser { /** * Flag shows that if EOF and not whole packet has been read then remaining fields will be just ignored without exception. */ public static final int FLAG_SKIP_REMAINING_FIELDS_IF_EOF = 1; /** * Flag to recognize negative expression results by zero. * * @since 1.4.0 */ public static final int FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO = 2; /** * Empty structure array */ private static final JBBPFieldStruct[] EMPTY_STRUCT_ARRAY = new JBBPFieldStruct[0]; /** * the Compiled block contains compiled script and extra information. */ private final JBBPCompiledBlock compiledBlock; /** * The Bit order for stream operations. */ private final JBBPBitOrder bitOrder; /** * Special flags for parsing process. */ private final int flags; /** * Custom field type processor for the parser, it can be null. */ private final JBBPCustomFieldTypeProcessor customFieldTypeProcessor; /** * The Variable contains the last parsing counter value. */ private long finalStreamByteCounter; /** * Constructor. * * @param source the source script to parse binary blocks and streams, must * not be null * @param bitOrder the bit order for bit reading operations, must not be null * @param customFieldTypeProcessor custom field type processor for the parser instance, it can be null * @param flags special flags for parsing process * @see #FLAG_SKIP_REMAINING_FIELDS_IF_EOF */ private JBBPParser(final String source, final JBBPBitOrder bitOrder, final JBBPCustomFieldTypeProcessor customFieldTypeProcessor, final int flags) { JBBPUtils.assertNotNull(source, "Script is null"); JBBPUtils.assertNotNull(bitOrder, "Bit order is null"); this.customFieldTypeProcessor = customFieldTypeProcessor; this.bitOrder = bitOrder; this.flags = flags; try { this.compiledBlock = JBBPCompiler.compile(source, customFieldTypeProcessor); } catch (IOException ex) { throw new RuntimeException("Can't compile script for unexpected IOException", ex); } } /** * Ensure that an array length is not a negative one. * * @param length the array length to be checked * @param name the name information of a field, it can be null */ private static void assertArrayLength(final int length, final JBBPNamedFieldInfo name) { if (length < 0) { throw new JBBPParsingException("Detected negative calculated array length for field '" + (name == null ? "" : name.getFieldPath()) + "\' [" + JBBPUtils.int2msg(length) + ']'); } } /** * Prepare a parser for a script and a bit order. * * @param script a text script contains field order and types reference, it must not be null * @param bitOrder the bit order for reading operations, it must not be null * @return the prepared parser for the script * @see JBBPBitOrder#LSB0 * @see JBBPBitOrder#MSB0 */ public static JBBPParser prepare(final String script, final JBBPBitOrder bitOrder) { return new JBBPParser(script, bitOrder, null, 0); } /** * Prepare a parser for a script with defined bit order and special flags. * * @param script a text script contains field order and types reference, it * must not be null * @param bitOrder the bit order for reading operations, it must not be null * @param flags special flags for parsing * @return the prepared parser for the script * @see JBBPBitOrder#LSB0 * @see JBBPBitOrder#MSB0 * @see #FLAG_SKIP_REMAINING_FIELDS_IF_EOF * @since 1.1 */ public static JBBPParser prepare(final String script, final JBBPBitOrder bitOrder, final int flags) { return new JBBPParser(script, bitOrder, null, flags); } /** * Prepare a parser for a script with defined bit order and special flags. * * @param script a text script contains field order and types reference, it * must not be null * @param bitOrder the bit order for reading operations, it must not be null * @param customFieldTypeProcessor custom field type processor, it can be null * @param flags special flags for parsing * @return the prepared parser for the script * @see JBBPBitOrder#LSB0 * @see JBBPBitOrder#MSB0 * @see #FLAG_SKIP_REMAINING_FIELDS_IF_EOF * @since 1.1.1 */ public static JBBPParser prepare(final String script, final JBBPBitOrder bitOrder, final JBBPCustomFieldTypeProcessor customFieldTypeProcessor, final int flags) { return new JBBPParser(script, bitOrder, customFieldTypeProcessor, flags); } /** * Prepare a parser for a script with default bit order (LSB0) use. * * @param script a text script contains field order and types reference, it must not be null * @return the prepared parser for the script * @see JBBPBitOrder#LSB0 */ public static JBBPParser prepare(final String script) { return JBBPParser.prepare(script, JBBPBitOrder.LSB0); } /** * Prepare a parser for a script with default bit order (LSB0) use and with defined custom type field processor. * * @param script a text script contains field order and types reference, it * must not be null * @param customFieldTypeProcessor custom field type processor, can be null * @return the prepared parser for the script * @see JBBPBitOrder#LSB0 * @since 1.2.0 */ public static JBBPParser prepare(final String script, final JBBPCustomFieldTypeProcessor customFieldTypeProcessor) { return JBBPParser.prepare(script, JBBPBitOrder.LSB0, customFieldTypeProcessor, 0); } /** * Prepare a parser for a script with default bit order (LSB0) use and special flags * * @param script a text script contains field order and types reference, it * must not be null * @param flags special flags for parsing * @return the prepared parser for the script * @see JBBPBitOrder#LSB0 * @see #FLAG_SKIP_REMAINING_FIELDS_IF_EOF * @since 1.1 */ public static JBBPParser prepare(final String script, final int flags) { return JBBPParser.prepare(script, JBBPBitOrder.LSB0, flags); } /** * Inside method to parse a structure. * * @param inStream the input stream, must not be null * @param positionAtCompiledBlock the current position in the compiled script * block * @param varFieldProcessor a processor to process var fields, it can be null * but it will thrown NPE if a var field is met * @param namedNumericFieldMap the named numeric field map * @param positionAtNamedFieldList the current position at the named field * list * @param positionAtVarLengthProcessors the current position at the variable * array length processor list * @param skipStructureFields the flag shows that content of fields must be * skipped because the structure is skipped * @return list of read fields for the structure * @throws IOException it will be thrown for transport errors */ private List parseStruct(final JBBPBitInputStream inStream, final JBBPIntCounter positionAtCompiledBlock, final JBBPVarFieldProcessor varFieldProcessor, final JBBPNamedNumericFieldMap namedNumericFieldMap, final JBBPIntCounter positionAtNamedFieldList, final JBBPIntCounter positionAtVarLengthProcessors, final boolean skipStructureFields) throws IOException { final List structureFields = skipStructureFields ? null : new ArrayList<>(); final byte[] compiled = this.compiledBlock.getCompiledData(); boolean endStructureNotMet = true; while (endStructureNotMet && positionAtCompiledBlock.get() < compiled.length) { if (!inStream.hasAvailableData() && (flags & FLAG_SKIP_REMAINING_FIELDS_IF_EOF) != 0) { // Break reading because the ignore flag for EOF has been set break; } final int c = compiled[positionAtCompiledBlock.getAndIncrement()] & 0xFF; final boolean wideCode = (c & JBBPCompiler.FLAG_WIDE) != 0; final int ec = wideCode ? compiled[positionAtCompiledBlock.getAndIncrement()] & 0xFF : 0; final boolean extraFieldNumAsExpr = (ec & JBBPCompiler.EXT_FLAG_EXTRA_AS_EXPRESSION) != 0; final int code = (ec << 8) | c; final boolean fieldTypeDiff = (ec & JBBPCompiler.EXT_FLAG_EXTRA_DIFF_TYPE) != 0; final JBBPNamedFieldInfo name = (code & JBBPCompiler.FLAG_NAMED) == 0 ? null : compiledBlock.getNamedFields()[positionAtNamedFieldList.getAndIncrement()]; final JBBPByteOrder byteOrder = (code & JBBPCompiler.FLAG_LITTLE_ENDIAN) == 0 ? JBBPByteOrder.BIG_ENDIAN : JBBPByteOrder.LITTLE_ENDIAN; final boolean resultNotIgnored = !skipStructureFields; final int extraFieldNumExprResult; if (extraFieldNumAsExpr) { final JBBPIntegerValueEvaluator evaluator = this.compiledBlock.getArraySizeEvaluators()[positionAtVarLengthProcessors .getAndIncrement()]; int resultOfExpression; if (resultNotIgnored) { resultOfExpression = evaluator .eval(inStream, positionAtCompiledBlock.get(), this.compiledBlock, namedNumericFieldMap); if ((this.flags & FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO) != 0) { resultOfExpression = Math.max(resultOfExpression, 0); } } else { resultOfExpression = 0; } extraFieldNumExprResult = resultOfExpression; } else { extraFieldNumExprResult = 0; } final boolean wholeStreamArray; final int arrayLength; final int packedArraySizeOffset; switch (code & (JBBPCompiler.FLAG_ARRAY | (JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM << 8))) { case JBBPCompiler.FLAG_ARRAY: { final int pos = positionAtCompiledBlock.get(); arrayLength = JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); packedArraySizeOffset = positionAtCompiledBlock.get() - pos; wholeStreamArray = false; } break; case (JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM << 8): { wholeStreamArray = resultNotIgnored; packedArraySizeOffset = 0; arrayLength = 0; } break; case JBBPCompiler.FLAG_ARRAY | (JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM << 8): { final JBBPIntegerValueEvaluator evaluator = this.compiledBlock.getArraySizeEvaluators()[positionAtVarLengthProcessors .getAndIncrement()]; int resultOfExpression; if (resultNotIgnored) { resultOfExpression = evaluator .eval(inStream, positionAtCompiledBlock.get(), this.compiledBlock, namedNumericFieldMap); if ((this.flags & FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO) != 0) { resultOfExpression = Math.max(resultOfExpression, 0); } } else { resultOfExpression = 0; } arrayLength = resultOfExpression; packedArraySizeOffset = 0; assertArrayLength(arrayLength, name); wholeStreamArray = false; } break; default: { // it is not an array, just a single field packedArraySizeOffset = 0; wholeStreamArray = false; arrayLength = -1; } break; } JBBPAbstractField singleAtomicField = null; try { switch (code & 0xF) { case JBBPCompiler.CODE_RESET_COUNTER: { if (resultNotIgnored) { inStream.resetCounter(); } } break; case JBBPCompiler.CODE_ALIGN: { final int alignValue = extraFieldNumAsExpr ? extraFieldNumExprResult : JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); if (resultNotIgnored) { inStream.align(alignValue); } } break; case JBBPCompiler.CODE_SKIP: { final int skipByteNumber = extraFieldNumAsExpr ? extraFieldNumExprResult : JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); if (resultNotIgnored) { if (fieldTypeDiff) { singleAtomicField = new JBBPFieldInt(name, skipByteNumber); } else { if (skipByteNumber > 0) { final long skippedBytes = inStream.skip(skipByteNumber); if (skippedBytes != skipByteNumber) { throw new EOFException( "Can't skip " + skipByteNumber + " byte(s), skipped only " + skippedBytes + " byte(s)"); } } } } } break; case JBBPCompiler.CODE_BIT: { final int numberOfBits = extraFieldNumAsExpr ? extraFieldNumExprResult : JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); if (resultNotIgnored) { final JBBPBitNumber bitNumber = JBBPBitNumber.decode(numberOfBits); if (arrayLength < 0) { final int read = inStream.readBitField(bitNumber); singleAtomicField = new JBBPFieldBit(name, read & 0xFF, bitNumber); } else { structureFields.add(new JBBPFieldArrayBit(name, inStream.readBitsArray(wholeStreamArray ? -1 : arrayLength, bitNumber), bitNumber)); } } } break; case JBBPCompiler.CODE_VAR: { final int extraField = extraFieldNumAsExpr ? extraFieldNumExprResult : JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); if (resultNotIgnored) { if (arrayLength < 0) { singleAtomicField = varFieldProcessor .readVarField(inStream, name, extraField, byteOrder, namedNumericFieldMap); JBBPUtils.assertNotNull(singleAtomicField, "A Var processor must not return null as a result of a field reading"); if (singleAtomicField instanceof JBBPAbstractArrayField) { throw new JBBPParsingException( "A Var field processor has returned an array value instead of a field value [" + name + ':' + extraField + ']'); } if (singleAtomicField.getNameInfo() != name) { throw new JBBPParsingException( "Detected wrong name for a read field , must be " + name + " but detected " + singleAtomicField.getNameInfo() + ']'); } } else { final JBBPAbstractArrayField array = varFieldProcessor .readVarArray(inStream, wholeStreamArray ? -1 : arrayLength, name, extraField, byteOrder, namedNumericFieldMap); JBBPUtils.assertNotNull(array, "A Var processor must not return null as a result of an array field reading [" + name + ':' + extraField + ']'); if (array.getNameInfo() != name) { throw new JBBPParsingException( "Detected wrong name for a read field array, must be " + name + " but detected " + array.getNameInfo() + ']'); } structureFields.add(array); } } } break; case JBBPCompiler.CODE_CUSTOMTYPE: { final int extraData = extraFieldNumAsExpr ? extraFieldNumExprResult : JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); if (resultNotIgnored) { final JBBPFieldTypeParameterContainer fieldTypeInfo = this.compiledBlock.getCustomTypeFields()[JBBPUtils .unpackInt(compiled, positionAtCompiledBlock)]; final JBBPAbstractField field = this.customFieldTypeProcessor .readCustomFieldType(inStream, this.bitOrder, this.flags, fieldTypeInfo, name, extraData, wholeStreamArray, arrayLength); JBBPUtils.assertNotNull(field, "Must not return null as read result"); if (arrayLength < 0) { singleAtomicField = field; } else { structureFields.add(field); } } } break; case JBBPCompiler.CODE_BYTE: { if (resultNotIgnored) { if (arrayLength < 0) { singleAtomicField = fieldTypeDiff ? new JBBPFieldUInt(name, inStream.readInt(byteOrder) & 0xFFFFFFFFL) : new JBBPFieldByte(name, (byte) inStream.readByte()); } else { structureFields.add(fieldTypeDiff ? new JBBPFieldArrayUInt(name, inStream.readIntArray(wholeStreamArray ? -1 : arrayLength, byteOrder)) : new JBBPFieldArrayByte(name, inStream.readByteArray(wholeStreamArray ? -1 : arrayLength, byteOrder))); } } } break; case JBBPCompiler.CODE_UBYTE: { if (resultNotIgnored) { if (arrayLength < 0) { singleAtomicField = new JBBPFieldUByte(name, (byte) inStream.readByte()); } else { structureFields.add(new JBBPFieldArrayUByte(name, inStream.readByteArray(wholeStreamArray ? -1 : arrayLength, byteOrder))); } } } break; case JBBPCompiler.CODE_BOOL: { if (resultNotIgnored) { if (arrayLength < 0) { singleAtomicField = fieldTypeDiff ? new JBBPFieldString(name, inStream.readString(byteOrder)) : new JBBPFieldBoolean(name, inStream.readBoolean()); } else { structureFields.add(fieldTypeDiff ? new JBBPFieldArrayString(name, inStream.readStringArray(wholeStreamArray ? -1 : arrayLength, byteOrder)) : new JBBPFieldArrayBoolean(name, inStream.readBoolArray(wholeStreamArray ? -1 : arrayLength)) ); } } } break; case JBBPCompiler.CODE_INT: { if (resultNotIgnored) { if (arrayLength < 0) { singleAtomicField = fieldTypeDiff ? new JBBPFieldFloat(name, inStream.readFloat(byteOrder)) : new JBBPFieldInt(name, inStream.readInt(byteOrder)); } else { structureFields.add(fieldTypeDiff ? new JBBPFieldArrayFloat(name, inStream.readFloatArray(wholeStreamArray ? -1 : arrayLength, byteOrder)) : new JBBPFieldArrayInt(name, inStream.readIntArray(wholeStreamArray ? -1 : arrayLength, byteOrder)) ); } } } break; case JBBPCompiler.CODE_LONG: { if (resultNotIgnored) { if (arrayLength < 0) { singleAtomicField = fieldTypeDiff ? new JBBPFieldDouble(name, inStream.readDouble(byteOrder)) : new JBBPFieldLong(name, inStream.readLong(byteOrder)); } else { structureFields.add(fieldTypeDiff ? new JBBPFieldArrayDouble(name, inStream.readDoubleArray(wholeStreamArray ? -1 : arrayLength, byteOrder)) : new JBBPFieldArrayLong(name, inStream.readLongArray(wholeStreamArray ? -1 : arrayLength, byteOrder)) ); } } } break; case JBBPCompiler.CODE_SHORT: { if (resultNotIgnored) { if (arrayLength < 0) { final int value = inStream.readUnsignedShort(byteOrder); singleAtomicField = new JBBPFieldShort(name, (short) value); } else { structureFields.add(new JBBPFieldArrayShort(name, inStream.readShortArray(wholeStreamArray ? -1 : arrayLength, byteOrder))); } } } break; case JBBPCompiler.CODE_USHORT: { if (resultNotIgnored) { if (arrayLength < 0) { final int value = inStream.readUnsignedShort(byteOrder); singleAtomicField = new JBBPFieldUShort(name, (short) value); } else { structureFields.add(new JBBPFieldArrayUShort(name, inStream.readShortArray(wholeStreamArray ? -1 : arrayLength, byteOrder))); } } } break; case JBBPCompiler.CODE_STRUCT_START: { if (arrayLength < 0) { final List structFields = parseStruct(inStream, positionAtCompiledBlock, varFieldProcessor, namedNumericFieldMap, positionAtNamedFieldList, positionAtVarLengthProcessors, skipStructureFields); // skip offset JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); if (resultNotIgnored) { structureFields .add(new JBBPFieldStruct(name, structFields.toArray(ARRAY_FIELD_EMPTY))); } } else { final int nameFieldCurrent = positionAtNamedFieldList.get(); final int varLenProcCurrent = positionAtVarLengthProcessors.get(); final JBBPFieldStruct[] result; if (resultNotIgnored) { if (wholeStreamArray) { // read till the stream end final List list = new ArrayList<>(); while (inStream.hasAvailableData()) { positionAtNamedFieldList.set(nameFieldCurrent); positionAtVarLengthProcessors.set(varLenProcCurrent); final List fieldsForStruct = parseStruct(inStream, positionAtCompiledBlock, varFieldProcessor, namedNumericFieldMap, positionAtNamedFieldList, positionAtVarLengthProcessors, skipStructureFields); list.add(new JBBPFieldStruct(name, fieldsForStruct)); final int structStart = JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); if (inStream.hasAvailableData()) { positionAtCompiledBlock.set(structStart + (wideCode ? 2 : 1)); } } result = list.isEmpty() ? EMPTY_STRUCT_ARRAY : list.toArray(EMPTY_STRUCT_ARRAY); } else { // read number of items if (arrayLength == 0) { // skip the structure result = EMPTY_STRUCT_ARRAY; parseStruct(inStream, positionAtCompiledBlock, varFieldProcessor, namedNumericFieldMap, positionAtNamedFieldList, positionAtVarLengthProcessors, true); JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); } else { result = new JBBPFieldStruct[arrayLength]; for (int i = 0; i < arrayLength; i++) { final List fieldsForStruct = parseStruct(inStream, positionAtCompiledBlock, varFieldProcessor, namedNumericFieldMap, positionAtNamedFieldList, positionAtVarLengthProcessors, skipStructureFields); final int structBodyStart = JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); result[i] = new JBBPFieldStruct(name, fieldsForStruct); if (i < arrayLength - 1) { // not the last positionAtNamedFieldList.set(nameFieldCurrent); positionAtVarLengthProcessors.set(varLenProcCurrent); positionAtCompiledBlock .set(structBodyStart + packedArraySizeOffset + (wideCode ? 2 : 1)); } } } } if (result != null) { structureFields.add(new JBBPFieldArrayStruct(name, result)); } } else { parseStruct(inStream, positionAtCompiledBlock, varFieldProcessor, namedNumericFieldMap, positionAtNamedFieldList, positionAtVarLengthProcessors, skipStructureFields); JBBPUtils.unpackInt(compiled, positionAtCompiledBlock); } } } break; case JBBPCompiler.CODE_STRUCT_END: { // we just left the method and the caller must process the structure offset start address for the structure endStructureNotMet = false; } break; default: throw new Error("Detected unexpected field type! Contact developer! [" + code + ']'); } } catch (IOException ex) { if (name == null) { throw ex; } else { throw new JBBPParsingException( "Can't parse field '" + name.getFieldPath() + "' for IOException", ex); } } if (singleAtomicField != null) { structureFields.add(singleAtomicField); if (namedNumericFieldMap != null && singleAtomicField instanceof JBBPNumericField && name != null) { namedNumericFieldMap.putField((JBBPNumericField) singleAtomicField); } } } return structureFields; } /** * Parse an input stream. * * @param in an input stream which content should be parsed, it must not be * null * @return the parsed content as the root structure * @throws IOException it will be thrown for transport errors */ public JBBPFieldStruct parse(final InputStream in) throws IOException { return this.parse(in, null, null); } /** * Parse am input stream with defined external value provider. * * @param in an input stream which content will be parsed, it must not be null * @param varFieldProcessor a var field processor, it may be null if there is * not any var field in a script, otherwise NPE will be thrown during parsing * @param externalValueProvider an external value provider, it can be null but * only if the script doesn't have fields desired the provider * @return the parsed content as the root structure * @throws IOException it will be thrown for transport errors */ public JBBPFieldStruct parse(final InputStream in, final JBBPVarFieldProcessor varFieldProcessor, final JBBPExternalValueProvider externalValueProvider) throws IOException { final JBBPBitInputStream bitInStream = in instanceof JBBPBitInputStream ? (JBBPBitInputStream) in : new JBBPBitInputStream(in, bitOrder); this.finalStreamByteCounter = bitInStream.getCounter(); final JBBPNamedNumericFieldMap fieldMap; if (this.compiledBlock.hasEvaluatedSizeArrays() || this.compiledBlock.hasVarFields()) { fieldMap = new JBBPNamedNumericFieldMap(externalValueProvider); } else { fieldMap = null; } if (this.compiledBlock.hasVarFields()) { JBBPUtils.assertNotNull(varFieldProcessor, "The Script contains VAR fields, a var field processor must be provided"); } try { return new JBBPFieldStruct(new JBBPNamedFieldInfo("", "", -1), parseStruct(bitInStream, new JBBPIntCounter(), varFieldProcessor, fieldMap, new JBBPIntCounter(), new JBBPIntCounter(), false)); } finally { this.finalStreamByteCounter = bitInStream.getCounter(); } } /** * Get the parse flags. * * @return the parser flags * @see #FLAG_SKIP_REMAINING_FIELDS_IF_EOF */ public int getFlags() { return this.flags; } /** * Parse a byte array content. * * @param array a byte array which content should be parsed, it must not be * null * @return the parsed content as the root structure * @throws IOException it will be thrown for transport errors */ public JBBPFieldStruct parse(final byte[] array) throws IOException { JBBPUtils.assertNotNull(array, "Array must not be null"); return this.parse(new ByteArrayInputStream(array), null, null); } /** * Parse a byte array content. * * @param array a byte array which content should be parsed, it must not be * null * @param varFieldProcessor a var field processor, it may be null if there is * not any var field in a script, otherwise NPE will be thrown during parsing * @param externalValueProvider an external value provider, it can be null but * only if the script doesn't have fields desired the provider * @return the parsed content as the root structure * @throws IOException it will be thrown for transport errors */ public JBBPFieldStruct parse(final byte[] array, final JBBPVarFieldProcessor varFieldProcessor, final JBBPExternalValueProvider externalValueProvider) throws IOException { JBBPUtils.assertNotNull(array, "Array must not be null"); return this.parse(new ByteArrayInputStream(array), varFieldProcessor, externalValueProvider); } /** * Get the final input stream byte counter value for the last parsing * operation. It is loaded just after exception or parsing completion. NB: It * is appropriate one only if the parsing didn't make any counter reset * operation. * * @return the last parsing byte counter value */ public long getFinalStreamByteCounter() { return this.finalStreamByteCounter; } /** * Get compiled block containing compiled information for the parser. * * @return compiled block, must not be null * @since 1.3.0 */ public JBBPCompiledBlock getCompiledBlock() { return this.compiledBlock; } /** * Convert the prepared parser into sources. It doesn't provide way to define different flag for conversion, it uses default flags for converters and provided for short fast way. * * @param target target to generate sources, must not be null * @param name name of result, depends on target, must not be null, for instance class name (example 'com.test.jbbp.Parser') * @return list of source items generated during operation, must not be null and must not be empty * @throws IllegalArgumentException if target is unsupported * @see JBBPToJavaConverter * @see JBBPToJavaConverter.Builder * @since 1.3.0 */ public List convertToSrc(final TargetSources target, final String name) { JBBPUtils.assertNotNull(name, "Name must not be null"); if (target == TargetSources.JAVA) { final Properties metadata = new Properties(); metadata.setProperty("script", this.compiledBlock.getSource()); metadata.setProperty("name", name); metadata.setProperty("target", target.name()); metadata.setProperty("converter", JBBPToJavaConverter.class.getCanonicalName()); final int nameStart = name.lastIndexOf('.'); final String packageName; final String className; if (nameStart < 0) { packageName = ""; className = name; } else { packageName = name.substring(0, nameStart); className = name.substring(nameStart + 1); } final String resultSources = JBBPToJavaConverter.makeBuilder(this).setMainClassPackage(packageName) .setMainClassName(className).build().convert(); final Map resultMap = Collections.singletonMap(name.replace('.', '/') + ".java", resultSources); return Collections.singletonList(new ResultSrcItem() { @Override public Properties getMetadata() { return metadata; } @Override public Map getResult() { return resultMap; } }); } throw new IllegalArgumentException("Unsupported target : " + target); } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/JBBPVarFieldProcessor.java000066400000000000000000000074041445000440600327560ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractArrayField; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import java.io.IOException; /** * The Interface describes a class which can process VAR fields from a stream. * * @since 1.0 */ public interface JBBPVarFieldProcessor { /** * Read a field array from a stream. The Method must read a field array from a stream and return the value with the provided field name info. * * @param inStream the data source bit stream, it must not be null * @param arraySize the array size, if it is negative one then whole stream must be read * @param fieldName the field name info for the VAR field, it can be null for anonymous fields * @param extraValue the extra value for the field, by default it is 0, it is the integer value after ':' char in the field type * @param byteOrder the byte order for the field, it must not be null * @param numericFieldMap the numeric field map for the session, it must not be null, it can be used for access to already read values of another numeric fields. * @return a field array without nulls as values, it must not return null * @throws IOException it can be thrown for transport errors or another process exceptions */ JBBPAbstractArrayField readVarArray(JBBPBitInputStream inStream, int arraySize, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException; /** * Read a field from a stream. The Method must read a field from a stream and return it with the provided name field info. * * @param inStream the data source bit stream, it must not be null * @param fieldName the field name info for the VAR field, it can be null for * anonymous fields * @param extraValue the extra value for the field, by default it is 0, it is * the integer value after ':' char in the field type * @param byteOrder the byte order for the field, it must not be null * @param numericFieldMap the numeric field map for the session, it must not * be null, it can be used for access to already read values of another * numeric fields. * @return a read field object, it must not return null * @throws IOException it should be thrown for transport errors */ JBBPAbstractField readVarField(JBBPBitInputStream inStream, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException; } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/ResultSrcItem.java000066400000000000000000000022761445000440600314730ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp; import java.util.Map; import java.util.Properties; /** * Contains result of conversion of JBBPParser into source. * * @since 1.3.0 */ public interface ResultSrcItem { /** * Get metadata generated during operation for the item, depends on converter. * * @return the metadata container as properties, must not be null */ Properties getMetadata(); /** * Get generated sources mapped by some key which defined by converter. * * @return map containing result of conversion, must not be null and can't be empty. */ Map getResult(); } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/000077500000000000000000000000001445000440600276665ustar00rootroot00000000000000JBBPCompiledBlock.java000066400000000000000000000216251445000440600336250ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.compiler.varlen.JBBPIntegerValueEvaluator; import com.igormaznitsa.jbbp.exceptions.JBBPException; import com.igormaznitsa.jbbp.exceptions.JBBPIllegalArgumentException; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.util.ArrayList; import java.util.List; /** * The Class describes a data block contains compiled information for a bin * parser script. * * @since 1.0 */ public final class JBBPCompiledBlock { private static final JBBPNamedFieldInfo[] ARRAY_FIELDINFO_EMPTY = new JBBPNamedFieldInfo[0]; private static final JBBPIntegerValueEvaluator[] ARRAY_INTEVALUATOR_EMPTY = new JBBPIntegerValueEvaluator[0]; private static final JBBPFieldTypeParameterContainer[] ARRAY_FIELDTYPE_EMPTY = new JBBPFieldTypeParameterContainer[0]; /** * The Array of named field info items. */ private final JBBPNamedFieldInfo[] namedFieldData; /** * The Source which was used for compilation. */ private final String source; /** * The Compiled script data. */ private final byte[] compiledArray; /** * The Array of variable size array evaluators. */ private final JBBPIntegerValueEvaluator[] arraySizeEvaluators; /** * The Array of variable size array evaluators. */ private final JBBPFieldTypeParameterContainer[] customTypeFields; /** * The Flag shows that the compiled block contains var fields. */ private final boolean hasVarFields; /** * The Class * * @param source the source used for compilation, must not be null * @param namedFields named field info array * @param arraySizeEvaluators array size evaluator array * @param compiledData compiled data block * @param hasVarFields the flag shows that te block contains var fields */ private JBBPCompiledBlock(final String source, final JBBPNamedFieldInfo[] namedFields, final JBBPIntegerValueEvaluator[] arraySizeEvaluators, final byte[] compiledData, final boolean hasVarFields, final JBBPFieldTypeParameterContainer[] customTypeFields) { this.source = source; this.namedFieldData = namedFields; this.hasVarFields = hasVarFields; this.compiledArray = compiledData; this.arraySizeEvaluators = arraySizeEvaluators; this.customTypeFields = customTypeFields; } /** * The Method allows to create the new builder. * * @return the new builder */ public static Builder prepare() { return new Builder(); } /** * Get the source which was used for compilation * * @return the source as string */ public String getSource() { return this.source; } /** * Check that the compiled block has var fields or arrays. * * @return true if the compiled block contains any VAR field or VAR array, * false otherwise */ public boolean hasVarFields() { return this.hasVarFields; } /** * Check that the compiled block contains array fields with calculated size. * * @return true if calculated size arrays are presented, false otherwise */ public boolean hasEvaluatedSizeArrays() { return this.arraySizeEvaluators != null; } /** * Get the compiled data block * * @return the compiled data block contains the byte code of the script */ public byte[] getCompiledData() { return this.compiledArray; } /** * Get array contains the named field array * * @return the named field item array */ public JBBPNamedFieldInfo[] getNamedFields() { return this.namedFieldData; } /** * Get array contains parameter info of detected custom type fields. * * @return the array contains the info for every registered custom type field. */ public JBBPFieldTypeParameterContainer[] getCustomTypeFields() { return this.customTypeFields; } /** * Get the array size evaluators * * @return the array size evaluators, it can be null if there is not any * variable size array */ public JBBPIntegerValueEvaluator[] getArraySizeEvaluators() { return this.arraySizeEvaluators; } /** * Find a field for its path. * * @param fieldPath a field path * @return a field to be found for the path, null otherwise */ public JBBPNamedFieldInfo findFieldForPath(final String fieldPath) { JBBPNamedFieldInfo result = null; for (final JBBPNamedFieldInfo f : this.namedFieldData) { if (f.getFieldPath().equals(fieldPath)) { result = f; break; } } return result; } /** * Find offset of a field in the compiled block for its field path. * * @param fieldPath a field path, it must not be null * @return the offset as integer for the field path * @throws JBBPException if the field is not found */ public int findFieldOffsetForPath(final String fieldPath) { for (final JBBPNamedFieldInfo f : this.namedFieldData) { if (f.getFieldPath().equals(fieldPath)) { return f.getFieldOffsetInCompiledBlock(); } } throw new JBBPIllegalArgumentException("Unknown field path [" + fieldPath + ']'); } /** * Inside helper to build a compiled block */ public final static class Builder { private final List namedFields = new ArrayList<>(); private final List varLenProcessors = new ArrayList<>(); private final List customTypeFields = new ArrayList<>(); private String source; private byte[] compiledData; private boolean hasVarFields; /** * Build a compiled block based on the data and check that all needed data * is provided. * * @return a compiled block * @throws NullPointerException if needed info is not provided */ public JBBPCompiledBlock build() { JBBPUtils.assertNotNull(source, "Source is not defined"); JBBPUtils.assertNotNull(compiledData, "Compiled data is not defined"); return new JBBPCompiledBlock(this.source, this.namedFields.toArray(ARRAY_FIELDINFO_EMPTY), this.varLenProcessors.isEmpty() ? null : this.varLenProcessors.toArray(ARRAY_INTEVALUATOR_EMPTY), this.compiledData, this.hasVarFields, this.customTypeFields.toArray(ARRAY_FIELDTYPE_EMPTY)); } /** * Set the flag that the compiled block has var fields or array. * * @param flag the flag, true shows that the compiled block contains a var * file, false otherwise * @return this object */ public Builder setHasVarFields(final boolean flag) { this.hasVarFields = flag; return this; } /** * Set the source. * * @param source the source for compiled block. * @return this object */ public Builder setSource(final String source) { this.source = source; return this; } /** * Set the compiled data block. * * @param compiledData the compiled data block. * @return this object */ public Builder setCompiledData(final byte[] compiledData) { this.compiledData = compiledData; return this; } /** * Set the named field info items. * * @param namedFields named field info item list * @return this object */ public Builder setNamedFieldData(final List namedFields) { this.namedFields.clear(); if (namedFields != null) { this.namedFields.addAll(namedFields); } return this; } /** * Set the variable size array evaluators * * @param evaluators list of evaluators, it can be null * @return this object */ public Builder setArraySizeEvaluators(final List evaluators) { this.varLenProcessors.clear(); if (evaluators != null) { this.varLenProcessors.addAll(evaluators); } return this; } /** * Set list of fields processed by custom type field processor. * * @param list list of field token info, it can be null * @return this object */ public Builder setCustomTypeFields(final List list) { this.customTypeFields.clear(); if (list != null) { this.customTypeFields.addAll(list); } return this; } } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/JBBPCompiler.java000066400000000000000000000667771445000440600327700ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPToken; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPTokenizer; import com.igormaznitsa.jbbp.compiler.varlen.JBBPEvaluatorFactory; import com.igormaznitsa.jbbp.compiler.varlen.JBBPIntegerValueEvaluator; import com.igormaznitsa.jbbp.exceptions.JBBPCompilationException; import com.igormaznitsa.jbbp.exceptions.JBBPException; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPFieldDouble; import com.igormaznitsa.jbbp.model.JBBPFieldFloat; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldUInt; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** * The Class implements the compiler of a bin source script represented as a * text value into byte codes. * * @since 1.0 */ public final class JBBPCompiler { /** * The Byte code of the 'ALIGN' command. */ public static final int CODE_ALIGN = 0x01; /** * The Byte code of the 'BIT' command. */ public static final int CODE_BIT = 0x02; /** * The Byte code of the 'BOOL' (boolean) command. */ public static final int CODE_BOOL = 0x03; /** * The Byte code of the 'UBYTE' (unsigned byte) command. */ public static final int CODE_UBYTE = 0x04; /** * The Byte code of the 'BYTE' command. */ public static final int CODE_BYTE = 0x05; /** * p * The Byte code of the 'USHORT' (unsigned short) command. */ public static final int CODE_USHORT = 0x06; /** * The Byte code of the 'SHORT' command. */ public static final int CODE_SHORT = 0x07; /** * The Byte code of the 'INT' (integer) command. */ public static final int CODE_INT = 0x08; /** * The Byte code of the 'LONG' command. */ public static final int CODE_LONG = 0x09; /** * The Byte code of the 'STRUCTURE_START' command. */ public static final int CODE_STRUCT_START = 0x0A; /** * The Byte code of the 'STRUCTURE_END' command. */ public static final int CODE_STRUCT_END = 0x0B; /** * The Byte code of the SKIP command. */ public static final int CODE_SKIP = 0x0C; /** * The Byte code of the VAR command. It describes a request to an external * processor to load values from a stream. */ public static final int CODE_VAR = 0x0D; /** * The Byte code of the RESET COUNTER command. It resets the inside counter of * the input stream. */ public static final int CODE_RESET_COUNTER = 0x0E; /** * The Byte code shows that field should be processed by custom field type * processor. */ public static final int CODE_CUSTOMTYPE = 0x0F; /** * The Byte-Code Flag shows that the field is a named one. */ public static final int FLAG_NAMED = 0x10; /** * The Byte-Code Flag shows that the field is an array but it must be omitted * for unlimited field arrays. */ public static final int FLAG_ARRAY = 0x20; /** * The Byte-Code Flag shows that a multi-byte field must be decoded as * Little-endian one. */ public static final int FLAG_LITTLE_ENDIAN = 0x40; /** * The Flag shows that the byte code is wide and contains extra byte in the * next position of compiled block. */ public static final int FLAG_WIDE = 0x80; /** * The flag (placed only in the second byte of wide codes) shows that the * field is an array which calculated size or unlimited and must be read till * the end of a stream. */ public static final int EXT_FLAG_EXPRESSION_OR_WHOLESTREAM = 0x01; /** * The flag shows that the extra numeric value for field should be recognized * not as number but as expression. */ public static final int EXT_FLAG_EXTRA_AS_EXPRESSION = 0x02; /** * The Flag shows that the type of data should be recognized differently. * as float (if int), as double (if long), as virtual value (if skip). * * @since 1.4.0 */ public static final int EXT_FLAG_EXTRA_DIFF_TYPE = 0x04; public static JBBPCompiledBlock compile(final String script) throws IOException { return compile(script, null); } private static void assertTokenNotArray(final String fieldType, final JBBPToken token) { if (token.getArraySizeAsString() != null) { final String fieldName = token.getFieldName() == null ? "" : token.getFieldName(); throw new JBBPCompilationException('\'' + fieldType + "' can't be array (" + fieldName + ')', token); } } private static void assertTokenNamed(final String fieldType, final JBBPToken token) { if (token.getFieldName() == null) { throw new JBBPCompilationException('\'' + fieldType + "' must be named", token); } } private static void assertTokenNotNamed(final String fieldType, final JBBPToken token) { if (token.getFieldName() != null) { throw new JBBPCompilationException( '\'' + fieldType + "' must not be named (" + token.getFieldName() + ')', token); } } private static void assertTokenHasExtraData(final String fieldType, final JBBPToken token) { if (token.getFieldTypeParameters().getExtraData() == null) { throw new JBBPCompilationException('\'' + fieldType + "\' doesn't have extra value", token); } } /** * Compile a text script into its byte code representation/ * * @param script a text script to be compiled, must not be null. * @param customTypeFieldProcessor processor to process custom type fields, * can be null * @return a compiled block for the script. * @throws IOException it will be thrown for an inside IO error. * @throws JBBPException it will be thrown for any logical or work exception * for the parser and compiler */ public static JBBPCompiledBlock compile(final String script, final JBBPCustomFieldTypeProcessor customTypeFieldProcessor) throws IOException { JBBPUtils.assertNotNull(script, "Script must not be null"); final JBBPCompiledBlock.Builder builder = JBBPCompiledBlock.prepare().setSource(script); final List namedFields = new ArrayList<>(); final List customTypeFields = new ArrayList<>(); final List varLengthEvaluators = new ArrayList<>(); final ByteArrayOutputStream out = new ByteArrayOutputStream(); int offset = 0; final List structureStack = new ArrayList<>(); final JBBPTokenizer parser = new JBBPTokenizer(script, customTypeFieldProcessor); int fieldUnrestrictedArrayOffset = -1; boolean hasVarFields = false; for (final JBBPToken token : parser) { if (token.isComment()) { continue; } final int code = prepareCodeForToken(token, customTypeFieldProcessor); final int startFieldOffset = offset; final int extraCode = code >>> 8; out.write(code); offset++; if ((code & FLAG_WIDE) != 0) { out.write(extraCode); offset++; } StructStackItem currentClosedStructure = null; boolean writeExtraFieldNumberInCompiled = false; int extraFieldNumberAsInt = -1; int customTypeFieldIndex = -1; final boolean extraFieldNumericDataAsExpression = ((code >>> 8) & EXT_FLAG_EXTRA_AS_EXPRESSION) != 0; final boolean fieldTypeDiff = ((code >>> 8) & EXT_FLAG_EXTRA_DIFF_TYPE) != 0; switch (code & 0xF) { case CODE_BOOL: case CODE_BYTE: case CODE_UBYTE: case CODE_SHORT: case CODE_USHORT: case CODE_INT: case CODE_CUSTOMTYPE: case CODE_LONG: { if ((code & 0x0F) == CODE_CUSTOMTYPE) { if (extraFieldNumericDataAsExpression) { varLengthEvaluators.add(JBBPEvaluatorFactory.getInstance() .make(token.getFieldTypeParameters().getExtraDataExpression(), namedFields, out.toByteArray())); } else { final String extraDataAsStr = token.getFieldTypeParameters().getExtraData(); if (extraDataAsStr == null) { extraFieldNumberAsInt = 0; } else { try { extraFieldNumberAsInt = Integer.parseInt(extraDataAsStr); } catch (NumberFormatException ex) { throw new JBBPCompilationException("Can't parse extra data, must be numeric", token); } } writeExtraFieldNumberInCompiled = true; } if (customTypeFieldProcessor .isAllowed(token.getFieldTypeParameters(), token.getFieldName(), extraFieldNumberAsInt, token.isArray())) { customTypeFieldIndex = customTypeFields.size(); customTypeFields.add(token.getFieldTypeParameters()); } else { throw new JBBPCompilationException("Illegal parameters for custom type field", token); } } } break; case CODE_SKIP: { if (fieldTypeDiff) { assertTokenNotArray("val", token); assertTokenNamed("val", token); assertTokenHasExtraData("val", token); } else { assertTokenNotArray("skip", token); assertTokenNotNamed("skip", token); } if (extraFieldNumericDataAsExpression) { varLengthEvaluators.add(JBBPEvaluatorFactory.getInstance() .make(token.getFieldTypeParameters().getExtraDataExpression(), namedFields, out.toByteArray())); } else { final String extraNumberAsStr = token.getFieldTypeParameters().getExtraData(); writeExtraFieldNumberInCompiled = true; if (extraNumberAsStr == null) { extraFieldNumberAsInt = 1; } else { try { extraFieldNumberAsInt = Integer.parseInt(extraNumberAsStr); if (!fieldTypeDiff) { assertNonNegativeValue(extraFieldNumberAsInt, token); } } catch (final NumberFormatException ex) { extraFieldNumberAsInt = -1; } } } } break; case CODE_ALIGN: { assertTokenNotArray("align", token); assertTokenNotNamed("align", token); if (extraFieldNumericDataAsExpression) { varLengthEvaluators.add(JBBPEvaluatorFactory.getInstance() .make(token.getFieldTypeParameters().getExtraDataExpression(), namedFields, out.toByteArray())); } else { final String extraNumberAsStr = token.getFieldTypeParameters().getExtraData(); writeExtraFieldNumberInCompiled = true; if (extraNumberAsStr == null) { extraFieldNumberAsInt = 1; } else { try { extraFieldNumberAsInt = Integer.parseInt(extraNumberAsStr); assertNonNegativeValue(extraFieldNumberAsInt, token); } catch (NumberFormatException ex) { extraFieldNumberAsInt = -1; } if (extraFieldNumberAsInt <= 0) { throw new JBBPCompilationException("'align' size must be greater than zero [" + token.getFieldTypeParameters().getExtraData() + ']', token); } } } } break; case CODE_BIT: { if (extraFieldNumericDataAsExpression) { varLengthEvaluators.add(JBBPEvaluatorFactory.getInstance() .make(token.getFieldTypeParameters().getExtraDataExpression(), namedFields, out.toByteArray())); } else { final String extraFieldNumAsStr = token.getFieldTypeParameters().getExtraData(); writeExtraFieldNumberInCompiled = true; if (extraFieldNumAsStr == null) { extraFieldNumberAsInt = 1; } else { try { extraFieldNumberAsInt = Integer.parseInt(extraFieldNumAsStr); assertNonNegativeValue(extraFieldNumberAsInt, token); } catch (NumberFormatException ex) { extraFieldNumberAsInt = -1; } if (extraFieldNumberAsInt < 1 || extraFieldNumberAsInt > 8) { throw new JBBPCompilationException( "Bit-width must be 1..8 [" + token.getFieldTypeParameters().getExtraData() + ']', token); } } } } break; case CODE_VAR: { hasVarFields = true; if (extraFieldNumericDataAsExpression) { varLengthEvaluators.add(JBBPEvaluatorFactory.getInstance() .make(token.getFieldTypeParameters().getExtraDataExpression(), namedFields, out.toByteArray())); } else { final String extraFieldNumStr = token.getFieldTypeParameters().getExtraData(); writeExtraFieldNumberInCompiled = true; if (extraFieldNumStr == null) { extraFieldNumberAsInt = 0; } else { try { extraFieldNumberAsInt = Integer.parseInt(extraFieldNumStr); } catch (NumberFormatException ex) { throw new JBBPCompilationException( "Can't parse the extra value of a VAR field, must be integer [" + token.getFieldTypeParameters().getExtraData() + ']', token); } } } } break; case CODE_RESET_COUNTER: { assertTokenNotArray("Reset counter", token); assertTokenNotNamed("Reset counter", token); assertTokenHasNotExtraData("Reset counter", token); } break; case CODE_STRUCT_START: { final boolean arrayReadTillEnd = (code & FLAG_ARRAY) != 0 && (extraCode & EXT_FLAG_EXPRESSION_OR_WHOLESTREAM) != 0 && "_".equals(token.getArraySizeAsString()); structureStack.add(new StructStackItem( namedFields.size() + ((code & JBBPCompiler.FLAG_NAMED) == 0 ? 0 : 1), startFieldOffset, arrayReadTillEnd, code, token)); } break; case CODE_STRUCT_END: { if (structureStack.isEmpty()) { throw new JBBPCompilationException("Detected structure close tag without opening one", token); } else { if (fieldUnrestrictedArrayOffset >= 0) { final StructStackItem startOfStruct = structureStack.get(structureStack.size() - 1); if (startOfStruct.arrayToReadTillEndOfStream && fieldUnrestrictedArrayOffset != startOfStruct.startStructureOffset) { throw new JBBPCompilationException( "Detected unlimited array of structures but there is already presented one", token); } } currentClosedStructure = structureStack.remove(structureStack.size() - 1); offset += writePackedInt(out, currentClosedStructure.startStructureOffset); } } break; default: throw new Error( "Detected unsupported compiled code, notify the developer please [" + code + ']'); } if ((code & FLAG_ARRAY) == 0) { if (structureStack.isEmpty() && (code & 0x0F) != CODE_STRUCT_END && fieldUnrestrictedArrayOffset >= 0) { throw new JBBPCompilationException("Detected field defined after unlimited array", token); } } else { if ((extraCode & EXT_FLAG_EXPRESSION_OR_WHOLESTREAM) != 0) { if ("_".equals(token.getArraySizeAsString())) { if (fieldUnrestrictedArrayOffset >= 0) { throw new JBBPCompilationException( "Detected two or more unlimited arrays [" + script + ']', token); } else { fieldUnrestrictedArrayOffset = startFieldOffset; } } else { varLengthEvaluators.add(JBBPEvaluatorFactory.getInstance() .make(token.getArraySizeAsString(), namedFields, out.toByteArray())); } } else { final int fixedArraySize = token.getArraySizeAsInt(); if (fixedArraySize <= 0) { throw new JBBPCompilationException( "Detected an array with negative or zero fixed length", token); } offset += writePackedInt(out, fixedArraySize); } } if (writeExtraFieldNumberInCompiled) { offset += writePackedInt(out, extraFieldNumberAsInt); } if (customTypeFieldIndex >= 0) { offset += writePackedInt(out, customTypeFieldIndex); } if ((code & FLAG_NAMED) != 0) { final String normalizedName = JBBPUtils.normalizeFieldNameOrPath(token.getFieldName()); assertName(normalizedName, token); registerNamedField(normalizedName, structureStack.isEmpty() ? 0 : structureStack.get(structureStack.size() - 1).namedFieldCounter, startFieldOffset, namedFields, token); } else { if (currentClosedStructure != null && (currentClosedStructure.code & FLAG_NAMED) != 0) { // it is structure, process field names final String normalizedName = JBBPUtils.normalizeFieldNameOrPath(currentClosedStructure.token.getFieldName()); for (int i = namedFields.size() - 1; i >= 0; i--) { final JBBPNamedFieldInfo f = namedFields.get(i); if (f.getFieldOffsetInCompiledBlock() <= currentClosedStructure.startStructureOffset) { break; } final String newFullName = normalizedName + '.' + f.getFieldPath(); namedFields.set(i, new JBBPNamedFieldInfo(newFullName, f.getFieldName(), f.getFieldOffsetInCompiledBlock())); } } } } if (!structureStack.isEmpty()) { throw new JBBPCompilationException( "Detected non-closed " + structureStack.size() + " structure(s)"); } final byte[] compiledBlock = out.toByteArray(); if (fieldUnrestrictedArrayOffset >= 0) { compiledBlock[fieldUnrestrictedArrayOffset] = (byte) (compiledBlock[fieldUnrestrictedArrayOffset] & ~FLAG_ARRAY); } return builder .setNamedFieldData(namedFields) .setArraySizeEvaluators(varLengthEvaluators) .setCustomTypeFields(customTypeFields) .setCompiledData(compiledBlock) .setHasVarFields(hasVarFields) .build(); } private static void assertTokenHasNotExtraData(final String fieldType, final JBBPToken token) { if (token.getFieldTypeParameters().getExtraData() != null) { throw new JBBPCompilationException('\'' + fieldType + "\' has extra value", token); } } /** * The Method checks a value for negative. * * @param value a value to be checked * @param token the tokens related to the value * @throws JBBPCompilationException if the value is a negative one */ private static void assertNonNegativeValue(final int value, final JBBPToken token) { if (value < 0) { throw new JBBPCompilationException( "Detected unsupported negative value for a field must have only zero or a positive one", token); } } /** * The Method check that a field name supports business rules. * * @param name the name to be checked * @param token the token contains the name * @throws JBBPCompilationException if the name doesn't support business rules */ private static void assertName(final String name, final JBBPToken token) { if (name.indexOf('.') >= 0) { throw new JBBPCompilationException("Detected disallowed char '.' in name [" + name + ']', token); } } /** * Register a name field info item in a named field list. * * @param normalizedName normalized name of the named field * @param offset the named field offset * @param namedFields the named field info list for registration * @param token the token for the field * @throws JBBPCompilationException if there is already a registered field for * the path */ private static void registerNamedField(final String normalizedName, final int structureBorder, final int offset, final List namedFields, final JBBPToken token) { for (int i = namedFields.size() - 1; i >= structureBorder; i--) { final JBBPNamedFieldInfo info = namedFields.get(i); if (info.getFieldPath().equals(normalizedName)) { throw new JBBPCompilationException( "Duplicated named field detected [" + normalizedName + ']', token); } } namedFields.add(new JBBPNamedFieldInfo(normalizedName, normalizedName, offset)); } /** * Write an integer value in packed form into an output stream. * * @param out the output stream to be used to write the value into * @param value the value to be written into the output stream * @return the length of packed data in bytes * @throws IOException it will be thrown for any IO problems */ private static int writePackedInt(final OutputStream out, final int value) throws IOException { final byte[] packedInt = JBBPUtils.packInt(value); out.write(packedInt); return packedInt.length; } /** * The Method prepares a byte-code for a token field type and modifiers. * * @param token a token to be processed, must not be null * @param customTypeFieldProcessor custom type field processor for the parser, * it can be null * @return the prepared byte code for the token */ private static int prepareCodeForToken(final JBBPToken token, final JBBPCustomFieldTypeProcessor customTypeFieldProcessor) { int result = -1; switch (token.getType()) { case ATOM: { final JBBPFieldTypeParameterContainer descriptor = token.getFieldTypeParameters(); result = descriptor.getByteOrder() == JBBPByteOrder.LITTLE_ENDIAN ? FLAG_LITTLE_ENDIAN : 0; final boolean hasExpressionAsExtraNumber = descriptor.hasExpressionAsExtraData(); result |= token.getArraySizeAsString() == null ? 0 : (token.isVarArrayLength() ? FLAG_ARRAY | FLAG_WIDE | (EXT_FLAG_EXPRESSION_OR_WHOLESTREAM << 8) : FLAG_ARRAY); result |= hasExpressionAsExtraNumber ? FLAG_WIDE | (EXT_FLAG_EXTRA_AS_EXPRESSION << 8) : 0; result |= token.getFieldTypeParameters().isSpecialField() ? FLAG_WIDE | (EXT_FLAG_EXTRA_DIFF_TYPE << 8) : 0; result |= token.getFieldName() == null ? 0 : FLAG_NAMED; final String name = descriptor.getTypeName().toLowerCase(Locale.ENGLISH); switch (name) { case "skip": case "val": result |= CODE_SKIP; break; case "align": result |= CODE_ALIGN; break; case "bit": result |= CODE_BIT; break; case "var": result |= CODE_VAR; break; case "bool": case JBBPFieldString.TYPE_NAME: result |= CODE_BOOL; break; case "ubyte": result |= CODE_UBYTE; break; case "byte": case JBBPFieldUInt.TYPE_NAME: result |= CODE_BYTE; break; case "ushort": result |= CODE_USHORT; break; case "short": result |= CODE_SHORT; break; case "int": case JBBPFieldFloat.TYPE_NAME: result |= CODE_INT; break; case "long": case JBBPFieldDouble.TYPE_NAME: result |= CODE_LONG; break; case "reset$$": result |= CODE_RESET_COUNTER; break; default: boolean unsupportedType = true; if (customTypeFieldProcessor != null) { for (final String s : customTypeFieldProcessor.getCustomFieldTypes()) { if (name.equals(s)) { result |= CODE_CUSTOMTYPE; unsupportedType = false; break; } } } if (unsupportedType) { throw new JBBPCompilationException( "Unsupported type [" + descriptor.getTypeName() + ']', token); } break; } } break; case COMMENT: { // doesn't contain code } break; case STRUCT_START: { result = token.getArraySizeAsString() == null ? 0 : (token.isVarArrayLength() ? FLAG_ARRAY | FLAG_WIDE | (EXT_FLAG_EXPRESSION_OR_WHOLESTREAM << 8) : FLAG_ARRAY); result |= token.getFieldName() == null ? 0 : FLAG_NAMED; result |= CODE_STRUCT_START; } break; case STRUCT_END: { result = CODE_STRUCT_END; } break; default: throw new Error("Unsupported type detected, contact developer! [" + token.getType() + ']'); } return result; } /** * Inside auxiliary class to keep information about structures. */ private static final class StructStackItem { /** * The Structure start offset. */ private final int startStructureOffset; /** * The Start byte-code of the structure. */ private final int code; /** * The Parsed Token. */ private final JBBPToken token; /** * Named field counter value for the structure start. */ private final int namedFieldCounter; /** * Flag shows that the structure is array which should be read till end of stream */ private final boolean arrayToReadTillEndOfStream; /** * The Constructor. * * @param namedFieldCounter the named field counter value for the structure * start * @param startStructureOffset the offset of the start structure byte-code * instruction * @param arrayToReadTillEnd if true then it is array to read till end * @param code the start byte code * @param token the token */ private StructStackItem(final int namedFieldCounter, final int startStructureOffset, final boolean arrayToReadTillEnd, final int code, final JBBPToken token) { this.namedFieldCounter = namedFieldCounter; this.arrayToReadTillEndOfStream = arrayToReadTillEnd; this.startStructureOffset = startStructureOffset; this.code = code; this.token = token; } } } JBBPCompilerUtils.java000066400000000000000000000106241445000440600337060ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.FLAG_ARRAY; import com.igormaznitsa.jbbp.exceptions.JBBPCompilationException; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.util.List; /** * Class contains specific common auxiliary methods for parser and compiler classes. * * @since 1.0 */ public final class JBBPCompilerUtils { private JBBPCompilerUtils() { } /** * Find a named field info index in a list for its path. * * @param fieldPath a field path, it must not be null. * @param namedFields a list contains named field info items. * @return the index of a field for the path if found one, -1 otherwise */ public static int findIndexForFieldPath(final String fieldPath, final List namedFields) { final String normalized = JBBPUtils.normalizeFieldNameOrPath(fieldPath); int result = -1; for (int i = namedFields.size() - 1; i >= 0; i--) { final JBBPNamedFieldInfo f = namedFields.get(i); if (normalized.equals(f.getFieldPath())) { result = i; break; } } return result; } /** * Find a named field info for its path inside a named field list. * * @param fieldPath a field path, must not be null. * @param namedFields a named field list. * @return found item for the path, null otherwise */ public static JBBPNamedFieldInfo findForFieldPath(final String fieldPath, final List namedFields) { final String normalized = JBBPUtils.normalizeFieldNameOrPath(fieldPath); JBBPNamedFieldInfo result = null; for (int i = namedFields.size() - 1; i >= 0; i--) { final JBBPNamedFieldInfo f = namedFields.get(i); if (normalized.equals(f.getFieldPath())) { result = f; break; } } return result; } /** * Check a field in a compiled list defined by its named field info, that the field is not an array and it is not inside a structure array. * * @param fieldToCheck a named field info to be checked, must not be null * @param namedFieldList a named field info list, must not be null. * @param compiledScript a compiled script body */ public static void assertFieldIsNotArrayOrInArray(final JBBPNamedFieldInfo fieldToCheck, final List namedFieldList, final byte[] compiledScript) { // check that the field is not array if ((compiledScript[fieldToCheck.getFieldOffsetInCompiledBlock()] & FLAG_ARRAY) != 0) { throw new JBBPCompilationException( "An Array field can't be used as array size [" + fieldToCheck.getFieldPath() + ']'); } if (fieldToCheck.getFieldPath().indexOf('.') >= 0) { // the field in structure, check that the structure is not an array or not in an array final String[] splitFieldPath = JBBPUtils.splitString(fieldToCheck.getFieldPath(), '.'); final StringBuilder fieldPath = new StringBuilder(); // process till the field name because we have already checked the field for (int i = 0; i < splitFieldPath.length - 1; i++) { if (fieldPath.length() != 0) { fieldPath.append('.'); } fieldPath.append(splitFieldPath[i]); final JBBPNamedFieldInfo structureEnd = JBBPCompilerUtils.findForFieldPath(fieldPath.toString(), namedFieldList); if ((compiledScript[structureEnd.getFieldOffsetInCompiledBlock()] & FLAG_ARRAY) != 0) { throw new JBBPCompilationException( "Field from structure array can't be use as array size [" + fieldToCheck.getFieldPath() + ';' + structureEnd.getFieldPath() + ']'); } } } } } JBBPNamedFieldInfo.java000066400000000000000000000060121445000440600337130ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.Serializable; /** * The Class describes a named field info item. Such objects are presented * inside of compiled blocks only for fields which have names. * * @since 1.0 */ public final class JBBPNamedFieldInfo implements Serializable { private static final long serialVersionUID = -3830704680293509892L; /** * The Field path. */ private final String fieldPath; /** * The Field name. */ private final String fieldName; /** * The Field byte-code offset in the compiled block. */ private final int offsetInCompiledBlock; /** * The Constructor * * @param fieldPath the field path * @param fieldName the field name * @param offsetInCompiledBlock the offset in the compiled block for the field */ public JBBPNamedFieldInfo(final String fieldPath, final String fieldName, final int offsetInCompiledBlock) { this.fieldPath = JBBPUtils.normalizeFieldNameOrPath(fieldPath); this.fieldName = JBBPUtils.normalizeFieldNameOrPath(fieldName); this.offsetInCompiledBlock = offsetInCompiledBlock; } /** * Get the field path. * * @return the field path as string */ public String getFieldPath() { return this.fieldPath; } /** * Get the field name. * * @return the field name as string */ public String getFieldName() { return this.fieldName; } /** * Get the field offset in the compiled byte-code block. * * @return the field offset in the byte-code block */ public int getFieldOffsetInCompiledBlock() { return this.offsetInCompiledBlock; } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (this == obj) { return true; } boolean result = false; if (obj instanceof JBBPNamedFieldInfo) { final JBBPNamedFieldInfo that = (JBBPNamedFieldInfo) obj; result = this.fieldPath.equals(that.fieldPath) && this.offsetInCompiledBlock == that.offsetInCompiledBlock; } return result; } @Override public int hashCode() { return this.offsetInCompiledBlock; } @Override public String toString() { return this.getClass().getSimpleName() + "[fieldPath=" + this.fieldPath + ", fieldName=" + this.fieldName + ", offsetInCompiledBlock=" + this.offsetInCompiledBlock + ']'; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/conversion/000077500000000000000000000000001445000440600320535ustar00rootroot00000000000000CompiledBlockVisitor.java000066400000000000000000000306551445000440600367370ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/conversion/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.conversion; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.JBBPCompiler; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.compiler.varlen.JBBPIntegerValueEvaluator; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.utils.JBBPIntCounter; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Visitor implements Template pattern to visitSpecial all fields of compiled parser data block. * * @since 1.3.0 */ public class CompiledBlockVisitor { /** * Compiled block to be processed, must not be null. */ protected final JBBPCompiledBlock compiledBlock; /** * Parser flags to be used for the translation. */ protected final int parserFlags; /** * The Constructor. * * @param parserFlags parser flags * @param notNullCompiledBlock compiled parser data block, must not be null */ public CompiledBlockVisitor(final int parserFlags, final JBBPCompiledBlock notNullCompiledBlock) { this.parserFlags = parserFlags; this.compiledBlock = notNullCompiledBlock; } /** * Auxiliary function to check that parser flag to skip remaining fields without exception is set. * * @return true if FLAG_SKIP_REMAINING_FIELDS_IF_EOF flag is set, false otherwise */ protected boolean isFlagSkipRemainingFieldsIfEOF() { return (this.parserFlags & JBBPParser.FLAG_SKIP_REMAINING_FIELDS_IF_EOF) != 0; } /** * The Main method of the class, it processes compiled block and make calls to template methods. * * @return the instance of the visitor, must not be null */ public final CompiledBlockVisitor visit() { this.visitStart(); final byte[] compiledData = this.compiledBlock.getCompiledData(); JBBPIntCounter positionAtCompiledBlock = new JBBPIntCounter(0); int positionAtNamedFieldList = 0; int positionAtVarLengthProcessors = 0; while (positionAtCompiledBlock.get() < compiledData.length) { final int theOffset = positionAtCompiledBlock.get(); final int c = compiledData[positionAtCompiledBlock.getAndIncrement()] & 0xFF; final boolean wideCode = (c & JBBPCompiler.FLAG_WIDE) != 0; final int ec = wideCode ? compiledData[positionAtCompiledBlock.getAndIncrement()] & 0xFF : 0; final boolean altFileType = (ec & JBBPCompiler.EXT_FLAG_EXTRA_DIFF_TYPE) != 0; final boolean extraFieldNumAsExpr = (ec & JBBPCompiler.EXT_FLAG_EXTRA_AS_EXPRESSION) != 0; final int code = (ec << 8) | c; final JBBPNamedFieldInfo name = (code & JBBPCompiler.FLAG_NAMED) == 0 ? null : this.compiledBlock.getNamedFields()[positionAtNamedFieldList++]; final JBBPByteOrder byteOrder = (code & JBBPCompiler.FLAG_LITTLE_ENDIAN) == 0 ? JBBPByteOrder.BIG_ENDIAN : JBBPByteOrder.LITTLE_ENDIAN; final JBBPIntegerValueEvaluator extraFieldValueEvaluator; if (extraFieldNumAsExpr) { extraFieldValueEvaluator = this.compiledBlock.getArraySizeEvaluators()[positionAtVarLengthProcessors++]; } else { extraFieldValueEvaluator = null; } final JBBPIntegerValueEvaluator arraySizeEvaluator; boolean readWholeStream = false; switch (code & (JBBPCompiler.FLAG_ARRAY | (JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM << 8))) { case JBBPCompiler.FLAG_ARRAY: { arraySizeEvaluator = new IntConstValueEvaluator( JBBPUtils.unpackInt(compiledData, positionAtCompiledBlock)); } break; case (JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM << 8): { arraySizeEvaluator = new IntConstValueEvaluator(-1); readWholeStream = true; } break; case JBBPCompiler.FLAG_ARRAY | (JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM << 8): { arraySizeEvaluator = this.compiledBlock.getArraySizeEvaluators()[positionAtVarLengthProcessors++]; } break; default: { // it is not an array, just a single field arraySizeEvaluator = null; } break; } final int theCode = code & 0xF; switch (theCode) { case JBBPCompiler.CODE_RESET_COUNTER: { visitActionItem(theOffset, code, null); } break; case JBBPCompiler.CODE_SKIP: case JBBPCompiler.CODE_ALIGN: { final JBBPIntegerValueEvaluator evaluator = extraFieldNumAsExpr ? extraFieldValueEvaluator : new IntConstValueEvaluator( JBBPUtils.unpackInt(compiledData, positionAtCompiledBlock)); if (altFileType) { if (theCode == JBBPCompiler.CODE_SKIP) { visitValField(theOffset, byteOrder, name, evaluator); } else { throw new Error("Unexpected code:" + theCode); } } else { visitActionItem(theOffset, theCode, evaluator); } } break; case JBBPCompiler.CODE_BIT: { final JBBPIntegerValueEvaluator numberOfBits = extraFieldNumAsExpr ? extraFieldValueEvaluator : new IntConstValueEvaluator( JBBPUtils.unpackInt(compiledData, positionAtCompiledBlock)); visitBitField(theOffset, byteOrder, name, numberOfBits, arraySizeEvaluator); } break; case JBBPCompiler.CODE_BOOL: case JBBPCompiler.CODE_BYTE: case JBBPCompiler.CODE_UBYTE: case JBBPCompiler.CODE_SHORT: case JBBPCompiler.CODE_USHORT: case JBBPCompiler.CODE_INT: case JBBPCompiler.CODE_LONG: { visitPrimitiveField(theOffset, theCode, name, byteOrder, readWholeStream, altFileType, arraySizeEvaluator); } break; case JBBPCompiler.CODE_STRUCT_START: { visitStructureStart(theOffset, byteOrder, name, arraySizeEvaluator); } break; case JBBPCompiler.CODE_STRUCT_END: { JBBPUtils.unpackInt(compiledData, positionAtCompiledBlock); visitStructureEnd(theOffset, name); } break; case JBBPCompiler.CODE_VAR: { final JBBPIntegerValueEvaluator extraDataValueEvaluator = extraFieldNumAsExpr ? extraFieldValueEvaluator : new IntConstValueEvaluator( JBBPUtils.unpackInt(compiledData, positionAtCompiledBlock)); visitVarField(theOffset, name, byteOrder, readWholeStream, arraySizeEvaluator, extraDataValueEvaluator); } break; case JBBPCompiler.CODE_CUSTOMTYPE: { final JBBPIntegerValueEvaluator extraDataValueEvaluator = extraFieldNumAsExpr ? extraFieldValueEvaluator : new IntConstValueEvaluator( JBBPUtils.unpackInt(compiledData, positionAtCompiledBlock)); final JBBPFieldTypeParameterContainer fieldTypeInfo = this.compiledBlock.getCustomTypeFields()[JBBPUtils .unpackInt(compiledData, positionAtCompiledBlock)]; visitCustomField(theOffset, fieldTypeInfo, name, byteOrder, readWholeStream, arraySizeEvaluator, extraDataValueEvaluator); } break; default: throw new Error("Unexpected code, contact developer!"); } } this.visitEnd(); return this; } /** * Visit an action item (like skip, align or reset counter command) * * @param offsetInCompiledBlock offset in the compiled block * @param actionType the action type * @param nullableArgument argument for action, it can be null * @see JBBPCompiler#CODE_RESET_COUNTER * @see JBBPCompiler#CODE_ALIGN * @see JBBPCompiler#CODE_SKIP */ public void visitActionItem(int offsetInCompiledBlock, int actionType, JBBPIntegerValueEvaluator nullableArgument) { } /** * Visit field contains virtual field with VAL type. * * @param offsetInCompiledBlock offset in the compiled block * @param byteOrder byteOrder * @param nameFieldInfo name of the field, must not be null * @param expression expression to calculate value * @since 1.4.0 */ public void visitValField(int offsetInCompiledBlock, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nameFieldInfo, JBBPIntegerValueEvaluator expression) { } /** * Visit a primitive data field * * @param offsetInCompiledBlock offset in the compiled block * @param primitiveType the primitive type * @param nullableNameFieldInfo field info, null if the field is anonymous one * @param byteOrder byte order for the field, must not be null * @param readWholeStreamAsArray if true then it is array with unknown size till the stream end * @param altFieldType flag shows that field type is alternative one, INT should be recognized as FLOAT and LONG as DOUBLE and BOOL as STRING * @param nullableArraySize array size if the field is array, null if the field is not array or variable length array * @see JBBPCompiler#CODE_BYTE * @see JBBPCompiler#CODE_UBYTE * @see JBBPCompiler#CODE_SHORT * @see JBBPCompiler#CODE_USHORT * @see JBBPCompiler#CODE_BOOL * @see JBBPCompiler#CODE_INT * @see JBBPCompiler#CODE_LONG * @see JBBPCompiler#CODE_SKIP */ public void visitPrimitiveField(int offsetInCompiledBlock, int primitiveType, JBBPNamedFieldInfo nullableNameFieldInfo, JBBPByteOrder byteOrder, boolean readWholeStreamAsArray, boolean altFieldType, JBBPIntegerValueEvaluator nullableArraySize) { } /** * Visit a variable field (which is defined with var data type) * * @param offsetInCompiledBlock offset in the compiled block * @param nullableNameFieldInfo field info, null if the field is anonymous one * @param byteOrder byte order for the field, must not be null * @param readWholeStreamIntoArray true if whole stream should be read as array of var type, false otherwise * @param nullableArraySize if not null then evaluator of array size to be read from stream * @param extraDataValueEvaluator if not null then extra data evaluator for the var field */ public void visitVarField(int offsetInCompiledBlock, JBBPNamedFieldInfo nullableNameFieldInfo, JBBPByteOrder byteOrder, boolean readWholeStreamIntoArray, JBBPIntegerValueEvaluator nullableArraySize, JBBPIntegerValueEvaluator extraDataValueEvaluator) { } public void visitCustomField(int offsetInCompiledBlock, JBBPFieldTypeParameterContainer notNullFieldType, JBBPNamedFieldInfo nullableNameFieldInfo, JBBPByteOrder byteOrder, boolean readWholeStream, JBBPIntegerValueEvaluator nullableArraySizeEvaluator, JBBPIntegerValueEvaluator extraDataValueEvaluator) { } public void visitBitField(int offsetInCompiledBlock, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNameFieldInfo, JBBPIntegerValueEvaluator notNullFieldSize, JBBPIntegerValueEvaluator nullableArraySize) { } public void visitStructureStart(int offsetInCompiledBlock, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNameFieldInfo, JBBPIntegerValueEvaluator nullableArraySize) { } public void visitStructureEnd(int offsetInCompiledBlock, JBBPNamedFieldInfo nullableNameFieldInfo) { } public void visitStart() { } public void visitEnd() { } } ExpressionEvaluatorVisitor.java000066400000000000000000000056131445000440600402460ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/conversion/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.conversion; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; /** * Interface describes a visitor for compiled expressions. * * @since 1.3.0 */ public interface ExpressionEvaluatorVisitor { /** * Start visit. * * @return the visitor instance, must not be null */ ExpressionEvaluatorVisitor visitStart(); /** * Visit special field (like stream counter) * * @param specialField special value to be visited, must not be null * @return the visitor instance, must not be null */ ExpressionEvaluatorVisitor visitSpecial(Special specialField); /** * Visit field item, it can be or named field or external field (which name starts with $) * * @param nullableNameFieldInfo name info for the field, it will be null for external fields * @param nullableExternalFieldName name of external field, it will be null for regular field * @return the visitor instance, must not be null */ ExpressionEvaluatorVisitor visitField(JBBPNamedFieldInfo nullableNameFieldInfo, String nullableExternalFieldName); /** * Visit operator * * @param operator operator item to be visited, must not be null * @return the visitor instance, must not be null */ ExpressionEvaluatorVisitor visitOperator(Operator operator); /** * Visit integer constant * * @param value integer constant * @return the visitor instance, must not be null */ ExpressionEvaluatorVisitor visitConstant(int value); /** * End of expression * * @return the visitor instance, must not be null */ ExpressionEvaluatorVisitor visitEnd(); enum Special { STREAM_COUNTER } enum Operator { ADD("+", 2, 200), SUB("-", 2, 200), MUL("*", 2, 300), MOD("%", 2, 300), NOT("~", 1, 500), OR("|", 2, 50), AND("&", 2, 150), XOR("^", 2, 100), DIV("/", 2, 300), LSHIFT("<<", 2, 175), RSHIFT(">>", 2, 175), URSHIFT(">>>", 2, 175), UNARY_PLUS("+", 1, 500), UNARY_MINUS("-", 1, 500); public final int priority; public final int argsNumber; public final String text; Operator(final String text, final int argsNumber, final int priority) { this.argsNumber = argsNumber; this.priority = priority; this.text = text; } } } IntConstValueEvaluator.java000066400000000000000000000033411445000440600372610ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/conversion/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.conversion; import com.igormaznitsa.jbbp.JBBPNamedNumericFieldMap; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.varlen.JBBPIntegerValueEvaluator; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; /** * Auxiliary class to process expression with only integer constant. * * @since 1.3.0 */ final class IntConstValueEvaluator implements JBBPIntegerValueEvaluator { private static final long serialVersionUID = 4640385518512384490L; /** * The Field contains integer constant. */ private final int value; IntConstValueEvaluator(final int value) { this.value = value; } @Override public int eval(final JBBPBitInputStream inStream, final int currentCompiledBlockOffset, final JBBPCompiledBlock block, final JBBPNamedNumericFieldMap fieldMap) { return this.value; } @Override public void visitItems(JBBPCompiledBlock block, int currentCompiledBlockOffset, ExpressionEvaluatorVisitor visitor) { visitor.visitStart(); visitor.visitConstant(this.value); visitor.visitEnd(); } } JBBPToJavaConverter.java000066400000000000000000002320761445000440600363630ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/conversion/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.conversion; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_ALIGN; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_BOOL; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_BYTE; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_INT; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_LONG; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_RESET_COUNTER; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_SHORT; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_SKIP; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_UBYTE; import static com.igormaznitsa.jbbp.compiler.JBBPCompiler.CODE_USHORT; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.compiler.varlen.JBBPIntegerValueEvaluator; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.mapper.JBBPMapper; import com.igormaznitsa.jbbp.utils.JavaSrcTextBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** * Converter to produce Java class sources (1.6+) from JBBPParser. If a parser * contains variable field, custom fields or external values in expressions then * the result class will be abstract one and its abstract methods must be * implemented in successor. * * @since 1.3.0 */ @SuppressWarnings("SpellCheckingInspection") public final class JBBPToJavaConverter extends CompiledBlockVisitor { private static final int FLAG_DETECTED_CUSTOM_FIELDS = 1; private static final int FLAG_DETECTED_EXTERNAL_FIELDS = 2; private static final int FLAG_DETECTED_VAR_FIELDS = 4; private static final int FLAG_ADD_ASSERT_NOT_NEGATIVE_EXPR = 8; private static final Set RESERVED_JAVA_KEYWORDS; /** * Name of the field to be used as link to the root structure instance in * child structures. */ private static final String NAME_ROOT_STRUCT = "_Root_"; /** * Name of the field to keep information about parser flags. */ private static final String NAME_PARSER_FLAGS = "_ParserFlags_"; /** * Name of the input stream argument. */ private static final String NAME_INPUT_STREAM = "In"; /** * Name of the output stream argument. */ private static final String NAME_OUTPUT_STREAM = "Out"; static { final Set reserved = new HashSet<>(); reserved.add("abstract"); reserved.add("assert"); reserved.add("boolean"); reserved.add("break"); reserved.add("byte"); reserved.add("case"); reserved.add("catch"); reserved.add("char"); reserved.add("class"); reserved.add("continue"); reserved.add("default"); reserved.add("do"); reserved.add("double"); reserved.add("else"); reserved.add("enum"); reserved.add("extends"); reserved.add("final"); reserved.add("finally"); reserved.add("float"); reserved.add("for"); reserved.add("if"); reserved.add("implements"); reserved.add("import"); reserved.add("instanceof"); reserved.add("int"); reserved.add("interface"); reserved.add("long"); reserved.add("native"); reserved.add("new"); reserved.add("package"); reserved.add("private"); reserved.add("protected"); reserved.add("public"); reserved.add("return"); reserved.add("short"); reserved.add("static"); reserved.add("strictfp"); reserved.add("super"); reserved.add("switch"); reserved.add("synchronized"); reserved.add("this"); reserved.add("throw"); reserved.add("throws"); reserved.add("transient"); reserved.add("try"); reserved.add("void"); reserved.add("volatile"); reserved.add("while"); reserved.add("true"); reserved.add("null"); reserved.add("false"); reserved.add("var"); reserved.add("const"); reserved.add("goto"); RESERVED_JAVA_KEYWORDS = Collections.unmodifiableSet(reserved); } /** * Detected flags. */ private final AtomicInteger flagSet = new AtomicInteger(); /** * Map of detected named fields to their name field info object. */ private final Map foundNamedFields = new HashMap<>(); /** * Counter of anonymous fields to generate unique names. */ private final AtomicInteger anonymousFieldCounter = new AtomicInteger(); /** * Counter of special fields to generate their unique names. */ private final AtomicInteger specialFieldsCounter = new AtomicInteger(); /** * The List implements stack of current processing structures. The 0 contains * the root. */ private final List structStack = new ArrayList<>(); /** * Text buffer for the special section. */ private final JavaSrcTextBuffer specialSection = new JavaSrcTextBuffer(); /** * Text buffer for the special methods. */ private final JavaSrcTextBuffer specialMethods = new JavaSrcTextBuffer(); /** * The Builder instance to be used as the data source for the parser. It must * not be null. */ private final Builder builder; /** * The Field contains conversion result after process end. */ private String result; /** * The Only Constructor based on a builder instance. * * @param builder a builder instance, must not be null */ private JBBPToJavaConverter(final Builder builder) { super(builder.parserFlags, builder.srcParser.getCompiledBlock()); this.builder = builder; } /** * Make new builder. * * @param parser parser instance to be used as the base for translation, must * not be null * @return the new builder instance, must not be null. */ public static Builder makeBuilder(final JBBPParser parser) { return new Builder(parser); } /** * Do conversion. * * @return generated class with needed parameters as text, must not be null. */ public String convert() { return ((JBBPToJavaConverter) this.visit()).getResult(); } private void registerNamedField(final JBBPNamedFieldInfo fieldInfo, final FieldType fieldType) { if (fieldInfo != null) { if (this.foundNamedFields.containsKey(fieldInfo)) { throw new Error("Detected duplication of named field : " + fieldInfo); } this.foundNamedFields .put(fieldInfo, new NamedFieldInfo(fieldInfo, this.getCurrentStruct(), fieldType)); } } private Struct getCurrentStruct() { return this.structStack.get(0); } @Override public void visitStart() { this.flagSet.set(0); this.foundNamedFields.clear(); this.anonymousFieldCounter.set(1234); this.specialFieldsCounter.set(1); this.specialSection.clean(); this.structStack.clear(); this.specialMethods.clean(); final Struct rootStruct = new Struct(null, this.builder.mainClassName, "public"); this.structStack.add(rootStruct); } /** * Get result of the conversion process. * * @return the result, it will not be null if the process completed without * errors. */ public String getResult() { return this.result; } @Override public void visitEnd() { final JavaSrcTextBuffer buffer = new JavaSrcTextBuffer(); if (this.builder.headComment != null) { buffer.printCommentMultiLinesWithIndent(this.builder.headComment); } if (this.builder.mainClassPackage != null && this.builder.mainClassPackage.length() != 0) { buffer.print("package ").print(this.builder.mainClassPackage).println(";"); } buffer.println(); buffer.println("import com.igormaznitsa.jbbp.model.*;"); buffer.println("import com.igormaznitsa.jbbp.io.*;"); buffer.println("import com.igormaznitsa.jbbp.compiler.*;"); buffer.println("import com.igormaznitsa.jbbp.compiler.tokenizer.*;"); buffer.println("import java.io.IOException;"); buffer.println("import java.util.*;"); if (this.builder.addBinAnnotations) { buffer.println("import com.igormaznitsa.jbbp.mapper.Bin;"); buffer.println("import com.igormaznitsa.jbbp.mapper.BinType;"); } buffer.println(); this.specialSection.println(); this.specialSection.printJavaDocLinesWithIndent( "The Constant contains parser flags\n@see JBBPParser#FLAG_SKIP_REMAINING_FIELDS_IF_EOF\n@see JBBPParser#FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO"); this.specialSection.indent() .printf("protected static final int %s = %d;", NAME_PARSER_FLAGS, this.parserFlags); final int detected = this.flagSet.get(); if ((detected & FLAG_DETECTED_CUSTOM_FIELDS) != 0) { this.specialMethods.printJavaDocLinesWithIndent( "Reading of custom fields\n@param sourceStruct source structure holding the field, must not be null\n@param inStream the input stream, must not be null\n@param typeParameterContainer info about field type, must not be null\n@param nullableNamedFieldInfo info abut field name, it can be null\n@param extraValue value from extra field part, default value 0\n@param readWholeStream flag to read the stream as array till the stream end if true\n@param arraySize if array then it is zero or great\n@exception IOException if data can't be read\n@return read value as abstract field, must not be null"); this.specialMethods.println( "public abstract JBBPAbstractField readCustomFieldType(Object sourceStruct, JBBPBitInputStream inStream, JBBPFieldTypeParameterContainer typeParameterContainer, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException;"); this.specialMethods.println(); this.specialMethods.printJavaDocLinesWithIndent( "Writing custom fields\n@param sourceStruct source structure holding the field, must not be null\n@param outStream the output stream, must not be null\n@param fieldValue value to be written\n@param typeParameterContainer info about field type, must not be null\n@param nullableNamedFieldInfo info abut field name, it can be null\n@param extraValue value from extra field part, default value is 0\n@param wholeArray true if to write whole array\n@param arraySize if array then it is zero or great\n@exception IOException if data can't be written"); this.specialMethods.println( "public abstract void writeCustomFieldType(Object sourceStruct, JBBPBitOutputStream outStream, JBBPAbstractField fieldValue, JBBPFieldTypeParameterContainer typeParameterContainer, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean wholeArray, int arraySize) throws IOException;"); } if ((detected & FLAG_DETECTED_EXTERNAL_FIELDS) != 0) { if (!this.specialMethods.isEmpty()) { this.specialMethods.println(); } this.specialMethods.printJavaDocLinesWithIndent( "Method is called from expressions to provide value\n@param sourceStruct source structure holding the field, must not be null\n@param valueName name of value to be provided, must not be null\n@return integer value for the named parameter"); this.specialMethods .println("public abstract int getNamedValue(Object sourceStruct, String valueName);"); } if ((detected & FLAG_DETECTED_VAR_FIELDS) != 0) { if (!this.specialMethods.isEmpty()) { this.specialMethods.println(); } this.specialMethods.printJavaDocLinesWithIndent( "Read variable field\n@param sourceStruct source structure holding the field, must not be null\n@param inStream the input stream, must not be null\n@param byteOrder\n@param nullableNamedFieldInfo info abut field name, it can be null\n@param extraValue value from extra field part, -1 if not defined\n@return\n@exception IOException"); this.specialMethods.println( "public abstract JBBPAbstractField readVarField(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue) throws IOException;"); this.specialMethods.println(); this.specialMethods.printJavaDocLinesWithIndent( "Read variable array field\n@param sourceStruct source structure holding the field, must not be null\n@param inStream the input stream, must not be null\n@param byteOrder byte order to be used for reading, must not be null\n@param nullableNamedFieldInfo info abut field name, it can be null\n@param extraValue value from extra field part, -1 if not defined\n@param readWholeStream if true then whole stream should be read\n@param arraySize size of array to read (if whole stream flag is false)\n@return array object contains read data, must not be null\n@exception IOException if error during data reading"); this.specialMethods.println( "public abstract JBBPAbstractArrayField readVarArray(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException;"); this.specialMethods.println(); this.specialMethods.printJavaDocLinesWithIndent( "Read variable field\n@param sourceStruct source structure holding the field, must not be null\n@param value field value, must not be null\n@param outStream the output stream, must not be null,\n@param byteOrder byte order to be used for reading, must not be null\n@param nullableNamedFieldInfo info abut field name, it can be null\n@param extraValue value from extra field part, -1 if not defined\n@exception IOException it is thrown if any transport error during operation"); this.specialMethods.println( "public abstract void writeVarField(Object sourceStruct, JBBPAbstractField value, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue) throws IOException;"); this.specialMethods.println(); this.specialMethods.printJavaDocLinesWithIndent( "Write variable array\n@param sourceStruct source structure holding the field, must not be null\n@param array array value to be written, must not be null\n@param outStream the output stream, must not be null\n@param byteOrder byte order to be used for reading, must not be null\n@param nullableNamedFieldInfo info abut field name, it can be null\n@param extraValue value from extra field part, -1 if not defined\n@param arraySizeToWrite\n@exception IOException it is thrown if any transport error during operation"); this.specialMethods.println( "public abstract void writeVarArray(Object sourceStruct, JBBPAbstractArrayField array, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, int arraySizeToWrite) throws IOException;"); } if ((detected & FLAG_ADD_ASSERT_NOT_NEGATIVE_EXPR) != 0) { if (!this.specialMethods.isEmpty()) { this.specialMethods.println(); } this.specialMethods.println( "private static int assrtExprNotNeg(final int value) { if (value<0) throw new IllegalArgumentException(\"Negative value in expression\"); return value; }"); } final String specialMethodsText = this.specialMethods.toString(); final boolean hasAbstractMethods = (this.flagSet.get() & (FLAG_DETECTED_CUSTOM_FIELDS | FLAG_DETECTED_VAR_FIELDS | FLAG_DETECTED_EXTERNAL_FIELDS)) != 0 || this.builder.doMainClassAbstract; buffer.printJavaDocLinesWithIndent( "Generated from JBBP script by internal JBBP Class Source Generator"); final Struct rootStruct = this.structStack.get(0); if (this.builder.addNewInstanceMethods) { rootStruct.misc.println(String.format("public Object %s(Class aClass) {", JBBPMapper.MAKE_CLASS_INSTANCE_METHOD_NAME)); rootStruct.misc.incIndent(); for (final Struct c : rootStruct.children) { rootStruct.misc.indent().println(String.format("if (aClass == %s.class) {", c.className)); rootStruct.misc.incIndent(); rootStruct.misc.indent().println(String.format("return new %s(this);", c.className)); rootStruct.misc.decIndent(); rootStruct.misc.indent().println("}"); } rootStruct.misc.indent().println("return null;"); rootStruct.misc.decIndent(); rootStruct.misc.println("}"); } rootStruct.write(buffer, hasAbstractMethods ? "abstract" : null, this.builder.superClass, this.builder.mainClassImplements, this.builder.mapSubClassesInterfaces, this.builder.mapSubClassesSuperclasses, this.specialSection.toString(), specialMethodsText.length() == 0 ? null : specialMethodsText, this.builder.mainClassCustomText, true ); this.result = buffer.toString(); } @Override public void visitStructureStart(final int offsetInCompiledBlock, final JBBPByteOrder byteOrder, final JBBPNamedFieldInfo nullableNameFieldInfo, final JBBPIntegerValueEvaluator nullableArraySize) { final String structName = (nullableNameFieldInfo == null ? makeAnonymousStructName() : prepFldName(nullableNameFieldInfo.getFieldName())).toLowerCase(Locale.ENGLISH); final String structBaseTypeName = structName.toUpperCase(Locale.ENGLISH); final String arraySizeIn = nullableArraySize == null ? null : evaluatorToString(NAME_INPUT_STREAM, offsetInCompiledBlock, nullableArraySize, this.flagSet, true); final String arraySizeOut = nullableArraySize == null ? null : evaluatorToString(NAME_OUTPUT_STREAM, offsetInCompiledBlock, nullableArraySize, this.flagSet, true); final Struct newStruct = new Struct(this.getCurrentStruct(), structBaseTypeName, "public" + (builder.internalClassesNotStatic ? "" : " static")); final String fieldModifier = makeModifier(nullableNameFieldInfo); final String toType; if (this.builder.generateFields) { toType = ""; } else { toType = '(' + structBaseTypeName + ')'; } final String structType; final String pathToRootObject = this.structStack.size() == 1 ? "this" : NAME_ROOT_STRUCT; if (nullableArraySize == null) { structType = structBaseTypeName; if (this.builder.generateFields) { printField(nullableNameFieldInfo, byteOrder, false, offsetInCompiledBlock, getCurrentStruct().getFields().indent(), null, fieldModifier, structType, structName); } processSkipRemainingFlag(); processSkipRemainingFlagForWriting("this." + structName); this.getCurrentStruct().getReadFunc().indent() .printf("if ( this.%1$s == null) { this.%1$s = new %2$s(%3$s);}", structName, structType, pathToRootObject) .printf(" %s.read(%s);%n", toType.length() == 0 ? "this." + structName : '(' + toType + "this." + structName + ')', NAME_INPUT_STREAM); this.getCurrentStruct().getWriteFunc().indent() .print(toType.length() == 0 ? structName : '(' + toType + structName + ')') .println(".write(Out);"); } else { structType = structBaseTypeName + " []"; if (this.builder.generateFields) { printField(nullableNameFieldInfo, byteOrder, true, offsetInCompiledBlock, getCurrentStruct().getFields().indent(), null, fieldModifier, structType, structName); } processSkipRemainingFlag(); processSkipRemainingFlagForWriting("this." + structName); if ("-1".equals(arraySizeIn)) { this.getCurrentStruct().getReadFunc().indent() .printf( "List<%3$s> __%1$s_tmplst__ = new ArrayList<%3$s>(); while (%5$s.hasAvailableData()){ __%1$s_tmplst__.add(new %3$s(%4$s).read(%5$s));} this.%1$s = __%1$s_tmplst__.toArray(new %3$s[__%1$s_tmplst__.size()]);__%1$s_tmplst__ = null;%n", structName, arraySizeIn, structBaseTypeName, pathToRootObject, NAME_INPUT_STREAM); this.getCurrentStruct().getWriteFunc().indent() .printf("for (int I=0;I stack = new ArrayList<>(); @Override public ExpressionEvaluatorVisitor visitStart() { this.stack.clear(); return this; } @Override public ExpressionEvaluatorVisitor visitSpecial(final Special specialField) { this.stack.add(specialField); return this; } @Override public ExpressionEvaluatorVisitor visitField(final JBBPNamedFieldInfo nullableNameFieldInfo, final String nullableExternalFieldName) { if (nullableNameFieldInfo != null) { this.stack.add(nullableNameFieldInfo); } else if (nullableExternalFieldName != null) { detectedFlagsSet.set(detectedFlagsSet.get() | FLAG_DETECTED_EXTERNAL_FIELDS); this.stack.add(nullableExternalFieldName); } return this; } @Override public ExpressionEvaluatorVisitor visitOperator(final Operator operator) { this.stack.add(operator); return this; } @Override public ExpressionEvaluatorVisitor visitConstant(final int value) { this.stack.add(value); return this; } private String arg2str(final Object obj) { if (obj instanceof ExprTreeItem) { return obj.toString(); } else if (obj instanceof Special) { if (obj == Special.STREAM_COUNTER) { return "(int)" + streamName + ".getCounter()"; } throw new Error("Unexpected special"); } else if (obj instanceof Integer) { if ((Integer) obj < 0) { return '(' + obj.toString() + ')'; } else { return obj.toString(); } } else if (obj instanceof String) { return String.format("%s.getNamedValue(this, \"%s\")", (getCurrentStruct().isRoot() ? "this" : "this." + NAME_ROOT_STRUCT), obj.toString()); } else if (obj instanceof JBBPNamedFieldInfo) { final NamedFieldInfo namedFieldInfo = foundNamedFields.get(obj); final String fieldPath = namedFieldInfo.makeSrcPath(getCurrentStruct()); String result; switch (namedFieldInfo.fieldType) { case BOOL: { result = '(' + fieldPath + "?1:0)"; } break; case CUSTOM: case VAR: { result = "((JBBPNumericField)" + fieldPath + ").getAsInt()"; } break; default: { result = "(int)" + fieldPath; } break; } return result; } else { return null; } } @Override public ExpressionEvaluatorVisitor visitEnd() { buffer.setLength(0); for (int i = 0; i < this.stack.size(); i++) { if (this.stack.get(i) instanceof Operator) { final Operator op = (Operator) this.stack.remove(i); i--; ExprTreeItem newItem = new ExprTreeItem(op); for (int j = 0; j < op.argsNumber; j++) { final Object val = this.stack.remove(i); i--; if (newItem.right == null) { newItem.right = val; } else { newItem.left = val; } } i++; this.stack.add(i, newItem); } } if (this.stack.size() != 1) { throw new IllegalStateException("Stack must have only element"); } final Object result = this.stack.remove(0); if (result instanceof ExprTreeItem) { buffer.append('(').append(result.toString()).append(')'); } else { buffer.append(arg2str(result)); } return this; } class ExprTreeItem { final Operator op; Object left; Object right; ExprTreeItem(Operator op) { this.op = op; } boolean doesNeedBrackets(Object obj) { if (!(obj instanceof ExprTreeItem)) { return false; } final ExprTreeItem that = (ExprTreeItem) obj; return that.op.priority <= this.op.priority || ((that.op == Operator.LSHIFT || that.op == Operator.RSHIFT || that.op == Operator.URSHIFT) && (this.op == Operator.LSHIFT || this.op == Operator.RSHIFT || this.op == Operator.URSHIFT)); } @Override public String toString() { String leftStr = arg2str(this.left); String rightStr = arg2str(this.right); if (doesNeedBrackets(this.left)) { leftStr = '(' + leftStr + ')'; } if (doesNeedBrackets(this.right)) { rightStr = '(' + rightStr + ')'; } return (leftStr == null ? "" : leftStr) + this.op.text + (rightStr == null ? "" : rightStr); } } }; evaluator.visitItems(this.compiledBlock, offsetInBlock, visitor); String result = buffer.toString(); if (result.startsWith("(") && result.endsWith(")")) { try { result = Integer.toString(Integer.parseInt(result.substring(1, result.length() - 1).trim())); } catch (NumberFormatException ex) { // ignore the excepton because it is checking exception } } if (!(evaluator instanceof IntConstValueEvaluator)) { if (doResultPostprocessing) { if ((this.parserFlags & JBBPParser.FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO) != 0) { result = "java.lang.Math.max(0," + result + ')'; } else { detectedFlagsSet.set(detectedFlagsSet.get() | FLAG_ADD_ASSERT_NOT_NEGATIVE_EXPR); result = "assrtExprNotNeg(" + result + ')'; } } } return result; } @Override public void visitActionItem(final int offsetInCompiledBlock, final int actionType, final JBBPIntegerValueEvaluator nullableArgument) { final String valueTxtIn = nullableArgument == null ? "1" : evaluatorToString(NAME_INPUT_STREAM, offsetInCompiledBlock, nullableArgument, this.flagSet, true); final String valueTxtOut = nullableArgument == null ? "1" : evaluatorToString(NAME_OUTPUT_STREAM, offsetInCompiledBlock, nullableArgument, this.flagSet, true); switch (actionType) { case CODE_RESET_COUNTER: { getCurrentStruct().getReadFunc().println(NAME_INPUT_STREAM + ".resetCounter();"); getCurrentStruct().getWriteFunc().println(NAME_OUTPUT_STREAM + ".resetCounter();"); } break; case CODE_ALIGN: { getCurrentStruct().getReadFunc().indent().print(NAME_INPUT_STREAM + ".align(") .print(valueTxtIn).println(");"); getCurrentStruct().getWriteFunc().indent().print(NAME_OUTPUT_STREAM + ".align(") .print(valueTxtOut).println(");"); } break; case CODE_SKIP: { getCurrentStruct().getReadFunc().indent().print(NAME_INPUT_STREAM + ".skip(") .print(valueTxtIn).println(");"); getCurrentStruct().getWriteFunc().indent() .printf("for(int I=0; I<%s; I++) %s.write(0);%n", valueTxtOut, NAME_OUTPUT_STREAM); } break; default: { throw new Error("Detected unknown action, contact developer!"); } } } private enum FieldType { BOOL(BinType.BOOL, BinType.BOOL_ARRAY, CODE_BOOL, "boolean", "boolean", "%s.readBoolean()", "%s.readBoolArray(%s)", "%s.write(%s ? 1 : 0)", "for(int I=0;I<%3$s;I++){%1$s.write(%2$s[I] ? 1 : 0);}", "for(int I=0;I<%2$s.length;I++){%1$s.write(%2$s[I] ? 1 : 0);}"), BYTE(BinType.BYTE, BinType.BYTE_ARRAY, CODE_BYTE, "byte", "byte", "(byte)%s.readByte()", "%s.readByteArray(%s, %s)", "%s.write(%s)", "%1$s.writeBytes(%2$s, %3$s, %4$s)", "%1$s.writeBytes(%2$s, %2$s.length, %3$s)"), UBYTE(BinType.UBYTE, BinType.UBYTE_ARRAY, CODE_UBYTE, "char", "byte", "(char)(%s.readByte() & 0xFF)", "%s.readByteArray(%s, %s)", "%s.write(%s)", "%1$s.writeBytes(%2$s, %3$s, %4$s)", "%1$s.writeBytes(%2$s, %2$s.length, %3$s)"), SHORT(BinType.SHORT, BinType.SHORT_ARRAY, CODE_SHORT, "short", "short", "(short)%s.readUnsignedShort(%s)", "%s.readShortArray(%s,%s)", "%s.writeShort(%s,%s)", "for(int I=0;I<%3$s;I++){%1$s.writeShort(%2$s[I],%4$s);}", "for(int I=0;I<%2$s.length;I++){%1$s.writeShort(%2$s[I],%3$s);}"), USHORT(BinType.USHORT, BinType.USHORT_ARRAY, CODE_USHORT, "char", "char", "(char)%s.readUnsignedShort(%s)", "%s.readUShortArray(%s,%s)", "%s.writeShort(%s,%s)", "for(int I=0;I<%3$s;I++){%1$s.writeShort(%2$s[I],%4$s);}", "for(int I=0;I<%2$s.length;I++){%1$s.writeShort(%2$s[I],%3$s);}"), INT(BinType.INT, BinType.INT_ARRAY, CODE_INT, "int", "int", "%s.readInt(%s)", "%s.readIntArray(%s,%s)", "%s.writeInt(%s,%s)", "for(int I=0;I<%3$s;I++){%1$s.writeInt(%2$s[I],%4$s);}", "for(int I=0;I<%2$s.length;I++){%1$s.writeInt(%2$s[I],%3$s);}"), LONG(BinType.LONG, BinType.LONG_ARRAY, CODE_LONG, "long", "long", "%s.readLong(%s)", "%s.readLongArray(%s,%s)", "%s.writeLong(%s,%s)", "for(int I=0;I<%3$s;I++){%1$s.writeLong(%2$s[I],%4$s);}", "for(int I=0;I<%2$s.length;I++){%1$s.writeLong(%2$s[I],%3$s);}"), CUSTOM(BinType.UNDEFINED, BinType.UNDEFINED, -1, "", "", "", "", "", "", ""), VAR(BinType.UNDEFINED, BinType.UNDEFINED, -2, "", "", "", "", "", "", ""), VAL(BinType.UNDEFINED, BinType.UNDEFINED, -3, "int", "", "", "", "", "", ""), BIT(BinType.BIT, BinType.BIT_ARRAY, -4, "", "", "", "", "", "", ""), FLOAT(BinType.FLOAT, BinType.FLOAT_ARRAY, -5, "float", "float", "%s.readFloat(%s)", "%s.readFloatArray(%s,%s)", "%s.writeFloat(%s,%s)", "for(int I=0;I<%3$s;I++){%1$s.writeFloat(%2$s[I],%4$s);}", "for(int I=0;I<%2$s.length;I++){%1$s.writeFloat(%2$s[I],%3$s);}"), DOUBLE(BinType.DOUBLE, BinType.DOUBLE_ARRAY, -6, "double", "double", "%s.readDouble(%s)", "%s.readDoubleArray(%s,%s)", "%s.writeDouble(%s,%s)", "for(int I=0;I<%3$s;I++){%1$s.writeDouble(%2$s[I],%4$s);}", "for(int I=0;I<%2$s.length;I++){%1$s.writeDouble(%2$s[I],%3$s);}"), STRING(BinType.UNDEFINED, BinType.UNDEFINED, -7, "String", "String", "%s.readString(%s)", "%s.readStringArray(%s,%s)", "%s.writeString(%s,%s)", "for(int I=0;I<%3$s;I++){%1$s.writeString(%2$s[I],%4$s);}", "%1$s.writeStringArray(%2$s,%3$s)"), UINT(BinType.UINT, BinType.UINT_ARRAY, -8, "long", "long", "%s.readUInt(%s)", "%s.readUIntArray(%s,%s)", "%s.writeUInt(%s,%s)", "for(int I=0;I<%3$s;I++){%1$s.writeUInt(%2$s[I],%4$s);}", "for(int I=0;I<%2$s.length;I++){%1$s.writeUInt(%2$s[I],%3$s);}"), UNKNOWN(BinType.UNDEFINED, BinType.UNDEFINED, Integer.MIN_VALUE, "", "", "", "", "", "", ""); private final BinType binType; private final BinType binTypeArray; private final int code; private final String javaSingleType; private final String javaArrayType; private final String methodReadOne; private final String methodReadArray; private final String methodWriteOne; private final String methodWriteArray; private final String methodWriteArrayWithUnknownSize; FieldType( final BinType binType, final BinType binTypeArray, final int code, final String javaSingleType, final String javaArrayType, final String readOne, final String readArray, final String writeOne, final String writeArray, final String writeArrayWithUnknownSize ) { this.binTypeArray = binTypeArray; this.binType = binType; this.code = code; this.methodWriteArrayWithUnknownSize = writeArrayWithUnknownSize; this.javaSingleType = javaSingleType; this.javaArrayType = javaArrayType; this.methodReadArray = readArray; this.methodReadOne = readOne; this.methodWriteArray = writeArray; this.methodWriteOne = writeOne; } static FieldType findForCode(final int code) { for (final FieldType t : values()) { if (t.code == code) { return t; } } return UNKNOWN; } void assertNotUnknown() { if (this == UNKNOWN) { throw new Error("Call method for unknown type"); } } String asJavaSingleFieldType() { assertNotUnknown(); return this.javaSingleType; } String asJavaArrayFieldType() { assertNotUnknown(); return this.javaArrayType; } String makeReaderForSingleField(final String streamName, final JBBPByteOrder byteOrder) { assertNotUnknown(); return String.format(this.methodReadOne, streamName, "JBBPByteOrder." + byteOrder.name()); } String makeWriterForSingleField(final String streamName, final String fieldName, final JBBPByteOrder byteOrder) { assertNotUnknown(); return String .format(this.methodWriteOne, streamName, fieldName, "JBBPByteOrder." + byteOrder.name()); } String makeReaderForArray(final String streamName, final String arraySize, final JBBPByteOrder byteOrder) { assertNotUnknown(); return String .format(this.methodReadArray, streamName, arraySize, "JBBPByteOrder." + byteOrder.name()); } String makeWriterForArray(final String streamName, final String fieldName, final String arraySize, final JBBPByteOrder byteOrder) { assertNotUnknown(); return String.format(this.methodWriteArray, streamName, fieldName, arraySize, "JBBPByteOrder." + byteOrder.name()); } String makeWriterForArrayWithUnknownSize(final String streamName, final String fieldName, final JBBPByteOrder byteOrder) { assertNotUnknown(); return String.format(this.methodWriteArrayWithUnknownSize, streamName, fieldName, "JBBPByteOrder." + byteOrder.name()); } BinType getBinType() { return this.binType; } BinType getBinTypeArray() { return this.binTypeArray; } } /** * Builder to build instance of converter. */ public static final class Builder { /** * Set of interfaces to be implemented by the main result class. */ private final Set mainClassImplements = new HashSet<>(); /** * The Parser to provide compiled data. */ private final JBBPParser srcParser; /** * Interfaces to be implemented by generated subclasses, also getters return * the interface type. */ private final Map mapSubClassesInterfaces = new HashMap<>(); /** * Superclasses to be extended by generated subclasses. */ private final Map mapSubClassesSuperclasses = new HashMap<>(); /** * Imternal classes must not be static. */ private boolean internalClassesNotStatic; /** * Flag to add Bin annotations to fields. * * @since 2.0.0 */ private boolean addBinAnnotations; /** * Flage to add newInstance methods into generated classes. * * @since 2.0.0 */ private boolean addNewInstanceMethods; /** * The Package name for the result class. */ private String mainClassPackage; /** * The Result class name for the result class. */ private String mainClassName; /** * The Flag to force the result class as abstract one. */ private boolean doMainClassAbstract; /** * The Name of class to be extended by the result class. */ private String superClass; /** * The Comment to be placed before package info. */ private String headComment; /** * Flag to lockBuilder the builder. */ private boolean lockBuilder; /** * Parser flags. */ private int parserFlags; /** * Generate getters and setters. */ private boolean addGettersSetters; /** * Text to be inserted into custom section of the resut class. */ private String mainClassCustomText; /** * Flag to generate fields in result class file. * * @since 1.4.0 */ private boolean generateFields; private Builder(final JBBPParser parser) { this.srcParser = parser; this.parserFlags = parser.getFlags(); this.generateFields = true; } private void assertNonLocked() { if (this.lockBuilder) { throw new IllegalStateException("Builder already locked"); } } /** * Map inside structures to mainClassImplements, a structure class will * implement mapped interface and getter of the structure object will return * interface object as result. * * @param mapClassNameToInterface map with structure path as the key and the * interface name as value, it can be null. Names of structures should be * in the lower case form amd dot separated for their hierarchy. (example: * "a.b.c") * @return the builder instance, must not be null */ public Builder setMapSubClassesInterfaces(final Map mapClassNameToInterface) { assertNonLocked(); this.mapSubClassesInterfaces.clear(); if (mapClassNameToInterface != null) { this.mapSubClassesInterfaces.putAll(mapClassNameToInterface); } return this; } /** * Add superclasses to classes generated for inside structures, a structure * class will extend mapped class. * * @param mapClassNameToSuperclasses map with structure path as the key and * the interface name as value, it can be null. Names of structures * should be in the lower case form amd dot separated for their hierarchy. * (example: "a.b.c") * @return the builder instance, must not be null * @since 1.4.0 */ public Builder setMapSubClassesSuperclasses( final Map mapClassNameToSuperclasses) { assertNonLocked(); this.mapSubClassesSuperclasses.clear(); if (mapClassNameToSuperclasses != null) { this.mapSubClassesSuperclasses.putAll(mapClassNameToSuperclasses); } return this; } /** * Set custom text, the text will be added into the end of the result class. * * @param value text value, it can be null * @return the builder instance, must not be null */ public Builder setMainClassCustomText(final String value) { assertNonLocked(); this.mainClassCustomText = value; return this; } /** * Set flag to generate getters setters. NB! All fields will be private * ones in the case. * * @param value flag, if true then generate getters setters, false otherwise * @return the builder instance, must not be null */ public Builder setAddGettersSetters(final boolean value) { assertNonLocked(); this.addGettersSetters = value; return this; } /** * Set the parser flags for the generated class, by default the flags are * imported from the base parser. * * @param value parser flags. * @return the builder instance, must not be null */ public Builder setParserFlags(final int value) { assertNonLocked(); this.parserFlags = value; return this; } /** * Turn on adding of Bin annotations to generated fields. * * @return the builder instance, must not be null * @since 2.0.0 */ public Builder addBinAnnotations() { assertNonLocked(); this.addBinAnnotations = true; return this; } /** * Turn on generate newInstance methods compatible with JBBPMapper. * * @return the builder instance, must not be null * @since 2.0.0 */ public Builder addNewInstanceMethods() { assertNonLocked(); this.addNewInstanceMethods = true; return this; } /** * Set the package for the generated class. * * @param value name of the package, it can be empty or null in the case the * class will be in the default package * @return the builder instance, must not be null */ public Builder setMainClassPackage(final String value) { assertNonLocked(); this.mainClassPackage = value; return this; } /** * Set flag to force abstract modifier for the generated class, by default * the class is abstract one only if it contains abstract methods. * * @param value true if to force the abstract modifier, false otherwise * @return the builder instance, must not be null */ public Builder setDoMainClassAbstract(final boolean value) { assertNonLocked(); this.doMainClassAbstract = value; return this; } /** * The Name of the generated class. Must be provided. * * @param value the class name for the generated class, must not be null * @return the builder instance, must not be null */ public Builder setMainClassName(final String value) { assertNonLocked(); this.mainClassName = value; return this; } /** * Don't make insternal generated classes as static ones * * @return the builder instance, must not be null * @since 1.4.0 */ public Builder doInternalClassesNonStatic() { assertNonLocked(); this.internalClassesNotStatic = true; return this; } /** * Set the superclass for the main generated class. NB! Also it affects * read and write methods of the main class, the class will be used as the * return type. * * @param value the superclass name, it can be null * @return the builder instance, must not be null */ public Builder setSuperClass(final String value) { assertNonLocked(); this.superClass = value; return this; } /** * Set interfaces to be added into 'implements' for the generated class. * * @param values interface names * @return the builder instance, must not be null */ public Builder setMainClassImplements(final String... values) { assertNonLocked(); Collections.addAll(this.mainClassImplements, values); return this; } /** * Set commentaries placed just before first package directive of the * generated class. * * @param text text to be used as comment, it can be null * @return the builder instance, must not be null */ public Builder setHeadComment(final String text) { assertNonLocked(); this.headComment = text; return this; } /** * Disable generate fields, useful if some super class extended and its * fields should be used instead of generated ones. If disable then all code * will be generated but without class fields. By default field generate is * enabled. * * @return the builder instance, must not be null * @since 1.4.0 */ public Builder disableGenerateFields() { this.generateFields = false; return this; } /** * Build converter with provided parameters. NB! It locks builder parameters * and they can't be changed in future. * * @return a converter instance. */ public JBBPToJavaConverter build() { this.lockBuilder = true; if (this.mainClassName == null) { throw new NullPointerException("Class name must not be null"); } return new JBBPToJavaConverter(this); } } private static class Struct { private final String classModifiers; private final String className; private final Struct parent; private final List children = new ArrayList<>(); private final JavaSrcTextBuffer fields = new JavaSrcTextBuffer(); private final JavaSrcTextBuffer readFunc = new JavaSrcTextBuffer(); private final JavaSrcTextBuffer writeFunc = new JavaSrcTextBuffer(); private final JavaSrcTextBuffer gettersSetters = new JavaSrcTextBuffer(); private final JavaSrcTextBuffer misc = new JavaSrcTextBuffer(); private final String path; private Struct(final Struct parent, final String className, final String classModifiers) { this.path = parent == null ? "" : parent.path + (parent.path.length() == 0 ? "" : ".") + className.toLowerCase(Locale.ENGLISH); this.classModifiers = classModifiers; this.className = className; this.parent = parent; if (this.parent != null) { this.parent.children.add(this); } } private static String interfaces2str(final Set set) { final StringBuilder buffer = new StringBuilder(); for (final String s : set) { if (buffer.length() > 0) { buffer.append(','); } buffer.append(s); } return buffer.toString(); } boolean isRoot() { return this.parent == null; } String getPath() { return this.path; } Struct findRoot() { if (this.parent == null) { return this; } return this.parent.findRoot(); } void write( final JavaSrcTextBuffer buffer, final String extraModifier, final String superClass, final Set implementedInterfaces, final Map mapStructInterfaces, final Map mapStructSuperclasses, final String commonSectionText, final String specialMethods, final String customText, final boolean useSuperclassForReadWrite) { final String interfaceForGetSet = mapStructInterfaces == null ? null : mapStructInterfaces.get(this.getPath()); buffer.indent().printf( "%s%sclass %s%s%s {%n", this.classModifiers, extraModifier == null ? " " : ' ' + extraModifier + ' ', this.className, superClass != null ? " extends " + superClass + ' ' : "", interfaceForGetSet == null ? implementedInterfaces != null && !implementedInterfaces.isEmpty() ? " implements " + interfaces2str(implementedInterfaces) + ' ' : "" : " implements " + interfaceForGetSet ); buffer.incIndent(); if (commonSectionText != null) { buffer.printLinesWithIndent(commonSectionText); } for (final Struct c : this.children) { c.write(buffer, null, mapStructSuperclasses.get(c.getPath()), null, mapStructInterfaces, mapStructSuperclasses, null, null, null, false); } buffer.println(); buffer.printLinesWithIndent(this.fields.toString()); if (this.parent != null) { buffer.indent() .println("private final " + findRoot().className + ' ' + NAME_ROOT_STRUCT + ';'); } buffer.println(); buffer.indent().print("public ").print(this.className).print(" (") .print(this.parent == null ? "" : (findRoot().className + " root")) .println(") {"); buffer.incIndent(); if (this.parent != null) { buffer.indent().print(NAME_ROOT_STRUCT).print(" = ").println("root;"); } buffer.decIndent(); buffer.indent().println("}"); buffer.println(); buffer.indent().printf("public %s read(final JBBPBitInputStream In) throws IOException {%n", useSuperclassForReadWrite && superClass != null ? superClass : this.className); buffer.incIndent(); buffer.printLinesWithIndent(this.readFunc.toString()); buffer.indent().println("return this;"); buffer.decIndent(); buffer.indent().println("}"); buffer.println(); buffer.indent() .printf("public %s write(final JBBPBitOutputStream Out) throws IOException {%n", useSuperclassForReadWrite && superClass != null ? superClass : this.className); buffer.incIndent(); buffer.printLinesWithIndent(this.writeFunc.toString()); buffer.indent().println("return this;"); buffer.decIndent(); buffer.indent().println("}"); if (specialMethods != null) { buffer.println(); buffer.printLinesWithIndent(specialMethods); buffer.println(); } if (!this.gettersSetters.isEmpty()) { buffer.println(); buffer.printLinesWithIndent(this.gettersSetters.toString()); buffer.println(); } if (!this.misc.isEmpty()) { buffer.println(); buffer.printLinesWithIndent(this.misc.toString()); buffer.println(); } if (customText != null && customText.length() != 0) { buffer.printCommentLinesWithIndent("------ Custom section START"); buffer.printLinesWithIndent(customText); buffer.printCommentLinesWithIndent("------ Custom section END"); } buffer.decIndent(); buffer.indent().println("}"); } JavaSrcTextBuffer getMisc() { return this.misc; } JavaSrcTextBuffer getWriteFunc() { return this.writeFunc; } JavaSrcTextBuffer getReadFunc() { return this.readFunc; } JavaSrcTextBuffer getFields() { return this.fields; } JavaSrcTextBuffer getGettersSetters() { return this.gettersSetters; } } private final class NamedFieldInfo { final JBBPNamedFieldInfo info; final Struct struct; final FieldType fieldType; NamedFieldInfo(final JBBPNamedFieldInfo info, final Struct struct, final FieldType fieldType) { this.info = info; this.struct = struct; this.fieldType = fieldType; } private String toFieldIfNotEmpty(final String structPath) { return structPath.isEmpty() ? "" : structPath + "."; } String makeSrcPath(final Struct currentStruct) { final String fieldName = prepFldName(info.getFieldName()); if (this.struct == currentStruct) { return "this." + fieldName; } else { final String structPath = this.struct.getPath(); if (currentStruct.isRoot()) { return "this." + toFieldIfNotEmpty(structPath) + fieldName; } else { return "this." + NAME_ROOT_STRUCT + '.' + toFieldIfNotEmpty(structPath) + fieldName; } } } } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/tokenizer/000077500000000000000000000000001445000440600317005ustar00rootroot00000000000000JBBPFieldTypeParameterContainer.java000066400000000000000000000102011445000440600405050ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/tokenizer/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.tokenizer; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPFieldDouble; import com.igormaznitsa.jbbp.model.JBBPFieldFloat; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldUInt; import java.io.Serializable; /** * The Class is a container to keep parsed field type parameters. * * @since 1.0 */ public final class JBBPFieldTypeParameterContainer implements Serializable { private static final long serialVersionUID = 1557492283811982431L; /** * The Byte order for the field. */ private final JBBPByteOrder byteOrder; /** * The field type. */ private final String typeName; /** * Extra data for the field (for instance number of bits for a bit field). */ private final String extraData; /** * The Constructor * * @param byteOrder the byte order for the field, must not be null * @param typeName the type of the field, can be null * @param extraData the extra data placed after ':' char, can be null */ public JBBPFieldTypeParameterContainer(final JBBPByteOrder byteOrder, final String typeName, final String extraData) { this.byteOrder = byteOrder; this.typeName = typeName; this.extraData = extraData; } /** * Get the byte order for the field. It can be null. * * @return the defined byte order for the field. */ public JBBPByteOrder getByteOrder() { return this.byteOrder; } /** * Get the type name of the field. * * @return the type name as String. */ public String getTypeName() { return this.typeName; } /** * Get the extra data for the field. * * @return the extra data as string or null. */ public String getExtraData() { return this.extraData; } /** * Extract expression for extra data. * * @return extracted expression from extra data, null if it is not extra data */ public String getExtraDataExpression() { String result = null; if (hasExpressionAsExtraData()) { result = this.extraData.substring(1, this.extraData.length() - 1); } return result; } /** * Check that the extra data is expression. * * @return true if the extra data is expression, false otherwise */ public boolean hasExpressionAsExtraData() { return this.extraData != null && this.extraData.startsWith("(") && this.extraData.endsWith(")"); } /** * Check that the type is a special one ('floatj', 'doublej', 'stringj', 'uint' or 'value'). * * @return true if the type is a special one * @see JBBPFieldFloat#TYPE_NAME * @see JBBPFieldDouble#TYPE_NAME * @see JBBPFieldString#TYPE_NAME * @see JBBPFieldUInt#TYPE_NAME * @since 1.4.0 */ public boolean isSpecialField() { return this.typeName.equals(JBBPFieldFloat.TYPE_NAME) || this.typeName.equals(JBBPFieldDouble.TYPE_NAME) || this.typeName.equals(JBBPFieldString.TYPE_NAME) || this.typeName.equals(JBBPFieldUInt.TYPE_NAME) || this.typeName.equals("val"); } @Override public String toString() { final StringBuilder result = new StringBuilder(); if (byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { result.append('<'); } result.append(this.typeName); if (extraData != null) { int insertIndex = typeName.indexOf(' '); if (insertIndex < 0) { insertIndex = result.length(); } else { insertIndex++; } result.insert(insertIndex, ':' + extraData); } return result.toString(); } } JBBPToken.java000066400000000000000000000117031445000440600342040ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/tokenizer/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.tokenizer; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.Serializable; /** * The Class describes a token from parsed script. * * @since 1.0 */ public final class JBBPToken implements Serializable { private static final long serialVersionUID = 7864654729087070154L; /** * The Token type. It must not be null. */ private final JBBPTokenType type; /** * The Field name value. It can be null. */ private final String fieldName; /** * The Array size string value. It can be null if there is not any defined array size. */ private final String arraySize; /** * Parsed field type part. */ private final JBBPFieldTypeParameterContainer fieldTypeParameters; /** * The Token position in the script string, the first char is 0. */ private final int position; /** * The Constructor * * @param type the token type, it can't be null * @param position the token position in the script string * @param fieldTypeParameters the parsed field type, it can be null. * @param arrayLength the string value of array size, it can be null * @param fieldName the field name, it can be null */ JBBPToken(final JBBPTokenType type, final int position, final JBBPFieldTypeParameterContainer fieldTypeParameters, final String arrayLength, final String fieldName) { JBBPUtils.assertNotNull(type, "Type must not be null"); this.type = type; this.position = position; this.fieldTypeParameters = fieldTypeParameters; this.fieldName = fieldName; this.arraySize = arrayLength; } /** * Get the token position value in the string * * @return the position value, the first char is 0. If the value is negative one then the position is undefined. */ public int getPosition() { return this.position; } /** * Get the token type. * * @return get the token type. * @see JBBPTokenType */ public JBBPTokenType getType() { return this.type; } /** * Get the field name of the value represented by the token, * * @return the field name as string or null if the field doesn't have defined field name. */ public String getFieldName() { return this.fieldName; } /** * Get the field type parameters. * * @return the field type parameters */ public JBBPFieldTypeParameterContainer getFieldTypeParameters() { return this.fieldTypeParameters; } /** * Check that the token is array. * * @return true if the token represents an array, false otherwise */ public boolean isArray() { return this.arraySize != null; } /** * Check that the toke is commentaries. * * @return true if the token represents commentaries, false otherwise */ public boolean isComment() { return this.type == JBBPTokenType.COMMENT; } /** * Check that the array is not fixed size one. * * @return true if the field represents a non-fixed size array */ public boolean isVarArrayLength() { return isArray() && !JBBPUtils.isNumber(this.arraySize); } /** * Get the array size value in its raw form. * * @return the array size value as string, if the field is not an array then null. */ public String getArraySizeAsString() { return this.arraySize; } /** * Get numeric representation of the array size. * * @return the parsed numeric representation of the array size value or null if it can't be parsed * @throws NullPointerException will be thrown if the array size value is null */ public Integer getArraySizeAsInt() { if (this.arraySize == null) { throw new NullPointerException("Array size is not defined"); } try { return Integer.valueOf(this.arraySize.trim()); } catch (NumberFormatException ex) { return null; } } @Override public String toString() { final StringBuilder result = new StringBuilder(); result.append(this.type.name()).append(' '); if (this.fieldTypeParameters != null) { result.append(this.fieldTypeParameters).append(' '); } if (this.arraySize != null) { result.append('[').append(this.arraySize).append("] "); } if (this.fieldName != null) { result.append(this.fieldName); } if (this.type != JBBPTokenType.COMMENT) { result.append(';'); } return result.toString(); } } JBBPTokenType.java000066400000000000000000000017671445000440600350570ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/tokenizer/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.tokenizer; /** * The Enumeration represents allowed token types to be met in binary block parser script. * * @since 1.0 */ public enum JBBPTokenType { /** * A Commentaries. */ COMMENT, /** * A Structure opening token. */ STRUCT_START, /** * A Regular field or field array. */ ATOM, /** * A Structure closing token. */ STRUCT_END } JBBPTokenizer.java000066400000000000000000000275511445000440600351060ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/tokenizer/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.tokenizer; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.exceptions.JBBPTokenizerException; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPFieldDouble; import com.igormaznitsa.jbbp.model.JBBPFieldFloat; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldUInt; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.util.HashSet; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * The Class implements a token parser which parses a String to binary block * parser tokens and check their format. * * @since 1.0 */ @SuppressWarnings("IterableAndIterator") public final class JBBPTokenizer implements Iterable, Iterator { /** * The Pattern to break a string to tokens. * WARNING! DO NOT UNESCAPE '}' AND '{' CHARS BECAUSE IT MAKES INCOMPATIBILITY WITH ANDROID! */ private static final Pattern PATTERN = Pattern.compile( "\\s*//.*$|\\s*(\\})|\\s*([^\\s;\\[\\]\\}\\{]+)?\\s*(?:\\[\\s*([^\\[\\]\\{\\};]+)\\s*\\])?\\s*([^\\d\\s;\\[\\]\\}\\{/][^\\s;\\[\\]\\}\\{/]*)?\\s*([\\{;])", Pattern.MULTILINE); /** * The Pattern to break field type to parameters. */ private static final Pattern FIELD_TYPE_BREAK_PATTERN = Pattern.compile("^([<>])?([\\w][\\w$]*)(?::((?:[-]?\\d+)|(?:\\(.+\\))))?$"); /** * Inside table to keep disabled names for fields. */ private static final Set GLOBAL_RESERVED_TYPE_NAMES; static { GLOBAL_RESERVED_TYPE_NAMES = new HashSet<>(); GLOBAL_RESERVED_TYPE_NAMES.add(JBBPFieldFloat.TYPE_NAME); GLOBAL_RESERVED_TYPE_NAMES.add(JBBPFieldDouble.TYPE_NAME); GLOBAL_RESERVED_TYPE_NAMES.add(JBBPFieldString.TYPE_NAME); GLOBAL_RESERVED_TYPE_NAMES.add(JBBPFieldUInt.TYPE_NAME); GLOBAL_RESERVED_TYPE_NAMES.add("bit"); GLOBAL_RESERVED_TYPE_NAMES.add("bool"); GLOBAL_RESERVED_TYPE_NAMES.add("byte"); GLOBAL_RESERVED_TYPE_NAMES.add("ubyte"); GLOBAL_RESERVED_TYPE_NAMES.add("short"); GLOBAL_RESERVED_TYPE_NAMES.add("ushort"); GLOBAL_RESERVED_TYPE_NAMES.add("int"); GLOBAL_RESERVED_TYPE_NAMES.add("long"); GLOBAL_RESERVED_TYPE_NAMES.add("align"); GLOBAL_RESERVED_TYPE_NAMES.add("skip"); GLOBAL_RESERVED_TYPE_NAMES.add("var"); GLOBAL_RESERVED_TYPE_NAMES.add("val"); GLOBAL_RESERVED_TYPE_NAMES.add("$"); } private final Matcher matcher; private final String processingString; private final Set reservedTypeNames; /** * The Next available token. */ private JBBPToken nextItem; /** * The Field contains deferred error. */ private JBBPTokenizerException detectedException; private int lastCharSubstringFound = -1; /** * Constructor. * * @param str a string to be parsed, must not be null. */ public JBBPTokenizer(final String str) { this(str, null); } /** * Constructor. * * @param str a string to be parsed, must not be null. * @param customFieldTypeProcessor custom field type processor, it can be null */ public JBBPTokenizer(final String str, final JBBPCustomFieldTypeProcessor customFieldTypeProcessor) { JBBPUtils.assertNotNull(str, "String must not be null"); if (customFieldTypeProcessor == null) { this.reservedTypeNames = GLOBAL_RESERVED_TYPE_NAMES; } else { this.reservedTypeNames = new HashSet<>(GLOBAL_RESERVED_TYPE_NAMES); for (final String customType : customFieldTypeProcessor.getCustomFieldTypes()) { JBBPUtils.assertNotNull(customType, "Type must not be null"); this.reservedTypeNames.add(customType); } } this.processingString = str; this.matcher = PATTERN.matcher(this.processingString); readNextItem(); } /** * Case sensitive check that the name is among global reserved ones. * * @param name the name to check, must not be null * @return true if the name is global reserved one, false otherwise. */ public static boolean isGlobalReservedName(final String name) { return GLOBAL_RESERVED_TYPE_NAMES.contains(name); } /** * Inside method to read the next token from the string and place it into * inside storage. */ private void readNextItem() { if (matcher.find()) { final String groupWholeFound = this.matcher.group(0); final String groupWholeFoundTrimmed = groupWholeFound.trim(); final String groupCloseStruct = this.matcher.group(1); final String groupTypeOrName = this.matcher.group(2); final String groupArrayLength = this.matcher.group(3); final String groupName = this.matcher.group(4); final String groupEnder = this.matcher.group(5); final String skipString = this.processingString.substring(Math.max(this.lastCharSubstringFound, 0), matcher.start()) .trim(); if (skipString.length() != 0 && !skipString.startsWith("//")) { this.detectedException = new JBBPTokenizerException(skipString, this.processingString, Math.max(this.lastCharSubstringFound, 0)); } else { JBBPTokenType type = JBBPTokenType.ATOM; if (groupWholeFoundTrimmed.startsWith("//")) { type = JBBPTokenType.COMMENT; } else if ("{".equals(groupEnder)) { // { type = JBBPTokenType.STRUCT_START; if (groupName != null) { final int position = matcher.start() + groupWholeFound.length() - groupWholeFoundTrimmed.length(); this.detectedException = new JBBPTokenizerException( "Wrong structure format, it must have only name (and may be array definition)", this.processingString, position); return; } } else if (groupCloseStruct != null) { type = JBBPTokenType.STRUCT_END; } else if (groupTypeOrName == null) { final int position = matcher.start() + groupWholeFound.length() - groupWholeFoundTrimmed.length(); this.detectedException = new JBBPTokenizerException("Detected atomic field definition without type", this.processingString, position); return; } String fieldType = groupTypeOrName; int position = matcher.start(); final String fieldName; if (type == JBBPTokenType.COMMENT) { fieldName = matcher.group(0).trim().substring(2).trim(); position += groupWholeFound.indexOf('/'); } else { if (type == JBBPTokenType.STRUCT_START) { fieldName = fieldType; fieldType = null; } else { fieldName = groupName; } position += groupWholeFound.length() - groupWholeFound.trim().length(); this.detectedException = checkFieldName(fieldName, position); if (this.detectedException != null) { return; } } JBBPFieldTypeParameterContainer parsedType = null; if (fieldType != null) { final Matcher typeMatcher = FIELD_TYPE_BREAK_PATTERN.matcher(fieldType); boolean wrongFormat = true; if (typeMatcher.find()) { final String groupTypeByteOrder = typeMatcher.group(1); final String groupTypeName = typeMatcher.group(2); final String groupTypeExtraField = typeMatcher.group(3); wrongFormat = false; JBBPByteOrder byteOrder; if (groupTypeByteOrder != null) { if (">".equals(groupTypeByteOrder)) { byteOrder = JBBPByteOrder.BIG_ENDIAN; } else if ("<".equals(groupTypeByteOrder)) { byteOrder = JBBPByteOrder.LITTLE_ENDIAN; } else { throw new Error( "Illegal byte order char, unexpected error, contact developer please [" + fieldType + ']'); } } else { byteOrder = JBBPByteOrder.BIG_ENDIAN; } parsedType = new JBBPFieldTypeParameterContainer(byteOrder, groupTypeName, groupTypeExtraField); } if (wrongFormat) { this.detectedException = new JBBPTokenizerException("Wrong format of type definition [" + fieldType + ']', this.processingString, position); return; } } this.nextItem = new JBBPToken(type, position, parsedType, groupArrayLength, fieldName); lastCharSubstringFound = matcher.end(); } } else { if (this.lastCharSubstringFound < 0) { this.detectedException = new JBBPTokenizerException("Wrong format of whole string", this.processingString, 0); } else { final String restOfString = this.processingString.substring(this.lastCharSubstringFound); if (restOfString.trim().length() != 0) { throw new JBBPTokenizerException( "Can't recognize a part of script [" + restOfString + ']', this.processingString, this.lastCharSubstringFound); } } this.nextItem = null; } } /** * Check a field name * * @param name the name to be checked. * @param position the token position in the string. * @return JBBPTokenizerException if the field name has wrong chars or * presented in disabled name set, null otherwise */ private JBBPTokenizerException checkFieldName(final String name, final int position) { if (name != null) { final String normalized = JBBPUtils.normalizeFieldNameOrPath(name); if (normalized.indexOf('.') >= 0) { return new JBBPTokenizerException("Field name must not contain '.' char", this.processingString, position); } if (normalized.length() > 0) { if (normalized.equals("_") || normalized.equals("$$") || normalized.startsWith("$") || Character.isDigit(normalized.charAt(0)) ) { return new JBBPTokenizerException("'" + name + "' can't be field name", this.processingString, position); } for (int i = 1; i < normalized.length(); i++) { final char chr = normalized.charAt(i); if (chr != '_' && !Character.isLetterOrDigit(chr)) { return new JBBPTokenizerException("Char '" + chr + "' not allowed in name", this.processingString, position); } } } } return null; } @Override public Iterator iterator() { return this; } @Override public boolean hasNext() { return !(this.detectedException == null && this.nextItem == null); } @Override public JBBPToken next() { if (this.detectedException != null) { final JBBPTokenizerException ex = this.detectedException; this.detectedException = null; throw ex; } final JBBPToken current = this.nextItem; if (current == null) { throw new NoSuchElementException("Parsing has been completed"); } readNextItem(); return current; } /** * The Operation is unsupported one. */ @Override public void remove() { throw new UnsupportedOperationException("Unsupported operation"); } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/varlen/000077500000000000000000000000001445000440600311555ustar00rootroot00000000000000JBBPEvaluatorFactory.java000066400000000000000000000056111445000440600356740ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/varlen/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.varlen; import com.igormaznitsa.jbbp.compiler.JBBPCompilerUtils; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.util.List; /** * The Factory generates a special evaluator which is appropriate for variable array size text. * It is a singleton and can't be created directly, only through the special getInstance method. * * @since 1.0 */ public final class JBBPEvaluatorFactory { private static final JBBPEvaluatorFactory INSTANCE = new JBBPEvaluatorFactory(); private JBBPEvaluatorFactory() { } /** * Get an Instance of the factory. * * @return the factory INSTANCE. */ public static JBBPEvaluatorFactory getInstance() { return INSTANCE; } /** * Make an appropriate evaluator for an expression text. * * @param expression an expression text, must not be null * @param namedFields a named field list * @param compiledScript a compiled script block * @return a generated evaluator, it will not be null in any case * @see JBBPExpressionEvaluator * @see JBBPOnlyFieldEvaluator */ public JBBPIntegerValueEvaluator make(final String expression, final List namedFields, final byte[] compiledScript) { final JBBPIntegerValueEvaluator result; if (JBBPExpressionEvaluator.hasExpressionOperators(expression)) { // expression result = new JBBPExpressionEvaluator(expression, namedFields, compiledScript); } else { // only field int index = -1; if (expression.startsWith("$")) { result = new JBBPOnlyFieldEvaluator(expression.substring(1), index); } else { for (int i = namedFields.size() - 1; i >= 0; i--) { final JBBPNamedFieldInfo field = namedFields.get(i); if (expression.equals(field.getFieldPath())) { index = i; break; } } if (index < 0) { result = new JBBPExpressionEvaluator(expression, namedFields, compiledScript); } else { JBBPCompilerUtils .assertFieldIsNotArrayOrInArray(namedFields.get(index), namedFields, compiledScript); result = new JBBPOnlyFieldEvaluator(null, index); } } } return result; } } JBBPExpressionEvaluator.java000066400000000000000000000617371445000440600364370ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/varlen/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.varlen; import static com.igormaznitsa.jbbp.utils.JBBPUtils.ARRAY_STRING_EMPTY; import com.igormaznitsa.jbbp.JBBPNamedNumericFieldMap; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.JBBPCompilerUtils; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.conversion.ExpressionEvaluatorVisitor; import com.igormaznitsa.jbbp.exceptions.JBBPCompilationException; import com.igormaznitsa.jbbp.exceptions.JBBPEvalException; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.model.JBBPNumericField; import com.igormaznitsa.jbbp.utils.JBBPIntCounter; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * The Class implements an evaluator which can calculate an expression. * * @since 1.0 */ public final class JBBPExpressionEvaluator implements JBBPIntegerValueEvaluator { private static final long serialVersionUID = -2951446352849455161L; /** * Code for a left bracket. */ private static final int PSEUDOCODE_LEFT_BRACKET = 0; /** * Code for a variable. */ private static final int CODE_VAR = 1; /** * Code for a variable provided by an external provider. */ private static final int CODE_EXTVAR = 2; /** * Code for a constant. */ private static final int CODE_CONST = 3; /** * Code for unary bit NOT operator. */ private static final int CODE_NOT = 4; /** * Code for unary '-' operator. */ private static final int CODE_UNARYMINUS = 5; /** * Code for unary '+' operator. */ private static final int CODE_UNARYPLUS = 6; /** * Code for ADD '+' operator. */ private static final int CODE_ADD = 7; /** * Code for SUB '-' operator. */ private static final int CODE_MINUS = 8; /** * Code for MUL '*' operator. */ private static final int CODE_MUL = 9; /** * Code for DIV '/' operator. */ private static final int CODE_DIV = 10; /** * Code for MOD '%' operator. */ private static final int CODE_MOD = 11; /** * Code for bit OR '|' operator. */ private static final int CODE_OR = 12; /** * Code for bit XOR '^' operator. */ private static final int CODE_XOR = 13; /** * Code for bit AND '&' operator. */ private static final int CODE_AND = 14; /** * Code for left shift '<<' operator. */ private static final int CODE_LSHIFT = 15; /** * Code for right shift '>>' operator. */ private static final int CODE_RSHIFT = 16; /** * Code for right sign shift '>>>' operator. */ private static final int CODE_RSIGNSHIFT = 17; /** * Array of operator priorities for their codes. */ private static final int[] PRIORITIES = new int[] {0, 1000, 1000, 1000, 500, 500, 500, 200, 200, 300, 300, 300, 50, 100, 150, 175, 175, 175}; /** * Array of operator symbols mapped to their codes. */ private static final String[] SYMBOLS = new String[] {"(", "", "", "", "~", "-", "+", "+", "-", "*", "/", "%", "|", "^", "&", "<<", ">>", ">>>"}; /** * Array of first chars of operators to recognize a string as possible expression. */ private static final char[] OPERATOR_FIRST_CHARS = new char[] {'(', '+', '-', '*', '/', '%', '|', '&', '^', '~', ')', '>', '<'}; /** * The Pattern to parse an expression. */ private static final Pattern PATTERN = Pattern.compile("([0-9]+)|([()])|(<<|>>>|>>|[%*+\\-/&|^~])|([\\S][^<>\\s+%*\\-/()&|^~]*)"); /** * The Array contains byte code of compiled expression. */ private final byte[] compiledExpression; /** * The String contains the script source for expression. */ private final String expressionSource; /** * The Array contains external variable names which values should be provided externally. */ private final String[] externalValueNames; /** * Max stack depth for the expression. * * @since 1.2.1 */ private final int maxStackDepth; /** * The Constructor. It makes compilation an expression into internal representation. * * @param expression a source expression, must not be null * @param namedFields a named field info list, must not be null * @param compiledData the current compiled data block of JBBP parent script for the expression, must not be null * @throws JBBPCompilationException if any problem in compilation */ public JBBPExpressionEvaluator(final String expression, final List namedFields, final byte[] compiledData) { this.expressionSource = expression; final Matcher matcher = PATTERN.matcher(expression); int lastFound = -1; final ByteArrayOutputStream compiledScript = new ByteArrayOutputStream(256); final List operationStack = new ArrayList<>(); boolean prevoperator = false; int unaryOperatorCode = -1; boolean theFirstInTheSubExpression = true; int counterOperators = 0; int counterVarsAndConstants = 0; final List externalValueNameList = new ArrayList<>(); while (matcher.find()) { if (lastFound >= 0) { // check for skipped substring final String subString = expression.substring(lastFound, matcher.start()); if (subString.trim().length() != 0) { throw new JBBPCompilationException( "Can't recognize part of expression '" + subString + "' [" + expression + ']'); } } lastFound = matcher.end(); final String number = matcher.group(1); final String bracket = matcher.group(2); final String operator = matcher.group(3); final String variable = matcher.group(4); if (variable != null) { prevoperator = false; counterVarsAndConstants++; final String normalized = JBBPUtils.normalizeFieldNameOrPath(variable); final int nameIndex; final boolean extValue; if (normalized.startsWith("$")) { extValue = true; nameIndex = externalValueNameList.size(); externalValueNameList.add(normalized.substring(1)); } else { extValue = false; nameIndex = JBBPCompilerUtils.findIndexForFieldPath(normalized, namedFields); if (nameIndex < 0) { throw new JBBPCompilationException("Unknown variable [" + variable + ']'); } JBBPCompilerUtils.assertFieldIsNotArrayOrInArray(namedFields.get(nameIndex), namedFields, compiledData); } try { compiledScript.write(extValue ? CODE_EXTVAR : CODE_VAR); compiledScript.write(JBBPUtils.packInt(nameIndex)); if (unaryOperatorCode > 0) { switch (unaryOperatorCode) { case CODE_ADD: { // do nothing } break; case CODE_MINUS: case CODE_UNARYMINUS: { compiledScript.write(CODE_UNARYMINUS); } break; case CODE_NOT: { compiledScript.write(CODE_NOT); } break; default: throw new Error("Unsupported unary operator [" + SYMBOLS[unaryOperatorCode] + ']'); } unaryOperatorCode = -1; } } catch (IOException ex) { throw new Error("Unexpected IO exception", ex); } theFirstInTheSubExpression = false; } else if (operator != null) { counterOperators++; final int code; switch (operator) { case "+": code = CODE_ADD; break; case "-": code = CODE_MINUS; break; case "*": code = CODE_MUL; break; case "%": code = CODE_MOD; break; case "/": code = CODE_DIV; break; case "&": code = CODE_AND; break; case "|": code = CODE_OR; break; case "^": code = CODE_XOR; break; case "~": code = CODE_NOT; break; case "<<": code = CODE_LSHIFT; break; case ">>": code = CODE_RSHIFT; break; case ">>>": code = CODE_RSIGNSHIFT; break; default: throw new Error("Detected unsupported operator, contact developer [" + operator + ']'); } if (theFirstInTheSubExpression) { assertUnaryOperator(operator); unaryOperatorCode = codeToUnary(code); } else if (prevoperator) { if (unaryOperatorCode > 0) { assertUnaryOperator(operator); operationStack.add(unaryOperatorCode); unaryOperatorCode = codeToUnary(code); } else { assertUnaryOperator(operator); unaryOperatorCode = codeToUnary(code); } } else { unaryOperatorCode = -1; final int currentPriority = PRIORITIES[code]; while (!operationStack.isEmpty()) { final int top = operationStack.get(operationStack.size() - 1); if (PRIORITIES[top] >= currentPriority) { operationStack.remove(operationStack.size() - 1); compiledScript.write(top); } else { break; } } operationStack.add(code); } prevoperator = true; theFirstInTheSubExpression = false; } else if (bracket != null) { prevoperator = false; if ("(".equals(bracket)) { if (unaryOperatorCode > 0) { operationStack.add(unaryOperatorCode); unaryOperatorCode = -1; } operationStack.add(PSEUDOCODE_LEFT_BRACKET); theFirstInTheSubExpression = true; } else if (")".equals(bracket)) { boolean metLeftPart = false; while (!operationStack.isEmpty()) { final int top = operationStack.remove(operationStack.size() - 1); if (top != PSEUDOCODE_LEFT_BRACKET) { compiledScript.write(top); } else { metLeftPart = true; break; } } if (!metLeftPart) { throw new JBBPCompilationException( "Detected unclosed bracket [" + this.expressionSource + ']'); } } else { throw new Error( "Detected unsupported bracket, connect developer for the error [" + bracket + ']'); } } else if (number != null) { counterVarsAndConstants++; prevoperator = false; try { int parsed = Integer.parseInt(number); if (unaryOperatorCode >= 0) { switch (unaryOperatorCode) { case CODE_UNARYPLUS: case CODE_ADD: { // do nothing } break; case CODE_UNARYMINUS: case CODE_MINUS: { parsed = -parsed; } break; case CODE_NOT: { parsed = ~parsed; } break; default: { throw new Error("Unsupported unary operator [" + SYMBOLS[unaryOperatorCode] + ']'); } } } unaryOperatorCode = -1; compiledScript.write(CODE_CONST); try { compiledScript.write(JBBPUtils.packInt(parsed)); } catch (IOException ex) { throw new RuntimeException("Unexpected IO exception", ex); } } catch (NumberFormatException ex) { throw new JBBPCompilationException( "Can't parse a numeric constant, only decimal integers are supported '" + number + "' [" + this.expressionSource + ']', null, ex); } theFirstInTheSubExpression = false; } } if (unaryOperatorCode > 0) { throw new JBBPCompilationException( "Unary operator without argument '" + SYMBOLS[unaryOperatorCode] + "' [" + this.expressionSource + ']'); } if (counterOperators == 0) { if (counterVarsAndConstants == 0) { throw new JBBPCompilationException("Empty expression [" + this.expressionSource + ']'); } else if (counterVarsAndConstants > 1) { throw new JBBPCompilationException("No operators [" + this.expressionSource + ']'); } } while (!operationStack.isEmpty()) { final int top = operationStack.remove(operationStack.size() - 1); if (top == PSEUDOCODE_LEFT_BRACKET) { throw new JBBPCompilationException( "Detected unclosed bracket [" + this.expressionSource + ']'); } compiledScript.write(top); } if (lastFound < 0) { throw new JBBPCompilationException( "Can't extract expression [" + this.expressionSource + ']'); } this.compiledExpression = compiledScript.toByteArray(); this.externalValueNames = externalValueNameList.isEmpty() ? null : externalValueNameList.toArray(ARRAY_STRING_EMPTY); this.maxStackDepth = calculateMaxStackDepth(); } /** * Encode code of an operator to code of similar unary operator. * * @param code a code of operator. * @return code of an unary similar operator if it exists, the same code otherwise */ private static int codeToUnary(final int code) { final int result; switch (code) { case CODE_MINUS: result = CODE_UNARYMINUS; break; case CODE_ADD: result = CODE_UNARYPLUS; break; default: result = code; break; } return result; } private static String code2operator(final int code) { final String result; switch (code) { case CODE_AND: result = "&"; break; case CODE_UNARYPLUS: case CODE_ADD: result = "+"; break; case CODE_OR: result = "|"; break; case CODE_DIV: result = "/"; break; case CODE_MUL: result = "*"; break; case CODE_MOD: result = "%"; break; case CODE_LSHIFT: result = "<<"; break; case CODE_RSHIFT: result = ">>"; break; case CODE_RSIGNSHIFT: result = ">>>"; break; case CODE_UNARYMINUS: case CODE_MINUS: result = "-"; break; case CODE_XOR: result = "^"; break; default: result = "CODE:" + code; break; } return result; } /** * Check that a string has a char of operators. * * @param str a string to be checked, must not be null * @return true if the string contains a char of an operator, false otherwise */ public static boolean hasExpressionOperators(final String str) { boolean result = false; for (final char chr : OPERATOR_FIRST_CHARS) { if (str.indexOf(chr) >= 0) { result = true; break; } } return result; } /** * Check that a string represents a unary operator. * * @param operator an operator to be checked, must not be null. * @throws JBBPCompilationException if the operator is not supported unary operator. */ private void assertUnaryOperator(final String operator) { if (!("+".equals(operator) || "-".equals(operator) || "~".equals(operator))) { throw new JBBPCompilationException( "Wrong unary operator '" + operator + "' [" + this.expressionSource + ']'); } } private int calculateMaxStackDepth() { int stackMaxPosition = 0; int stackPosition = 0; final JBBPIntCounter counter = new JBBPIntCounter(); while (counter.get() < this.compiledExpression.length) { final int code = this.compiledExpression[counter.getAndIncrement()]; switch (code) { case CODE_EXTVAR: case CODE_VAR: case CODE_CONST: { JBBPUtils.unpackInt(this.compiledExpression, counter); stackPosition++; stackMaxPosition = Math.max(stackPosition, stackMaxPosition); } break; case CODE_AND: case CODE_ADD: case CODE_OR: case CODE_DIV: case CODE_MUL: case CODE_MOD: case CODE_LSHIFT: case CODE_RSHIFT: case CODE_RSIGNSHIFT: case CODE_MINUS: case CODE_XOR: { if (stackPosition < 2) { throw new JBBPEvalException("Operator '" + code2operator(code) + "' needs two operands", this); } // decrease for one position stackPosition--; } break; case CODE_UNARYMINUS: case CODE_UNARYPLUS: case CODE_NOT: { // stack not changed if (stackPosition < 1) { throw new JBBPEvalException("Operator '" + code2operator(code) + "' needs operand", this); } } break; default: throw new Error("Detected unsupported operation, contact developer"); } } if (stackPosition != 1) { throw new JBBPEvalException( "Wrong expression [" + this.expressionSource + "] (" + stackPosition + ':' + stackMaxPosition + ')', this); } return stackMaxPosition; } /** * Get the max stack depth needed for the expression. * * @return max stack depth for expression * @since 1.2.1 */ public int getMaxStackDepth() { return this.maxStackDepth; } /** * Evaluate the expression. * * @param inStream the input stream of data, must not be null * @param currentCompiledBlockOffset the current offset inside the compiled JBBP script * @param compiledBlockData the compiled JBBP script, must not be null * @param fieldMap the named field info map, must not be null * @return calculated integer result of the expression * @throws JBBPEvalException if there is any problem during processing */ @Override public int eval(final JBBPBitInputStream inStream, final int currentCompiledBlockOffset, final JBBPCompiledBlock compiledBlockData, final JBBPNamedNumericFieldMap fieldMap) { final int[] stack = new int[this.maxStackDepth]; int stackDepth = 0; final JBBPIntCounter counter = new JBBPIntCounter(); while (counter.get() < this.compiledExpression.length) { final int code = this.compiledExpression[counter.getAndIncrement()]; switch (code) { case CODE_EXTVAR: case CODE_VAR: { final int index = JBBPUtils.unpackInt(this.compiledExpression, counter); final int value; if (code == CODE_EXTVAR) { value = "$".equals(this.externalValueNames[index]) ? (int) inStream.getCounter() : fieldMap .getExternalFieldValue(this.externalValueNames[index], compiledBlockData, this); } else { final JBBPNamedFieldInfo namedField = compiledBlockData.getNamedFields()[index]; final JBBPNumericField numericField = fieldMap.get(namedField); if (numericField == null) { throw new java.lang.ArithmeticException( "Can't find field '" + namedField.getFieldName() + "' among numeric fields"); } else { value = fieldMap.get(namedField).getAsInt(); } } stack[stackDepth++] = value; } break; case CODE_CONST: { stack[stackDepth++] = JBBPUtils.unpackInt(this.compiledExpression, counter); } break; case CODE_ADD: { final int top = stack[--stackDepth]; stack[stackDepth - 1] += top; } break; case CODE_AND: { final int top = stack[--stackDepth]; stack[stackDepth - 1] &= top; } break; case CODE_OR: { final int top = stack[--stackDepth]; stack[stackDepth - 1] |= top; } break; case CODE_XOR: { final int top = stack[--stackDepth]; stack[stackDepth - 1] ^= top; } break; case CODE_MINUS: { final int top = stack[--stackDepth]; stack[stackDepth - 1] -= top; } break; case CODE_UNARYMINUS: { stack[stackDepth - 1] = -stack[stackDepth - 1]; } break; case CODE_UNARYPLUS: { // do nothing } break; case CODE_NOT: { stack[stackDepth - 1] = ~stack[stackDepth - 1]; } break; case CODE_DIV: { final int top = stack[--stackDepth]; stack[stackDepth - 1] /= top; } break; case CODE_MUL: { final int top = stack[--stackDepth]; stack[stackDepth - 1] *= top; } break; case CODE_MOD: { final int top = stack[--stackDepth]; stack[stackDepth - 1] %= top; } break; case CODE_LSHIFT: { final int top = stack[--stackDepth]; stack[stackDepth - 1] <<= top; } break; case CODE_RSHIFT: { final int top = stack[--stackDepth]; stack[stackDepth - 1] >>= top; } break; case CODE_RSIGNSHIFT: { final int top = stack[--stackDepth]; stack[stackDepth - 1] >>>= top; } break; default: throw new Error("Detected unsupported operation, contact developer"); } } return stack[0]; } @Override public void visitItems(final JBBPCompiledBlock block, final int currentCompiledBlockOffset, final ExpressionEvaluatorVisitor visitor) { visitor.visitStart(); final JBBPIntCounter counter = new JBBPIntCounter(); while (counter.get() < this.compiledExpression.length) { final int code = this.compiledExpression[counter.getAndIncrement()]; switch (code) { case CODE_EXTVAR: case CODE_VAR: { final int index = JBBPUtils.unpackInt(this.compiledExpression, counter); if (code == CODE_EXTVAR) { if ("$".equals(this.externalValueNames[index])) { visitor.visitSpecial(ExpressionEvaluatorVisitor.Special.STREAM_COUNTER); } else { visitor.visitField(null, this.externalValueNames[index]); } } else { visitor.visitField(block.getNamedFields()[index], null); } } break; case CODE_CONST: visitor.visitConstant(JBBPUtils.unpackInt(this.compiledExpression, counter)); break; case CODE_ADD: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.ADD); break; case CODE_AND: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.AND); break; case CODE_OR: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.OR); break; case CODE_XOR: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.XOR); break; case CODE_MINUS: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.SUB); break; case CODE_UNARYMINUS: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.UNARY_MINUS); break; case CODE_UNARYPLUS: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.UNARY_PLUS); break; case CODE_NOT: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.NOT); break; case CODE_DIV: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.DIV); break; case CODE_MUL: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.MUL); break; case CODE_MOD: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.MOD); break; case CODE_LSHIFT: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.LSHIFT); break; case CODE_RSHIFT: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.RSHIFT); break; case CODE_RSIGNSHIFT: visitor.visitOperator(ExpressionEvaluatorVisitor.Operator.URSHIFT); break; default: throw new Error("Detected unsupported operation, contact developer"); } } visitor.visitEnd(); } @Override public String toString() { return this.expressionSource; } } JBBPIntegerValueEvaluator.java000066400000000000000000000040061445000440600366540ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/varlen/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.varlen; import com.igormaznitsa.jbbp.JBBPNamedNumericFieldMap; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.conversion.ExpressionEvaluatorVisitor; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import java.io.Serializable; /** * The Interface describes a class which can evaluate and provide an integer value. * * @since 1.0 */ public interface JBBPIntegerValueEvaluator extends Serializable { /** * Calculate an integer value. * * @param inStream a bit input stream * @param currentCompiledBlockOffset the current offset in the compiled block * @param block a compiled script block * @param fieldMap a named numeric field map * @return calculated value as integer */ int eval(JBBPBitInputStream inStream, int currentCompiledBlockOffset, JBBPCompiledBlock block, JBBPNamedNumericFieldMap fieldMap); /** * Visit all expression items * * @param block the compiled block contains value, must not be null * @param currentCompiledBlockOffset the offset of the data in the compiled block * @param visitor the visitor to visit items, must not be null * @since 1.3.0 */ void visitItems(JBBPCompiledBlock block, int currentCompiledBlockOffset, ExpressionEvaluatorVisitor visitor); } JBBPOnlyFieldEvaluator.java000066400000000000000000000066771445000440600361670ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/varlen/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.varlen; import com.igormaznitsa.jbbp.JBBPNamedNumericFieldMap; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.conversion.ExpressionEvaluatorVisitor; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.model.JBBPNumericField; /** * Class implements an evaluator which works with only field. * * @since 1.0 */ public final class JBBPOnlyFieldEvaluator implements JBBPIntegerValueEvaluator { private static final long serialVersionUID = -1031131501937541693L; /** * The Index in named field area for the field which is used by the evaluator. */ private final int namedFieldIndex; /** * An External field name which value will be requested by the evaluator. It * can be null. */ private final String externalFieldName; /** * The Constructor. * * @param externalFieldName the external field name, it can be null. * @param namedFieldIndex the index of a named field in named field area. */ public JBBPOnlyFieldEvaluator(final String externalFieldName, final int namedFieldIndex) { this.externalFieldName = externalFieldName; this.namedFieldIndex = namedFieldIndex; } @Override public int eval(final JBBPBitInputStream inStream, final int currentCompiledBlockOffset, final JBBPCompiledBlock block, final JBBPNamedNumericFieldMap fieldMap) { final int result; if (this.externalFieldName == null) { final JBBPNamedFieldInfo namedField = block.getNamedFields()[this.namedFieldIndex]; final JBBPNumericField numericField = fieldMap.get(namedField); if (numericField == null) { throw new java.lang.ArithmeticException( "Can't find field '" + namedField.getFieldName() + "' among numeric fields"); } else { result = numericField.getAsInt(); } } else { result = this.externalFieldName.equals("$") ? (int) inStream.getCounter() : fieldMap.getExternalFieldValue(this.externalFieldName, block, this); } return result; } @Override public String toString() { return this.externalFieldName == null ? "NamedFieldIndex=" + this.namedFieldIndex : this.externalFieldName; } @Override public void visitItems(final JBBPCompiledBlock block, final int currentCompiledBlockOffset, final ExpressionEvaluatorVisitor visitor) { visitor.visitStart(); if (this.externalFieldName == null) { visitor.visitField(block.getNamedFields()[this.namedFieldIndex], null); } else { if (this.externalFieldName.equals("$")) { visitor.visitSpecial(ExpressionEvaluatorVisitor.Special.STREAM_COUNTER); } else { visitor.visitField(null, this.externalFieldName); } } visitor.visitEnd(); } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/000077500000000000000000000000001445000440600302355ustar00rootroot00000000000000JBBPCompilationException.java000066400000000000000000000037771445000440600356320ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPToken; /** * The Exception can be thrown during compilation phase and may provide the problematic token. * * @since 1.0 */ public class JBBPCompilationException extends JBBPException { private static final long serialVersionUID = -7567503709641292590L; /** * The Problematic token, it can be null. */ private final JBBPToken token; /** * A Constructor. * * @param text the text of message for the exception. */ public JBBPCompilationException(final String text) { this(text, null); } /** * A Constructor. * * @param text the text of message for the exception. * @param token the problematic token, it can be null. */ public JBBPCompilationException(final String text, final JBBPToken token) { super(text); this.token = token; } /** * A Constructor. * * @param text the text of message for the exception * @param token the problematic token for the exception, it can be null * @param cause the root cause for the exception */ public JBBPCompilationException(final String text, final JBBPToken token, final Throwable cause) { super(text, cause); this.token = token; } /** * Get the problematic token. * * @return the problematic token or null if the token is undefined */ public JBBPToken getToken() { return this.token; } } JBBPEvalException.java000066400000000000000000000036161445000440600342330ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; import com.igormaznitsa.jbbp.compiler.varlen.JBBPIntegerValueEvaluator; /** * The Exception is thrown if any errors during execution of an array length evaluator. * * @since 1.0 */ public class JBBPEvalException extends JBBPException { private static final long serialVersionUID = -8580688001091915787L; /** * The Evaluator which is the cause of the exception. */ private final JBBPIntegerValueEvaluator evaluator; /** * A Constructor. * * @param message a message, can be null * @param evaluator a cause evaluator, can be null */ public JBBPEvalException(final String message, final JBBPIntegerValueEvaluator evaluator) { this(message, evaluator, null); } /** * A Constructor. * * @param message a message, can be null * @param evaluator a cause evaluator, can be null * @param cause a cause exception, can be null */ public JBBPEvalException(final String message, final JBBPIntegerValueEvaluator evaluator, Throwable cause) { super(message, cause); this.evaluator = evaluator; } /** * get the cause evaluator. * * @return the cause evaluator for the exception, can be null */ public JBBPIntegerValueEvaluator getEvaluator() { return this.evaluator; } } JBBPException.java000066400000000000000000000023321445000440600334150ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; /** * The Root Exception for all JBBP exceptions. * * @since 1.0 */ public class JBBPException extends RuntimeException { private static final long serialVersionUID = -3311082983804835019L; /** * A Constructor. * * @param message the message for the exception */ public JBBPException(final String message) { super(message); } /** * A Constructor. * * @param message the message for the exception * @param cause the root cause for the exception */ public JBBPException(final String message, final Throwable cause) { super(message, cause); } } JBBPFinderException.java000066400000000000000000000040531445000440600345470ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; import com.igormaznitsa.jbbp.model.JBBPAbstractField; /** * The Exception can be thrown during search field operations, * * @since 1.0 */ public class JBBPFinderException extends JBBPException { private static final long serialVersionUID = 4499929218503483986L; /** * The Name or the path of a field to be searched. It may contain null. */ private final String nameOrPath; /** * The Field type of a field to be searched. It may contain null. */ private final Class fieldType; /** * The Constructor. * * @param message the exception message * @param nameOrPath the name of the path for used for search process, it can be null * @param fieldType the field type used for the search process, it can be null */ public JBBPFinderException(final String message, final String nameOrPath, final Class fieldType) { super(message); this.nameOrPath = nameOrPath; this.fieldType = fieldType; } /** * Get the name or the path used for search. * * @return the name or the path used for search, it can be null */ public String getNameOrPath() { return this.nameOrPath; } /** * Get the field type used for search. * * @return the field type used for search, it can be null */ public Class getFieldType() { return this.fieldType; } } JBBPIOException.java000066400000000000000000000020011445000440600336360ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; /** * The Exception can be thrown for transport errors * * @since 1.1.1 */ public class JBBPIOException extends JBBPException { private static final long serialVersionUID = 1151192250682443895L; public JBBPIOException(final String message) { super(message); } public JBBPIOException(final String message, final Throwable cause) { super(message, cause); } } JBBPIllegalArgumentException.java000066400000000000000000000020651445000440600364150ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; /** * The Exception can be thrown when detected incompatible or unusable parameter. * * @since 1.1.1 */ public class JBBPIllegalArgumentException extends JBBPIOException { private static final long serialVersionUID = 2811626713945893782L; public JBBPIllegalArgumentException(String message) { super(message); } public JBBPIllegalArgumentException(String message, Throwable cause) { super(message, cause); } } JBBPMapperException.java000066400000000000000000000055641445000440600345740ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import java.lang.reflect.Field; /** * The Exception describes an error during processing of mapping of bin fields to class fields. * * @since 1.0 */ public class JBBPMapperException extends JBBPException { private static final long serialVersionUID = -5643926527601318948L; /** * The JBBP field which mapping generates the exception. */ private final JBBPAbstractField field; /** * The Class field which processing generates the exception. */ private final transient Field mappingClassField; /** * The class which mapping generates the exception. */ private final Class mappingClass; /** * The Constructor. * * @param message the text message describes the exception * @param field the JBBP field which processing generates the exception * @param mappingClass the class which mapping generates the exception * @param mappingClassField the class field which mapping is wrong * @param cause the root cause for the exception, it can be null */ public JBBPMapperException(final String message, final JBBPAbstractField field, final Class mappingClass, final Field mappingClassField, final Throwable cause) { super(message, cause); this.field = field; this.mappingClassField = mappingClassField; this.mappingClass = mappingClass; } /** * Get the JBBP field related to the exception. * * @return the JBBP filed which mapping generates the exception, it can be null */ public JBBPAbstractField getField() { return this.field; } /** * Get a class which mapping generates the exception. * * @return the class which mapping generates the exception, it can be null */ public Class getMappingClass() { return this.mappingClass; } /** * Get a class field related to the exception. * * @return a class field which was processing as exception thrown, it can be null */ public Field getMappingClassField() { return this.mappingClassField; } @Override public String toString() { return this.getMessage() + " [ " + this.getMappingClassField() + " -> " + this.getField() + ']'; } } JBBPNumericFieldValueConversionException.java000066400000000000000000000034571445000440600407600ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptionspackage com.igormaznitsa.jbbp.exceptions; import com.igormaznitsa.jbbp.model.JBBPNumericField; /** * Exception to indicate error during value conversion process. * * @since 2.0.4 */ public class JBBPNumericFieldValueConversionException extends JBBPException { private final JBBPNumericField source; /** * Get source field. * * @return source field for exception, can be null */ public JBBPNumericField getSource() { return this.source; } /** * Constructor to provide source field and message. * * @param source source field. can be null * @param message message, can be null */ public JBBPNumericFieldValueConversionException(final JBBPNumericField source, final String message) { this(source, message, null); } /** * Constructor to provide source field, message and cause error. * * @param source source field. can be null * @param message message, can be null * @param cause cause error, can be null */ public JBBPNumericFieldValueConversionException(final JBBPNumericField source, final String message, final Throwable cause) { super(message, cause); this.source = source; } @Override public String toString() { final StringBuilder result = new StringBuilder(getClass().getName()); final String message = this.getLocalizedMessage(); final JBBPNumericField source = this.getSource(); result.append('('); result .append("message=").append(message).append(',') .append("source=").append(source); final Throwable cause = this.getCause(); if (cause != null) { result.append(",cause=").append(cause.getClass().getName()); } result.append(')'); return result.toString(); } } JBBPParsingException.java000066400000000000000000000024061445000440600347430ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; /** * The Exception can be thrown during parsing process. * * @since 1.0 */ public class JBBPParsingException extends JBBPCompilationException { private static final long serialVersionUID = 3397335881291955769L; /** * A Constructor. * * @param message the exception error message */ public JBBPParsingException(final String message) { this(message, null); } /** * A Constructor. * * @param message the exception error message * @param cause the root cause for the exception */ public JBBPParsingException(final String message, final Throwable cause) { super(message, null, cause); } } JBBPTokenizerException.java000066400000000000000000000107071445000440600353150ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; /** * The Exception can be thrown during parsing sources for tokens and allows to figure * out the position of the problematic token. * * @since 1.0 */ public class JBBPTokenizerException extends JBBPCompilationException { private static final long serialVersionUID = -1132154077305893246L; /** * The Token position. */ private final int position; private final String errorPart; /** * Constructor. * * @param message the exception message. * @param script the script contains error, can be null * @param pos the position of a problematic token inside sources. * @since 2.0.3 */ public JBBPTokenizerException(final String message, final String script, final int pos) { super(message); this.errorPart = script == null ? "" : extractErrorPartText(script, pos); this.position = pos; } /** * Auxiliary internal method to extract error part from script around specific position. * * @param script the error script to be processed, must not be null * @param errorPosition the error position in the script * @return error part of the script as string, must not be null * @since 2.0.3 */ private static String extractErrorPartText(final String script, final int errorPosition) { if (script.length() == 0 || errorPosition >= script.length() || errorPosition < 0) { return ""; } final int maxLengthWing = 16; final StringBuilder buffer = new StringBuilder(); buffer.append(script.charAt(errorPosition)); int errorPositionAtBuffer = 0; int leftPosition = errorPosition - 1; int rightPosition = errorPosition + 1; int leftNonSpaceCounter = 0; int rightNonSpaceCounter = 0; for (int i = 0; i < maxLengthWing; i++) { if (leftPosition >= 0) { final char chr = script.charAt(leftPosition); if (Character.isISOControl(chr) || (i > 2 && leftNonSpaceCounter > 0 && Character.isSpaceChar(chr))) { leftPosition = -1; } else { buffer.insert(0, chr); leftNonSpaceCounter += Character.isSpaceChar(chr) ? 1 : 0; errorPositionAtBuffer++; leftPosition--; } } if (rightPosition >= 0 && rightPosition < script.length()) { final char chr = script.charAt(rightPosition); if (Character.isISOControl(chr) || (i > 2 && rightNonSpaceCounter > 0 && Character.isSpaceChar(chr))) { rightPosition = -1; } else { buffer.append(chr); rightNonSpaceCounter += Character.isSpaceChar(chr) ? 1 : 0; rightPosition++; } } } final String errorMarkerLeft = " ->"; final String errorMarkerRight = "<- "; buffer.insert(errorPositionAtBuffer + 1, errorMarkerRight); buffer.insert(errorPositionAtBuffer, errorMarkerLeft); errorPositionAtBuffer += errorMarkerLeft.length(); if (Character.isISOControl(buffer.charAt(errorPositionAtBuffer))) { String hex = Integer.toHexString(buffer.charAt(errorPositionAtBuffer)); hex = "\\u" + "0000".substring(hex.length()) + hex; buffer.delete(errorPositionAtBuffer, errorPositionAtBuffer + 1); buffer.insert(errorPositionAtBuffer, hex); } return buffer.toString().trim(); } /** * Get error part of script where error position marked by !>..<! * * @return error part of the script in position, or empty if it was impossible to extract the part * @since 2.0.3 */ public String getErrorPart() { return this.errorPart; } /** * get the position in sources of the problematic token. * * @return the position or -1 if the position is unknown. */ public int getPosition() { return this.position; } @Override public String toString() { return this.getLocalizedMessage() + " (pos=" + this.position + ", errorPart=\"" + this.errorPart + "\")"; } } JBBPTooManyFieldsFoundException.java000066400000000000000000000035271445000440600370560ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; import com.igormaznitsa.jbbp.model.JBBPAbstractField; /** * The Exception will be thrown during search operation for only field, if there are too many variants. * * @since 1.0 */ public class JBBPTooManyFieldsFoundException extends JBBPFinderException { private static final long serialVersionUID = -7805676497685397609L; /** * Number of found variants. */ private final int numberOfInstances; /** * The Constructor. * * @param numberOfInstances the number of found variants. * @param message the exception message * @param nameOrPath the name or the path used for search * @param fieldType the field type used for search */ public JBBPTooManyFieldsFoundException(final int numberOfInstances, final String message, final String nameOrPath, final Class fieldType) { super(message, nameOrPath, fieldType); this.numberOfInstances = numberOfInstances; } /** * Get the number of found instances. * * @return the number of found instances as integer */ public int getNumberOfFoundInstances() { return this.numberOfInstances; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/000077500000000000000000000000001445000440600264635ustar00rootroot00000000000000AbstractMappedClassFieldObserver.java000066400000000000000000000676711445000440600356240ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import com.igormaznitsa.jbbp.exceptions.JBBPException; import com.igormaznitsa.jbbp.exceptions.JBBPIllegalArgumentException; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinFieldFilter; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.mapper.JBBPMapper; import com.igormaznitsa.jbbp.mapper.MappedFieldRecord; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldShort; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldUInt; import com.igormaznitsa.jbbp.utils.BinAnnotationWrapper; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.List; /** * Abstract class to collect, order and process all fields in a mapped class. * since 1.1 */ public abstract class AbstractMappedClassFieldObserver { /** * Inside auxiliary method to read object field value. * * @param obj an object which field is read * @param record field record, must not be null * @return a value from the field of the object * @throws JBBPException if the field can't be read * @since 2.0 */ private static Object readFieldValue(final Object obj, final MappedFieldRecord record) { try { if (record.getter == null) { return record.mappingField.get(obj); } else { return record.getter.invoke(obj); } } catch (Exception ex) { throw new JBBPException("Can't get value from field [" + record + ']', ex); } } /** * Check that a field defined as an array. * * @param field a field which is checked * @throws IllegalArgumentException if the field is not an array */ private static void assertFieldArray(final Field field) { if (!field.getType().isArray()) { throw new IllegalArgumentException( "Detected non-array field marked to be written as an array [" + field + ']'); } } /** * Process an object. It works only with classes and fields marked by Bin annotations. It doesn't process classes and fields marked by DslBinCustom annotations. * * @param obj an object which is an instance of a mapped class, must not be null * @param field a field where the object has been found, it can be null for first call * @param binAnnotationWrapper wrapper to replace Bin annotation values for processing fields, can be null to be ignored * @param customFieldProcessor a processor for custom fields, it can be null * @see Bin * @since 2.0.2 */ protected void processObject( final Object obj, final Field field, final BinAnnotationWrapper binAnnotationWrapper, final Object customFieldProcessor ) { this.processObject(obj, field, binAnnotationWrapper, null, customFieldProcessor); } /** * Process an object. It works only with classes and fields marked by Bin annotations. It doesn't process classes and fields marked by DslBinCustom annotations. * * @param obj an object which is an instance of a mapped class, must not be null * @param field a field where the object has been found, it can be null for first call * @param binAnnotationWrapper wrapper to replace Bin annotation values for processing fields, can be null to be ignored * @param binFieldFilter filter for mapped fields, allows to exclude some of them, can be null * @param customFieldProcessor a processor for custom fields, it can be null * @see Bin * @since 2.0.4 */ protected void processObject( final Object obj, final Field field, final BinAnnotationWrapper binAnnotationWrapper, final BinFieldFilter binFieldFilter, final Object customFieldProcessor ) { JBBPUtils.assertNotNull(obj, "Object must not be null"); final List orderedFields = JBBPMapper.findAffectedFields(obj, binFieldFilter); final Bin clazzAnno = obj.getClass().getAnnotation(Bin.class); final Bin fieldAnno = field == null ? null : field.getAnnotation(Bin.class); final Bin binAnno = clazzAnno == null ? fieldAnno : clazzAnno; if (binFieldFilter == null || binFieldFilter.isAllowed(binAnno, field)) { this.onStructStart(obj, field, binAnno); for (final MappedFieldRecord rec : orderedFields) { final Bin annotation = binAnnotationWrapper == null ? rec.binAnnotation : binAnnotationWrapper.setWrapped(rec.binAnnotation); if (binFieldFilter == null || binFieldFilter.isAllowed(annotation, rec.mappingField)) { if (annotation.custom() && customFieldProcessor == null) { throw new JBBPIllegalArgumentException( "Class '" + obj.getClass().getName() + "' contains field '" + rec.mappingField.getName() + "' which is custom one, you must provide JBBPCustomFieldWriter instance to save it."); } processObjectField(obj, rec, annotation, customFieldProcessor, binFieldFilter); } } this.onStructEnd(obj, field, binAnno); } } /** * Inside auxiliary method to process a field of an object. * * @param obj the object which field under processing, must not be null * @param fieldRecord internal record about the field, must not be null * @param annotation the annotation to be used as data source about the field, * must not be null * @param customFieldProcessor an object which will be provided for processing * of custom fields, must not be null if object contains custom fields * @since 2.0.4 */ protected void processObjectField( final Object obj, final MappedFieldRecord fieldRecord, final Bin annotation, final Object customFieldProcessor ) { this.processObjectField(obj, fieldRecord, annotation, customFieldProcessor, null); } /** * Inside auxiliary method to process a field of an object. * * @param obj the object which field under processing, must not be null * @param fieldRecord internal record about the field, must not be null * @param annotation the annotation to be used as data source about the field, * must not be null * @param customFieldProcessor an object which will be provided for processing * of custom fields, must not be null if object contains custom fields * @param binFieldFilter filter allows to exclude some fields from process, can be null * @since 2.0.4 */ protected void processObjectField( final Object obj, final MappedFieldRecord fieldRecord, final Bin annotation, final Object customFieldProcessor, final BinFieldFilter binFieldFilter ) { final Field field = fieldRecord.mappingField; if (annotation.custom()) { this.onFieldCustom(obj, field, annotation, customFieldProcessor, readFieldValue(obj, fieldRecord)); } else { final Class fieldType = field.getType(); final BinAnnotationWrapper wrapper = annotation instanceof BinAnnotationWrapper ? (BinAnnotationWrapper) annotation : null; final BinType type; if (annotation.type() == BinType.UNDEFINED) { type = BinType.findCompatible(fieldType); } else { type = annotation.type(); } final boolean reverseBits = annotation.bitOrder() == JBBPBitOrder.MSB0; switch (type) { case BIT: { final JBBPBitNumber bitNumber = annotation.bitNumber(); if (fieldType == boolean.class) { this.onFieldBits(obj, field, annotation, bitNumber, ((Boolean) readFieldValue(obj, fieldRecord)) ? 0xFF : 0x00); } else { byte value = ((Number) readFieldValue(obj, fieldRecord)).byteValue(); if (reverseBits) { value = JBBPUtils.reverseBitsInByte(bitNumber, value); } this.onFieldBits(obj, field, annotation, bitNumber, value); } } break; case BOOL: { if (fieldType == boolean.class) { onFieldBool(obj, field, annotation, (Boolean) readFieldValue(obj, fieldRecord)); } else { onFieldBool(obj, field, annotation, ((Number) readFieldValue(obj, fieldRecord)).longValue() != 0); } } break; case BYTE: case UBYTE: { byte value = ((Number) readFieldValue(obj, fieldRecord)).byteValue(); if (reverseBits) { value = JBBPUtils.reverseBitsInByte(value); } this.onFieldByte(obj, field, annotation, type == BinType.BYTE, value); } break; case SHORT: case USHORT: { short value; if (fieldType == char.class) { value = (short) ((Character) readFieldValue(obj, fieldRecord)).charValue(); } else { value = ((Number) readFieldValue(obj, fieldRecord)).shortValue(); } if (reverseBits) { value = (short) JBBPFieldShort.reverseBits(value); } this.onFieldShort(obj, field, annotation, type == BinType.SHORT, value); } break; case INT: { int value; value = ((Number) readFieldValue(obj, fieldRecord)).intValue(); if (reverseBits) { value = (int) JBBPFieldInt.reverseBits(value); } this.onFieldInt(obj, field, annotation, value); } break; case UINT: { long value; value = ((Number) readFieldValue(obj, fieldRecord)).longValue(); if (reverseBits) { value = (int) JBBPFieldUInt.reverseBits(value); } this.onFieldUInt(obj, field, annotation, (int) value); } break; case FLOAT: { float value; if (float.class == fieldType) { value = (Float) readFieldValue(obj, fieldRecord); } else { value = ((Number) readFieldValue(obj, fieldRecord)).floatValue(); } if (reverseBits) { value = Float.intBitsToFloat((int) JBBPFieldInt.reverseBits(Float.floatToIntBits(value))); } this.onFieldFloat(obj, field, annotation, value); } break; case STRING: { String value; final Object valueAsObject = readFieldValue(obj, fieldRecord); if (valueAsObject != null) { value = String.valueOf(valueAsObject); if (reverseBits) { value = JBBPFieldString.reverseBits(value); } } else { value = null; } this.onFieldString(obj, field, annotation, value); } break; case LONG: { long value = ((Number) readFieldValue(obj, fieldRecord)).longValue(); if (reverseBits) { value = JBBPFieldLong.reverseBits(value); } this.onFieldLong(obj, field, annotation, value); } break; case DOUBLE: { double value; if (float.class == fieldType) { value = (Float) readFieldValue(obj, fieldRecord); } else if (double.class == fieldType) { value = (Double) readFieldValue(obj, fieldRecord); } else { value = ((Number) readFieldValue(obj, fieldRecord)).doubleValue(); } if (reverseBits) { value = Double.longBitsToDouble(JBBPFieldLong.reverseBits(Double.doubleToLongBits(value))); } this.onFieldDouble(obj, field, annotation, value); } break; case STRUCT: { processObject(readFieldValue(obj, fieldRecord), field, wrapper, binFieldFilter, customFieldProcessor); } break; default: { final Object array = readFieldValue(obj, fieldRecord); switch (type) { case BIT_ARRAY: { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); final JBBPBitNumber bitNumber = annotation.bitNumber(); if (fieldType.getComponentType() == boolean.class) { for (int i = 0; i < len; i++) { this.onFieldBits(obj, field, annotation, bitNumber, (Boolean) Array.get(array, i) ? 0xFF : 0x00); } } else { for (int i = 0; i < len; i++) { byte value = ((Number) Array.get(array, i)).byteValue(); if (reverseBits) { value = JBBPUtils.reverseBitsInByte(bitNumber, value); } this.onFieldBits(obj, field, annotation, bitNumber, value); } } this.onArrayEnd(obj, field, annotation); } break; case BOOL_ARRAY: { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); for (int i = 0; i < len; i++) { this.onFieldBool(obj, field, annotation, (Boolean) Array.get(array, i)); } this.onArrayEnd(obj, field, annotation); } break; case UBYTE_ARRAY: case BYTE_ARRAY: { final boolean signed = type == BinType.BYTE_ARRAY; if (fieldType == String.class) { final String strValue = (String) readFieldValue(obj, fieldRecord); this.onArrayStart(obj, field, annotation, strValue.length()); for (int i = 0; i < strValue.length(); i++) { byte value = (byte) strValue.charAt(i); if (reverseBits) { value = JBBPUtils.reverseBitsInByte(value); } this.onFieldByte(obj, field, annotation, signed, value); } } else { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); for (int i = 0; i < len; i++) { byte value = ((Number) Array.get(array, i)).byteValue(); if (reverseBits) { value = JBBPUtils.reverseBitsInByte(value); } this.onFieldByte(obj, field, annotation, signed, value); } } this.onArrayEnd(obj, field, annotation); } break; case SHORT_ARRAY: case USHORT_ARRAY: { final boolean signed = type == BinType.SHORT_ARRAY; if (fieldType == String.class) { final String str = (String) readFieldValue(obj, fieldRecord); this.onArrayStart(obj, field, annotation, str.length()); for (int i = 0; i < str.length(); i++) { short value = (short) str.charAt(i); if (reverseBits) { value = (short) JBBPFieldShort.reverseBits(value); } this.onFieldShort(obj, field, annotation, signed, value); } } else { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); if (fieldType.getComponentType() == char.class) { for (int i = 0; i < len; i++) { short value = (short) ((Character) Array.get(array, i)).charValue(); if (reverseBits) { value = (short) JBBPFieldShort.reverseBits(value); } this.onFieldShort(obj, field, annotation, signed, value); } } else { for (int i = 0; i < len; i++) { short value = ((Number) Array.get(array, i)).shortValue(); if (reverseBits) { value = (short) JBBPFieldShort.reverseBits(value); } this.onFieldShort(obj, field, annotation, signed, value); } } this.onArrayEnd(obj, field, annotation); } } break; case FLOAT_ARRAY: { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); for (int i = 0; i < len; i++) { float value = Array.getFloat(array, i); if (reverseBits) { value = Float .intBitsToFloat((int) JBBPFieldInt.reverseBits(Float.floatToIntBits(value))); } this.onFieldFloat(obj, field, annotation, value); } this.onArrayEnd(obj, field, annotation); } break; case UINT_ARRAY: { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); for (int i = 0; i < len; i++) { long value = ((Number) Array.get(array, i)).longValue(); if (reverseBits) { value = JBBPFieldUInt.reverseBits(value); } this.onFieldUInt(obj, field, annotation, (int) value); } this.onArrayEnd(obj, field, annotation); } break; case INT_ARRAY: { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); for (int i = 0; i < len; i++) { int value = ((Number) Array.get(array, i)).intValue(); if (reverseBits) { value = (int) JBBPFieldInt.reverseBits(value); } this.onFieldInt(obj, field, annotation, value); } this.onArrayEnd(obj, field, annotation); } break; case LONG_ARRAY: { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); for (int i = 0; i < len; i++) { long value = ((Number) Array.get(array, i)).longValue(); if (reverseBits) { value = JBBPFieldLong.reverseBits(value); } this.onFieldLong(obj, field, annotation, value); } this.onArrayEnd(obj, field, annotation); } break; case STRING_ARRAY: { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); for (int i = 0; i < len; i++) { final Object value = Array.get(array, i); String nullableStrValue = value == null ? null : String.valueOf(value); if (nullableStrValue != null && reverseBits) { nullableStrValue = JBBPFieldString.reverseBits(nullableStrValue); } this.onFieldString(obj, field, annotation, nullableStrValue); } this.onArrayEnd(obj, field, annotation); } break; case DOUBLE_ARRAY: { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); for (int i = 0; i < len; i++) { double value = ((Number) Array.get(array, i)).doubleValue(); if (reverseBits) { value = Double .longBitsToDouble(JBBPFieldLong.reverseBits(Double.doubleToLongBits(value))); } this.onFieldDouble(obj, field, annotation, value); } this.onArrayEnd(obj, field, annotation); } break; case STRUCT_ARRAY: { assertFieldArray(field); final int len = Array.getLength(array); this.onArrayStart(obj, field, annotation, len); for (int i = 0; i < len; i++) { this.processObject(Array.get(array, i), field, wrapper, binFieldFilter, customFieldProcessor); } this.onArrayEnd(obj, field, annotation); } break; default: { throw new Error( "Unexpected situation for field type, contact developer [" + type + ']'); } } } break; } } } /** * Notification about custom field. * * @param obj the object instance, must not be null * @param field the custom field, must not be null * @param annotation the annotation for the field, must not be null * @param customFieldProcessor processor for custom fields, must not be null * @param value the value of the custom field */ protected void onFieldCustom(final Object obj, final Field field, final Bin annotation, final Object customFieldProcessor, final Object value) { } /** * Notification about bit field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param bitNumber number of bits for the field, must not be null * @param value the value of the field */ protected void onFieldBits(final Object obj, final Field field, final Bin annotation, final JBBPBitNumber bitNumber, final int value) { } /** * Notification about boolean field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param value the value of the field */ protected void onFieldBool(final Object obj, final Field field, final Bin annotation, final boolean value) { } /** * Notification about byte field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param signed flag shows that the field id signed * @param value the value of the field */ protected void onFieldByte(final Object obj, final Field field, final Bin annotation, final boolean signed, final int value) { } /** * Notification about short field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param signed flag shows that the field id signed * @param value the value of the field */ protected void onFieldShort(final Object obj, final Field field, final Bin annotation, final boolean signed, final int value) { } /** * Notification about integer field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param value the value of the field */ protected void onFieldInt(final Object obj, final Field field, final Bin annotation, final int value) { } /** * Notification about unsigned integer field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param value the value of the field * @since 2.0.4 */ protected void onFieldUInt(final Object obj, final Field field, final Bin annotation, final int value) { } /** * Notification about float field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param value the value of the field * @since 1.4.0 */ protected void onFieldFloat(final Object obj, final Field field, final Bin annotation, final float value) { } /** * Notification about string field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param value the value of the field * @since 1.4.0 */ protected void onFieldString(final Object obj, final Field field, final Bin annotation, final String value) { } /** * Notification about double field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param value the value of the field * @since 1.4.0 */ protected void onFieldDouble(final Object obj, final Field field, final Bin annotation, final double value) { } /** * Notification about long field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param value the value of the field */ protected void onFieldLong(final Object obj, final Field field, final Bin annotation, final long value) { } /** * Notification of start of "structure" field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null */ protected void onStructStart(final Object obj, final Field field, final Bin annotation) { } /** * Notification of end of "structure" field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null */ protected void onStructEnd(final Object obj, final Field field, final Bin annotation) { } /** * Notification of start of "array" field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for field, must not be null * @param length the length of the array */ protected void onArrayStart(final Object obj, final Field field, final Bin annotation, final int length) { } /** * Notification of end of "array" field. * * @param obj the object instance, must not be null * @param field the field, must not be null * @param annotation the annotation for the field, must not be null */ protected void onArrayEnd(final Object obj, final Field field, final Bin annotation) { } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitInputStream.java000066400000000000000000001053141445000440600327020ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import com.igormaznitsa.jbbp.utils.JBBPSystemProperty; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.EOFException; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; /** * A Filter stream implementing a countable bit stream interface. It allows read * not only bytes but also bits from an input stream. The Class is not a * thread-safe one. * * @since 1.0 */ public class JBBPBitInputStream extends FilterInputStream implements JBBPCountableBitStream { /** * The Initial an Array buffer size for whole stream read. */ protected static final int INITIAL_ARRAY_BUFFER_SIZE = JBBPSystemProperty.PROPERTY_INPUT_INITIAL_ARRAY_BUFFER_SIZE.getAsInteger(32); /** * Flag shows that bit operations must be processed for MSB0 (most significant * bit 0) mode. */ private final boolean msb0; /** * The Inside bit buffer, */ private int bitBuffer; /** * The Inside counter of bits in the bit buffer. */ private int bitsInBuffer; /** * The Byte counter. */ private long byteCounter; /** * Inside temp variable to keep the bit buffer temporarily. */ private int markedBitBuffer; /** * Inside temp variable to keep the bit buffer counter temporarily. */ private int markedBitsInBuffer; /** * Inside temp variable to keep the byte counter temporarily. */ private long markedByteCounter; /** * A Constructor, the LSB0 bit order will be used by default. * * @param in an input stream to be filtered. */ public JBBPBitInputStream(final InputStream in) { this(in, JBBPBitOrder.LSB0); } /** * A Constructor. * * @param in an input stream to be filtered. * @param order a bit order mode for the filter. * @see JBBPBitOrder#LSB0 * @see JBBPBitOrder#MSB0 */ public JBBPBitInputStream(final InputStream in, final JBBPBitOrder order) { super(in); this.bitsInBuffer = 0; this.msb0 = order == JBBPBitOrder.MSB0; } /** * Read array of boolean values. * * @param items number of items to be read, if less than zero then read whole * stream till the end * @return read values as boolean array * @throws IOException it will be thrown for transport error */ public boolean[] readBoolArray(final int items) throws IOException { int pos = 0; byte[] buffer; if (items < 0) { buffer = new byte[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (true) { final int read = this.read(buffer, pos, buffer.length - pos); if (read < 0) { break; } pos += read; if (buffer.length == pos) { final byte[] newBuffer = new byte[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } } } else { // number buffer = new byte[items]; int len = items; while (len > 0) { final int read = this.read(buffer, pos, len); if (read < 0) { throw new EOFException("Have read only " + pos + " bit portions instead of " + items); } pos += read; len -= read; } } final boolean[] result = new boolean[pos]; for (int i = 0; i < pos; i++) { result[i] = buffer[i] != 0; } return result; } @Override public int read(final byte[] array, final int offset, final int length) throws IOException { if (this.bitsInBuffer == 0) { int readBytes = 0; int tempOffset = offset; int tempLength = length; while (tempLength > 0) { int read = this.in.read(array, tempOffset, tempLength); if (read < 0) { readBytes = readBytes == 0 ? read : readBytes; break; } tempLength -= read; tempOffset += read; readBytes += read; this.byteCounter += read; } if (this.msb0) { int index = offset; int number = readBytes; while (number > 0) { array[index] = JBBPUtils.reverseBitsInByte(array[index]); index++; number--; } } return readBytes; } else { int count = length; int i = offset; while (count > 0) { final int nextByte = this.readBits(JBBPBitNumber.BITS_8); if (nextByte < 0) { break; } count--; array[i++] = (byte) nextByte; } return length - count; } } /** * Read array of bit sequence. * * @param bitNumber bit number for each bit sequence item, must be 1..8 * @param items number of items to be read, if less than zero then read whole * stream till the end * @return array of read bit items as a byte array * @throws IOException it will be thrown for any transport problem during the * operation */ public byte[] readBitsArray(final int items, final JBBPBitNumber bitNumber) throws IOException { return _readArray(items, bitNumber); } /** * Read number of bytes for the stream. * * @param items number of items to be read, if less than zero then read whole * stream till the end * @return read byte items as a byte array * @throws IOException it will be thrown for any transport problem during the * operation */ public byte[] readByteArray(final int items) throws IOException { return _readArray(items, null); } /** * Read number of bytes for the stream. Invert their order if byte order is LITTLE_ENDIAN * * @param items number of items to be read, if less than zero then read whole * stream till the end * @param byteOrder desired order of bytes * @return read byte items as a byte array, if byte order is LITTLE_ENDIAN then the result array will be reversed one * @throws IOException it will be thrown for any transport problem during the * operation * @see JBBPByteOrder#LITTLE_ENDIAN * @since 1.3.0 */ public byte[] readByteArray(final int items, final JBBPByteOrder byteOrder) throws IOException { final byte[] result = _readArray(items, null); if (byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { JBBPUtils.reverseArray(result); } return result; } @Override public synchronized void mark(final int readLimit) { in.mark(readLimit); this.markedBitBuffer = this.bitBuffer; this.markedByteCounter = this.byteCounter; this.markedBitsInBuffer = this.bitsInBuffer; } private byte[] _readArray(final int items, final JBBPBitNumber bitNumber) throws IOException { final boolean readByteArray = bitNumber == null; int pos = 0; if (items < 0) { byte[] buffer = new byte[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (true) { final int next = readByteArray ? read() : readBits(bitNumber); if (next < 0) { break; } if (buffer.length == pos) { final byte[] newBuffer = new byte[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } buffer[pos++] = (byte) next; } if (buffer.length == pos) { return buffer; } final byte[] result = new byte[pos]; System.arraycopy(buffer, 0, result, 0, pos); return result; } else { // number final byte[] buffer = new byte[items]; if (readByteArray) { final int read = this.read(buffer, 0, items); if (read != items) { throw new EOFException( "Have read only " + read + " byte(s) instead of " + items + " byte(s)"); } } else { for (int i = 0; i < items; i++) { final int next = readBits(bitNumber); if (next < 0) { throw new EOFException("Have read only " + i + " bit portions instead of " + items); } buffer[i] = (byte) next; } } return buffer; } } /** * Read number of short items from the input stream. * * @param items number of items to be read from the input stream, if less than * zero then all stream till the end will be read * @param byteOrder the order of bytes to be used to decode short values * @return read items as a short array * @throws IOException it will be thrown for any transport problem during the * operation * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN */ public short[] readShortArray(final int items, final JBBPByteOrder byteOrder) throws IOException { int pos = 0; if (items < 0) { short[] buffer = new short[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (hasAvailableData()) { final int next = readUnsignedShort(byteOrder); if (buffer.length == pos) { final short[] newBuffer = new short[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } buffer[pos++] = (short) next; } if (buffer.length == pos) { return buffer; } final short[] result = new short[pos]; System.arraycopy(buffer, 0, result, 0, pos); return result; } else { // number final short[] buffer = new short[items]; for (int i = 0; i < items; i++) { buffer[i] = (short) readUnsignedShort(byteOrder); } return buffer; } } /** * Read number of unsigned integer items from the input stream. * * @param items number of items to be read from the input stream, if less than * zero then all stream till the end will be read * @param byteOrder the order of bytes to be used to decode values * @return read items as an unsigned integer array represented through long * @throws IOException it will be thrown for any transport problem during the * operation * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 2.0.4 */ public long[] readUIntArray(final int items, final JBBPByteOrder byteOrder) throws IOException { int pos = 0; if (items < 0) { long[] buffer = new long[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (hasAvailableData()) { final long next = readUInt(byteOrder); if (buffer.length == pos) { final long[] newBuffer = new long[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } buffer[pos++] = next; } if (buffer.length == pos) { return buffer; } final long[] result = new long[pos]; System.arraycopy(buffer, 0, result, 0, pos); return result; } else { // number final long[] buffer = new long[items]; for (int i = 0; i < items; i++) { buffer[i] = readUInt(byteOrder); } return buffer; } } /** * Read number of unsigned short items from the input stream. * * @param items number of items to be read from the input stream, if less than * zero then all stream till the end will be read * @param byteOrder the order of bytes to be used to decode short values * @return read items as a char array * @throws IOException it will be thrown for any transport problem during the * operation * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 1.3 */ public char[] readUShortArray(final int items, final JBBPByteOrder byteOrder) throws IOException { int pos = 0; if (items < 0) { char[] buffer = new char[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (hasAvailableData()) { final int next = readUnsignedShort(byteOrder); if (buffer.length == pos) { final char[] newBuffer = new char[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } buffer[pos++] = (char) next; } if (buffer.length == pos) { return buffer; } final char[] result = new char[pos]; System.arraycopy(buffer, 0, result, 0, pos); return result; } else { // number final char[] buffer = new char[items]; for (int i = 0; i < items; i++) { buffer[i] = (char) readUnsignedShort(byteOrder); } return buffer; } } /** * Read number of integer items from the input stream. * * @param items number of items to be read from the input stream, if less than * zero then all stream till the end will be read * @param byteOrder the order of bytes to be used to decode values * @return read items as an integer array * @throws IOException it will be thrown for any transport problem during the * operation * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN */ public int[] readIntArray(final int items, final JBBPByteOrder byteOrder) throws IOException { int pos = 0; if (items < 0) { int[] buffer = new int[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (hasAvailableData()) { final int next = readInt(byteOrder); if (buffer.length == pos) { final int[] newBuffer = new int[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } buffer[pos++] = next; } if (buffer.length == pos) { return buffer; } final int[] result = new int[pos]; System.arraycopy(buffer, 0, result, 0, pos); return result; } else { // number final int[] buffer = new int[items]; for (int i = 0; i < items; i++) { buffer[i] = readInt(byteOrder); } return buffer; } } /** * Read number of float items from the input stream. * * @param items number of items to be read from the input stream, if less than * zero then all stream till the end will be read * @param byteOrder the order of bytes to be used to decode values * @return read items as float array * @throws IOException it will be thrown for any transport problem during the * operation * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 1.4.0 */ public float[] readFloatArray(final int items, final JBBPByteOrder byteOrder) throws IOException { int pos = 0; if (items < 0) { float[] buffer = new float[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (hasAvailableData()) { final float next = readFloat(byteOrder); if (buffer.length == pos) { final float[] newBuffer = new float[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } buffer[pos++] = next; } if (buffer.length == pos) { return buffer; } final float[] result = new float[pos]; System.arraycopy(buffer, 0, result, 0, pos); return result; } else { // number final float[] buffer = new float[items]; for (int i = 0; i < items; i++) { buffer[i] = readFloat(byteOrder); } return buffer; } } /** * Read a unsigned short value from the stream. * * @param byteOrder he order of bytes to be used to decode the read value * @return the unsigned short value read from stream * @throws IOException it will be thrown for any transport problem during the * operation * @throws EOFException if the end of the stream has been reached * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN */ public int readUnsignedShort(final JBBPByteOrder byteOrder) throws IOException { final int b0 = this.read(); if (b0 < 0) { throw new EOFException(); } final int b1 = this.read(); if (b1 < 0) { throw new EOFException(); } return byteOrder == JBBPByteOrder.BIG_ENDIAN ? (b0 << 8) | b1 : (b1 << 8) | b0; } /** * Read an integer value from the stream. * * @param byteOrder the order of bytes to be used to decode the read value * @return the integer value from the stream * @throws IOException it will be thrown for any transport problem during the * operation * @throws EOFException if the end of the stream has been reached * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN */ public int readInt(final JBBPByteOrder byteOrder) throws IOException { if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { return (readUnsignedShort(byteOrder) << 16) | readUnsignedShort(byteOrder); } else { return readUnsignedShort(byteOrder) | (readUnsignedShort(byteOrder) << 16); } } /** * Read an unsigned integer value from the stream. * * @param byteOrder the order of bytes to be used to decode the read value * @return the unsigned integer value from the stream * @throws IOException it will be thrown for any transport problem during the * operation * @throws EOFException if the end of the stream has been reached * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 2.0.4 */ public long readUInt(final JBBPByteOrder byteOrder) throws IOException { if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { return (((long) readUnsignedShort(byteOrder) << 16) | readUnsignedShort(byteOrder)); } else { return readUnsignedShort(byteOrder) | ((long) readUnsignedShort(byteOrder) << 16); } } /** * Read a float value from the stream. * * @param byteOrder the order of bytes to be used to decode the read value * @return the float value from the stream * @throws IOException it will be thrown for any transport problem during the * operation * @throws EOFException if the end of the stream has been reached * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 1.4.0 */ public float readFloat(final JBBPByteOrder byteOrder) throws IOException { final int value; if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { value = (readUnsignedShort(byteOrder) << 16) | readUnsignedShort(byteOrder); } else { value = readUnsignedShort(byteOrder) | (readUnsignedShort(byteOrder) << 16); } return Float.intBitsToFloat(value); } /** * Read a long value from the stream. * * @param byteOrder the order of bytes to be used to decode the read value * @return the long value from stream * @throws IOException it will be thrown for any transport problem during the * operation * @throws EOFException if the end of the stream has been reached * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN */ public long readLong(final JBBPByteOrder byteOrder) throws IOException { if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { return (((long) readInt(byteOrder) & 0xFFFFFFFFL) << 32) | ((long) readInt(byteOrder) & 0xFFFFFFFFL); } else { return ((long) readInt(byteOrder) & 0xFFFFFFFFL) | (((long) readInt(byteOrder) & 0xFFFFFFFFL) << 32); } } /** * Read a double value from the stream. * * @param byteOrder the order of bytes to be used to decode the read value * @return the double value from stream * @throws IOException it will be thrown for any transport problem during the * operation * @throws EOFException if the end of the stream has been reached * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 1.4.0 */ public double readDouble(final JBBPByteOrder byteOrder) throws IOException { final long value; if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { value = (((long) readInt(byteOrder) & 0xFFFFFFFFL) << 32) | ((long) readInt(byteOrder) & 0xFFFFFFFFL); } else { value = ((long) readInt(byteOrder) & 0xFFFFFFFFL) | (((long) readInt(byteOrder) & 0xFFFFFFFFL) << 32); } return Double.longBitsToDouble(value); } /** * Get the current fully read byte counter. * * @return the number of bytes read fully from the stream */ @Override public long getCounter() { return this.byteCounter; } /** * Get the value of the inside bit buffer * * @return the bit buffer state */ @Override public int getBitBuffer() { return this.bitBuffer; } /** * Get the number of buffered bits in the inside bit buffer * * @return the number of buffered bits in the bit buffer */ @Override public int getBufferedBitsNumber() { return this.bitsInBuffer; } /** * Get the bit order option for read operations. * * @return the bit order parameter * @see JBBPBitOrder#LSB0 * @see JBBPBitOrder#MSB0 */ @Override public JBBPBitOrder getBitOrder() { return this.msb0 ? JBBPBitOrder.MSB0 : JBBPBitOrder.LSB0; } /** * Read bit field from the stream, if it is impossible then IOException will be thrown. * * @param numOfBitsToRead field width, must not be null * @return read value from the stream * @throws IOException it will be thrown if EOF or troubles to read the stream * @since 1.3.0 */ public byte readBitField(final JBBPBitNumber numOfBitsToRead) throws IOException { final int value = this.readBits(numOfBitsToRead); if (value < 0) { throw new EOFException("Can't read bits from stream [" + numOfBitsToRead + ']'); } return (byte) value; } /** * Read number of bits from the input stream. It reads bits from input stream * since 0 bit and make reversion to return bits in the right order when 0 bit * is 0 bit. if the stream is completed early than the data read then reading * is just stopped and read value returned. The First read bit is placed as * 0th bit. * * @param numOfBitsToRead the number of bits to be read, must be 1..8 * @return the read bits as integer, -1 if the end of stream has been reached * @throws IOException it will be thrown for transport errors to be read * @throws NullPointerException if number of bits to be read is null */ public int readBits(final JBBPBitNumber numOfBitsToRead) throws IOException { int result; final int numOfBitsAsNumber = numOfBitsToRead.getBitNumber(); if (this.bitsInBuffer == 0 && numOfBitsAsNumber == 8) { result = this.readByteFromStream(); if (result >= 0) { this.byteCounter++; } return result; } else { result = 0; if (numOfBitsAsNumber == this.bitsInBuffer) { result = this.bitBuffer; this.bitBuffer = 0; this.bitsInBuffer = 0; this.byteCounter++; return result; } int i = numOfBitsAsNumber; int theBitBuffer = this.bitBuffer; int theBitBufferCounter = this.bitsInBuffer; final boolean doIncCounter = theBitBufferCounter != 0; while (i > 0) { if (theBitBufferCounter == 0) { if (doIncCounter) { this.byteCounter++; } final int nextByte = this.readByteFromStream(); if (nextByte < 0) { if (i == numOfBitsAsNumber) { return nextByte; } else { break; } } else { theBitBuffer = nextByte; theBitBufferCounter = 8; } } result = (result << 1) | (theBitBuffer & 1); theBitBuffer >>= 1; theBitBufferCounter--; i--; } this.bitBuffer = theBitBuffer; this.bitsInBuffer = theBitBufferCounter; return JBBPUtils.reverseBitsInByte(JBBPBitNumber.decode(numOfBitsAsNumber - i), (byte) result) & 0xFF; } } /** * Read a boolean value saved as a byte. * * @return true if the value is not zero, false if the value is zero * @throws IOException it will be thrown for transport errors. */ public boolean readBoolean() throws IOException { final int read = this.read(); if (read < 0) { throw new EOFException("Can't read a boolean value"); } return read != 0; } /** * Read a byte value. * * @return the byte value read from the stream * @throws IOException it will be thrown for transport errors. */ public int readByte() throws IOException { final int read = this.read(); if (read < 0) { throw new EOFException("Can't read a byte value"); } return read; } @Override public synchronized void reset() throws IOException { in.reset(); this.bitBuffer = this.markedBitBuffer; this.byteCounter = this.markedByteCounter; this.bitsInBuffer = this.markedBitsInBuffer; } /** * Read number of long items from the input stream. * * @param items number of items to be read from the input stream, if less than * zero then all stream till the end will be read * @param byteOrder the order of bytes to be used to decode values * @return read items as a long array * @throws IOException it will be thrown for any transport problem during the * operation * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN */ public long[] readLongArray(final int items, final JBBPByteOrder byteOrder) throws IOException { int pos = 0; if (items < 0) { long[] buffer = new long[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (hasAvailableData()) { final long next = readLong(byteOrder); if (buffer.length == pos) { final long[] newBuffer = new long[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } buffer[pos++] = next; } if (buffer.length == pos) { return buffer; } final long[] result = new long[pos]; System.arraycopy(buffer, 0, result, 0, pos); return result; } else { // number final long[] buffer = new long[items]; for (int i = 0; i < items; i++) { buffer[i] = readLong(byteOrder); } return buffer; } } /** * Read padding bytes from the stream and ignore them to align the stream * counter. * * @param alignByteNumber the byte number to align the stream * @throws IOException it will be thrown for transport errors * @throws EOFException it will be thrown if the stream end has been reached * the before align border. */ public void align(final long alignByteNumber) throws IOException { this.alignByte(); if (alignByteNumber > 0) { long padding = (alignByteNumber - (this.byteCounter % alignByteNumber)) % alignByteNumber; while (padding > 0) { final int skippedByte = this.read(); if (skippedByte < 0) { throw new EOFException("Can't align for " + alignByteNumber + " byte(s)"); } padding--; } } } @Override public long skip(final long numOfBytes) throws IOException { if (this.bitsInBuffer == 0) { final long r = in.skip(numOfBytes); this.byteCounter += (int) r; return r; } else { long i = numOfBytes; long count = 0L; while (i > 0) { final int nxt = readBits(JBBPBitNumber.BITS_8); if (nxt < 0) { break; } count++; i--; } return count; } } /** * Inside method to read a byte from stream. * * @return the read byte or -1 if the end of the stream has been reached * @throws IOException it will be thrown for transport errors */ private int readByteFromStream() throws IOException { int result = this.in.read(); if (result >= 0 && this.msb0) { result = JBBPUtils.reverseBitsInByte((byte) result) & 0xFF; } return result; } /** * Read number of double items from the input stream. * * @param items number of items to be read from the input stream, if less than * zero then all stream till the end will be read * @param byteOrder the order of bytes to be used to decode values * @return read items as a double array * @throws IOException it will be thrown for any transport problem during the * operation * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 1.4.0 */ public double[] readDoubleArray(final int items, final JBBPByteOrder byteOrder) throws IOException { int pos = 0; if (items < 0) { double[] buffer = new double[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (hasAvailableData()) { final long next = readLong(byteOrder); if (buffer.length == pos) { final double[] newBuffer = new double[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } buffer[pos++] = Double.longBitsToDouble(next); } if (buffer.length == pos) { return buffer; } final double[] result = new double[pos]; System.arraycopy(buffer, 0, result, 0, pos); return result; } else { // number final double[] buffer = new double[items]; for (int i = 0; i < items; i++) { buffer[i] = readDouble(byteOrder); } return buffer; } } /** * Align read byte. If there are unread bits in inside bit buffer, it will be * aligned to read the next byte from stream instead of reading from the bit * buffer. */ public void alignByte() { if (this.bitsInBuffer > 0 && this.bitsInBuffer < 8) { this.byteCounter++; this.bitsInBuffer = 0; } } /** * Check that there is available data to read from stream. NB: It will read * one byte from the stream and will save it into inside bit buffer. * * @return true if there is data to be read * @throws IOException it will be thrown for transport errors */ public boolean hasAvailableData() throws IOException { return this.bitsInBuffer > 0 || loadNextByteInBuffer() >= 0; } /** * Read the next stream byte into bit buffer. * * @return the read byte or -1 if the end of stream has been reached. * @throws IOException it will be thrown for transport errors */ private int loadNextByteInBuffer() throws IOException { final int value = this.readByteFromStream(); if (value < 0) { return value; } this.bitBuffer = value; this.bitsInBuffer = 8; return value; } /** * Reset the byte counter for the stream. The Inside bit buffer will be reset * also if it is not full. * * @see #align(long) * @see #alignByte() * @see #getBitBuffer() * @see #getBufferedBitsNumber() * @see #hasAvailableData() . */ @Override public void resetCounter() { if (this.bitsInBuffer < 8) { this.bitsInBuffer = 0; this.bitBuffer = 0; } this.byteCounter = 0L; } @Override public int read(final byte[] array) throws IOException { return this.read(array, 0, array.length); } @Override public int read() throws IOException { final int result; if (this.bitsInBuffer == 0) { result = this.readByteFromStream(); if (result >= 0) { this.byteCounter++; } return result; } else { return this.readBits(JBBPBitNumber.BITS_8); } } private IOException makeIOExceptionForWrongPrefix(final int prefix) { return new IOException("Wrong string prefix:" + prefix); } /** * Read string in UTF8 format. * * @param byteOrder byte order, must not be null * @return read string, can be null * @throws IOException it will be thrown for transport error or wrong format * @see JBBPBitOutputStream#writeString(String, JBBPByteOrder) * @since 1.4.0 */ public String readString(final JBBPByteOrder byteOrder) throws IOException { final int prefix = this.readByte(); final int len; if (prefix == 0) { len = 0; } else if (prefix == 0xFF) { len = -1; } else if (prefix < 0x80) { len = prefix; } else if ((prefix & 0xF0) == 0x80) { switch (prefix & 0x0F) { case 1: { len = this.readByte(); } break; case 2: { len = this.readUnsignedShort(byteOrder); } break; case 3: { int buffer; if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { buffer = (this.readByte() << 16) | (this.readByte() << 8) | this.readByte(); } else { buffer = this.readByte() | (this.readByte() << 8) | (this.readByte() << 16); } len = buffer; } break; case 4: { len = this.readInt(byteOrder); } break; default: { throw makeIOExceptionForWrongPrefix(prefix); } } } else { throw makeIOExceptionForWrongPrefix(prefix); } final String result; if (len < 0) { result = null; } else if (len == 0) { result = ""; } else { result = JBBPUtils.utf8ToStr(this.readByteArray(len)); } return result; } /** * Read array of strings from stream. * * @param items number of items, or -1 if read whole stream * @param byteOrder order of bytes in structure, must not be null * @return array, it can contain null among values, must not be null * @throws IOException thrown for transport errors * @see JBBPBitOutputStream#writeStringArray(String[], JBBPByteOrder) * @since 1.4.0 */ public String[] readStringArray(final int items, final JBBPByteOrder byteOrder) throws IOException { int pos = 0; if (items < 0) { String[] buffer = new String[INITIAL_ARRAY_BUFFER_SIZE]; // till end while (hasAvailableData()) { final String next = readString(byteOrder); if (buffer.length == pos) { final String[] newBuffer = new String[buffer.length << 1]; System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); buffer = newBuffer; } buffer[pos++] = next; } if (buffer.length == pos) { return buffer; } final String[] result = new String[pos]; System.arraycopy(buffer, 0, result, 0, pos); return result; } else { // number final String[] buffer = new String[items]; for (int i = 0; i < items; i++) { buffer[i] = readString(byteOrder); } return buffer; } } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitNumber.java000066400000000000000000000042051445000440600316540ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; /** * Constants allowed bit numbers for bit operations. * * @since 1.0 */ public enum JBBPBitNumber { /** * One bit. */ BITS_1(1, 0x01), /** * Two bits. */ BITS_2(2, 0x03), /** * Three bits. */ BITS_3(3, 0x07), /** * Four bits. */ BITS_4(4, 0x0F), /** * Five bits. */ BITS_5(5, 0x1F), /** * Six bits. */ BITS_6(6, 0x3F), /** * Seven bits. */ BITS_7(7, 0x7F), /** * Eight bits. */ BITS_8(8, 0xFF); /** * Number of bits. */ private final int numberOfBits; /** * Mask for the number of bits. */ private final int mask; JBBPBitNumber(final int numberOfBits, final int mask) { this.numberOfBits = numberOfBits; this.mask = mask; } /** * Decode a numeric value to a constant. * * @param numberOfBits the numeric value to be decoded * @return decoded constant * @throws IllegalArgumentException if the value less than 1 or greater than 8 */ public static JBBPBitNumber decode(final int numberOfBits) { if (numberOfBits <= 0 || numberOfBits > 8) { throw new IllegalArgumentException("Unsupported bit number, allowed 1..8"); } return values()[numberOfBits - 1]; } /** * Get the mask for the number of bits. * * @return the mask for the number of bits */ public int getMask() { return this.mask; } /** * Get the numeric value of the bit number. * * @return the number of bits as integer */ public int getBitNumber() { return this.numberOfBits; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitOrder.java000066400000000000000000000020631445000440600314770ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; /** * Constants for bit order of reading operations. * * @since 1.0 */ public enum JBBPBitOrder { /** * Most Significant Bit First means that the most significant bit will arrive first, the 7th bit will be read as the first one. */ MSB0, /** * Least Significant Bit First means that the least significant bit will arrive first, the 0th bit will be read as the first one. * It is default order for Java. */ LSB0 } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitOutputStream.java000066400000000000000000000326031445000440600331030ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; /** * The Filter allows to write bit by bit into an output stream and count the written byte number. The Class is not a thread-safe one. * * @since 1.0 */ public class JBBPBitOutputStream extends FilterOutputStream implements JBBPCountableBitStream { /** * Flag shows that bit operations must be processed for MSB0 (most significant * bit 0) mode. */ private final boolean msb0; /** * Inside bit buffer. */ private int bitBuffer; /** * Number of bits inside the bit buffer. */ private int bitBufferCount; /** * The byte counter of written bytes. */ private long byteCounter; /** * A Constructor. The Default LSB0 bit mode will be used for bit writing operations. * * @param out the output stream to be filtered. */ public JBBPBitOutputStream(final OutputStream out) { this(out, JBBPBitOrder.LSB0); } /** * A Constructor. * * @param out an output stream to be filtered. * @param order a bit writing mode to used for writing operations. * @see JBBPBitOrder#LSB0 * @see JBBPBitOrder#MSB0 */ public JBBPBitOutputStream(final OutputStream out, final JBBPBitOrder order) { super(out); this.msb0 = order == JBBPBitOrder.MSB0; } /** * Get the bit mode for writing operations. * * @return the bit order for reading operations. * @see JBBPBitOrder#LSB0 * @see JBBPBitOrder#MSB0 */ @Override public JBBPBitOrder getBitOrder() { return this.msb0 ? JBBPBitOrder.MSB0 : JBBPBitOrder.LSB0; } /** * Write a signed short value into the output stream. * * @param value a value to be written. Only two bytes will be written. * @param byteOrder the byte order of the value bytes to be used for writing. * @throws IOException it will be thrown for transport errors * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN */ public void writeShort(final int value, final JBBPByteOrder byteOrder) throws IOException { if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { this.write(value >>> 8); this.write(value); } else { this.write(value); this.write(value >>> 8); } } /** * Write an integer value into the output stream. * * @param value a value to be written into the output stream. * @param byteOrder the byte order of the value bytes to be used for writing. * @throws IOException it will be thrown for transport errors * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN */ public void writeInt(final int value, final JBBPByteOrder byteOrder) throws IOException { if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { this.writeShort(value >>> 16, byteOrder); this.writeShort(value, byteOrder); } else { this.writeShort(value, byteOrder); this.writeShort(value >>> 16, byteOrder); } } /** * Write an unsigned integer value into the output stream. * * @param value a value to be written into the output stream. * @param byteOrder the byte order of the value bytes to be used for writing. * @throws IOException it will be thrown for transport errors * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 2.0.4 */ public void writeUInt(final long value, final JBBPByteOrder byteOrder) throws IOException { final int v = (int) value; if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { this.writeShort(v >>> 16, byteOrder); this.writeShort(v, byteOrder); } else { this.writeShort(v, byteOrder); this.writeShort(v >>> 16, byteOrder); } } /** * Write an float value into the output stream. * * @param value a value to be written into the output stream. * @param byteOrder the byte order of the value bytes to be used for writing. * @throws IOException it will be thrown for transport errors * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 1.4.0 */ public void writeFloat(final float value, final JBBPByteOrder byteOrder) throws IOException { final int intValue = Float.floatToIntBits(value); if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { this.writeShort(intValue >>> 16, byteOrder); this.writeShort(intValue, byteOrder); } else { this.writeShort(intValue, byteOrder); this.writeShort(intValue >>> 16, byteOrder); } } /** * Write a long value into the output stream. * * @param value a value to be written into the output stream. * @param byteOrder the byte order of the value bytes to be used for writing. * @throws IOException it will be thrown for transport errors * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN */ public void writeLong(final long value, final JBBPByteOrder byteOrder) throws IOException { if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { this.writeInt((int) (value >>> 32), byteOrder); this.writeInt((int) value, byteOrder); } else { this.writeInt((int) value, byteOrder); this.writeInt((int) (value >>> 32), byteOrder); } } /** * Write a double value into the output stream. * * @param value a value to be written into the output stream. * @param byteOrder the byte order of the value bytes to be used for writing. * @throws IOException it will be thrown for transport errors * @see JBBPByteOrder#BIG_ENDIAN * @see JBBPByteOrder#LITTLE_ENDIAN * @since 1.4.0 */ public void writeDouble(final double value, final JBBPByteOrder byteOrder) throws IOException { final long longValue = Double.doubleToLongBits(value); if (byteOrder == JBBPByteOrder.BIG_ENDIAN) { this.writeInt((int) (longValue >>> 32), byteOrder); this.writeInt((int) longValue, byteOrder); } else { this.writeInt((int) longValue, byteOrder); this.writeInt((int) (longValue >>> 32), byteOrder); } } /** * Get number of bytes written into the output stream. * * @return the long value contains number of bytes written into the stream */ @Override public long getCounter() { return this.byteCounter; } /** * Get the inside bit buffer value. * * @return the inside bit buffer value */ @Override public int getBitBuffer() { return this.bitBuffer; } /** * Get the number of bits cached in the inside bit buffer. * * @return the number of cached bits in the bit buffer */ @Override public int getBufferedBitsNumber() { return this.bitBufferCount; } /** * Flush the bit buffer into the output stream * * @throws IOException it will be thrown for transport errors */ private void flushBitBuffer() throws IOException { if (this.bitBufferCount > 0) { this.bitBufferCount = 0; writeByte(this.bitBuffer); } } @Override public void flush() throws IOException { flushBitBuffer(); this.out.flush(); } @Override public void write(final byte[] b, final int off, final int len) throws IOException { if (this.msb0 || this.bitBufferCount != 0) { int i = off; int cnt = len; while (cnt > 0) { this.write(b[i++]); cnt--; } } else { out.write(b, off, len); this.byteCounter += len; } } @Override public void write(final byte[] b) throws IOException { this.write(b, 0, b.length); } /** * Write bits into the output stream. * * @param value the value which bits will be written in the output stream * @param bitNumber number of bits from the value to be written, must be in 1..8 * @throws IOException it will be thrown for transport errors * @throws IllegalArgumentException it will be thrown for wrong bit number */ public void writeBits(final int value, final JBBPBitNumber bitNumber) throws IOException { if (this.bitBufferCount == 0 && bitNumber == JBBPBitNumber.BITS_8) { write(value); } else { final int initialMask; int mask; initialMask = 1; mask = initialMask << this.bitBufferCount; int accumulator = value; int i = bitNumber.getBitNumber(); while (i > 0) { this.bitBuffer = this.bitBuffer | ((accumulator & 1) == 0 ? 0 : mask); accumulator >>= 1; mask = mask << 1; i--; this.bitBufferCount++; if (this.bitBufferCount == 8) { this.bitBufferCount = 0; writeByte(this.bitBuffer); mask = initialMask; this.bitBuffer = 0; } } } } /** * Write padding bytes to align the stream counter for the border. * * @param alignByteNumber the alignment border * @throws IOException it will be thrown for transport errors */ public void align(final long alignByteNumber) throws IOException { if (this.bitBufferCount > 0) { this.writeBits(0, JBBPBitNumber.decode(8 - this.bitBufferCount)); } if (alignByteNumber > 0) { long padding = (alignByteNumber - (this.byteCounter % alignByteNumber)) % alignByteNumber; while (padding > 0) { this.out.write(0); this.byteCounter++; padding--; } } } /** * Inside method to write a byte into wrapped stream. * * @param value a byte value to be written * @throws IOException it will be thrown for transport problems */ private void writeByte(int value) throws IOException { if (this.msb0) { value = JBBPUtils.reverseBitsInByte((byte) value) & 0xFF; } this.out.write(value); this.byteCounter++; } @Override public void close() throws IOException { this.flush(); this.out.close(); } @Override public void write(final int value) throws IOException { if (this.bitBufferCount == 0) { writeByte(value); } else { writeBits(value, JBBPBitNumber.BITS_8); } } /** * Write number of items from byte array into stream * * @param array array, must not be null * @param length number of items to be written, if -1 then whole array * @param byteOrder order of bytes, if LITTLE_ENDIAN then array will be reversed * @throws IOException it will be thrown if any transport error * @see JBBPByteOrder#LITTLE_ENDIAN * @since 1.3.0 */ public void writeBytes(final byte[] array, final int length, final JBBPByteOrder byteOrder) throws IOException { if (byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { int i = length < 0 ? array.length - 1 : length - 1; while (i >= 0) { this.write(array[i--]); } } else { this.write(array, 0, length < 0 ? array.length : length); } } /** * Reset the byte counter for the stream. The Inside bit buffer will be reset also. */ @Override public void resetCounter() { this.bitBuffer = 0; this.bitBufferCount = 0; this.byteCounter = 0L; } /** * Write string in UTF8 format into stream. * the byte order in saved char data will be BIG_ENDIAN * Format: PREFIX(FF=null | 0=empty | 0x8packedLength) LENGTH[packedLength] DATA_ARRAY[LENGTH] * * @param value string to be written, can be null * @param order order of bytes in written data (it doesn't affect encoded UTF8 array) * @throws IOException i twill be thrown if transport error * @since 1.4.0 */ public void writeString(final String value, final JBBPByteOrder order) throws IOException { if (value == null) { this.write(0xFF); } else if (value.length() == 0) { this.write(0); } else { final byte[] array = JBBPUtils.strToUtf8(value); final int len = array.length; if (len < 0x80) { this.write(len); } else if ((len & 0xFFFFFF00) == 0) { this.write(0x81); this.write(len); } else if ((len & 0xFFFF0000) == 0) { this.write(0x82); this.writeShort(len, order); } else if ((len & 0xFF000000) == 0) { this.write(0x83); if (order == JBBPByteOrder.BIG_ENDIAN) { this.write(len >>> 16); this.write(len >>> 8); this.write(len); } else { this.write(len); this.write(len >>> 8); this.write(len >>> 16); } } else { this.write(0x84); this.writeInt(len, order); } this.write(array); } } /** * Write array of strings in stream in UTF8 format * the byte order in saved char data will be BIG_ENDIAN * * @param value array to be written, must not be null but can contain null values * @param order byte order to write char data, must not be null * @throws IOException it will be thrown for transport errors * @see #writeString(String, JBBPByteOrder) * @since 1.4.0 */ public void writeStringArray(final String[] value, final JBBPByteOrder order) throws IOException { for (final String s : value) { this.writeString(s, order); } } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPByteOrder.java000066400000000000000000000022271445000440600316660ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; /** * Constants define byte order for multi-byte values to be read or written into streams. * * @since 1.0 */ public enum JBBPByteOrder { /** * The Big-Endian order. Big-endian systems store the most significant byte of * a word in the smallest address and the least significant byte is stored in * the largest address. The Default order for Java and Network. */ BIG_ENDIAN, /** * The Little-Endian order. Little-endian systems store the * least significant byte in the smallest address. */ LITTLE_ENDIAN } JBBPCountableBitStream.java000066400000000000000000000027551445000440600334450ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; /** * The Interface describes a stream which can manipulate bits and count number of bytes. * * @since 1.0 */ public interface JBBPCountableBitStream { /** * Get the number of bytes passed from the stream. * * @return the number of passed bytes */ long getCounter(); /** * Get the bit order for the stream. * * @return the bit order * @see JBBPBitOrder#LSB0 * @see JBBPBitOrder#MSB0 */ JBBPBitOrder getBitOrder(); /** * Reset the inside byte counter of the stream, inside bit buffer will be reset. */ void resetCounter(); /** * Get the inside stream bit buffer. * * @return the value from inside the stream bit buffer */ int getBitBuffer(); /** * Get the number of bits cached in the bit buffer. * * @return the number of bits cached in the stream bit buffer */ int getBufferedBitsNumber(); } JBBPCustomFieldWriter.java000066400000000000000000000036051445000440600333240ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import com.igormaznitsa.jbbp.mapper.Bin; import java.io.IOException; import java.lang.reflect.Field; /** * The Interface describes an object which can save data for custom fields in mapped classes. * * @see Bin * @see JBBPOut * @since 1.1 */ public interface JBBPCustomFieldWriter { /** * To write the field data into the bit stream. * * @param context the JBBPOut context, must not be null * @param outStream the current context output stream, must not be null * @param instanceToSave the mapped class instance, must not be null * @param instanceCustomField the field of the mapped class which data must be saved into the stream, must not be null * @param fieldAnnotation the field Bin annotation which can be used as extra data source about the field, must not be null * @param value the value found in the field, can be null * @throws IOException it will be thrown if it is impossible to process field data and save them into the stream */ void writeCustomField(final JBBPOut context, final JBBPBitOutputStream outStream, final Object instanceToSave, final Field instanceCustomField, final Bin fieldAnnotation, final Object value) throws IOException; } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPOut.java000066400000000000000000001244061445000440600305420ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import com.igormaznitsa.jbbp.exceptions.JBBPIOException; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinFieldFilter; import com.igormaznitsa.jbbp.mapper.JBBPMapper; import com.igormaznitsa.jbbp.model.JBBPFieldShort; import com.igormaznitsa.jbbp.utils.BinAnnotationWrapper; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; /** * The Class implements some kind of DSL to form binary blocks. The Class is not * a thread-safe one. * * @since 1.0 */ public final class JBBPOut extends AbstractMappedClassFieldObserver { /** * The Default byte outOrder. */ public static final JBBPByteOrder DEFAULT_BYTE_ORDER = JBBPByteOrder.BIG_ENDIAN; /** * The Default bit outOrder. */ public static final JBBPBitOrder DEFAULT_BIT_ORDER = JBBPBitOrder.LSB0; /** * The Bit outOrder for operations. */ private final JBBPBitOrder bitOrder; /** * The Bit stream for operations. */ private final JBBPBitOutputStream outStream; /** * If the DSL session was started for an external byte array output stream * then it will be saved into the variable. */ private final ByteArrayOutputStream originalByteArrayOutStream; /** * Flag shows that all commands must be skipped till the End. */ private boolean processCommands = true; /** * The Byte outOrder for operations of multi-byte value output. */ private JBBPByteOrder byteOrder; /** * The Flags shows that the processing has been ended. */ private boolean ended; /** * The Constructor. * * @param outStream the output stream for the session, it must not be null. * @param byteOrder the byte outOrder for the session, it must not be null. * @param bitOrder the bit outOrder for the session, it must not be null * @throws IllegalArgumentException if defined a bit stream which parameters * incompatible with defined ones */ private JBBPOut(final OutputStream outStream, final JBBPByteOrder byteOrder, final JBBPBitOrder bitOrder) { JBBPUtils.assertNotNull(outStream, "Out stream must not be null"); JBBPUtils.assertNotNull(byteOrder, "Byte order must not be null"); JBBPUtils.assertNotNull(bitOrder, "Bit order must not be null"); this.outStream = outStream instanceof JBBPBitOutputStream ? (JBBPBitOutputStream) outStream : new JBBPBitOutputStream(outStream, bitOrder); this.bitOrder = this.outStream.getBitOrder(); if (this.bitOrder != bitOrder) { throw new IllegalArgumentException( "Detected JBBPBitOutputStream as argument with already defined different bit order [" + this.bitOrder + ']'); } this.byteOrder = byteOrder; if (outStream instanceof ByteArrayOutputStream) { this.originalByteArrayOutStream = (ByteArrayOutputStream) outStream; } else { this.originalByteArrayOutStream = null; } } /** * Start a DSL session for defined both byte outOrder and bit outOrder parameters. * * @param byteOrder the byte outOrder to be used for the session * @param bitOrder the bit outOrder to be used for the session * @return the new DSL session generated with the parameters and inside byte * array stream. */ public static JBBPOut BeginBin(final JBBPByteOrder byteOrder, final JBBPBitOrder bitOrder) { return new JBBPOut(new ByteArrayOutputStream(), byteOrder, bitOrder); } /** * Start a DSL session for a defined stream with defined parameters. * * @param out the defined stream * @param byteOrder the byte outOrder for the session * @param bitOrder the bit outOrder for the session * @return the new DSL session generated for the stream with parameters */ public static JBBPOut BeginBin(final OutputStream out, final JBBPByteOrder byteOrder, final JBBPBitOrder bitOrder) { return new JBBPOut(out, byteOrder, bitOrder); } /** * Start a DSL session for default parameters and inside byte array stream. * * @return the new DSL session generated with the default parameters and * inside byte array stream. */ public static JBBPOut BeginBin() { return new JBBPOut(new ByteArrayOutputStream(), DEFAULT_BYTE_ORDER, DEFAULT_BIT_ORDER); } /** * Start a DSL session for default parameters and inside byte array stream * with defined start size. * * @param initialSize the start size of inside buffer of the byte array output * stream, must be positive * @return the new DSL session generated with the default parameters and * inside byte array stream. */ public static JBBPOut BeginBin(final int initialSize) { return new JBBPOut(new ByteArrayOutputStream(initialSize), DEFAULT_BYTE_ORDER, DEFAULT_BIT_ORDER); } /** * Start a DSL session for a defined output stream and default parameters. * * @param out an output stream to write session data, must not be null. * @return the new DSL session generated for the default parameters and the * output stream. */ public static JBBPOut BeginBin(final OutputStream out) { return new JBBPOut(out, DEFAULT_BYTE_ORDER, DEFAULT_BIT_ORDER); } /** * Start a DSL session for default bit outOrder and defined byte outOrder. It will * be using inside byte array stream. * * @param byteOrder the byte outOrder for the session, it must not be null. * @return the new DSL session */ public static JBBPOut BeginBin(final JBBPByteOrder byteOrder) { return new JBBPOut(new ByteArrayOutputStream(), byteOrder, DEFAULT_BIT_ORDER); } /** * Start a DSL session for default byte outOrder and defined bite outOrder. It will * be using inside byte array stream. * * @param bitOrder the bite outOrder for the session, it must not be null. * @return the new DSL session */ public static JBBPOut BeginBin(final JBBPBitOrder bitOrder) { return new JBBPOut(new ByteArrayOutputStream(), DEFAULT_BYTE_ORDER, bitOrder); } /** * Inside wrapper of not null assertion with text for arrays. * * @param array an object to be checked for null. */ private static void assertArrayNotNull(final Object array) { JBBPUtils.assertNotNull(array, "Array must not be null"); } /** * Inside wrapper of not null assertion with text for strings. * * @param str an object to be checked for null. */ private static void assertStringNotNull(final String str) { JBBPUtils.assertNotNull(str, "String must not be null"); } /** * Align the current stream for 1 byte. If there are any bites inside bit * cache then they will be saved and the stream will be positioning to the * next byte. It works relative to the byte output counter. * * @return the DSL session * @throws IOException it will be thrown for transport errors * @see JBBPOut#ResetCounter() */ public JBBPOut Align() throws IOException { assertNotEnded(); if (this.processCommands) { this.outStream.align(0); } return this; } /** * Align number of bytes in the stream to the value. It works relative to the * byte output counter. * * @param value the byte border to align the stream. * @return the DSL session * @throws IOException it will be thrown for transport errors * @see JBBPOut#ResetCounter() */ public JBBPOut Align(final int value) throws IOException { assertNotEnded(); if (this.processCommands) { this.outStream.align(value); } return this; } /** * Skip number of bytes in the stream, zero bytes will be written and also * will be aligned inside bit cache even if the value is 0. * * @param numberOfBytes the number of bytes to be skipped * @return the DSL session * @throws IOException it will be thrown for transport errors * @throws IllegalArgumentException it will be thrown if the value is negative * one */ public JBBPOut Skip(int numberOfBytes) throws IOException { assertNotEnded(); if (this.processCommands) { if (numberOfBytes < 0) { throw new IllegalArgumentException("Value is negative"); } this.Align(); while (numberOfBytes > 0) { this.outStream.write(0); numberOfBytes--; } } return this; } /** * Define the byte outOrder for next session operations. * * @param value the byte outOrder to be used in next operations, must not be null * @return the DSL session */ public JBBPOut ByteOrder(final JBBPByteOrder value) { assertNotEnded(); JBBPUtils.assertNotNull(value, "Byte order must not be null"); if (this.processCommands) { this.byteOrder = value; } return this; } /** * Write a bit into the session. * * @param value true if the bit is 1, 0 otherwise * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Bit(final boolean value) throws IOException { assertNotEnded(); if (this.processCommands) { this.outStream.writeBits(value ? 1 : 0, JBBPBitNumber.BITS_1); } return this; } /** * Write the lowest bit from a byte value. * * @param value the byte value which lowest bit will be written into the * stream * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Bit(final byte value) throws IOException { assertNotEnded(); if (this.processCommands) { this._writeBits(JBBPBitNumber.BITS_1, value); } return this; } /** * Write lowest bits of bytes from an array. * * @param value a byte array, lowest bit of each byte will be saved as a bit * into the output stream, it must not be null * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Bit(final byte[] value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final byte b : value) { this._writeBits(JBBPBitNumber.BITS_1, b); } } return this; } /** * Write lowest bits of integers from an array. * * @param value an integer array, lowest bit of each integer value will be * saved as a bit into the output stream, it must not be null * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Bit(final int... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final int b : value) { this._writeBits(JBBPBitNumber.BITS_1, b); } } return this; } /** * Write bits represented as boolean flags into the output stream. * * @param value a boolean array which values will be saved into the output * stream as bits, true is bit on, false is bit off. It must not be null * @return the DSL session. * @throws IOException it will be thrown for transport errors */ public JBBPOut Bit(final boolean... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final boolean b : value) { this._writeBits(JBBPBitNumber.BITS_1, b ? 1 : 0); } } return this; } /** * Inside auxiliary method to write bits into output stream without check of * the session end * * @param numberOfBits number of bits from value to save, it must not be null * @param value the value which bits must be saved * @throws IOException it will be thrown for transport errors */ private void _writeBits(final JBBPBitNumber numberOfBits, final int value) throws IOException { this.outStream.writeBits(value, numberOfBits); } /** * Write bits from a value into the output stream * * @param numberOfBits the number of bits to be saved * @param value the value which bits must be saved * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Bits(final JBBPBitNumber numberOfBits, final int value) throws IOException { assertNotEnded(); JBBPUtils.assertNotNull(numberOfBits, "Number of bits must not be null"); if (this.processCommands) { _writeBits(numberOfBits, value); } return this; } /** * Write bits of each integer value from an array into the output stream. * * @param numberOfBits the number of bits to be saved * @param value an integer array which elements will be used as sources of * bits, it must not be null * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Bits(final JBBPBitNumber numberOfBits, final int... value) throws IOException { assertNotEnded(); JBBPUtils.assertNotNull(value, "Array must not be null"); if (this.processCommands) { for (final int v : value) { _writeBits(numberOfBits, v); } } return this; } /** * Write bits of each byte value from an array into the output stream. * * @param numberOfBits the number of bits to be saved * @param value a byte array which elements will be used as sources of bits, * it must not be null * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Bits(final JBBPBitNumber numberOfBits, final byte[] value) throws IOException { assertNotEnded(); JBBPUtils.assertNotNull(value, "Array must not be null"); if (this.processCommands) { for (final byte b : value) { _writeBits(numberOfBits, b); } } return this; } /** * Inside auxiliary speed version of byte writing method * * @param value a byte to write into session stream * @throws IOException it will be thrown for transport errors */ private void _writeByte(final int value) throws IOException { this.outStream.write(value); } /** * Write the lower byte of an integer value into the session stream. * * @param value an integer value which byte should be written into * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Byte(final int value) throws IOException { assertNotEnded(); if (this.processCommands) { _writeByte(value); } return this; } /** * Write the lower byte of an integer value into the session stream. * * @param value an integer array which values will be byte sources to write * their lower byte into the stream * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Byte(final int... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final int v : value) { _writeByte(v); } } return this; } /** * Write a byte array into the session stream. * * @param value a byte array to be written * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Byte(final byte[] value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { this.outStream.write(value); } return this; } /** * Write String chars trimmed to bytes, only the lower 8 bit will be saved per * char code. * * @param str a String which chars should be trimmed to bytes and saved * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Byte(final String str) throws IOException { assertNotEnded(); assertStringNotNull(str); if (this.processCommands) { for (int i = 0; i < str.length(); i++) { this.outStream.write(str.charAt(i)); } } return this; } /** * Write String chars trimmed to bytes, only the lower 8 bit will be saved per * char code. * * @param str a String which chars should be trimmed to bytes and saved * @param bitOrder the bit outOrder to save bytes * @return the DSL session * @throws IOException it will be thrown for transport errors * @since 1.1 */ public JBBPOut Byte(final String str, final JBBPBitOrder bitOrder) throws IOException { assertNotEnded(); assertStringNotNull(str); if (this.processCommands) { for (int i = 0; i < str.length(); i++) { byte value = (byte) str.charAt(i); if (bitOrder == JBBPBitOrder.MSB0) { value = JBBPUtils.reverseBitsInByte(value); } this.outStream.write(value); } } return this; } /** * Write chars of a String as encoded Utf8 byte array. There will not be aby information about string length. * * @param str a String which bytes should be written as Utf8, must not be null * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Utf8(final String str) throws IOException { assertNotEnded(); assertStringNotNull(str); if (this.processCommands) { this.outStream.write(JBBPUtils.strToUtf8(str)); } return this; } /** * Write string into output stream with length information. * the byte order in saved char data will be BIG_ENDIAN * * @param str string to be written, it can be null * @return the DSL session * @throws IOException it will be thrown for transport errors * @see JBBPBitOutputStream#writeString(String, JBBPByteOrder) * @since 1.4.0 */ public JBBPOut String(String str) throws IOException { this.outStream.writeString(str, this.byteOrder); return this; } /** * Write string array as sequence of strings with information about string length. * the byte order in saved char data will be BIG_ENDIAN * * @param strings array of strings, must not be null but can contain null * @return the DSL session * @throws IOException it will be thrown for transport errors * @see JBBPBitOutputStream#writeString(String, JBBPByteOrder) * @since 1.4.0 */ public JBBPOut Strings(final String... strings) throws IOException { for (final String s : strings) { this.String(s); } return this; } /** * Write a boolean value into the session stream as a byte. * * @param value a boolean value to be written, true is 1, false is 0 * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Bool(final boolean value) throws IOException { assertNotEnded(); if (this.processCommands) { this.outStream.write(value ? 1 : 0); } return this; } /** * Write a boolean value into the session stream as a byte. * * @param value a boolean value to be written, true is 1, false is 0 * @param bitOrder bit outOrder for saving data * @return the DSL session * @throws IOException it will be thrown for transport errors * @since 1.1 */ public JBBPOut Bool(final boolean value, final JBBPBitOrder bitOrder) throws IOException { assertNotEnded(); if (this.processCommands) { this.outStream.write(value ? bitOrder == JBBPBitOrder.MSB0 ? 0x80 : 1 : 0); } return this; } /** * Write boolean values from an array into the session stream as bytes. * * @param value a boolean array to be saved as bytes, true is 1, false is 0. * It must not be null * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Bool(final boolean... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final boolean b : value) { this.outStream.write(b ? 1 : 0); } } return this; } /** * Inside auxiliary method to write a short value into the session stream * * @param value a value to be written * @throws IOException it will be thrown for transport errors */ private void _writeShort(final int value) throws IOException { this.outStream.writeShort(value, this.byteOrder); } /** * Write lower pair of bytes of an integer value into the session stream as a * short value. * * @param value an integer value which lower pair of bytes will be written * into * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Short(final int value) throws IOException { assertNotEnded(); if (this.processCommands) { _writeShort(value); } return this; } /** * Write codes of chars as 16 bit values into the stream. * * @param str the string which chars will be written, must not be null * @return the DSL session * @throws IOException it will be thrown for transport errors * @since 1.1 */ public JBBPOut Short(final String str) throws IOException { assertNotEnded(); if (this.processCommands) { for (int i = 0; i < str.length(); i++) { _writeShort(str.charAt(i)); } } return this; } /** * Write codes of chars as 16 bit values into the stream. * * @param str the string which chars will be written, must not be null * @param bitOrder the bit outOrder * @return the DSL session * @throws IOException it will be thrown for transport errors * @since 1.1 */ public JBBPOut Short(final String str, final JBBPBitOrder bitOrder) throws IOException { assertNotEnded(); if (this.processCommands) { final boolean msb0 = bitOrder == JBBPBitOrder.MSB0; for (int i = 0; i < str.length(); i++) { short value = (short) str.charAt(i); if (msb0) { value = (short) JBBPFieldShort.reverseBits(value); } _writeShort(value); } } return this; } /** * Write short values from an array * * @param value a short value array which values should be written into, it * must not be null * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Short(final short[] value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final short v : value) { this._writeShort(v); } } return this; } /** * Write short values from a char array * * @param value a char array which values should be written into, it * must not be null * @return the DSL session * @throws IOException it will be thrown for transport errors * @since 1.3 */ public JBBPOut Short(final char[] value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final char v : value) { this._writeShort(v); } } return this; } /** * Write lower pair of bytes of each integer value from an integer array into * the session stream as a short value. * * @param value an integer array which values will be written into * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Short(final int... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final int v : value) { this._writeShort(v); } } return this; } /** * Inside auxiliary method to write an integer value into the session stream * without extra checking. * * @param value an integer value to be written into * @throws IOException it will be thrown for transport errors */ private void _writeInt(final int value) throws IOException { this.outStream.writeInt(value, this.byteOrder); } /** * Write an integer value into the session stream. * * @param value an integer value to be written into * @return the DSl session * @throws IOException it will be thrown for transport errors */ public JBBPOut Int(final int value) throws IOException { assertNotEnded(); if (this.processCommands) { _writeInt(value); } return this; } /** * Write each integer value from an integer array into the session stream. * * @param value an integer array which values should be written into * @return the DSl session * @throws IOException it will be thrown for transport errors */ public JBBPOut Int(final int... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final int v : value) { _writeInt(v); } } return this; } @Override protected void onFieldUInt(final Object obj, final Field field, final Bin annotation, final int value) { final JBBPByteOrder old = this.byteOrder; try { this.byteOrder = annotation.byteOrder(); this.UInt(value); } catch (IOException ex) { throw new JBBPIOException("Can't write unsigned int value", ex); } finally { this.byteOrder = old; } } /** * Write a float value array as integer bits into the stream. * * @param value a float array which values will be written into * @return the DSL session * @throws IOException it will be thrown for transport errors * @see Float#floatToIntBits(float) * @since 1.4.0 */ public JBBPOut Float(final float... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final float f : value) { _writeFloat(f); } } return this; } /** * Inside auxiliary method to write a long value into the session stream * without checking. * * @param value a long value to be written into * @throws IOException it will be thrown for transport errors */ private void _writeLong(final long value) throws IOException { this.outStream.writeLong(value, this.byteOrder); } /** * Inside auxiliary method to write a double value into the session stream * without checking. * * @param value a double value to be written into * @throws IOException it will be thrown for transport errors * @since 1.4.0 */ private void _writeDouble(final double value) throws IOException { this.outStream.writeDouble(value, this.byteOrder); } /** * Inside auxiliary method to write a float value into the session stream * without checking. * * @param value a float value to be written into * @throws IOException it will be thrown for transport errors * @since 1.4.0 */ private void _writeFloat(final float value) throws IOException { this.outStream.writeFloat(value, this.byteOrder); } /** * Write a long value into the session stream. * * @param value a long value to be written into * @return the DSL session * @throws IOException it will b e thrown for transport errors */ public JBBPOut Long(final long value) throws IOException { assertNotEnded(); if (this.processCommands) { _writeLong(value); } return this; } /** * Write a double value array as long bits into the stream. * * @param value a double array which values will be written into * @return the DSL session * @throws IOException it will be thrown for transport errors * @see Double#doubleToLongBits(double) * @since 1.4.0 */ public JBBPOut Double(final double... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final double d : value) { _writeDouble(d); } } return this; } /** * Reset the byte counter and the inside bit buffer of the output stream. it * is useful to align command because the command makes alignment for * the counter. * * @return the DSL context * @see JBBPOut#Align() * @see JBBPOut#Align(int) */ public JBBPOut ResetCounter() { assertNotEnded(); if (this.processCommands) { this.outStream.resetCounter(); } return this; } /** * Write each long value from a long value array into the session stream. * * @param value a long value array which values will be written into * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Long(final long... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final long l : value) { _writeLong(l); } } return this; } /** * Output data externally. * * @param processor a processor which will get the stream to write data, must * not be null * @param args optional arguments to be provided to the processor * @return the DSL context * @throws IOException it will be thrown for transport errors * @throws NullPointerException it will be thrown for null as a processor */ public JBBPOut Var(final JBBPOutVarProcessor processor, final Object... args) throws IOException { assertNotEnded(); JBBPUtils.assertNotNull(processor, "Var processor must not be null"); if (this.processCommands) { this.processCommands = processor.processVarOut(this, this.outStream, args); } return this; } /** * Flush inside buffers into the stream. Be careful with bit operations * because the operation will flush the inside bit buffer. * * @return the DSL session * @throws IOException it will be thrown for transport errors */ public JBBPOut Flush() throws IOException { assertNotEnded(); if (this.processCommands) { this.outStream.flush(); } return this; } /** * Flush the stream and end the session. * * @return if the session output stream is based on a byte array output stream * then the stream will be returned, null otherwise * @throws IOException it will be thrown for transport errors. */ public ByteArrayOutputStream End() throws IOException { assertNotEnded(); this.ended = true; this.outStream.flush(); return this.originalByteArrayOutStream; } /** * get the current byte counter value for the underlying stream. it has * appropriate value only if it was not reset. * * @return the current byte counter for the underlying stream. */ public long getByteCounter() { return this.outStream.getCounter(); } /** * Assert that the session has not ended. * * @throws IllegalStateException if the session has been ended */ protected void assertNotEnded() { if (this.ended) { throw new IllegalStateException(JBBPOut.class.getSimpleName() + " has been ended"); } } /** * Save fields of an object marked by Bin annotation. Fields will be ordered * through {@link Bin#order()} field, NB! By default Java doesn't keep field * outOrder. Ordered fields of class will be saved into internal cache for speed * but the cache can be reset through {@link JBBPMapper#clearFieldCache()} * Warning! it doesn't affect byte order provided in Bin annotations of object. * * @param object an object to be saved into stream, must not be null * @return the context * @throws IOException it will be thrown for any transport error * @see JBBPMapper#clearFieldCache() * @see #BinForceByteOrder(Object) * @see Bin * @since 1.1 */ public JBBPOut Bin(final Object object) throws IOException { return this.Bin(object, null, null, null); } /** * Save fields of an object marked by Bin annotation. Fields will be ordered * through {@link Bin#order()} field, NB! By default Java doesn't keep field * outOrder. Ordered fields of class will be saved into internal cache for speed * but the cache can be reset through {@link JBBPMapper#clearFieldCache()} * Warning! it doesn't affect byte order provided in Bin annotations of object. * * @param object an object to be saved into stream, must not be null * @param binFieldFilter filter to exclude some fields from process, can be null * @return the context * @throws IOException it will be thrown for any transport error * @see JBBPMapper#clearFieldCache() * @see #BinForceByteOrder(Object) * @see Bin * @since 2.0.4 */ public JBBPOut Bin(final Object object, final BinFieldFilter binFieldFilter) throws IOException { return this.Bin(object, null, null, binFieldFilter); } /** * Save fields of an object marked by Bin annotation. Fields will be ordered * through {@link Bin#order()} field, NB! By default Java doesn't keep field * outOrder. Ordered fields of class will be saved into internal cache for speed * but the cache can be reset through {@link JBBPMapper#clearFieldCache()} * Warning! it doesn't affect byte order provided in Bin annotations of object. * * @param object an object to be saved into stream, must not be null * @param customFieldWriter a custom field writer to be used for saving of * custom fields of the object, it can be null * @return the context * @see JBBPMapper#clearFieldCache() * @see Bin * @see #BinForceByteOrder(Object, JBBPCustomFieldWriter) * @since 1.1 */ public JBBPOut Bin(final Object object, final JBBPCustomFieldWriter customFieldWriter) { return this.Bin(object, null, customFieldWriter); } /** * Save fields of an object marked by Bin annotation. Fields will be ordered * through {@link Bin#order()} field, NB! By default Java doesn't keep field * outOrder. Ordered fields of class will be saved into internal cache for speed * but the cache can be reset through {@link JBBPMapper#clearFieldCache()} * Warning! it doesn't affect byte order provided in Bin annotations of object. * * @param object an object to be saved into stream, must not be null * @param customFieldWriter a custom field writer to be used for saving of * custom fields of the object, it can be null * @param binFieldFilter filter to exclude fields from process, can be null * @return the context * @see JBBPMapper#clearFieldCache() * @see Bin * @see #BinForceByteOrder(Object, JBBPCustomFieldWriter) * @since 2.0.4 */ public JBBPOut Bin(final Object object, final JBBPCustomFieldWriter customFieldWriter, final BinFieldFilter binFieldFilter) { return this.Bin(object, null, customFieldWriter, binFieldFilter); } /** * Save fields of object but bin annotation wrapper can be provided to replace some annnotation field values in all field annotations. * * @param object an object to be saved into stream, must not be null * @param binAnnotationWrapper wrapper for all bin annotations, can be null * @param customFieldWriter a custom field writer to be used for saving of * custom fields of the object, it can be null * @return the context * @since 2.0.2 */ public JBBPOut Bin(final Object object, final BinAnnotationWrapper binAnnotationWrapper, final JBBPCustomFieldWriter customFieldWriter) { return this.Bin(object, binAnnotationWrapper, customFieldWriter, null); } /** * Save fields of object but bin annotation wrapper can be provided to replace some annnotation field values in all field annotations. * * @param object an object to be saved into stream, must not be null * @param binAnnotationWrapper wrapper for all bin annotations, can be null * @param customFieldWriter a custom field writer to be used for saving of * custom fields of the object, it can be null * @param binFieldFilter filter to exclude some fields from process, can be null * @return the context * @since 2.0.4 */ public JBBPOut Bin(final Object object, final BinAnnotationWrapper binAnnotationWrapper, final JBBPCustomFieldWriter customFieldWriter, final BinFieldFilter binFieldFilter) { if (this.processCommands) { this.processObject(object, null, binAnnotationWrapper, binFieldFilter, customFieldWriter); } return this; } /** * Works like {@link #Bin(Object)} but forcing override of all annotation byte order values by the JBBPOut byte order. * * @param object an object to be saved into stream, must not be null * @return the context * @throws IOException it will be thrown for any transport error * @see JBBPMapper#clearFieldCache() * @see Bin * @see Bin#byteOrder() * @since 2.0.2 */ public JBBPOut BinForceByteOrder(final Object object) throws IOException { return this.BinForceByteOrder(object, null); } /** * Works like {@link #Bin(Object, JBBPCustomFieldWriter)} but forcing override of all annotation byte order values by the context byte order. * * @param object an object to be saved into stream, must not be null * @param customFieldWriter a custom field writer to be used for saving of * custom fields of the object, it can be null * @return the context * @see #ByteOrder(JBBPByteOrder) * @see Bin#byteOrder() * @since 2.0.2 */ public JBBPOut BinForceByteOrder(final Object object, final JBBPCustomFieldWriter customFieldWriter) { return this .Bin(object, new BinAnnotationWrapper().setByteOrder(this.byteOrder), customFieldWriter); } @Override protected void onFieldFloat(final Object obj, final Field field, final Bin annotation, final float value) { final JBBPByteOrder old = this.byteOrder; try { this.byteOrder = annotation.byteOrder(); this.Float(value); } catch (IOException ex) { throw new JBBPIOException("Can't write float value", ex); } finally { this.byteOrder = old; } } @Override protected void onFieldString(final Object obj, final Field field, final Bin annotation, final String value) { final JBBPByteOrder old = this.byteOrder; try { this.byteOrder = annotation.byteOrder(); this.String(value); } catch (IOException ex) { throw new JBBPIOException("Can't write string value", ex); } finally { this.byteOrder = old; } } @Override protected void onFieldDouble(final Object obj, final Field field, final Bin annotation, final double value) { final JBBPByteOrder old = this.byteOrder; try { this.byteOrder = annotation.byteOrder(); this.Double(value); } catch (IOException ex) { throw new JBBPIOException("Can't write double value", ex); } finally { this.byteOrder = old; } } @Override protected void onFieldLong(final Object obj, final Field field, final Bin annotation, final long value) { final JBBPByteOrder old = this.byteOrder; try { this.byteOrder = annotation.byteOrder(); this.Long(value); } catch (IOException ex) { throw new JBBPIOException("Can't write long value", ex); } finally { this.byteOrder = old; } } @Override protected void onFieldInt(final Object obj, final Field field, final Bin annotation, final int value) { final JBBPByteOrder old = this.byteOrder; try { this.byteOrder = annotation.byteOrder(); this.Int(value); } catch (IOException ex) { throw new JBBPIOException("Can't write int value", ex); } finally { this.byteOrder = old; } } /** * Write each long value as unsigned integer one into the session stream. * * @param value a long value array which values should be written into * @return the DSl session * @throws IOException it will be thrown for transport errors * @since 2.0.4 */ public JBBPOut UInt(final long... value) throws IOException { assertNotEnded(); assertArrayNotNull(value); if (this.processCommands) { for (final long v : value) { _writeInt((int) v); } } return this; } @Override protected void onFieldShort(final Object obj, final Field field, final Bin annotation, final boolean signed, final int value) { final JBBPByteOrder old = this.byteOrder; try { this.byteOrder = annotation.byteOrder(); this.Short(value); } catch (IOException ex) { throw new JBBPIOException("Can't write short value", ex); } finally { this.byteOrder = old; } } @Override protected void onFieldByte(final Object obj, final Field field, final Bin annotation, final boolean signed, final int value) { try { this.Byte(value); } catch (IOException ex) { throw new JBBPIOException("Can't write byte value", ex); } } @Override protected void onFieldBool(final Object obj, final Field field, final Bin annotation, final boolean value) { try { this.Bool(value, annotation.bitOrder()); } catch (IOException ex) { throw new JBBPIOException("Can't write bool value", ex); } } @Override protected void onFieldBits(final Object obj, final Field field, final Bin annotation, final JBBPBitNumber bitNumber, final int value) { try { this.Bits(bitNumber, value); } catch (IOException ex) { throw new JBBPIOException("Can't write bit value", ex); } } @Override protected void onFieldCustom(final Object obj, final Field field, final Bin annotation, final Object customFieldProcessor, final Object value) { try { final JBBPCustomFieldWriter writer = (JBBPCustomFieldWriter) customFieldProcessor; writer.writeCustomField(this, this.outStream, obj, field, annotation, value); } catch (IOException ex) { throw new JBBPIOException("Can't write custom field", ex); } } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPOutVarProcessor.java000066400000000000000000000025271445000440600331120ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import java.io.IOException; /** * The Interface describes a class which can write some custom values into the context output stream (DSL command Var()). * * @since 1.0 */ public interface JBBPOutVarProcessor { /** * Process a DSL Var() command. * * @param context the DSL context, must not be null * @param outStream the output stream for the context, must not be null * @param args optional arguments, can be null * @return true is to continue processing of DSL commands, false skip all commands till the End() * @throws IOException it should be thrown for transport errors */ boolean processVarOut(JBBPOut context, JBBPBitOutputStream outStream, Object... args) throws IOException; } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/000077500000000000000000000000001445000440600273405ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/Bin.java000066400000000000000000000121641445000440600307170ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.mapper; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.io.JBBPCustomFieldWriter; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.utils.JBBPTextWriter; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * The annotation describes a field in a class which can be mapped and loaded * from parsed a JBBP structure, also it can be used for whole class but in the * case be careful and use default name and path values. The Class is not thread safe. * * Since 2.0.0 was removed prefix 'out' for fields which contained it. * * @since 1.0 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.TYPE}) @Inherited public @interface Bin { /** * Name of a structure element to be mapped to the field. * * @return string name, if it is empty then field name will be used * as name */ String name() default ""; /** * Structure path inside to be mapped to the field. * * @return string path, if it is empty then the path is not used */ String path() default ""; /** * Custom type. It plays role only if {@link #type()} is UNDEFINED * Keep attention! The value plays role only in build of script and it is not used in mapping! * * @return type of the field, if empty then undefined * @see com.igormaznitsa.jbbp.utils.JBBPDslBuilder * @since 2.0.0 */ String customType() default ""; /** * Expression to represent array size of the field. * Keep attention! The value plays role only in build of script and it is not used in mapping! * * @return array size of the field, if empty then not defined * @see com.igormaznitsa.jbbp.utils.JBBPDslBuilder * @since 2.0.0 */ String arraySizeExpr() default ""; /** * Type of mapped parsed structure element. * * @return the mapped parsed structure element type * @see BinType */ BinType type() default BinType.UNDEFINED; /** * Order of bits for the field. * NB! The Value has strong effect! A Numeric field content will be inverted during mapping or saving of the field if the value is MSB0 (Java uses standard LSB0) * * @return LSB0 or MSB0 outOrder, LSB0 by default * @see JBBPBitOrder */ JBBPBitOrder bitOrder() default JBBPBitOrder.LSB0; /** * The Flag shows that the field must be processed by a defined externally * custom field processors during loading and saving. * * @return true if the mapping field must be processed externally, false * otherwise * @see JBBPMapperCustomFieldProcessor * @see JBBPCustomFieldWriter */ boolean custom() default false; /** * Expression as extra part of type. It means take part in type[:extra] * Keep attention! The value plays role only in build of script and it is not used in mapping! * * @return extra value, if empty then undefined * @see com.igormaznitsa.jbbp.utils.JBBPDslBuilder * @since 2.0.0 */ String paramExpr() default ""; /** * The Value defines how many bytes are actual ones in the field, works for numeric field and arrays and allows make mapping to bit fields. * * @return the number of lower bits, by default 8 bits * @see JBBPTextWriter#Bin(java.lang.Object...) * @see JBBPOut#Bin(java.lang.Object) * @see JBBPOut#Bin(java.lang.Object, com.igormaznitsa.jbbp.io.JBBPCustomFieldWriter) * @see JBBPBitNumber * @since 2.0.0 */ JBBPBitNumber bitNumber() default JBBPBitNumber.BITS_8; /** * Byte order to be used for operations. * * @return order of bytes to be used * @see JBBPByteOrder * @since 2.0.0 */ JBBPByteOrder byteOrder() default JBBPByteOrder.BIG_ENDIAN; /** * The Value defines the field order to show relative position in data stream. If -1then it is undefined. * * @return the outOrder of the field as number (the mapping will make ascending sorting) * @see JBBPTextWriter#Bin(java.lang.Object...) * @see JBBPOut#Bin(java.lang.Object) * @see JBBPOut#Bin(java.lang.Object, com.igormaznitsa.jbbp.io.JBBPCustomFieldWriter) * @since 2.0.0 */ int order() default -1; /** * Just either description of the field or some remark. * * @return the comment as String * @see JBBPTextWriter#Bin(java.lang.Object...) * @since 1.1 */ String comment() default ""; } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/BinFieldFilter.java000066400000000000000000000022711445000440600330270ustar00rootroot00000000000000/* * Copyright 2022 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.mapper; import java.lang.reflect.Field; /** * Filter allows cheeking Bin annotation and fields during mapping or writing operations. * * @see com.igormaznitsa.jbbp.io.JBBPOut * @see JBBPMapper#map * @see com.igormaznitsa.jbbp.io.JBBPOut#Bin * @since 2.0.4 */ public interface BinFieldFilter { /** * Check annotation and field that they allowed. * * @param annotation bin annotation, must not be null * @param field marked field, can be null * @return true if field allowed, false otherwise */ boolean isAllowed(Bin annotation, Field field); } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/BinType.java000066400000000000000000000203621445000440600315600ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.mapper; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBit; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayDouble; import com.igormaznitsa.jbbp.model.JBBPFieldArrayFloat; import com.igormaznitsa.jbbp.model.JBBPFieldArrayInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong; import com.igormaznitsa.jbbp.model.JBBPFieldArrayShort; import com.igormaznitsa.jbbp.model.JBBPFieldArrayString; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUShort; import com.igormaznitsa.jbbp.model.JBBPFieldBit; import com.igormaznitsa.jbbp.model.JBBPFieldBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldByte; import com.igormaznitsa.jbbp.model.JBBPFieldDouble; import com.igormaznitsa.jbbp.model.JBBPFieldFloat; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldShort; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.model.JBBPFieldUByte; import com.igormaznitsa.jbbp.model.JBBPFieldUInt; import com.igormaznitsa.jbbp.model.JBBPFieldUShort; /** * The Enum contains all supported bin types for mapper. * * @see JBBPMapper * @since 1.0 */ public enum BinType { /** * Undefined type, the mapper will be looking for compatible parsed field with * the mapping field. */ UNDEFINED(null, false), /** * A Mapping field will be mapped to a parsed bit field. */ BIT(JBBPFieldBit.class, false), /** * A Mapping field will be mapped to a parsed boolean field. */ BOOL(JBBPFieldBoolean.class, false), /** * A Mapping field will be mapped to a parsed byte field. */ BYTE(JBBPFieldByte.class, false), /** * A Mapping field will be mapped to a parsed unsigned byte field. */ UBYTE(JBBPFieldUByte.class, false), /** * A Mapping field will be mapped to a parsed short field. */ SHORT(JBBPFieldShort.class, false), /** * A Mapping field will be mapped to a parsed unsigned short field. */ USHORT(JBBPFieldUShort.class, false), /** * A Mapping field will be mapped to a parsed integer field. */ INT(JBBPFieldInt.class, false), /** * A Mapping field will be mapped to a parsed unsigned integer field. * * @since 2.0.4 */ UINT(JBBPFieldUInt.class, false), /** * A Mapping field will be mapped to a parsed double field. * * @since 1.4.0 */ DOUBLE(JBBPFieldDouble.class, false), /** * A Mapping field will be mapped to a parsed float field. * * @since 1.4.0 */ FLOAT(JBBPFieldFloat.class, false), /** * A Mapping field will be mapped to a parsed String field. * * @since 1.4.0 */ STRING(JBBPFieldString.class, false), /** * A Mapping field will be mapped to a parsed long field. */ LONG(JBBPFieldLong.class, false), /** * A Mapping field will be mapped to a parsed bit array field. */ BIT_ARRAY(JBBPFieldArrayBit.class, true), /** * A Mapping field will be mapped to a parsed boolean array field. */ BOOL_ARRAY(JBBPFieldArrayBoolean.class, true), /** * A Mapping field will be mapped to a parsed byte array field. */ BYTE_ARRAY(JBBPFieldArrayByte.class, true), /** * A Mapping field will be mapped to a parsed unsigned byte array field. */ UBYTE_ARRAY(JBBPFieldArrayUByte.class, true), /** * A Mapping field will be mapped to a parsed short array field. */ SHORT_ARRAY(JBBPFieldArrayShort.class, true), /** * A Mapping field will be mapped to a parsed unsigned short array field. */ USHORT_ARRAY(JBBPFieldArrayUShort.class, true), /** * A Mapping field will be mapped to a parsed integer array field. */ INT_ARRAY(JBBPFieldArrayInt.class, true), /** * A Mapping field will be mapped to a parsed unsigned integer array field. */ UINT_ARRAY(JBBPFieldArrayUInt.class, true), /** * A Mapping field will be mapped to a parsed long array field. */ LONG_ARRAY(JBBPFieldArrayLong.class, true), /** * A Mapping field will be mapped to a parsed float array field. * * @since 1.4.0 */ FLOAT_ARRAY(JBBPFieldArrayFloat.class, true), /** * A Mapping field will be mapped to a parsed String array field. * * @since 1.4.0 */ STRING_ARRAY(JBBPFieldArrayString.class, true), /** * A Mapping field will be mapped to a parsed double array field. * * @since 1.4.0 */ DOUBLE_ARRAY(JBBPFieldArrayDouble.class, true), /** * A Mapping field will be mapped to a parsed structure field. */ STRUCT(JBBPFieldStruct.class, false), /** * A Mapping field will be mapped to a parsed structure array field. */ STRUCT_ARRAY(JBBPFieldArrayStruct.class, true); /** * The field class for the value. */ private final Class fieldClass; /** * The Flag shows that the type describes an array. */ private final boolean arrayFlag; /** * The Field class for the value. * * @param fieldClass class for the type */ BinType(final Class fieldClass, final boolean array) { this.fieldClass = fieldClass; this.arrayFlag = array; } /** * Find compatible type for a class. * * @param fieldClazz the field class to find compatible type, must not be null * @return found compatible field type or null if not found */ public static BinType findCompatible(final Class fieldClazz) { BinType result; if (fieldClazz.isArray()) { final Class type = fieldClazz.getComponentType(); if (type.isPrimitive()) { if (type == byte.class) { result = BYTE_ARRAY; } else if (type == char.class) { result = USHORT_ARRAY; } else if (type == boolean.class) { result = BOOL_ARRAY; } else if (type == short.class) { result = SHORT_ARRAY; } else if (type == int.class) { result = INT_ARRAY; } else if (type == long.class) { result = LONG_ARRAY; } else if (type == float.class) { result = FLOAT_ARRAY; } else if (type == double.class) { result = DOUBLE_ARRAY; } else { result = null; } } else { result = type == String.class ? STRING_ARRAY : STRUCT_ARRAY; } } else if (fieldClazz.isPrimitive()) { if (fieldClazz == byte.class) { result = BYTE; } else if (fieldClazz == char.class) { result = USHORT; } else if (fieldClazz == boolean.class) { result = BOOL; } else if (fieldClazz == short.class) { result = SHORT; } else if (fieldClazz == int.class) { result = INT; } else if (fieldClazz == long.class) { result = LONG; } else if (fieldClazz == float.class) { result = FLOAT; } else if (fieldClazz == double.class) { result = DOUBLE; } else { result = null; } } else { if (fieldClazz == String.class) { result = STRING; } else { result = STRUCT; } } return result; } /** * Check that the type describes an array * * @return true if the type is an array, false otherwise */ public boolean isArray() { return this.arrayFlag; } /** * Get The Field class. * * @return the field class for the value, it can be null */ public Class getFieldClass() { return this.fieldClass; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/JBBPMapper.java000066400000000000000000000522651445000440600320770ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.mapper; import com.igormaznitsa.jbbp.exceptions.JBBPMapperException; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.model.BitEntity; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.utils.Function; import com.igormaznitsa.jbbp.utils.JBBPUtils; import com.igormaznitsa.jbbp.utils.NullableTriple; import com.igormaznitsa.jbbp.utils.ReflectUtils; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * The Class processes mapping of a parsed binary data to class fields. * * @since 1.0 */ public final class JBBPMapper { public static final String MAKE_CLASS_INSTANCE_METHOD_NAME = "newInstance"; /** * Flag to not throw exception if structure doesn't have value for a field. * * @since 1.1 */ public static final int FLAG_IGNORE_MISSING_VALUES = 1; private static final Map, List> CACHED_FIELDS = new ConcurrentHashMap<>(); /** * Create a class instance, map binary data of a structure for its path to its * fields and return the instance. * * @param the mapping class type * @param root a parsed structure to be used as the root, must not be null * @param structPath the path of a structure inside of the root to be mapped * to the class, must not be null * @param instance object to be filled by values, must not be null * @param instantiators functions to produce class instance by request, must not be null * @return the created and mapped instance of the mapping class * @throws JBBPMapperException for any error * @since 2.0.0 */ @SafeVarargs public static T map(final JBBPFieldStruct root, final String structPath, final T instance, final Function, Object>... instantiators) { return map(root, structPath, instance, null, instantiators); } /** * Create a class instance, map binary data of a structure for its path to its * fields and return the instance. * * @param the mapping class type * @param root a parsed structure to be used as the root, must not be null * @param structPath the path of a structure inside of the root to be mapped * to the class, must not be null * @param instance object to be filled by values, must not be null * @param flags special flags to tune mapping process * @param instantiators functions to produce class instance by request, * must not be null * @return the created and mapped instance of the mapping class * @throws JBBPMapperException for any error * @see #FLAG_IGNORE_MISSING_VALUES * @since 2.0.0 */ @SafeVarargs public static T map(final JBBPFieldStruct root, final String structPath, final T instance, final int flags, final Function, Object>... instantiators) { return map(root, structPath, instance, null, flags, instantiators); } /** * Create a class instance, map binary data of a structure for its path to its * fields and return the instance. * * @param the mapping class type * @param root a parsed structure to be used as the root, must not be null * @param structPath the path of a structure inside of the root to be mapped * to the class, must not be null * @param instance instance to be filled by values, must not be null * @param customFieldProcessor a custom field processor to provide custom * values, it can be null if there is not any mapping field desires the * processor * @param instantiators functions produce class instance by request, * must not be null * @return the created and mapped instance of the mapping class * @throws JBBPMapperException for any error * @since 2.0.0 */ @SafeVarargs public static T map(final JBBPFieldStruct root, final String structPath, final T instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final Function, Object>... instantiators) { return map(root, structPath, instance, customFieldProcessor, 0, instantiators); } /** * Create a class instance, map binary data of a structure for its path to its * fields and return the instance. * * @param the mapping class type * @param root a parsed structure to be used as the root, must not be null * @param structPath the path of a structure inside of the root to be mapped * to the class, must not be null * @param instance object to be filled by values, must not be null * @param customFieldProcessor a custom field processor to provide custom * values, it can be null if there is not any mapping field desires the * processor * @param flags special flags to tune mapping * @param instantiators functions produce class instance by request, * must not be null * @return the created and mapped instance of the mapping class * @throws JBBPMapperException for any error * @see #FLAG_IGNORE_MISSING_VALUES * @since 2.0.0 */ @SafeVarargs public static T map(final JBBPFieldStruct root, final String structPath, final T instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final int flags, final Function, Object>... instantiators) { JBBPUtils.assertNotNull(structPath, "Path must not be null"); final JBBPFieldStruct struct = root.findFieldForPathAndType(structPath, JBBPFieldStruct.class); if (struct == null) { throw new JBBPMapperException( "Can't find a structure field for its path [" + structPath + ']', null, instance.getClass(), null, null); } return map(struct, instance, customFieldProcessor, flags, instantiators); } /** * Create a class instance, map binary data of a structure to the instance and * return it. It will create a class instance through a hack method and its * constructor will not be called, thus use the method carefully. * * @param the mapping class type * @param root a parsed structure to be mapped to the class instance, must not * be null * @param instance object instance to be filled by values, must not be null * @param instantiators functions produce class instance by request, * must not be null * @return the created and mapped instance of the class * @throws JBBPMapperException for any error * @since 2.0.0 */ @SafeVarargs public static T map(final JBBPFieldStruct root, final T instance, final Function, Object>... instantiators) { return map(root, instance, null, instantiators); } /** * Create a class instance, map binary data of a structure to the instance and * return it. It will create a class instance through a hack method and its * constructor will not be called, thus use the method carefully. * * @param the mapping class type * @param root a parsed structure to be mapped to the class instance, must not * be null * @param instance the class to be instantiated and mapped, must not be * null * @param flags special flags to tune mapping process * @param instantiators functions produce class instance by request, * must not be null * @return the created and mapped instance of the class * @throws JBBPMapperException for any error * @see #FLAG_IGNORE_MISSING_VALUES * @since 2.0.0 */ @SafeVarargs public static T map(final JBBPFieldStruct root, final T instance, final int flags, final Function, Object>... instantiators) { return map(root, instance, null, flags, instantiators); } /** * Map a structure to a class instance. * * @param the mapping class type * @param rootStructure a structure to be mapped, must not be null * @param instance a class instance to be destination for map * operations, must not be null * @param customFieldProcessor a custom field processor to provide custom * values, it can be null if there is not any mapping field desires the * processor * @param instantiators functions to produce class instance by request, * must not be null * @return the processed class instance, the same which was the argument for * the method. * @throws JBBPMapperException for any error */ @SafeVarargs public static T map(final JBBPFieldStruct rootStructure, final T instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final Function, Object>... instantiators) { return map(rootStructure, instance, customFieldProcessor, 0, instantiators); } @SafeVarargs @SuppressWarnings("varargs") private static void processFieldOfMappedClass( final MappedFieldRecord record, final JBBPFieldStruct rootStructure, final Object instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final int flags, final BinFieldFilter binFieldFilter, final Function, Object>... instantiators ) { if (record.binAnnotation.custom()) { JBBPUtils.assertNotNull(customFieldProcessor, "There is a custom mapping field, in the case you must provide a custom mapping field processor"); final Object value = customFieldProcessor .prepareObjectForMapping(rootStructure, record.binAnnotation, record.mappingField); MappedFieldRecord.setFieldValue(instance, record.setter, record.mappingField, null, value); } else { final JBBPAbstractField binField; if (record.fieldPath.length() == 0) { binField = record.fieldName.length() == 0 ? rootStructure.findFieldForType(record.fieldType.getFieldClass()) : rootStructure .findFieldForNameAndType(record.fieldName, record.fieldType.getFieldClass()); } else { binField = rootStructure .findFieldForPathAndType(record.fieldPath, record.fieldType.getFieldClass()); } if (binField == null) { if ((flags & FLAG_IGNORE_MISSING_VALUES) != 0) { return; } throw new JBBPMapperException( "Can't find value for mapping field [" + record.mappingField + ']', null, record.mappingClass, record.mappingField, null); } if (record.bitWideField && record.mappedBitNumber != JBBPBitNumber.BITS_8 && ((BitEntity) binField).getBitWidth() != record.mappedBitNumber) { throw new JBBPMapperException( "Can't map mapping field because wrong field bitness [" + record.mappedBitNumber + "!=" + ((BitEntity) binField).getBitWidth().getBitNumber() + ']', null, record.mappingClass, record.mappingField, null); } record.proc.apply(record, rootStructure, instance, customFieldProcessor, binField, flags, binFieldFilter, instantiators); } } /** * Map a structure to a class instance. * * @param the mapping class type * @param rootStructure a structure to be mapped, must not be null * @param instance a class instance to be destination for map * operations, must not be null * @param customFieldProcessor a custom field processor to provide custom * values, it can be null if there is not any mapping field desires the * processor * @param flags special flags for mapping process * @param instantiators functions to produce class instance by request, must * not be null * @return the processed class instance, the same which was the argument for * the method. * @throws JBBPMapperException for any error * @see #FLAG_IGNORE_MISSING_VALUES * @since 1.1 */ @SafeVarargs @SuppressWarnings("varargs") public static T map(final JBBPFieldStruct rootStructure, final T instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final int flags, final Function, Object>... instantiators) { return map(rootStructure, instance, customFieldProcessor, flags, null, instantiators); } /** * Map a structure to a class instance. * * @param the mapping class type * @param rootStructure a structure to be mapped, must not be null * @param instance a class instance to be destination for map * operations, must not be null * @param customFieldProcessor a custom field processor to provide custom * values, it can be null if there is not any mapping field desires the * processor * @param flags special flags for mapping process * @param binFieldFilter filter allows to exclude some fields from process, can be null * @param instantiators functions to produce class instance by request, must * not be null * @return the processed class instance, the same which was the argument for * the method. * @throws JBBPMapperException for any error * @see #FLAG_IGNORE_MISSING_VALUES * @since 2.0.4 */ @SafeVarargs @SuppressWarnings("varargs") public static T map(final JBBPFieldStruct rootStructure, final T instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final int flags, final BinFieldFilter binFieldFilter, final Function, Object>... instantiators) { JBBPUtils.assertNotNull(rootStructure, "The Root structure must not be null"); JBBPUtils.assertNotNull(instance, "The Mapping class instance must not be null"); // Don't use forEach() for Android compatibility! for (final MappedFieldRecord record : findAffectedFields(instance, binFieldFilter)) { processFieldOfMappedClass( record, rootStructure, instance, customFieldProcessor, flags, binFieldFilter, instantiators ); } return instance; } /** * Get current number of classes which fields are cached in internal field cache. * * @return number of classes * @since 2.0.0 */ public static int getFieldCacheSize() { return CACHED_FIELDS.size(); } /** * Clear internal class field cache. * * @since 2.0.0 */ public static void clearFieldCache() { CACHED_FIELDS.clear(); } public static List findAffectedFields(final Object instance, final BinFieldFilter binFieldFilter) { final Class mappingClass = instance.getClass(); List result = CACHED_FIELDS.get(mappingClass); if (result == null) { result = new ArrayList<>(); final Bin defaultAnno = mappingClass.getAnnotation(Bin.class); // make chain of ancestors till java.lang.Object final List> listOfClassHierarchy = new ArrayList<>(); Class current = instance.getClass(); while (current != null) { final String packageName = current.getPackage().getName(); if (packageName.startsWith("java.") || packageName.startsWith("javax.") || packageName.startsWith("android.") ) { break; } listOfClassHierarchy.add(current); current = current.getSuperclass(); } for (final Class processingClazz : listOfClassHierarchy) { for (Field mappingField : processingClazz.getDeclaredFields()) { final int fieldModifiers = mappingField.getModifiers(); final Bin fieldAnno = mappingField.getAnnotation(Bin.class); final Bin mappedAnno; if ((fieldAnno == null && defaultAnno == null) || mappingField.getName().indexOf('$') >= 0) { continue; } mappedAnno = fieldAnno == null ? defaultAnno : fieldAnno; if (fieldAnno == null) { if (Modifier.isTransient(fieldModifiers) || Modifier.isStatic(fieldModifiers) || Modifier.isFinal(fieldModifiers)) { continue; } } else { final String disallowedModifier; if (Modifier.isStatic(fieldModifiers)) { disallowedModifier = "STATIC"; } else if (Modifier.isFinal(fieldModifiers)) { disallowedModifier = "FINAL"; } else { disallowedModifier = null; } if (disallowedModifier != null) { throw new JBBPMapperException("Detected @Bin marked " + disallowedModifier + " field", null, processingClazz, mappingField, null); } } final NullableTriple auxMethods = findAuxFieldMethods(processingClazz, mappingField); final Method fieldGenerator = auxMethods.getA(); final Method fieldGetter = auxMethods.getB(); final Method fieldSetter = auxMethods.getC(); if (mappingField.getType().isPrimitive() && fieldSetter == null && Modifier.isPrivate(mappingField.getModifiers())) { throw new JBBPMapperException( "Detected private primitive field, mapping requires setter", null, processingClazz, mappingField, null); } if (fieldGetter == null && fieldGenerator == null && !ReflectUtils.isPotentiallyAccessibleField(mappingField)) { mappingField = ReflectUtils.makeAccessible(mappingField); } try { result.add(new MappedFieldRecord(mappingField, fieldGenerator, fieldSetter, fieldGetter, mappingClass, mappedAnno)); } catch (IllegalStateException ex) { throw new JBBPMapperException(ex.getMessage(), null, mappingClass, mappingField, ex); } } } Collections.sort(result); CACHED_FIELDS.put(mappingClass, result); } if (binFieldFilter != null) { final List filteredRecords = new ArrayList<>(); for (final MappedFieldRecord r : result) { if (binFieldFilter.isAllowed(r.binAnnotation, r.mappingField)) { filteredRecords.add(r); } } result = filteredRecords; } return result; } private static NullableTriple findAuxFieldMethods(final Class klazz, final Field field) { final String lowerCasedFieldName = field.getName().toLowerCase(Locale.ENGLISH); final String generatorName = "make" + lowerCasedFieldName; final String getterName = "get" + lowerCasedFieldName; final String setterName = "set" + lowerCasedFieldName; Method generator = null; Method setter = null; Method getter = null; for (final Method m : klazz.getMethods()) { final Class[] args = m.getParameterTypes(); final String lcMethodName = m.getName().toLowerCase(Locale.ENGLISH); if (!Modifier.isPublic(m.getModifiers()) || Modifier.isStatic(m.getModifiers())) { continue; } if (args.length == 0) { if (generator == null && lcMethodName.equals(generatorName) ) { generator = m; } if (getter == null && lcMethodName.equals(getterName)) { getter = m; } } if (args.length == 1 && setter == null && lcMethodName.equals(setterName) && field.getType().isAssignableFrom(args[0])) { setter = m; } if (generator != null && setter != null && getter != null) { break; } } return new NullableTriple<>(generator, getter, setter); } } JBBPMapperCustomFieldProcessor.java000066400000000000000000000030341445000440600360450ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.mapper; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import java.lang.reflect.Field; /** * The Interface describes a processor which will be called during mapping of fields of a parsed binary structure to a mapping class instance, if they marked for custom processing. * * @since 1.0 */ public interface JBBPMapperCustomFieldProcessor { /** * Prepare an object to be set to a custom mapping field. * * @param parsedBlock the structure root of parsed binary packet, must not be null * @param annotation the annotation for mapping field, must not be null * @param field the mapping field in a mapping class, must not be null * @return an object which will be set to the field in a mapping class instance, it can be null for non-primitive fields */ Object prepareObjectForMapping(final JBBPFieldStruct parsedBlock, final Bin annotation, final Field field); } MappedFieldRecord.java000066400000000000000000000665201445000440600334460ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/mapperpackage com.igormaznitsa.jbbp.mapper; import static com.igormaznitsa.jbbp.mapper.JBBPMapper.MAKE_CLASS_INSTANCE_METHOD_NAME; import com.igormaznitsa.jbbp.exceptions.JBBPMapperException; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractArrayField; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBit; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong; import com.igormaznitsa.jbbp.model.JBBPFieldArrayShort; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUShort; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.model.JBBPNumericField; import com.igormaznitsa.jbbp.utils.Function; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; public final class MappedFieldRecord implements Comparable { private static final Function, Object> STATIC_MAKE_CLASS_INSTANCE_INSTANTIATOR = (Class klazz) -> { Class currentClass = klazz; Object result = null; boolean find; do { try { final Method method = currentClass.getMethod(MAKE_CLASS_INSTANCE_METHOD_NAME, Class.class); if (Modifier.isStatic(method.getModifiers())) { result = method.invoke(null, klazz); } } catch (IllegalAccessException ex) { throw new RuntimeException(String .format("Can't get access to static method %s(%ss) in %s", MAKE_CLASS_INSTANCE_METHOD_NAME, klazz, currentClass), ex); } catch (InvocationTargetException ex) { throw new RuntimeException(String .format("Can't call static method %s(%s) in %s", MAKE_CLASS_INSTANCE_METHOD_NAME, klazz, currentClass), ex); } catch (NoSuchMethodException ex) { // do nothing! } if (result == null) { if (currentClass.isLocalClass()) { currentClass = currentClass.getEnclosingClass(); find = currentClass != null; } else { find = false; } } else { find = false; } } while (find); return result; }; private static final Function, Object> DEFAULT_CONSTRUCTOR_INSTANTIATOR = (Class aClass) -> { try { if (!aClass.isLocalClass() || Modifier.isStatic(aClass.getModifiers())) { return aClass.getConstructor().newInstance(); } else { return null; } } catch (NoSuchMethodException ex) { return null; } catch (InvocationTargetException ex) { throw new RuntimeException( String.format("Error during default constructor call, class %s", aClass), ex); } catch (IllegalAccessException ex) { throw new RuntimeException( String.format("Can't get access to default constructor , class %s", aClass), ex); } catch (InstantiationException ex) { throw new RuntimeException(String.format("Can't make instance of class %s", aClass), ex); } }; private static final FieldProcessor PROC_ARRAYS = (record, rootStructure, instance, customFieldProcessor, binField, flags, binFieldFilter, instantiators) -> { if (binField instanceof JBBPAbstractArrayField) { if (binField instanceof JBBPFieldArrayStruct) { // structure final JBBPFieldArrayStruct structArray = (JBBPFieldArrayStruct) binField; final Class componentType = record.mappingField.getType().getComponentType(); Object valueArray = getFieldValue(instance, record.getter, record.mappingField); valueArray = valueArray == null ? Array.newInstance(componentType, structArray.size()) : valueArray; if (Array.getLength(valueArray) != structArray.size()) { throw new JBBPMapperException( "Can't map an array field for different expected size [" + Array.getLength(valueArray) + "!=" + structArray.size() + ']', binField, record.mappingClass, record.mappingField, null); } for (int i = 0; i < structArray.size(); i++) { final Object curInstance = Array.get(valueArray, i); if (curInstance == null) { Array.set(valueArray, i, JBBPMapper.map(structArray.getElementAt(i), tryMakeInstance(componentType, binField, instance, record.mappingField, instantiators), customFieldProcessor, 0, binFieldFilter, instantiators)); } else { Array.set(valueArray, i, JBBPMapper.map(structArray.getElementAt(i), curInstance, customFieldProcessor, 0, binFieldFilter)); } } setFieldValue(instance, record.setter, record.mappingField, binField, valueArray); } else { // primitive mapArrayField(instance, record.setter, record.mappingField, (JBBPAbstractArrayField) binField, record.binAnnotation.bitOrder() == JBBPBitOrder.MSB0); } } else { throw new JBBPMapperException("Can't map a non-array value to an array mapping field", binField, record.mappingClass, record.mappingField, null); } }; private static final FieldProcessor PROC_NUM = (record, rootStructure, instance, customFieldProcessor, binField, flags, binFieldFilter, instantiators) -> { if (binField instanceof JBBPNumericField) { mapNumericField(instance, record.setter, record.mappingField, (JBBPNumericField) binField, record.binAnnotation.bitOrder() == JBBPBitOrder.MSB0); } else if (binField instanceof JBBPFieldString) { if (record.mappingField.getType().isPrimitive()) { throw new JBBPMapperException("Can't map string to a primitive mapping field", binField, record.mappingClass, record.mappingField, null); } else { setFieldValue(instance, record.setter, record.mappingField, binField, ((JBBPFieldString) binField).getAsString()); } } else if (binField instanceof JBBPFieldStruct) { if (record.mappingField.getType().isPrimitive()) { throw new JBBPMapperException("Can't map structure to a primitive mapping field", binField, record.mappingClass, record.mappingField, null); } else { final Object curValue = getFieldValue(instance, record.getter, record.mappingField); if (curValue == null) { if (record.instanceMaker == null) { setFieldValue(instance, record.setter, record.mappingField, binField, JBBPMapper .map((JBBPFieldStruct) binField, tryMakeInstance(record.mappingField.getType(), binField, instance, record.mappingField, instantiators), customFieldProcessor, 0, binFieldFilter)); } else { try { JBBPMapper.map((JBBPFieldStruct) binField, record.instanceMaker.invoke(instance)); } catch (Exception ex) { throw new JBBPMapperException( "Can't map field which member generated by instance", binField, record.mappingClass, record.mappingField, ex); } } } else { setFieldValue(instance, record.setter, record.mappingField, binField, JBBPMapper.map((JBBPFieldStruct) binField, curValue, customFieldProcessor)); } } } else { boolean processed = false; if (record.mappingField.getType() == String.class && binField instanceof JBBPAbstractArrayField) { final String convertedValue = convertFieldValueToString((JBBPAbstractArrayField) binField); if (convertedValue != null) { setFieldValue(instance, record.setter, record.mappingField, binField, convertedValue); processed = true; } } if (!processed) { throw new JBBPMapperException("Can't map a field for its value incompatibility", binField, record.mappingClass, record.mappingField, null); } } }; public final Field mappingField; public final Class mappingClass; public final Method setter; public final Method getter; public final Method instanceMaker; public final Bin binAnnotation; public final boolean bitWideField; public final String fieldName; public final String fieldPath; public final JBBPBitNumber mappedBitNumber; public final BinType fieldType; public final FieldProcessor proc; MappedFieldRecord(final Field mappingField, final Method instanceMaker, final Method setter, final Method getter, final Class mappingClass, final Bin binAnnotation) { this.instanceMaker = instanceMaker; this.setter = setter; this.getter = getter; this.mappingField = mappingField; this.mappingClass = mappingClass; this.binAnnotation = binAnnotation; this.mappedBitNumber = binAnnotation.bitNumber(); if (binAnnotation.type() == BinType.UNDEFINED) { BinType compatibleBinType = BinType.findCompatible(mappingField.getType()); if (compatibleBinType == null) { throw new IllegalStateException("Can't find compatible mapped type for field"); } else if (this.mappedBitNumber.getBitNumber() < 8 && !(compatibleBinType == BinType.STRUCT || compatibleBinType == BinType.STRUCT_ARRAY)) { compatibleBinType = compatibleBinType.isArray() ? BinType.BIT_ARRAY : BinType.BIT; } this.fieldType = compatibleBinType; } else { this.fieldType = binAnnotation.type(); } this.bitWideField = this.fieldType == BinType.BIT || fieldType == BinType.BIT_ARRAY; this.fieldName = binAnnotation.name().length() == 0 ? mappingField.getName() : binAnnotation.name(); this.fieldPath = binAnnotation.path(); if (this.mappingField.getType().isArray()) { this.proc = PROC_ARRAYS; } else { this.proc = PROC_NUM; } } /** * Map a parsed array to an array field in mapping class. * * @param mappingClassInstance a mapping class instance, must not be null * @param setter detected setter for the field, can be null * @param mappingField a field in the mapping class to be set, must not be * null * @param arrayField a binary parsed array field, must not be null * @param invertBitOrder flag shows that values of an array must be bit * reversed before set */ private static void mapArrayField(final Object mappingClassInstance, final Method setter, final Field mappingField, final JBBPAbstractArrayField arrayField, final boolean invertBitOrder) { try { final Object value; if (arrayField instanceof JBBPFieldArrayLong && mappingField.getType().getComponentType() == double.class) { final long[] longArray = (long[]) arrayField.getValueArrayAsObject(invertBitOrder); final double[] doubleArray = new double[longArray.length]; for (int i = 0; i < longArray.length; i++) { doubleArray[i] = Double.longBitsToDouble(longArray[i]); } value = doubleArray; } else if (arrayField instanceof JBBPFieldArrayUInt && mappingField.getType().getComponentType() == double.class) { final long[] longArray = (long[]) arrayField.getValueArrayAsObject(invertBitOrder); final double[] doubleArray = new double[longArray.length]; for (int i = 0; i < longArray.length; i++) { doubleArray[i] = Double.longBitsToDouble(longArray[i]); } value = doubleArray; } else if (arrayField instanceof JBBPFieldArrayInt && mappingField.getType().getComponentType() == float.class) { final int[] intArray = (int[]) arrayField.getValueArrayAsObject(invertBitOrder); final float[] floatArray = new float[intArray.length]; for (int i = 0; i < intArray.length; i++) { floatArray[i] = Float.intBitsToFloat(intArray[i]); } value = floatArray; } else if (arrayField instanceof JBBPFieldArrayUInt && mappingField.getType().getComponentType() == float.class) { final long[] longArray = (long[]) arrayField.getValueArrayAsObject(invertBitOrder); final float[] floatArray = new float[longArray.length]; for (int i = 0; i < longArray.length; i++) { floatArray[i] = Float.intBitsToFloat((int) longArray[i]); } value = floatArray; } else if (arrayField instanceof JBBPFieldArrayUInt && mappingField.getType().getComponentType() == int.class) { final long[] longArray = (long[]) arrayField.getValueArrayAsObject(invertBitOrder); final int[] intArray = new int[longArray.length]; for (int i = 0; i < longArray.length; i++) { intArray[i] = (int) longArray[i]; } value = intArray; } else if (arrayField instanceof JBBPFieldArrayUShort && mappingField.getType().getComponentType() == char.class) { final short[] shortArray = (short[]) arrayField.getValueArrayAsObject(invertBitOrder); final char[] charArray = new char[shortArray.length]; for (int i = 0; i < shortArray.length; i++) { charArray[i] = (char) shortArray[i]; } value = charArray; } else { value = arrayField.getValueArrayAsObject(invertBitOrder); } if (setter == null) { mappingField.set(mappingClassInstance, value); } else { setter.invoke(mappingClassInstance, value); } } catch (IllegalAccessException ex) { throw new JBBPMapperException("Can't get access to a mapping field", arrayField, mappingClassInstance.getClass(), mappingField, ex); } catch (IllegalArgumentException ex) { throw new JBBPMapperException("Can't set argument to a mapping field", arrayField, mappingClassInstance.getClass(), mappingField, ex); } catch (InvocationTargetException ex) { throw new JBBPMapperException("Can't set argument to field through setter", arrayField, mappingClassInstance.getClass(), mappingField, ex); } } /** * Convert an array field into its string representation. * * @param field an array field to be converted, must not be null * @return the string representation of the array or null if the field can't * be converted */ private static String convertFieldValueToString(final JBBPAbstractArrayField field) { final StringBuilder result; if (field instanceof JBBPFieldArrayBit) { final JBBPFieldArrayBit array = (JBBPFieldArrayBit) field; result = new StringBuilder(array.size()); for (final byte b : array.getArray()) { result.append((char) (b & 0xFF)); } } else if (field instanceof JBBPFieldArrayByte) { final JBBPFieldArrayByte array = (JBBPFieldArrayByte) field; result = new StringBuilder(array.size()); for (final byte b : array.getArray()) { result.append((char) (b & 0xFF)); } } else if (field instanceof JBBPFieldArrayUByte) { final JBBPFieldArrayUByte array = (JBBPFieldArrayUByte) field; result = new StringBuilder(array.size()); for (final byte b : array.getArray()) { result.append((char) (b & 0xFF)); } } else if (field instanceof JBBPFieldArrayShort) { final JBBPFieldArrayShort array = (JBBPFieldArrayShort) field; result = new StringBuilder(array.size()); for (final short b : array.getArray()) { result.append((char) b); } } else if (field instanceof JBBPFieldArrayUShort) { final JBBPFieldArrayUShort array = (JBBPFieldArrayUShort) field; result = new StringBuilder(array.size()); for (final short b : array.getArray()) { result.append((char) b); } } else { result = null; } return result == null ? null : result.toString(); } /** * Map a parsed primitive numeric field to a primitive field in a mapping * class. * * @param mappingClassInstance the mapping class instance, must not be null * @param setter detected setter for field, can be null * @param mappingField a mapping field to set the value, must not be null * @param numericField a parsed numeric field which value should be used, must * not be null * @param invertBitOrder flag shows that the parsed numeric field value must * be reversed in its bit before setting */ private static void mapNumericField(final Object mappingClassInstance, final Method setter, final Field mappingField, final JBBPNumericField numericField, final boolean invertBitOrder) { final Class fieldClass = mappingField.getType(); try { if (fieldClass == byte.class) { final byte value = (byte) (invertBitOrder ? numericField.getAsInvertedBitOrder() : numericField.getAsInt()); if (setter == null) { mappingField.setByte(mappingClassInstance, value); } else { setter.invoke(mappingClassInstance, value); } } else if (fieldClass == boolean.class) { if (setter == null) { mappingField.setBoolean(mappingClassInstance, numericField.getAsBool()); } else { setter.invoke(mappingClassInstance, numericField.getAsBool()); } } else if (fieldClass == char.class) { final char value = (char) (invertBitOrder ? numericField.getAsInvertedBitOrder() : numericField.getAsInt()); if (setter == null) { mappingField.setChar(mappingClassInstance, value); } else { setter.invoke(mappingClassInstance, value); } } else if (fieldClass == short.class) { final short value = (short) (invertBitOrder ? numericField.getAsInvertedBitOrder() : numericField.getAsInt()); if (setter == null) { mappingField.setShort(mappingClassInstance, value); } else { setter.invoke(mappingClassInstance, value); } } else if (fieldClass == int.class) { final int value = (int) (invertBitOrder ? numericField.getAsInvertedBitOrder() : numericField.getAsInt()); if (setter == null) { mappingField.setInt(mappingClassInstance, value); } else { setter.invoke(mappingClassInstance, value); } } else if (fieldClass == long.class) { final long value = (invertBitOrder ? numericField.getAsInvertedBitOrder() : numericField.getAsLong()); if (setter == null) { mappingField.setLong(mappingClassInstance, value); } else { setter.invoke(mappingClassInstance, value); } } else if (fieldClass == float.class) { final float value; if (numericField instanceof JBBPFieldInt) { value = invertBitOrder ? Float.intBitsToFloat((int) numericField.getAsInvertedBitOrder()) : Float.intBitsToFloat(numericField.getAsInt()); } else { value = invertBitOrder ? Float.intBitsToFloat((int) numericField.getAsInvertedBitOrder()) : numericField.getAsFloat(); } if (setter == null) { mappingField.setFloat(mappingClassInstance, value); } else { setter.invoke(mappingClassInstance, value); } } else if (fieldClass == double.class) { final double value; if (numericField instanceof JBBPFieldLong) { value = invertBitOrder ? Double.longBitsToDouble(numericField.getAsInvertedBitOrder()) : Double.longBitsToDouble(numericField.getAsLong()); } else { value = invertBitOrder ? Double.longBitsToDouble(numericField.getAsInvertedBitOrder()) : numericField.getAsDouble(); } if (setter == null) { mappingField.setDouble(mappingClassInstance, value); } else { setter.invoke(mappingClassInstance, value); } } else { throw new JBBPMapperException( "Unsupported mapping class field type to be mapped for binary parsed data", (JBBPAbstractField) numericField, mappingClassInstance.getClass(), mappingField, null); } } catch (IllegalAccessException ex) { throw new JBBPMapperException("Can't get access to a mapping field", (JBBPAbstractField) numericField, mappingClassInstance.getClass(), mappingField, ex); } catch (IllegalArgumentException ex) { throw new JBBPMapperException("Can't set argument to a mapping field", (JBBPAbstractField) numericField, mappingClassInstance.getClass(), mappingField, ex); } catch (InvocationTargetException ex) { throw new JBBPMapperException("Can't set argument to a mapping field through setter", (JBBPAbstractField) numericField, mappingClassInstance.getClass(), mappingField, ex); } } /** * Get a value of a field from a class instance. * * @param classInstance a class instance object * @param getter method to get field value, can be null * @param classField a class field which value must be returned, must not be * null * @return the field value for the class instance */ private static Object getFieldValue(final Object classInstance, final Method getter, final Field classField) { try { if (getter == null) { return classField.get(classInstance); } else { return getter.invoke(classInstance); } } catch (IllegalArgumentException ex) { throw new JBBPMapperException("Can't set get value from a mapping field", null, classInstance.getClass(), classField, ex); } catch (IllegalAccessException ex) { throw new JBBPMapperException("Can't get access to a mapping field", null, classInstance.getClass(), classField, ex); } catch (InvocationTargetException ex) { throw new JBBPMapperException("Can't get field value through getter", null, classInstance.getClass(), classField, ex); } } /** * Set a value to a field of a class instance. Can't be used for static * fields! * * @param classInstance a class instance * @param setter setter for the field, can be null * @param classField a mapping class field which should be set by the value, * must not be null * @param binField a parsed bin field which value will be set, can be null * @param value a value to be set to the class field */ static void setFieldValue(final Object classInstance, final Method setter, final Field classField, final JBBPAbstractField binField, final Object value) { try { if (setter == null) { classField.set(classInstance, value); } else { setter.invoke(classInstance, value); } } catch (IllegalArgumentException ex) { throw new JBBPMapperException("Can't set value to a mapping field", binField, classInstance.getClass(), classField, ex); } catch (IllegalAccessException ex) { throw new JBBPMapperException("Can't get access to a mapping field", binField, classInstance.getClass(), classField, ex); } catch (InvocationTargetException ex) { throw new JBBPMapperException("Can't set field value through setter", binField, classInstance.getClass(), classField, ex); } } private static T tryMakeInstance( final Class type, final JBBPAbstractField binField, final Object mappingObject, final Field mappingField, final Function, Object>[] instantiators ) { T result = null; for (final Function, Object> instantiator : instantiators) { result = type.cast(instantiator.apply(type)); if (result != null) { break; } } if (result == null) { Exception detectedException = null; try { final Method method = mappingObject.getClass().getMethod(MAKE_CLASS_INSTANCE_METHOD_NAME, Class.class); if (!Modifier.isStatic(method.getModifiers())) { result = type.cast( mappingObject.getClass().getMethod(MAKE_CLASS_INSTANCE_METHOD_NAME, Class.class) .invoke(mappingObject, type)); } } catch (NoSuchMethodException ex) { // do nothing } catch (IllegalAccessException ex) { // WARNING! Don't replace by multi-catch for Android compatibility! detectedException = ex; } catch (InvocationTargetException ex) { detectedException = ex; } if (detectedException != null) { throw new RuntimeException(String .format("Error during %s(%s) call", MAKE_CLASS_INSTANCE_METHOD_NAME, mappingObject.getClass()), detectedException); } if (result == null) { result = type.cast(STATIC_MAKE_CLASS_INSTANCE_INSTANTIATOR.apply(type)); if (result == null) { result = type.cast(DEFAULT_CONSTRUCTOR_INSTANTIATOR.apply(type)); } } if (result == null) { throw new JBBPMapperException(String.format("Can't create instance of %s", type), binField, mappingObject.getClass(), mappingField, null); } } return result; } @Override public int compareTo(final MappedFieldRecord o) { final int thisOrder = this.binAnnotation.order(); final int thatOrder = o.binAnnotation.order(); final int result; if (thisOrder == thatOrder) { result = this.mappingField.getName().compareTo(o.mappingField.getName()); } else { result = thisOrder < thatOrder ? -1 : 1; } return result; } interface FieldProcessor { @SuppressWarnings("unchecked") void apply( MappedFieldRecord record, JBBPFieldStruct rootStructure, Object instance, JBBPMapperCustomFieldProcessor customFieldProcessor, JBBPAbstractField binField, int flags, BinFieldFilter binFieldFilter, Function, Object>... instantiators ); } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/000077500000000000000000000000001445000440600271545ustar00rootroot00000000000000AbstractFieldByteArray.java000066400000000000000000000027461445000440600343030ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Inside abstract class to keep common operations for byte array based entities. * * @param type of array item. * @since 1.1.1 */ abstract class AbstractFieldByteArray extends JBBPAbstractArrayField implements JBBPNumericArray { private static final long serialVersionUID = -884448637983315505L; protected final byte[] array; public AbstractFieldByteArray(final JBBPNamedFieldInfo name, final byte[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } @Override public boolean getAsBool(final int index) { return this.array[index] != 0; } @Override public long getAsLong(final int index) { return this.getAsInt(index); } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/BitEntity.java000066400000000000000000000017121445000440600317330ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.io.JBBPBitNumber; /** * The Interface describes a field which contains data which width is lesser or equals byte. * * @since 1.0 */ public interface BitEntity { /** * Get the width of the field. * * @return the width of the field, must not be null */ JBBPBitNumber getBitWidth(); } JBBPAbstractArrayField.java000066400000000000000000000047421445000440600341130ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.util.Iterator; import java.util.NoSuchElementException; /** * The Class is the ancestor for all field which represent arrays. * * @param type of field which can be contained in the array * @since 1.0 */ public abstract class JBBPAbstractArrayField extends JBBPAbstractField implements Iterable { private static final long serialVersionUID = -9007994400543951290L; /** * The Constructor. * * @param name the name descriptor for the array field, it can be null. */ public JBBPAbstractArrayField(final JBBPNamedFieldInfo name) { super(name); } /** * Get number of elements in the array. * * @return the array size */ public abstract int size(); /** * Get element from the array for its index. * * @param index the array index * @return the array element for its index */ public abstract T getElementAt(int index); /** * Get the value array as an object. * * @param reverseBits reverse bit order in values * @return the value array as an object */ public abstract Object getValueArrayAsObject(boolean reverseBits); /** * Generates an iterator to allow the array processing in loops. * * @return an iterator for the array */ @Override public Iterator iterator() { return new Iterator() { private int index = 0; @Override public boolean hasNext() { return this.index < size(); } @Override public T next() { if (this.index >= size()) { throw new NoSuchElementException(this.index + ">=" + size()); } return getElementAt(this.index++); } @Override public void remove() { throw new UnsupportedOperationException("Removing is unsupported here"); } }; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPAbstractField.java000066400000000000000000000055251445000440600331730ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; /** * The Class is the ancestor for all fields and arrays of fields. * * @since 1.0 */ public abstract class JBBPAbstractField implements Serializable, JBBPNamedField { private static final long serialVersionUID = 6072321705839048728L; /** * The Field contains the field name info */ protected final JBBPNamedFieldInfo fieldNameInfo; /** * The Variable can hold some payload. It is not used by JBBP and can be used * without restrictions. By default it is null. The Field is not thread safe. * * @since 1.2.0 */ protected Serializable payload; /** * The Constructor. * * @param namedField the name field info for the field, it can be null. */ public JBBPAbstractField(final JBBPNamedFieldInfo namedField) { this.fieldNameInfo = namedField; } /** * Get payload value saved by user. The Operation is not thread safe. * * @return payload value, can be null * @since 1.2.0 */ public Serializable getPayload() { return this.payload; } /** * Set payload value. The Operation is not thread safe. * * @param value payload value, can be null. * @since 1.2.0 */ public void setPayload(final Serializable value) { this.payload = value; } /** * Get the field name info. * * @return the field name info if it is presented, otherwise null */ @Override public JBBPNamedFieldInfo getNameInfo() { return this.fieldNameInfo; } /** * Get the field path. * * @return the field path or null if the field doesn't contain any field name * info */ public String getFieldPath() { return this.fieldNameInfo == null ? null : this.fieldNameInfo.getFieldPath(); } /** * Get the field name. * * @return the field name or null if the field doesn't contain any field name * info */ public String getFieldName() { return this.fieldNameInfo == null ? null : this.fieldNameInfo.getFieldName(); } /** * Get the field type in string representation. * * @return the string representation of field type, like 'int', 'long', 'bool * [123]' * @since 1.2.0 */ public abstract String getTypeAsString(); } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldArrayBit.java000066400000000000000000000063671445000440600331520ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes an array contains bit fields. * * @since 1.0 */ public final class JBBPFieldArrayBit extends JBBPAbstractArrayField implements BitEntity, JBBPNumericArray { private static final long serialVersionUID = -4589044511663149591L; /** * Number of value bits in values of the array. */ private final JBBPBitNumber bitNumber; /** * Bit values. */ private final byte[] array; /** * The Constructor. * * @param name the field name info, it can be null. * @param array the byte array contains values, it must not be null * @param bitNumber number of valuable bits in values of the array, it must * not be null */ public JBBPFieldArrayBit(final JBBPNamedFieldInfo name, final byte[] array, final JBBPBitNumber bitNumber) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); JBBPUtils.assertNotNull(bitNumber, "Bit number must not be null"); this.array = array; this.bitNumber = bitNumber; } /** * Get values as a byte array. * * @return the value array */ public byte[] getArray() { return this.array.clone(); } /** * Get the valuable bit number of values in the array. * * @return the valuable bit number, must not be null */ @Override public JBBPBitNumber getBitWidth() { return this.bitNumber; } @Override public int size() { return this.array.length; } @Override public JBBPFieldBit getElementAt(final int index) { final JBBPFieldBit result = new JBBPFieldBit(this.fieldNameInfo, this.getAsInt(index), this.bitNumber); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return this.array[index] & 0xFF; } @Override public long getAsLong(final int index) { return this.getAsInt(index); } @Override public boolean getAsBool(final int index) { return this.array[index] != 0; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final byte[] result; if (reverseBits) { result = this.array.clone(); for (int i = 0; i < result.length; i++) { result[i] = (byte) JBBPFieldBit.reverseBits(result[i], this.bitNumber); } } else { result = this.array.clone(); } return result; } @Override public String getTypeAsString() { return "bit:" + this.bitNumber.getBitNumber() + " [" + this.array.length + ']'; } } JBBPFieldArrayBoolean.java000066400000000000000000000045061445000440600337250ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes an array of boolean values. * * @since 1.0 */ public final class JBBPFieldArrayBoolean extends JBBPAbstractArrayField implements JBBPNumericArray { private static final long serialVersionUID = -7896549257985728694L; /** * The Inside value storage. */ private final boolean[] array; /** * The Constructor. * * @param name the field name info, it can be null * @param array the value array, it must not be null */ public JBBPFieldArrayBoolean(final JBBPNamedFieldInfo name, final boolean[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } /** * Get values of the array. * * @return values as a boolean array */ public boolean[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldBoolean getElementAt(final int index) { final JBBPFieldBoolean result = new JBBPFieldBoolean(this.fieldNameInfo, getAsBool(index)); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return this.array[index] ? 1 : 0; } @Override public long getAsLong(final int index) { return this.getAsInt(index); } @Override public boolean getAsBool(final int index) { return this.array[index]; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { return this.array.clone(); } @Override public String getTypeAsString() { return "bool " + '[' + this.array.length + ']'; } } JBBPFieldArrayByte.java000066400000000000000000000043601445000440600332470ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; /** * Describes a byte array. * * @since 1.0 */ public final class JBBPFieldArrayByte extends AbstractFieldByteArray implements JBBPNumericArray { private static final long serialVersionUID = -8100947416351943918L; /** * The Constructor. * * @param name the field name info, it can be null. * @param array the values array, it must not be null */ public JBBPFieldArrayByte(final JBBPNamedFieldInfo name, final byte[] array) { super(name, array); } /** * Get the values of the array. * * @return the values as a byte array */ public byte[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldByte getElementAt(final int index) { final JBBPFieldByte result = new JBBPFieldByte(this.fieldNameInfo, this.array[index]); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return this.array[index]; } @Override public long getAsLong(final int index) { return this.getAsInt(index); } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final byte[] result; if (reverseBits) { result = this.array.clone(); for (int i = 0; i < result.length; i++) { result[i] = (byte) JBBPFieldByte.reverseBits(result[i]); } } else { result = this.array.clone(); } return result; } @Override public String getTypeAsString() { return "byte " + '[' + this.array.length + ']'; } } JBBPFieldArrayDouble.java000066400000000000000000000053571445000440600335650ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a double array. * * @since 1.4.0 */ public final class JBBPFieldArrayDouble extends JBBPAbstractArrayField implements JBBPNumericArray { private static final long serialVersionUID = -2146959311724853264L; /** * Inside value storage. */ private final double[] array; /** * The Constructor. * * @param name a field name info, it can be null * @param array a value array, it must not be null */ public JBBPFieldArrayDouble(final JBBPNamedFieldInfo name, final double[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } /** * get the value array * * @return the value array as a long array */ public double[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldDouble getElementAt(final int index) { final JBBPFieldDouble result = new JBBPFieldDouble(this.fieldNameInfo, this.array[index]); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return (int) Math.round(this.array[index]); } @Override public long getAsLong(final int index) { return Math.round(this.array[index]); } public double getAsDouble(final int index) { return this.array[index]; } @Override public boolean getAsBool(final int index) { return Double.compare(this.array[index], 0.0d) != 0; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final double[] result; if (reverseBits) { result = this.array.clone(); for (int i = 0; i < result.length; i++) { result[i] = Double.longBitsToDouble(JBBPFieldLong.reverseBits(Double.doubleToLongBits(result[i]))); } } else { result = this.array.clone(); } return result; } @Override public String getTypeAsString() { return JBBPFieldDouble.TYPE_NAME + " [" + this.array.length + ']'; } } JBBPFieldArrayFloat.java000066400000000000000000000053061445000440600334120ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes an array of floats. * * @since 1.4.0 */ public final class JBBPFieldArrayFloat extends JBBPAbstractArrayField implements JBBPNumericArray { private static final long serialVersionUID = 6839868800303265190L; /** * Inside storage. */ private final float[] array; /** * The Constructor. * * @param name the field name info, it can be null. * @param array the value array, it must not be null. */ public JBBPFieldArrayFloat(final JBBPNamedFieldInfo name, final float[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } /** * Get values as an integer array. * * @return values as an integer array */ public float[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldFloat getElementAt(final int index) { final JBBPFieldFloat result = new JBBPFieldFloat(this.fieldNameInfo, this.array[index]); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return Math.round(this.array[index]); } public float getAsFloat(final int index) { return this.array[index]; } @Override public long getAsLong(final int index) { return this.getAsInt(index); } @Override public boolean getAsBool(final int index) { return this.array[index] != 0; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final float[] result; if (reverseBits) { result = this.array.clone(); for (int i = 0; i < result.length; i++) { result[i] = Float.intBitsToFloat((int) JBBPFieldInt.reverseBits(Float.floatToIntBits(result[i]))); } } else { result = this.array.clone(); } return result; } @Override public String getTypeAsString() { return JBBPFieldFloat.TYPE_NAME + " [" + this.array.length + ']'; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldArrayInt.java000066400000000000000000000050131445000440600331510ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes an array of integers. * * @since 1.0 */ public final class JBBPFieldArrayInt extends JBBPAbstractArrayField implements JBBPNumericArray { private static final long serialVersionUID = 6839868800303265190L; /** * Inside storage. */ private final int[] array; /** * The Constructor. * * @param name the field name info, it can be null. * @param array the value array, it must not be null. */ public JBBPFieldArrayInt(final JBBPNamedFieldInfo name, final int[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } /** * Get values as an integer array. * * @return values as an integer array */ public int[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldInt getElementAt(final int index) { final JBBPFieldInt result = new JBBPFieldInt(this.fieldNameInfo, this.array[index]); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return this.array[index]; } @Override public long getAsLong(final int index) { return this.getAsInt(index); } @Override public boolean getAsBool(final int index) { return this.array[index] != 0; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final int[] result; if (reverseBits) { result = this.array.clone(); for (int i = 0; i < result.length; i++) { result[i] = (int) JBBPFieldInt.reverseBits(result[i]); } } else { result = this.array.clone(); } return result; } @Override public String getTypeAsString() { return "int " + '[' + this.array.length + ']'; } } JBBPFieldArrayLong.java000066400000000000000000000050071445000440600332420ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a long array. * * @since 1.0 */ public final class JBBPFieldArrayLong extends JBBPAbstractArrayField implements JBBPNumericArray { private static final long serialVersionUID = -2146959300724853264L; /** * Inside value storage. */ private final long[] array; /** * The Constructor. * * @param name a field name info, it can be null * @param array a value array, it must not be null */ public JBBPFieldArrayLong(final JBBPNamedFieldInfo name, final long[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } /** * get the value array * * @return the value array as a long array */ public long[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldLong getElementAt(final int index) { final JBBPFieldLong result = new JBBPFieldLong(this.fieldNameInfo, this.array[index]); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return (int) this.array[index]; } @Override public long getAsLong(final int index) { return this.array[index]; } @Override public boolean getAsBool(final int index) { return this.array[index] != 0L; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final long[] result; if (reverseBits) { result = this.array.clone(); for (int i = 0; i < result.length; i++) { result[i] = JBBPFieldLong.reverseBits(result[i]); } } else { result = this.array.clone(); } return result; } @Override public String getTypeAsString() { return "long " + '[' + this.array.length + ']'; } } JBBPFieldArrayShort.java000066400000000000000000000050321445000440600334400ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a short array. * * @since 1.0 */ public final class JBBPFieldArrayShort extends JBBPAbstractArrayField implements JBBPNumericArray { private static final long serialVersionUID = 6119269534023759155L; /** * Inside value storage. */ private final short[] array; /** * The Constructor. * * @param name a field name info, it can be null. * @param array a value array, it must not be null */ public JBBPFieldArrayShort(final JBBPNamedFieldInfo name, final short[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } /** * Get the value array. * * @return the value array as a short array */ public short[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldShort getElementAt(final int index) { final JBBPFieldShort result = new JBBPFieldShort(this.fieldNameInfo, this.array[index]); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return this.array[index]; } @Override public long getAsLong(final int index) { return this.getAsInt(index); } @Override public boolean getAsBool(final int index) { return this.array[index] != 0; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final short[] result; if (reverseBits) { result = this.array.clone(); for (int i = 0; i < result.length; i++) { result[i] = (short) JBBPFieldShort.reverseBits(result[i]); } } else { result = this.array.clone(); } return result; } @Override public String getTypeAsString() { return "short " + '[' + this.array.length + ']'; } } JBBPFieldArrayString.java000066400000000000000000000043371445000440600336160ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2018 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes an array of strings. * * @since 1.4.0 */ public final class JBBPFieldArrayString extends JBBPAbstractArrayField { private static final long serialVersionUID = -220078798710257313L; /** * Inside value storage. */ private final String[] array; /** * The Constructor. * * @param name a field name info, it can be null. * @param array a value array, it must not be null bt can contain null. */ public JBBPFieldArrayString(final JBBPNamedFieldInfo name, final String[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } /** * Get the values as a short array. * * @return the values as a short array. */ public String[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldString getElementAt(final int index) { final JBBPFieldString result = new JBBPFieldString(this.fieldNameInfo, this.array[index]); result.payload = this.payload; return result; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final String[] result = this.array.clone(); if (reverseBits) { for (int i = 0; i < result.length; i++) { result[i] = JBBPFieldString.reverseBits(result[i]); } } return result; } @Override public String getTypeAsString() { return JBBPFieldString.TYPE_NAME + " [" + this.array.length + ']'; } } JBBPFieldArrayStruct.java000066400000000000000000000037521445000440600336340ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a structure array. It doesn't support operations to get an array value as a numeric one. * * @since 1.0 */ public final class JBBPFieldArrayStruct extends JBBPAbstractArrayField { private static final long serialVersionUID = 8614627616366111833L; /** * Inside value storage. */ private final JBBPFieldStruct[] structs; /** * The Constructor. * * @param name a field name info, it can be null * @param array a value array, it must not be null */ public JBBPFieldArrayStruct(final JBBPNamedFieldInfo name, final JBBPFieldStruct[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.structs = array; } /** * Get the value array. * * @return the value array as a structure array */ public JBBPFieldStruct[] getArray() { return this.structs.clone(); } @Override public int size() { return this.structs.length; } @Override public JBBPFieldStruct getElementAt(final int index) { return this.structs[index]; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { return this.structs.clone(); } @Override public String getTypeAsString() { return "{} [" + this.structs.length + ']'; } } JBBPFieldArrayUByte.java000066400000000000000000000042221445000440600333710ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; /** * Describes a unsigned byte array. * * @since 1.0 */ public final class JBBPFieldArrayUByte extends AbstractFieldByteArray { private static final long serialVersionUID = -2568935326782182401L; /** * The Constructor. * * @param name a field name info, it can be null. * @param array a value array, it must not be null */ public JBBPFieldArrayUByte(final JBBPNamedFieldInfo name, final byte[] array) { super(name, array); } /** * Get the value array as a byte array. * * @return the value array as a byte array */ public byte[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldUByte getElementAt(final int index) { final JBBPFieldUByte result = new JBBPFieldUByte(this.fieldNameInfo, this.array[index]); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return this.array[index] & 0xFF; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final byte[] result; if (reverseBits) { result = this.array.clone(); for (int i = 0; i < result.length; i++) { result[i] = (byte) JBBPFieldByte.reverseBits(result[i]); } } else { result = this.array.clone(); } return result; } @Override public String getTypeAsString() { return "ubyte " + '[' + this.array.length + ']'; } } JBBPFieldArrayUInt.java000066400000000000000000000056171445000440600332310ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a long array. * * @since 1.0 */ public final class JBBPFieldArrayUInt extends JBBPAbstractArrayField implements JBBPNumericArray { private static final long serialVersionUID = -2146953450724853264L; /** * Inside value storage. */ private final int[] array; /** * The Constructor. * * @param name a field name info, it can be null * @param array a value array, it must not be null */ public JBBPFieldArrayUInt(final JBBPNamedFieldInfo name, final int[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } /** * get the value array * * @return the value array as a long array */ public long[] getArray() { long[] result = new long[this.array.length]; for (int i = 0; i < this.array.length; i++) { result[i] = (long) this.array[i] & 0xFFFFFFFFL; } return result; } /** * Get internal array of signed integers representing unsigned integers. * * @return the internal integer array, must not be null * @since 2.0.4 */ public int[] getInternalArray() { return this.array; } @Override public int size() { return this.array.length; } @Override public JBBPFieldUInt getElementAt(final int index) { final JBBPFieldUInt result = new JBBPFieldUInt(this.fieldNameInfo, (long) this.array[index] & 0xFFFFFFFFL); result.payload = this.payload; return result; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final long[] result = new long[this.array.length]; for (int i = 0; i < result.length; i++) { result[i] = (reverseBits ? JBBPFieldInt.reverseBits(this.array[i]) : this.array[i]) & 0xFFFFFFFFL; } return result; } @Override public int getAsInt(final int index) { return this.array[index]; } @Override public long getAsLong(final int index) { return (long) this.array[index] & 0xFFFFFFFFL; } @Override public boolean getAsBool(final int index) { return this.array[index] != 0L; } @Override public String getTypeAsString() { return "uint " + '[' + this.array.length + ']'; } } JBBPFieldArrayUShort.java000066400000000000000000000050741445000440600335730ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a unsigned short array. * * @since 1.0 */ public final class JBBPFieldArrayUShort extends JBBPAbstractArrayField implements JBBPNumericArray { private static final long serialVersionUID = -220078798710257343L; /** * Inside value storage. */ private final short[] array; /** * The Constructor. * * @param name a field name info, it can be null. * @param array a value array, it must not be null */ public JBBPFieldArrayUShort(final JBBPNamedFieldInfo name, final short[] array) { super(name); JBBPUtils.assertNotNull(array, "Array must not be null"); this.array = array; } /** * Get the values as a short array. * * @return the values as a short array. */ public short[] getArray() { return this.array.clone(); } @Override public int size() { return this.array.length; } @Override public JBBPFieldUShort getElementAt(final int index) { final JBBPFieldUShort result = new JBBPFieldUShort(this.fieldNameInfo, this.array[index]); result.payload = this.payload; return result; } @Override public int getAsInt(final int index) { return this.array[index] & 0xFFFF; } @Override public long getAsLong(final int index) { return this.getAsInt(index); } @Override public boolean getAsBool(final int index) { return this.array[index] != 0; } @Override public Object getValueArrayAsObject(final boolean reverseBits) { final short[] result; if (reverseBits) { result = this.array.clone(); for (int i = 0; i < result.length; i++) { result[i] = (short) JBBPFieldUShort.reverseBits(result[i]); } } else { result = this.array.clone(); } return result; } @Override public String getTypeAsString() { return "ushort " + '[' + this.array.length + ']'; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldBit.java000066400000000000000000000056331445000440600321460ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a bit field. * * @since 1.0 */ public final class JBBPFieldBit extends JBBPAbstractField implements JBBPNumericField, BitEntity { private static final long serialVersionUID = 3113427734366331529L; /** * Inside value storage. */ private final int value; /** * The Value shows how many bits are really contain the value in the byte. */ private final JBBPBitNumber bitNumber; /** * The Constructor. * * @param name a field name info, it can be null. * @param value the field value * @param bitNumber number of valuable bits in the value, must not be null */ public JBBPFieldBit(final JBBPNamedFieldInfo name, final int value, final JBBPBitNumber bitNumber) { super(name); JBBPUtils.assertNotNull(bitNumber, "Number of bits must not be null"); this.bitNumber = bitNumber; this.value = value; } /** * Get the reversed bit representation of the value. * * @param value the value to be reversed * @param bits number of bits to be reversed, must not be null * @return the reversed value */ public static long reverseBits(final byte value, final JBBPBitNumber bits) { return JBBPUtils.reverseBitsInByte(value) >>> (8 - bits.getBitNumber()) & bits.getMask(); } /** * Get number of valuable bits in the value. It plays informative role and doesn't play role during numeric value getting. * * @return the number of valuable bits in the value. */ @Override public JBBPBitNumber getBitWidth() { return this.bitNumber; } @Override public double getAsDouble() { return this.value & 0xFF; } @Override public float getAsFloat() { return (float) (this.value & 0xFF); } @Override public int getAsInt() { return this.value & 0xFF; } @Override public long getAsLong() { return getAsInt(); } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return reverseBits((byte) this.value, this.bitNumber); } @Override public String getTypeAsString() { return "bit:" + this.bitNumber.getBitNumber(); } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldBoolean.java000066400000000000000000000040311445000440600327760ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; /** * Describes a boolean field. * * @since 1.0 */ public final class JBBPFieldBoolean extends JBBPAbstractField implements JBBPNumericField { private static final long serialVersionUID = 4165558936928450699L; /** * Inside value storage. */ private final boolean value; /** * The Constructor. * * @param name a field name info, it can be null. * @param value the field value */ public JBBPFieldBoolean(final JBBPNamedFieldInfo name, final boolean value) { super(name); this.value = value; } /** * Get the reversed bit representation of the value. But for boolean it doesn't work and made for compatibility * * @param value the value to be reversed * @return the reversed value */ public static long reverseBits(final boolean value) { return value ? 1 : 0; } @Override public double getAsDouble() { return value ? 1.0d : 0.0d; } @Override public float getAsFloat() { return value ? 1.0f : 0.0f; } @Override public int getAsInt() { return this.value ? 1 : 0; } @Override public long getAsLong() { return this.getAsInt(); } @Override public boolean getAsBool() { return this.value; } @Override public long getAsInvertedBitOrder() { return reverseBits(this.value); } @Override public String getTypeAsString() { return "bool"; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldByte.java000066400000000000000000000037741445000440600323370ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a byte field. * * @since 1.0 */ public final class JBBPFieldByte extends JBBPAbstractField implements JBBPNumericField { private static final long serialVersionUID = -446415543054607091L; /** * Inside value storage. */ private final byte value; /** * The Constructor. * * @param name a field name info, it can be null. * @param value the field value. */ public JBBPFieldByte(final JBBPNamedFieldInfo name, final byte value) { super(name); this.value = value; } /** * Get the reversed bit representation of the value. * * @param value the value to be reversed * @return the reversed value */ public static long reverseBits(final byte value) { return JBBPUtils.reverseBitsInByte(value); } @Override public double getAsDouble() { return this.value; } @Override public float getAsFloat() { return this.value; } @Override public int getAsInt() { return this.value; } @Override public long getAsLong() { return this.getAsInt(); } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return reverseBits(this.value); } @Override public String getTypeAsString() { return "byte"; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldDouble.java000066400000000000000000000035531445000440600326410ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; /** * Describes a double value field. * * @since 1.4.0 */ public final strictfp class JBBPFieldDouble extends JBBPAbstractField implements JBBPNumericField { public static final String TYPE_NAME = "doublej"; private static final long serialVersionUID = -7106473415348171461L; /** * Inside value storage. */ private final double value; /** * The Constructor. * * @param name a field name info, it can be null * @param value the field value */ public JBBPFieldDouble(final JBBPNamedFieldInfo name, final double value) { super(name); this.value = value; } @Override public double getAsDouble() { return this.value; } @Override public float getAsFloat() { return (float) this.value; } @Override public int getAsInt() { return (int) Math.round(this.value); } @Override public long getAsLong() { return Math.round(this.value); } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return JBBPFieldLong.reverseBits(Double.doubleToLongBits(this.value)); } @Override public String getTypeAsString() { return TYPE_NAME; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldFloat.java000066400000000000000000000035061445000440600324720ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; /** * Describes a float field. * * @since 1.4.0 */ public final strictfp class JBBPFieldFloat extends JBBPAbstractField implements JBBPNumericField { public static final String TYPE_NAME = "floatj"; private static final long serialVersionUID = 5493764792942829316L; /** * Inside value storage. */ private final float value; /** * The Constructor. * * @param name a field name info, it can be null. * @param value the field value */ public JBBPFieldFloat(final JBBPNamedFieldInfo name, final float value) { super(name); this.value = value; } @Override public float getAsFloat() { return this.value; } @Override public double getAsDouble() { return this.value; } @Override public int getAsInt() { return Math.round(this.value); } @Override public long getAsLong() { return this.getAsInt(); } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return JBBPFieldInt.reverseBits(Float.floatToIntBits(this.value)); } @Override public String getTypeAsString() { return TYPE_NAME; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldInt.java000066400000000000000000000044631445000440600321620ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes an integer field. * * @since 1.0 */ public final class JBBPFieldInt extends JBBPAbstractField implements JBBPNumericField { private static final long serialVersionUID = 5493764792042809716L; /** * Inside value storage. */ private final int value; /** * The Constructor. * * @param name a field name info, it can be null. * @param value the field value */ public JBBPFieldInt(final JBBPNamedFieldInfo name, final int value) { super(name); this.value = value; } /** * Get the reversed bit representation of the value. * * @param value the value to be reversed * @return the reversed value */ public static long reverseBits(final int value) { final int b0 = JBBPUtils.reverseBitsInByte((byte) value) & 0xFF; final int b1 = JBBPUtils.reverseBitsInByte((byte) (value >> 8)) & 0xFF; final int b2 = JBBPUtils.reverseBitsInByte((byte) (value >> 16)) & 0xFF; final int b3 = JBBPUtils.reverseBitsInByte((byte) (value >> 24)) & 0xFF; return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; } @Override public int getAsInt() { return this.value; } @Override public double getAsDouble() { return this.value; } @Override public float getAsFloat() { return (float) this.value; } @Override public long getAsLong() { return this.getAsInt(); } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return reverseBits(this.value); } @Override public String getTypeAsString() { return "int"; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldLong.java000066400000000000000000000053051445000440600323230ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a long value field. * * @since 1.0 */ public final class JBBPFieldLong extends JBBPAbstractField implements JBBPNumericField { private static final long serialVersionUID = -7006473435241171461L; /** * Inside value storage. */ private final long value; /** * The Constructor. * * @param name a field name info, it can be null * @param value the field value */ public JBBPFieldLong(final JBBPNamedFieldInfo name, final long value) { super(name); this.value = value; } /** * Get the reversed bit representation of the value. * * @param value the value to be reversed * @return the reversed value */ public static long reverseBits(final long value) { final long b0 = JBBPUtils.reverseBitsInByte((byte) value) & 0xFFL; final long b1 = JBBPUtils.reverseBitsInByte((byte) (value >> 8)) & 0xFFL; final long b2 = JBBPUtils.reverseBitsInByte((byte) (value >> 16)) & 0xFFL; final long b3 = JBBPUtils.reverseBitsInByte((byte) (value >> 24)) & 0xFFL; final long b4 = JBBPUtils.reverseBitsInByte((byte) (value >> 32)) & 0xFFL; final long b5 = JBBPUtils.reverseBitsInByte((byte) (value >> 40)) & 0xFFL; final long b6 = JBBPUtils.reverseBitsInByte((byte) (value >> 48)) & 0xFFL; final long b7 = JBBPUtils.reverseBitsInByte((byte) (value >> 56)) & 0xFFL; return (b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | (b4 << 24) | (b5 << 16) | (b6 << 8) | b7; } @Override public double getAsDouble() { return (double) this.value; } @Override public float getAsFloat() { return (float) this.value; } @Override public int getAsInt() { return (int) this.value; } @Override public long getAsLong() { return this.value; } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return reverseBits(this.value); } @Override public String getTypeAsString() { return "long"; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldShort.java000066400000000000000000000042251445000440600325230ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a short value field. * * @since 1.0 */ public final class JBBPFieldShort extends JBBPAbstractField implements JBBPNumericField { private static final long serialVersionUID = -6245423766682842050L; /** * Inside value storage. */ private final short value; /** * The Constructor. * * @param name a field name info. it can be null * @param value the field value */ public JBBPFieldShort(final JBBPNamedFieldInfo name, final short value) { super(name); this.value = value; } /** * Get the reversed bit representation of the value. * * @param value the value to be reversed * @return the reversed value */ public static long reverseBits(final short value) { final int b0 = JBBPUtils.reverseBitsInByte((byte) value) & 0xFF; final int b1 = JBBPUtils.reverseBitsInByte((byte) (value >> 8)) & 0xFF; return (short) (b0 << 8) | (short) b1; } @Override public double getAsDouble() { return this.value; } @Override public float getAsFloat() { return this.value; } @Override public int getAsInt() { return this.value; } @Override public long getAsLong() { return this.getAsInt(); } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return reverseBits(this.value); } @Override public String getTypeAsString() { return "short"; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldString.java000066400000000000000000000037171445000440600326770ustar00rootroot00000000000000/* * Copyright 2018 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; /** * Describes a java string object. * * @since 1.4.0 */ public final class JBBPFieldString extends JBBPAbstractField { public static final String TYPE_NAME = "stringj"; private static final long serialVersionUID = -2861961302858335702L; private final String str; /** * A Constructor. * * @param name a field name info, it can be null * @param nullableValue a value, it can be null */ public JBBPFieldString(final JBBPNamedFieldInfo name, final String nullableValue) { super(name); this.str = nullableValue; } /** * Get the reversed bit representation of the value. * * @param value the value to be reversed, can be null * @return the reversed value */ public static String reverseBits(final String value) { String result = null; if (value != null) { final char[] chars = value.toCharArray(); for (int i = 0; i < chars.length; i++) { chars[i] = (char) JBBPFieldUShort.reverseBits((short) chars[i]); } result = String.valueOf(chars); } return result; } /** * Get the saved value. * * @return the value as String, it can be null */ public String getAsString() { return this.str; } @Override public String getTypeAsString() { return TYPE_NAME; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldStruct.java000066400000000000000000000433061445000440600327130ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.exceptions.JBBPFinderException; import com.igormaznitsa.jbbp.exceptions.JBBPTooManyFieldsFoundException; import com.igormaznitsa.jbbp.mapper.BinFieldFilter; import com.igormaznitsa.jbbp.mapper.JBBPMapper; import com.igormaznitsa.jbbp.mapper.JBBPMapperCustomFieldProcessor; import com.igormaznitsa.jbbp.model.finder.JBBPFieldFinder; import com.igormaznitsa.jbbp.utils.Function; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.util.List; import static com.igormaznitsa.jbbp.utils.JBBPUtils.ARRAY_FIELD_EMPTY; /** * Describes a structure. * * @since 1.0 */ public final class JBBPFieldStruct extends JBBPAbstractField implements JBBPFieldFinder { private static final long serialVersionUID = -5862961302818335702L; /** * Structure fields. */ private final JBBPAbstractField[] fields; /** * A Constructor. * * @param name a field name info, it can be null * @param fields a field array, it must not be null */ public JBBPFieldStruct(final JBBPNamedFieldInfo name, final JBBPAbstractField[] fields) { super(name); JBBPUtils.assertNotNull(fields, "Array of fields must not be null"); this.fields = fields; } /** * A Constructor. * * @param name a field name info, it can be null * @param fields a field list, it must not be null */ public JBBPFieldStruct(final JBBPNamedFieldInfo name, final List fields) { this(name, fields.toArray(ARRAY_FIELD_EMPTY)); } /** * Get the fields of the structure as an array. * * @return the field array of the structure. */ public JBBPAbstractField[] getArray() { return this.fields.clone(); } @Override public JBBPAbstractField findFieldForPath(final String fieldPath) { final String[] parsedName = JBBPUtils.splitString(JBBPUtils.normalizeFieldNameOrPath(fieldPath), '.'); JBBPAbstractField found = this; final int firstIndex; if ("".equals(this.getFieldName())) { firstIndex = 0; } else if (parsedName[0].equals(this.getNameInfo().getFieldName())) { firstIndex = 1; found = this; } else { firstIndex = 0; found = null; } for (int i = firstIndex; found != null && i < parsedName.length; i++) { if (found instanceof JBBPFieldStruct) { found = ((JBBPFieldStruct) found).findFieldForName(parsedName[i]); } else { throw new JBBPFinderException( "Detected a field instead of a structure as one of nodes in the path '" + fieldPath + '\'', fieldPath, null); } } return found; } @Override public JBBPAbstractField findFieldForName(final String name) { final String normalizedName = JBBPUtils.normalizeFieldNameOrPath(name); JBBPAbstractField result = null; for (final JBBPAbstractField f : this.fields) { if (normalizedName.equals(f.getFieldName())) { result = f; break; } } return result; } @Override public T findFieldForType(final Class fieldType) { T result = null; int counter = 0; for (final JBBPAbstractField f : this.fields) { if (fieldType.isAssignableFrom(f.getClass())) { if (result == null) { result = fieldType.cast(f); } counter++; } } if (counter > 1) { throw new JBBPTooManyFieldsFoundException(counter, "Detected more than one field", null, fieldType); } return result; } @Override public T findFirstFieldForType(final Class fieldType) { T result = null; for (final JBBPAbstractField f : this.fields) { if (fieldType.isAssignableFrom(f.getClass())) { result = fieldType.cast(f); break; } } return result; } @Override public T findLastFieldForType(final Class fieldType) { T result = null; for (int i = this.fields.length - 1; i >= 0; i--) { final JBBPAbstractField f = this.fields[i]; if (fieldType.isAssignableFrom(f.getClass())) { result = fieldType.cast(f); break; } } return result; } @Override public T findFieldForNameAndType(final String fieldName, final Class fieldType) { final String normalizedName = JBBPUtils.normalizeFieldNameOrPath(fieldName); T result = null; for (final JBBPAbstractField f : this.fields) { if (fieldType.isAssignableFrom(f.getClass()) && normalizedName.equals(f.getFieldName())) { result = fieldType.cast(f); break; } } return result; } @Override public boolean nameExists(final String fieldName) { final String normalizedName = JBBPUtils.normalizeFieldNameOrPath(fieldName); boolean result = false; for (final JBBPAbstractField f : this.fields) { if (normalizedName.equals(f.getFieldName())) { result = true; break; } } return result; } @Override public boolean pathExists(final String fieldPath) { final String normalizedPath = JBBPUtils.normalizeFieldNameOrPath(fieldPath); boolean result = false; for (final JBBPAbstractField f : this.fields) { if (normalizedPath.equals(f.getFieldPath())) { result = true; break; } } return result; } @Override public T findFieldForPathAndType(final String fieldPath, final Class fieldType) { final JBBPAbstractField field = this.findFieldForPath(fieldPath); T result = null; if (field != null && fieldType.isAssignableFrom(field.getClass())) { result = fieldType.cast(field); } return result; } /** * Find a structure by its path and map the structure fields to a class * fields. * * @param a class type * @param path the path to the structure to be mapped, must not be null * @param instance object instance to be filled by values, must not be null * @param instantiators array of functions which can instantiate object of required class, must not be null * @return a mapped instance of the class, must not be null * @since 2.0.0 */ @SafeVarargs public final T mapTo(final String path, final T instance, final Function, Object>... instantiators) { return JBBPMapper.map(this, path, instance, instantiators); } /** * Find a structure by its path and map the structure fields to a class * fields. * * @param a class type * @param path the path to the structure to be mapped, must not be null * @param instance object instance to be filled by values, must not be null * @param flags special flags to tune mapping process * @param instantiators array of functions which can instantiate object of required class, must not be null * @return a mapped instance of the class, must not be null * @see JBBPMapper#FLAG_IGNORE_MISSING_VALUES * @since 2.0.0 */ @SafeVarargs public final T mapTo(final String path, final T instance, final int flags, final Function, Object>... instantiators) { return JBBPMapper.map(this, path, instance, flags, instantiators); } /** * Find a structure by its path and map the structure fields to a class * fields. * * @param a class type * @param path the path to the structure to be mapped, must not be null * @param instance object instance to be filled by values, must not be null * @param customFieldProcessor a custom field processor to provide values for custom mapping fields, it can be null if there is not any custom field * @param instantiators array of functions which can instantiate object of required class, must not be null * @return a mapped instance of the class, must not be null * @since 2.0.0 */ @SafeVarargs public final T mapTo(final String path, final T instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final Function, Object>... instantiators) { return JBBPMapper.map(this, path, instance, customFieldProcessor, instantiators); } /** * Find a structure by its path and map the structure fields to a class * fields. * * @param a class type * @param path the path to the structure to be mapped, must not be null * @param instance object instance to be filled by values, must not be null * @param customFieldProcessor a custom field processor to provide values for custom mapping fields, it can be null if there is not any custom field * @param flags special flags to tune mapping process * @param instantiators array of functions which can instantiate object of required class, must not be null * @return a mapped instance of the class, must not be null * @see JBBPMapper#FLAG_IGNORE_MISSING_VALUES * @since 2.0.0 */ @SafeVarargs public final T mapTo(final String path, final T instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final int flags, final Function, Object>... instantiators) { return JBBPMapper.map(this, path, instance, customFieldProcessor, flags, instantiators); } /** * Map the structure fields to object fields. * * @param expected result type * @param objectToMap an object to map fields of the structure, must not be * null * @param instantiators array of functions which can instantiate object of required class, must not be null * @return the same object from the arguments but with filled fields by values * of the structure */ @SafeVarargs public final T mapTo(final T objectToMap, final Function, Object>... instantiators) { return this.mapTo(objectToMap, (JBBPMapperCustomFieldProcessor) null, instantiators); } /** * Map the structure fields to object fields. * * @param expected result type * @param objectToMap an object to map fields of the structure, must not be * null * @param binFieldFilter filter allows to exclude some fields from process, can be null * @param instantiators array of functions which can instantiate object of required class, must not be null * @return the same object from the arguments but with filled fields by values * of the structure * @since 2.0.4 */ @SafeVarargs public final T mapTo(final T objectToMap, final BinFieldFilter binFieldFilter, final Function, Object>... instantiators) { return this.mapTo(objectToMap, null, binFieldFilter, instantiators); } /** * Map the structure fields to object fields. * * @param expected result type * @param instance object instance to be filled by values, must not be null * @param flags special flags to tune mapping process * @param instantiators array of functions which can instantiate object of required class, must not be null * @return the same object from the arguments but with filled fields by values * of the structure * @see JBBPMapper#FLAG_IGNORE_MISSING_VALUES * @since 2.0.0 */ @SafeVarargs public final T mapTo(final T instance, final int flags, final Function, Object>... instantiators) { return this.mapTo(instance, (JBBPMapperCustomFieldProcessor) null, flags, instantiators); } /** * Map the structure fields to object fields. * * @param expected result type * @param instance object instance to be filled by values, must not be null * @param flags special flags to tune mapping process * @param binFieldFilter filter to exclude some fields from process, can be null * @param instantiators array of functions which can instantiate object of required class, must not be null * @return the same object from the arguments but with filled fields by values * of the structure * @see JBBPMapper#FLAG_IGNORE_MISSING_VALUES * @since 2.0.4 */ @SafeVarargs public final T mapTo(final T instance, final int flags, final BinFieldFilter binFieldFilter, final Function, Object>... instantiators) { return this.mapTo(instance, null, flags, binFieldFilter, instantiators); } /** * Map the structure fields to object fields. * * @param expected result type * @param instance an object to map fields of the structure, must not be * null * @param customFieldProcessor a custom field processor to provide values for * custom mapping fields, it can be null if there is not any custom field * @param instantiators array of functions which can instantiate object of required class, must not be null * @return the same object from the arguments but with filled fields by values * of the structure */ @SafeVarargs public final T mapTo(final T instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final Function, Object>... instantiators) { return JBBPMapper.map(this, instance, customFieldProcessor, instantiators); } /** * Map the structure fields to object fields. * * @param expected result type * @param instance an object to map fields of the structure, must not be * null * @param customFieldProcessor a custom field processor to provide values for * custom mapping fields, it can be null if there is not any custom field * @param binFieldFilter filter to exclude some fields, can be null * @param instantiators array of functions which can instantiate object of required class, must not be null * @return the same object from the arguments but with filled fields by values * of the structure * @since 2.0.4 */ @SafeVarargs public final T mapTo(final T instance, final JBBPMapperCustomFieldProcessor customFieldProcessor, final BinFieldFilter binFieldFilter, final Function, Object>... instantiators) { return JBBPMapper.map(this, instance, customFieldProcessor, 0, binFieldFilter, instantiators); } /** * Map the structure fields to object fields. * * @param expected result type * @param objectToMap an object to map fields of the structure, must not be * null * @param customFieldProcessor a custom field processor to provide values for * custom mapping fields, it can be null if there is not any custom field * @param flags special flags to tune mapping process * @param instantiators array of functions which can instantiate object of required class, must not be null * @return the same object from the arguments but with filled fields by values * of the structure * @see JBBPMapper#FLAG_IGNORE_MISSING_VALUES * @since 1.1 */ @SafeVarargs public final T mapTo(final T objectToMap, final JBBPMapperCustomFieldProcessor customFieldProcessor, final int flags, final Function, Object>... instantiators) { return JBBPMapper.map(this, objectToMap, customFieldProcessor, flags, instantiators); } /** * Map the structure fields to object fields. * * @param expected result type * @param objectToMap an object to map fields of the structure, must not be * null * @param customFieldProcessor a custom field processor to provide values for * custom mapping fields, it can be null if there is not any custom field * @param flags special flags to tune mapping process * @param binFieldFilter filter to exclude some fields, can be null * @param instantiators array of functions which can instantiate object of required class, must not be null * @return the same object from the arguments but with filled fields by values * of the structure * @see JBBPMapper#FLAG_IGNORE_MISSING_VALUES * @since 2.0.4 */ @SafeVarargs public final T mapTo(final T objectToMap, final JBBPMapperCustomFieldProcessor customFieldProcessor, final int flags, final BinFieldFilter binFieldFilter, final Function, Object>... instantiators) { return JBBPMapper.map(this, objectToMap, customFieldProcessor, flags, binFieldFilter, instantiators); } @Override public String getTypeAsString() { return "{}"; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldUByte.java000066400000000000000000000040551445000440600324550ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a unsigned byte value field. * * @since 1.0 */ public final class JBBPFieldUByte extends JBBPAbstractField implements JBBPNumericField { private static final long serialVersionUID = -1280322774219967406L; /** * Inside value storage. */ private final byte value; /** * The Constructor. * * @param name a field name info, it can be null * @param value the field value */ public JBBPFieldUByte(final JBBPNamedFieldInfo name, final byte value) { super(name); this.value = value; } /** * Get the reversed bit representation of the value. * * @param value the value to be reversed * @return the reversed value */ public static long reverseBits(final byte value) { return JBBPUtils.reverseBitsInByte(value) & 0xFF; } @Override public double getAsDouble() { return this.value & 0xFF; } @Override public float getAsFloat() { return (float) (this.value & 0xFF); } @Override public int getAsInt() { return this.value & 0xFF; } @Override public long getAsLong() { return this.getAsInt(); } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return reverseBits(value); } @Override public String getTypeAsString() { return "ubyte"; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldUInt.java000066400000000000000000000045221445000440600323030ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.exceptions.JBBPNumericFieldValueConversionException; import java.util.Locale; /** * Describes a unsigned integer (32 bit) field. * * @since 2.0.4 */ public final strictfp class JBBPFieldUInt extends JBBPAbstractField implements JBBPNumericField { public static final String TYPE_NAME = "uint"; private static final long serialVersionUID = 354342324375674L; /** * Internal field keeps the value. */ private final int value; /** * The Constructor. * * @param name a field name info, it can be null. * @param value the field value */ public JBBPFieldUInt(final JBBPNamedFieldInfo name, final long value) { super(name); this.value = (int) value; } @Override public int getAsInt() { if (this.value >= 0) { return this.value; } else { throw new JBBPNumericFieldValueConversionException(this, "UINT 0x" + (Long.toHexString(this.value & 0xFFFFFFFFL).toUpperCase( Locale.ENGLISH)) + " can't be represented as INT"); } } @Override public double getAsDouble() { return (double) this.getAsLong(); } @Override public float getAsFloat() { return (float) this.getAsLong(); } @Override public long getAsLong() { return ((long) this.value & 0xFFFFFFFFL); } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return reverseBits(this.value & 0xFFFFFFFFL); } public static long reverseBits(final long value) { return JBBPFieldInt.reverseBits((int) value) & 0xFFFFFFFFL; } @Override public String getTypeAsString() { return TYPE_NAME; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldUShort.java000066400000000000000000000043101445000440600326430ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.utils.JBBPUtils; /** * Describes a unsigned short value field. * * @since 1.0 */ public final class JBBPFieldUShort extends JBBPAbstractField implements JBBPNumericField { private static final long serialVersionUID = 8028734964961601006L; /** * Inside value storage. */ private final short value; /** * The Constructor. * * @param name a field name info, it can be null * @param value the field value */ public JBBPFieldUShort(final JBBPNamedFieldInfo name, final short value) { super(name); this.value = value; } /** * Get the reversed bit representation of the value. * * @param value the value to be reversed * @return the reversed value */ public static long reverseBits(final short value) { final int b0 = JBBPUtils.reverseBitsInByte((byte) value) & 0xFF; final int b1 = JBBPUtils.reverseBitsInByte((byte) (value >> 8)) & 0xFF; return (long) ((b0 << 8) | b1) & 0xFFFFL; } @Override public double getAsDouble() { return this.value & 0xFFFF; } @Override public float getAsFloat() { return (float) (this.value & 0xFFFF); } @Override public int getAsInt() { return this.value & 0xFFFF; } @Override public long getAsLong() { return this.getAsInt(); } @Override public boolean getAsBool() { return this.value != 0; } @Override public long getAsInvertedBitOrder() { return reverseBits(this.value); } @Override public String getTypeAsString() { return "ushort"; } } JBBPInvertableBitOrder.java000066400000000000000000000016211445000440600341240ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/* * Copyright 2018 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; /** * The Interface describes a field which bit order can be inverted. * * @since 1.4.0 */ public interface JBBPInvertableBitOrder { /** * Get the value in inverted bit order. * * @return the value in inverted bit order */ long getAsInvertedBitOrder(); } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPNamedField.java000066400000000000000000000017061445000440600324510ustar00rootroot00000000000000/* * Copyright 2018 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; /** * The Interface describes a named field. * * @since 1.4.0 */ public interface JBBPNamedField { /** * Get field name info for the field. * * @return the field name info, it can be null for anonymous fields */ JBBPNamedFieldInfo getNameInfo(); } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPNumericArray.java000066400000000000000000000023531445000440600330610ustar00rootroot00000000000000/* * Copyright 2018 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; /** * The Interface describes array which elements can be provided as number or boolean. * * @since 1.4.0 */ public interface JBBPNumericArray { /** * Get element as integer. * * @param index index of element in array * @return the element as integer */ int getAsInt(int index); /** * Get element as long. * * @param index index of element in array * @return the element as long */ long getAsLong(int index); /** * Get element as boolean. * * @param index index of element in array * @return the element as boolean */ boolean getAsBool(final int index); } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPNumericField.java000066400000000000000000000041561445000440600330310ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import com.igormaznitsa.jbbp.exceptions.JBBPNumericFieldValueConversionException; /** * The Interface describes a field which content can be represented as a numeric value. * * @since 1.0 */ public interface JBBPNumericField extends JBBPInvertableBitOrder, JBBPNamedField { /** * Get the field value as integer. * * @return the field value as integer * @throws JBBPNumericFieldValueConversionException can be thrown if impossible to represent saved value as int */ int getAsInt(); /** * Get the field value as double. * * @return the field value as double * @throws JBBPNumericFieldValueConversionException can be thrown if impossible to represent saved value as double * @since 1.4.0 */ double getAsDouble(); /** * Get the field value as float. * * @return the field value as float * @throws JBBPNumericFieldValueConversionException can be thrown if impossible to represent saved value as float * @since 1.4.0 */ float getAsFloat(); /** * Get the field value as long * * @return the field value as long * @throws JBBPNumericFieldValueConversionException can be thrown if impossible to represent saved value as long */ long getAsLong(); /** * Get the field value as boolean, usually if the value is 0 then false, true otherwise. * * @return the field value as boolean * @throws JBBPNumericFieldValueConversionException can be thrown if impossible to represent saved value as boolean */ boolean getAsBool(); } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/finder/000077500000000000000000000000001445000440600304235ustar00rootroot00000000000000JBBPFieldFinder.java000066400000000000000000000067401445000440600340270ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/model/finder/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model.finder; import com.igormaznitsa.jbbp.model.JBBPAbstractField; /** * The Interface describes a class which can provide search for its inside field storage. * * @since 1.0 */ public interface JBBPFieldFinder { /** * Find the first met field for defined class. Field should be exactly instance of the class, not a successor. * * @param the class of the field * @param fieldType the field class for search * @return found field or null */ T findFirstFieldForType(Class fieldType); /** * Find the last met field for defined class. Field should be exactly * instance of the class, not a successor. * * @param the class of the field * @param fieldType the field class for search, must not be null * @return found field or null */ T findLastFieldForType(Class fieldType); /** * Find unique field for defined class. Field should be exactly * instance of the class and the field must be only one or not be presented else an exception will be thrown. * * @param the class of the field * @param fieldType the field class for search, must not be null * @return found field or null */ T findFieldForType(Class fieldType); /** * Find a field for its name and type pair. * * @param the class of the field * @param fieldName the field name for search, must not be null * @param fieldType the field class for search, must not be null * @return found field or null */ T findFieldForNameAndType(String fieldName, Class fieldType); /** * Find a field for its path and type pair. * * @param the class of the field * @param fieldPath the field path for search, must not be null * @param fieldType the field class for search, must not be null * @return found field or null */ T findFieldForPathAndType(String fieldPath, Class fieldType); /** * Find a field for its name. * * @param fieldName the field name for search, it must not be null * @return found field or null */ JBBPAbstractField findFieldForName(String fieldName); /** * Find a field for its path. * * @param fieldPath the field path for search, it must not be null * @return found field or null */ JBBPAbstractField findFieldForPath(String fieldPath); /** * Check that a field exists for a name * * @param fieldName a field name to check, it must not be null * @return true if the field exists, false otherwise */ boolean nameExists(String fieldName); /** * Check that a file exists for path * * @param fieldPath a field path to check, it must not be null * @return true if the field exists, false otherwise */ boolean pathExists(String fieldPath); } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/000077500000000000000000000000001445000440600272145ustar00rootroot00000000000000BinAnnotationWrapper.java000066400000000000000000000101271445000440600341050ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utilspackage com.igormaznitsa.jbbp.utils; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import java.lang.annotation.Annotation; /** * Auxiliary class to replace Bin annotation field values. * Not thread safe! * * @since 2.0.2 */ public final class BinAnnotationWrapper implements Bin { private Bin bin; private String name; private String path; private String customType; private String arraySizeExpr; private BinType type; private JBBPBitOrder bitOrder; private Boolean custom; private String paramExpr; private JBBPBitNumber bitNumber; private JBBPByteOrder byteOrder; private Integer order; private String comment; public BinAnnotationWrapper() { } public BinAnnotationWrapper setWrapped(final Bin bin) { this.bin = bin; return this; } public BinAnnotationWrapper setName(final String value) { this.name = value; return this; } @Override public String name() { assert this.bin != null; return this.name == null ? this.bin.name() : this.name; } public BinAnnotationWrapper setPath(final String value) { this.path = value; return this; } @Override public String path() { assert this.bin != null; return this.path == null ? this.bin.path() : this.path; } public BinAnnotationWrapper setCustomType(final String value) { this.customType = value; return this; } @Override public String customType() { assert this.bin != null; return this.customType == null ? this.bin.customType() : this.customType; } public BinAnnotationWrapper setArraySizeExpr(final String value) { this.arraySizeExpr = value; return this; } @Override public String arraySizeExpr() { assert this.bin != null; return this.arraySizeExpr == null ? this.bin.arraySizeExpr() : this.arraySizeExpr; } public BinAnnotationWrapper setType(final BinType value) { this.type = value; return this; } @Override public BinType type() { assert this.bin != null; return this.type == null ? this.bin.type() : this.type; } public BinAnnotationWrapper setBitOrder(final JBBPBitOrder value) { this.bitOrder = value; return this; } @Override public JBBPBitOrder bitOrder() { assert this.bin != null; return this.bitOrder == null ? this.bin.bitOrder() : this.bitOrder; } public BinAnnotationWrapper setCustom(final Boolean value) { this.custom = value; return this; } @Override public boolean custom() { assert this.bin != null; return this.custom == null ? this.bin.custom() : this.custom; } public BinAnnotationWrapper setParamExpr(final String value) { this.paramExpr = value; return this; } @Override public String paramExpr() { assert this.bin != null; return this.paramExpr == null ? this.bin.paramExpr() : this.paramExpr; } public BinAnnotationWrapper setBitNumber(final JBBPBitNumber value) { this.bitNumber = value; return this; } @Override public JBBPBitNumber bitNumber() { assert this.bin != null; return this.bitNumber == null ? this.bin.bitNumber() : this.bitNumber; } public BinAnnotationWrapper setByteOrder(final JBBPByteOrder value) { this.byteOrder = value; return this; } @Override public JBBPByteOrder byteOrder() { assert this.bin != null; return this.byteOrder == null ? this.bin.byteOrder() : this.byteOrder; } public BinAnnotationWrapper setOrder(final Integer value) { this.order = value; return this; } @Override public int order() { assert this.bin != null; return this.order == null ? this.bin.order() : this.order; } public BinAnnotationWrapper setComment(final String value) { this.comment = value; return this; } @Override public String comment() { assert this.bin != null; return this.comment == null ? this.bin.comment() : this.comment; } @Override public Class annotationType() { return Bin.class; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/Function.java000066400000000000000000000015651445000440600316530ustar00rootroot00000000000000/* * Copyright 2019 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; /** * Auxiliary functional interface. Added to support Android 3. * * @param the type of the input to the function * @param the type of the result of the function * @since 2.0.0 */ public interface Function { R apply(T t); } JBBPCustomFieldTypeProcessorAggregator.java000066400000000000000000000061651445000440600374310ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; import static com.igormaznitsa.jbbp.utils.JBBPUtils.ARRAY_STRING_EMPTY; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** * The Aggregator allows to join several custom field type processors. * * @since 1.2.0 */ public class JBBPCustomFieldTypeProcessorAggregator implements JBBPCustomFieldTypeProcessor { private final Map customTypeMap; private final String[] types; /** * Constructor. * * @param processors processors which should be joined. */ public JBBPCustomFieldTypeProcessorAggregator(final JBBPCustomFieldTypeProcessor... processors) { this.customTypeMap = new HashMap<>(); for (final JBBPCustomFieldTypeProcessor p : processors) { for (final String s : p.getCustomFieldTypes()) { JBBPUtils.assertNotNull(s, "Type must not be null"); if (this.customTypeMap.containsKey(s)) { throw new IllegalArgumentException("Detected duplicated field type [" + s + ']'); } this.customTypeMap.put(s, p); } } this.types = this.customTypeMap.keySet().toArray(ARRAY_STRING_EMPTY); } @Override public String[] getCustomFieldTypes() { return this.types; } @Override public boolean isAllowed(final JBBPFieldTypeParameterContainer fieldType, final String fieldName, final int extraData, final boolean isArray) { return this.customTypeMap.get(fieldType.getTypeName()) .isAllowed(fieldType, fieldName, extraData, isArray); } @Override public JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, int parserFlags, JBBPFieldTypeParameterContainer fieldType, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException { return this.customTypeMap.get(fieldType.getTypeName()) .readCustomFieldType(in, bitOrder, parserFlags, fieldType, fieldName, extraData, readWholeStream, arrayLength); } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPDslBuilder.java000066400000000000000000002161601445000440600325540ustar00rootroot00000000000000package com.igormaznitsa.jbbp.utils; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Locale; /** * Auxiliary builder to build string JBBP script through sequent method call. * NB! The Builder generates JBBP string script which can be compiled by parser! * * @see com.igormaznitsa.jbbp.JBBPParser * @since 1.4.0 */ public class JBBPDslBuilder { /** * The List contains items added into builder. */ protected final List items = new ArrayList<>(); /** * The Variable contains current byte order for all next fields. */ protected JBBPByteOrder byteOrder = JBBPByteOrder.BIG_ENDIAN; /** * Value contains number of currently opened structures. */ protected int openedStructCounter; /** * Constructor is private one because can't be called directly. */ protected JBBPDslBuilder() { } /** * Create new builder. * * @return the new instance of builder, must not be null */ public static JBBPDslBuilder Begin() { return new JBBPDslBuilder(); } protected static String assertTextNotNullAndTrimmedNotEmpty(final String text) { if (text == null) { throw new NullPointerException("Must not be null"); } if (text.trim().length() == 0) { throw new IllegalArgumentException("Must not be empty: " + text); } return text; } protected static String assertNameIfNotNull(final String name) { if (name != null) { if (name.equals("_")) { throw new IllegalArgumentException("Only underscore is not allowed as name"); } for (int i = 0; i < name.length(); i++) { final char c = name.charAt(i); if (i == 0 && Character.isDigit(c)) { throw new IllegalArgumentException("Digit can't be first char"); } if (!Character.isLetterOrDigit(c) && c != '_') { throw new IllegalArgumentException( "Only letters, digits and underscore allowed: " + name); } } } return name; } protected static String assertExpressionChars(final String expression) { if (expression == null) { throw new NullPointerException("Expression is null"); } if (expression.trim().length() == 0) { throw new IllegalArgumentException("Expression is empty"); } if (expression.contains("//")) { throw new IllegalArgumentException("Comment is not allowed"); } for (final char c : expression.toCharArray()) { switch (c) { case '\"': case ':': case ';': case '{': case '}': case '[': case ']': throw new IllegalArgumentException("Char is not allowed: " + c); } } return expression; } protected static int assertNotNegativeAndZero(final int value) { if (value == 0) { throw new IllegalArgumentException("must not be 0"); } if (value < 0) { throw new IllegalArgumentException("must not be negative"); } return value; } protected static StringBuilder doTabs(final boolean enable, final StringBuilder buffer, int tabs) { if (enable) { while (tabs > 0) { buffer.append('\t'); tabs--; } } return buffer; } protected void addItem(final Item item) { if (item instanceof ItemComment || item.name == null || item.name.length() == 0) { this.items.add(item); } else { int structCounter = 0; for (int i = this.items.size() - 1; i >= 0; i--) { final Item itm = this.items.get(i); if (itm instanceof ItemComment) { continue; } if (itm.type == BinType.STRUCT || itm.type == BinType.STRUCT_ARRAY) { if (structCounter == 0) { break; } structCounter++; } else if (itm instanceof ItemStructEnd) { structCounter--; } if (structCounter == 0) { final String thatName = itm.name; if (thatName != null && thatName.length() > 0 && item.name.equalsIgnoreCase(thatName)) { throw new IllegalArgumentException("Duplicated item name '" + item.name + '\''); } } } this.items.add(item); } } /** * Get number of items added into internal item list. * * @return number of added items */ public int size() { return this.items.size(); } /** * Auxiliary method to convert numeric value into array size expression. * * @param size size to be converted * @return string contains expression which can be used as array size. must not be null */ protected String arraySizeToString(final int size) { return size < 0 ? "_" : Integer.toString(size); } /** * Add 'align' directive with default value (1 byte). * * @return the builder instance, must not be null */ public JBBPDslBuilder Align() { return this.Align(1); } /** * Add 'align' directive. * * @param alignBytes number of bytes to be aligned, must not be zero or negative one * @return the builder instance, must not be null * @throws IllegalArgumentException if value is not acceptable */ public JBBPDslBuilder Align(final int alignBytes) { this.Align(Integer.toString(assertNotNegativeAndZero(alignBytes))); return this; } /** * Add 'align' directive with size calculated by expression. * * @param sizeExpression expression to be calculated to get align size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder Align(final String sizeExpression) { this.addItem(new ItemAlign(assertExpressionChars(sizeExpression))); return this; } /** * Add 'skip' directive with default value as one byte. * * @return the builder instance, must not be null */ public JBBPDslBuilder Skip() { return this.Skip(1); } /** * Add 'skip' directive with number of bytes to be skipped. * * @param bytesToSkip number of bytes to be skipped, must not be zero or negative one * @return the builder instance, must not be null * @throws IllegalArgumentException if value is not acceptable */ public JBBPDslBuilder Skip(final int bytesToSkip) { this.Skip(Integer.toString(assertNotNegativeAndZero(bytesToSkip))); return this; } /** * Add 'skip' directive with number of bytes to be skipped, their number calculated through expression. * * @param sizeExpression expression to be used to calculate number of bytes, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder Skip(final String sizeExpression) { this.addItem(new ItemSkip(assertExpressionChars(sizeExpression))); return this; } /** * Add anonymous custom variable. * * @param type custom type, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder Custom(final String type) { return this.Custom(type, null, null); } /** * Add var anonymous field. * * @return the builder instance, must not be null */ public JBBPDslBuilder Var() { return this.Var(null); } /** * Add named var field. * * @param name name of the field, can be null * @return the builder instance, must not be null */ public JBBPDslBuilder Var(final String name) { return this.Var(name, null); } /** * Add named var field. * * @param name name of the field, can be null * @param param optional parameter for the field, can be null * @return the builder instance, must not be null */ public JBBPDslBuilder Var(final String name, final String param) { return this.Custom("var", name, param); } /** * Add named custom variable. * * @param type custom type, must not be null * @param name name of the field, can be null for anonymous * @return the builder instance, must not be null */ public JBBPDslBuilder Custom(final String type, final String name) { return this.Custom(type, name, null); } /** * Add named parametric custom variable. * * @param type custom type, must not be null * @param name name of the field, can be null for anonymous * @param param optional parameter for the field, can be null * @return the builder instance, must not be null */ public JBBPDslBuilder Custom(final String type, final String name, final String param) { final ItemCustom custom = new ItemCustom(type, name, this.byteOrder); custom.bitLenExpression = param == null ? null : assertExpressionChars(param); this.addItem(custom); return this; } /** * Create new anonymous struct. * * @return the builder instance, must not be null */ public JBBPDslBuilder Struct() { return this.Struct(null); } /** * Create new named struct. * * @param name name of structure, it can be null for anonymous one * @return the builder instance, must not be null */ public JBBPDslBuilder Struct(final String name) { final Item item = new Item(BinType.STRUCT, name, this.byteOrder); this.addItem(item); this.openedStructCounter++; return this; } /** * Create anonymous structure array which size calculated by expression. * * @param sizeExpression expression to calculate array length, must not be null. * @return the builder instance, must not be null */ public JBBPDslBuilder StructArray(final String sizeExpression) { return this.StructArray(null, assertExpressionChars(sizeExpression)); } /** * Create anonymous fixed size structure array. * * @param size fixed size of the array, if negative one then read all steam until the end * @return the builder instance, must not be null */ public JBBPDslBuilder StructArray(final int size) { return this.StructArray(null, arraySizeToString(size)); } /** * Create named fixed length structure array. * * @param name name of the structure array, can be null for anonymous one * @param size fixed size of the array, if negative one then read all steam until the end * @return the builder instance, must not be null */ public JBBPDslBuilder StructArray(final String name, final int size) { return this.StructArray(name, arraySizeToString(size)); } /** * Create named structure array which size calculated by expression. * * @param name name of the structure array, can be null for anonymous one * @param sizeExpression expression to calculate array length, must not be null. * @return the builder instance, must not be null */ public JBBPDslBuilder StructArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.STRUCT_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); this.openedStructCounter++; return this; } /** * Add virtual field which not be read but its value will be calculated by expression * * @param name name of the value, it must not be null or empty * @param expression expression to calculate value, it must not be null or empty * @return the builder instance, must not be null */ public JBBPDslBuilder Val(final String name, final String expression) { final Item item = new ItemVal( assertTextNotNullAndTrimmedNotEmpty(name), assertExpressionChars(assertTextNotNullAndTrimmedNotEmpty(expression)).trim() ); this.addItem(item); return this; } /** * Add command to reset counter of the current read stream. * * @return the builder instance, must not be null * @see JBBPBitInputStream#resetCounter() */ public JBBPDslBuilder ResetCounter() { final Item item = new ItemResetCounter(); this.addItem(item); return this; } /** * Create anonymous var array with fixed size. * * @param size size of the array, if negative then read till end of stream. * @return the builder instance, must not be null */ public JBBPDslBuilder VarArray(final int size) { return this.VarArray(null, arraySizeToString(size), null); } /** * Create anonymous var array with size calculated through expression. * * @param sizeExpression expression to calculate size. * @return the builder instance, must not be null */ public JBBPDslBuilder VarArray(final String sizeExpression) { return this.VarArray(null, sizeExpression); } /** * Create named var array with fixed size. * * @param name name of the array, can be null for anonymous one * @param size size of the array, if negative then read till end of stream. * @return the builder instance, must not be null */ public JBBPDslBuilder VarArray(final String name, final int size) { return this.VarArray(name, arraySizeToString(size), null); } /** * Create named var array with fixed size. * * @param name name of the array, can be null for anonymous one * @param sizeExpression expression to calculate size of the array, must not be null. * @return the builder instance, must not be null */ public JBBPDslBuilder VarArray(final String name, final String sizeExpression) { return this.VarArray(name, sizeExpression, null); } /** * Create named var array with fixed size. * * @param name name of the array, can be null for anonymous one * @param size size of array, it can be negative if to read stream till end. * @param param optional parameter for the field, can be null * @return the builder instance, must not be null */ public JBBPDslBuilder VarArray(final String name, final int size, final String param) { return this.VarArray(name, arraySizeToString(size), param); } /** * Create named var array with fixed size. * * @param name name of the array, can be null for anonymous one * @param sizeExpression expression to calculate size of the array, must not be null. * @param param optional parameter for the field, can be null * @return the builder instance, must not be null */ public JBBPDslBuilder VarArray(final String name, final String sizeExpression, final String param) { return this.CustomArray("var", name, sizeExpression, param); } /** * Create anonymous custom type array with fixed size. * * @param type custom type, must not be null * @param size size of the array, if less than zero then read till end of stream. * @return the builder instance, must not be null */ public JBBPDslBuilder CustomArray(final String type, final int size) { return this.CustomArray(type, arraySizeToString(size)); } /** * Create anonymous custom type array with fixed size. * * @param type custom type, must not be null * @param size expression to calculate size of the array, must not be null. * @return the builder instance, must not be null */ public JBBPDslBuilder CustomArray(final String type, final String size) { return this.CustomArray(type, null, size, null); } /** * Create named custom type array with fixed size. * * @param type custom type, must not be null * @param name name of the array, can be null for anonymous one * @param size expression to calculate size of the array, must not be null. * @return the builder instance, must not be null */ public JBBPDslBuilder CustomArray(final String type, final String name, final String size) { return this.CustomArray(type, name, size, null); } /** * Create named custom type array with fixed size. * * @param type custom type, must not be null * @param name name of the array, can be null for anonymous one * @param size size of he array, if less than zero then read till end of stream. * @return the builder instance, must not be null */ public JBBPDslBuilder CustomArray(final String type, final String name, final int size) { return this.CustomArray(type, name, arraySizeToString(size), null); } /** * Create named custom type array which size calculated by expression. * * @param type custom type, must not be null * @param name name of the array, can be null for anonymous one * @param size size of the array, if negative then read till the end of stream. * @param param optional parameter for the field, can be null * @return the builder instance, must not be null */ public JBBPDslBuilder CustomArray(final String type, final String name, final int size, final String param) { return this.CustomArray(type, name, arraySizeToString(size), param); } /** * Create named custom type array which size calculated by expression. * * @param type custom type, must not be null * @param name name of the array, can be null for anonymous one * @param sizeExpression expression to calculate array length, must not be null. * @param param optional parameter for the field, can be null * @return the builder instance, must not be null */ public JBBPDslBuilder CustomArray(final String type, final String name, final String sizeExpression, final String param) { final ItemCustom item = new ItemCustom(type, name, this.byteOrder); item.array = true; item.bitLenExpression = param == null ? null : assertExpressionChars(param); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add directive to close currently opened structure or a structure array. * * @return the builder instance, must not be null * @throws IllegalStateException if there is not any opened struct */ public JBBPDslBuilder CloseStruct() { return this.CloseStruct(false); } /** * Add directive to close currently opened structure or all opened structures. * * @param closeAllOpened flag to close all opened structures if true, false if close only last opened structure * @return the builder instance, must not be null * @throws IllegalStateException if there is not any opened struct */ public JBBPDslBuilder CloseStruct(final boolean closeAllOpened) { if (this.openedStructCounter == 0) { throw new IllegalStateException("There is not any opened struct"); } this.addItem(new ItemStructEnd(closeAllOpened)); this.openedStructCounter = closeAllOpened ? 0 : this.openedStructCounter - 1; return this; } /** * Allows to check that there is an opened structure. * * @return true if there is any opened structure, false otherwise. */ public boolean hasOpenedStructs() { return this.openedStructCounter > 0; } /** * Add anonymous single bit field. * * @return the builder instance, must not be null */ public JBBPDslBuilder Bit() { return this.Bits(null, JBBPBitNumber.BITS_1); } /** * Add named single bit field. * * @param name name of the field, can be null for anonymous one * @return the builder instance, must not be null */ public JBBPDslBuilder Bit(final String name) { return this.Bits(name, JBBPBitNumber.BITS_1); } /** * Add anonymous fixed size bit field. * * @param bits, number of bits 1..7 * @return the builder instance, must not be null */ public JBBPDslBuilder Bits(final int bits) { return this.Bits(null, JBBPBitNumber.decode(bits)); } /** * Add anonymous fixed size bit field. * * @param bits, number of bits * @return the builder instance, must not be null */ public JBBPDslBuilder Bits(final JBBPBitNumber bits) { return this.Bits(null, bits); } /** * Add named fixed length bit field. * * @param name name of the field, if null then anonymous one * @param bits number of bits as length of the field, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder Bits(final String name, final JBBPBitNumber bits) { final Item item = new Item(BinType.BIT, name, this.byteOrder); item.bitNumber = bits; this.addItem(item); return this; } /** * Add named bit field which length calculated by expression. * * @param name name of the field, if null then anonymous one * @param bitLenExpression expression to calculate number of bits, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder Bits(final String name, final String bitLenExpression) { final Item item = new Item(BinType.BIT, name, this.byteOrder); item.bitLenExpression = assertExpressionChars(bitLenExpression); this.addItem(item); return this; } /** * Add anonymous fixed length bit array. * * @param bits length of the field, must not be null * @param size number of elements in array, if negative then till the end of stream * @return the builder instance, must not be null */ public JBBPDslBuilder BitArray(final JBBPBitNumber bits, final int size) { return this.BitArray(null, bits, arraySizeToString(size)); } /** * Add named fixed length bit array. * * @param name name of the array, if null then anonymous one * @param bits length of the field, must not be null * @param size number of elements in array, if negative then till the end of stream * @return the builder instance, must not be null */ public JBBPDslBuilder BitArray(final String name, final JBBPBitNumber bits, final int size) { return this.BitArray(name, bits, arraySizeToString(size)); } /** * Add named fixed length bit array, size of one bit field is calculated by expression. * * @param name name of the array, if null then anonymous one * @param bitLenExpression expression to calculate length of the bit field, must not be null * @param size number of elements in array, if negative then till the end of stream * @return the builder instance, must not be null */ public JBBPDslBuilder BitArray(final String name, final String bitLenExpression, final int size) { return this.BitArray(name, bitLenExpression, arraySizeToString(size)); } /** * Add anonymous bit array with size calculated through expression. * * @param bits length of the field, must not be null * @param sizeExpression expression to be used to calculate array size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder BitArray(final JBBPBitNumber bits, final String sizeExpression) { return this.BitArray(null, bits, assertExpressionChars(sizeExpression)); } /** * Add anonymous bit array with size calculated through expression. * * @param bitLenExpression expression to calculate length of the bit field, must not be null * @param sizeExpression expression to be used to calculate array size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder BitArray(final String bitLenExpression, final String sizeExpression) { return this.BitArray(null, bitLenExpression, assertExpressionChars(sizeExpression)); } /** * Add named bit array with size calculated through expression. * * @param name name of the array, if null then anonymous one * @param bits length of the field, must not be null * @param sizeExpression expression to be used to calculate array size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder BitArray(final String name, final JBBPBitNumber bits, final String sizeExpression) { final Item item = new Item(BinType.BIT_ARRAY, name, this.byteOrder); item.bitNumber = bits; item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add named bit array where each bit length is calculated through expression. * * @param name name of the array, if null then anonymous one * @param bitLenExpression expression to calculate length of the bit field, must not be null * @param sizeExpression expression to be used to calculate array size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder BitArray(final String name, final String bitLenExpression, final String sizeExpression) { final Item item = new Item(BinType.BIT_ARRAY, name, this.byteOrder); item.bitLenExpression = assertExpressionChars(bitLenExpression); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add anonymous boolean array with size calculated through expression. * * @param sizeExpression expression to be used to calculate array size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder BoolArray(final String sizeExpression) { return this.BoolArray(null, assertExpressionChars(sizeExpression)); } /** * Add named fixed size boolean array. * * @param name name of the array, it can be null for anonymous one * @param size number of elements in array, if negative then till the end of stream * @return the builder instance, must not be null */ public JBBPDslBuilder BoolArray(final String name, final int size) { return this.BoolArray(name, arraySizeToString(size)); } /** * Add anonymous fixed size boolean array. * * @param size number of elements in array, if negative then till the end of stream * @return the builder instance, must not be null */ public JBBPDslBuilder BoolArray(final int size) { return this.BoolArray(null, arraySizeToString(size)); } /** * Add named boolean array which length calculated through expression. * * @param name name of the array, it can be null for anonymous one * @param sizeExpression expression to calculate number of elements, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder BoolArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.BOOL_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add an anonymous boolean field. * * @return the builder instance, must not be null */ public JBBPDslBuilder Bool() { return this.Bool(null); } /** * Add named boolean field. * * @param name name of the field, can be null for anonymous one * @return the builder instance, must not be null */ public JBBPDslBuilder Bool(final String name) { final Item item = new Item(BinType.BOOL, name, this.byteOrder); this.addItem(item); return this; } /** * Add anonymous signed byte field. * * @return the builder instance, must not be null */ public JBBPDslBuilder Byte() { return this.Byte(null); } /** * Add named signed byte field. * * @param name name of the field, can be null for anonymous one * @return the builder instance, must not be null */ public JBBPDslBuilder Byte(final String name) { final Item item = new Item(BinType.BYTE, name, this.byteOrder); this.addItem(item); return this; } /** * Add anonymous byte array which size calculated through expression. * * @param sizeExpression expression to be used to calculate array length, must not be null or empty. * @return the builder instance, must not be null */ public JBBPDslBuilder ByteArray(final String sizeExpression) { return this.ByteArray(null, assertExpressionChars(sizeExpression)); } /** * Add anonymous fixed size byte array. * * @param size size of the array, if negative then read stream till the end. * @return the builder instance, must not be null */ public JBBPDslBuilder ByteArray(final int size) { return this.ByteArray(null, arraySizeToString(size)); } /** * Add named fixed size byte array. * * @param name name of the array, it can be null fo anonymous fields * @param size size of the array, if negative then read stream till the end. * @return the builder instance, must not be null */ public JBBPDslBuilder ByteArray(final String name, final int size) { return this.ByteArray(name, arraySizeToString(size)); } /** * Add named byte array which size calculated through expression. * * @param name name of the array, it can be null for anonymous fields * @param sizeExpression expression to be used to calculate array length, must not be null or empty. * @return the builder instance, must not be null */ public JBBPDslBuilder ByteArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.BYTE_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add anonymous unsigned byte field. * * @return the builder instance, must not be null */ public JBBPDslBuilder UByte() { return this.UByte(null); } /** * Add named unsigned byte field. * * @param name name of the field, can be null for anonymous one * @return the builder instance, must not be null */ public JBBPDslBuilder UByte(final String name) { final Item item = new Item(BinType.UBYTE, name, this.byteOrder); this.addItem(item); return this; } /** * Added anonymous unsigned byte array which size calculated through expression. * * @param sizeExpression expression to be used to calculate size, must not be null or empty * @return the builder instance, must not be null */ public JBBPDslBuilder UByteArray(final String sizeExpression) { return this.UByteArray(null, assertExpressionChars(sizeExpression)); } /** * Add anonymous fixed size unsigned byte array. * * @param size size of the array, if negative then read stream till the end. * @return the builder instance, must not be null */ public JBBPDslBuilder UByteArray(final int size) { return this.UByteArray(null, arraySizeToString(size)); } /** * Add named fixed size unsigned byte array. * * @param name name of the field, it can be null for anonymous one * @param size size of the array, if negative then read stream till the end. * @return the builder instance, must not be null */ public JBBPDslBuilder UByteArray(final String name, final int size) { return this.UByteArray(name, arraySizeToString(size)); } /** * Add named unsigned byte array which size calculated through expression. * * @param name name of the field, it can be null for anonymous one * @param sizeExpression expression to calculate array size, must ot be null or empty. * @return the builder instance, must not be null */ public JBBPDslBuilder UByteArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.UBYTE_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Added anonymous signed short field. * * @return the builder instance, must not be null */ public JBBPDslBuilder Short() { return this.Short(null); } /** * Add named signed short field. * * @param name name of the field, can be null for anonymous one * @return the builder instance, must not be null */ public JBBPDslBuilder Short(final String name) { final Item item = new Item(BinType.SHORT, name, this.byteOrder); this.addItem(item); return this; } /** * Add anonymous signed short array which size calculated through expression. * * @param sizeExpression expression to be used for calculation, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder ShortArray(final String sizeExpression) { return this.ShortArray(null, assertExpressionChars(sizeExpression)); } /** * Add anonymous fixed size signed short array. * * @param size size of the array, if negative then stream will be read till end * @return the builder instance, must not be null */ public JBBPDslBuilder ShortArray(final int size) { return this.ShortArray(null, arraySizeToString(size)); } /** * Add named fixed size signed short array. * * @param name name of the field, if null then anonymous * @param size size of the array, if negative then stream will be read till end * @return the builder instance, must not be null */ public JBBPDslBuilder ShortArray(final String name, final int size) { return this.ShortArray(name, arraySizeToString(size)); } /** * Add named fixed signed short array which size calculated through expression. * * @param name name of the field, if null then anonymous * @param sizeExpression expression to be used to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder ShortArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.SHORT_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add anonymous unsigned short field. * * @return the builder instance, must not be null */ public JBBPDslBuilder UShort() { return this.UShort(null); } /** * Add named unsigned short field. * * @param name name of the field, can be null for anonymous * @return the builder instance, must not be null */ public JBBPDslBuilder UShort(final String name) { final Item item = new Item(BinType.USHORT, name, this.byteOrder); this.addItem(item); return this; } /** * Add anonymous unsigned short array which size calculated through expression. * * @param sizeExpression expression to be used for calculation, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder UShortArray(final String sizeExpression) { return this.UShortArray(null, assertExpressionChars(sizeExpression)); } /** * Add anonymous fixed size unsigned short array. * * @param size sizeof the array, if negative then read till the end of stream * @return the builder instance, must not be null */ public JBBPDslBuilder UShortArray(final int size) { return this.UShortArray(null, arraySizeToString(size)); } /** * Add named fixed unsigned short array which size calculated through expression. * * @param name name of the field, if null then anonymous * @param sizeExpression expression to be used to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder UShortArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.USHORT_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add named fixed size unsigned short array. * * @param name name of the field, if null then anonymous * @param size size of the array, if negative then stream will be read till end * @return the builder instance, must not be null */ public JBBPDslBuilder UShortArray(final String name, final int size) { return this.UShortArray(name, arraySizeToString(size)); } /** * Add anonymous integer field. * * @return the builder instance, must not be null */ public JBBPDslBuilder Int() { return this.Int(null); } /** * Add named integer field. * * @param name name of the field, can be null for anonymous * @return the builder instance, must not be null */ public JBBPDslBuilder Int(final String name) { final Item item = new Item(BinType.INT, name, this.byteOrder); this.addItem(item); return this; } /** * Add anonymous integer array with size calculated through expression. * * @param sizeExpression expression to be used to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder IntArray(final String sizeExpression) { return this.IntArray(null, assertExpressionChars(sizeExpression)); } /** * Add anonymous fixed size integer array. * * @param size size of the array, if negative then read stream till the end * @return the builder instance, must not be null */ public JBBPDslBuilder IntArray(final int size) { return this.IntArray(null, arraySizeToString(size)); } /** * Add named fixed size integer array. * * @param name name of field, it can be null for anonymous * @param size size of the array, if negative then read stream till the end * @return the builder instance, must not be null */ public JBBPDslBuilder IntArray(final String name, final int size) { return this.IntArray(name, arraySizeToString(size)); } /** * Add named integer array with size calculated through expression. * * @param name name of field, can be nul for anonymous * @param sizeExpression expression to be used to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder IntArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.INT_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add anonymous long field. * * @return the builder instance, must not be null */ public JBBPDslBuilder Long() { return this.Long(null); } /** * Add named long field. * * @param name name of the field, can be null for anonymous * @return the builder instance, must not be null */ public JBBPDslBuilder Long(final String name) { final Item item = new Item(BinType.LONG, name, this.byteOrder); this.addItem(item); return this; } /** * Add anonymous long array which size calculated through expression. * * @param sizeExpression expression to be used to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder LongArray(final String sizeExpression) { return this.LongArray(null, assertExpressionChars(sizeExpression)); } /** * Add anonymous fixed size long array. * * @param size size of array, if negative then read till stream end * @return the builder instance, must not be null */ public JBBPDslBuilder LongArray(final int size) { return this.LongArray(null, arraySizeToString(size)); } /** * Add named fixed size long array field. * * @param name name of field, can be null for anonymous * @param size size of array, if negative then read till stream end * @return the builder instance, must not be null */ public JBBPDslBuilder LongArray(final String name, final int size) { return this.LongArray(name, arraySizeToString(size)); } /** * Add named long array which size calculated through expression. * * @param name name of the field, can be null for anonymous * @param sizeExpression expression to be used to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder LongArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.LONG_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add anonymous float field. * * @return the builder instance, must not be null */ public JBBPDslBuilder Float() { return this.Float(null); } /** * Add named float field * * @param name name of the field, can be null for anonymous * @return the builder instance, must not be null */ public JBBPDslBuilder Float(final String name) { final Item item = new Item(BinType.FLOAT, name, this.byteOrder); this.addItem(item); return this; } /** * Add anonymous float array which size calculated through expression. * * @param sizeExpression expression to be used to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder FloatArray(final String sizeExpression) { return this.FloatArray(null, assertExpressionChars(sizeExpression)); } /** * Add anonymous fixed size float array. * * @param size size of array, if negative then read till stream end * @return the builder instance, must not be null */ public JBBPDslBuilder FloatArray(final int size) { return this.FloatArray(null, arraySizeToString(size)); } /** * Add named fixed size float array. * * @param name name of field, null for anonymous * @param size size of array, read till stream end if negative * @return the builder instance, must not be null */ public JBBPDslBuilder FloatArray(final String name, final int size) { return this.FloatArray(name, arraySizeToString(size)); } /** * Add named float array which size calculated through expression. * * @param name name of the field, can be null for anonymous * @param sizeExpression expression to be used to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder FloatArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.FLOAT_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add anonymous double field. * * @return the builder instance, must not be null */ public JBBPDslBuilder Double() { return this.Double(null); } /** * Add anonymous unsigned int field. * * @return the builder instance, must not be null * @since 2.0.4 */ public JBBPDslBuilder UInt() { return this.UInt(null); } /** * Add named double field. * * @param name name of the field, can be null for anonymous * @return the builder instance, must not be null */ public JBBPDslBuilder Double(final String name) { final Item item = new Item(BinType.DOUBLE, name, this.byteOrder); this.addItem(item); return this; } /** * Add named unsigned int field. * * @param name name of the field, can be null for anonymous * @return the builder instance, must not be null * @since 2.0.4 */ public JBBPDslBuilder UInt(final String name) { final Item item = new Item(BinType.UINT, name, this.byteOrder); this.addItem(item); return this; } /** * Add comment, in case that a field followed by the comment, the comment will be placed on the same line as field definition. * * @param text text of comment, can be null * @return the builder instance, must not be null */ public JBBPDslBuilder Comment(final String text) { this.addItem(new ItemComment(text == null ? "" : text, false)); return this; } /** * Add comment which will be placed on new line. * * @param text text of comment, can be null * @return the builder instance, must not be null * @since 1.4.1 */ public JBBPDslBuilder NewLineComment(final String text) { this.addItem(new ItemComment(text == null ? "" : text, true)); return this; } /** * Add anonymous double array field which size calculated trough expression. * * @param sizeExpression expression to be used to calculate array size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder DoubleArray(final String sizeExpression) { return this.DoubleArray(null, assertExpressionChars(sizeExpression)); } /** * Add anonymous fixed size double array field. * * @param size size of the array, if negative then read till end of stream * @return the builder instance, must not be null */ public JBBPDslBuilder DoubleArray(final int size) { return this.DoubleArray(null, arraySizeToString(size)); } /** * Add anonymous fixed size unsigned int array field. * * @param size size of the array, if negative then read till end of stream * @return the builder instance, must not be null * @since 2.0.4 */ public JBBPDslBuilder UIntArray(final int size) { return this.UIntArray(null, arraySizeToString(size)); } /** * Add named fixed size double array field. * * @param name ame of the field, can be null for anonymous * @param size size of the array, if negative then read till end of stream * @return the builder instance, must not be null */ public JBBPDslBuilder DoubleArray(final String name, final int size) { return this.DoubleArray(name, arraySizeToString(size)); } /** * Add named unsigned integer array field which size calculated trough expression. * * @param name name of the field, can be null for anonymous * @param sizeExpression expression to be used to calculate array size, must not be null * @return the builder instance, must not be null * @since 2.0.4 */ public JBBPDslBuilder UIntArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.UINT_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add named double array field which size calculated trough expression. * * @param name name of the field, can be null for anonymous * @param sizeExpression expression to be used to calculate array size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder DoubleArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.DOUBLE_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Add named fixed size unsigned int array field. * * @param name ame of the field, can be null for anonymous * @param size size of the array, if negative then read till end of stream * @return the builder instance, must not be null * @since 2.0.4 */ public JBBPDslBuilder UIntArray(final String name, final int size) { return this.UIntArray(name, arraySizeToString(size)); } /** * Add anonymous string field. * * @return the builder instance, must not be null */ public JBBPDslBuilder String() { return this.String(null); } /** * Add named string field. * * @param name name of field, can be null for anonymous one * @return the builder instance, must not be null */ public JBBPDslBuilder String(final String name) { final Item item = new Item(BinType.STRING, name, this.byteOrder); this.addItem(item); return this; } /** * Add anonymous string array which size calculated through expression. * * @param sizeExpression expression to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder StringArray(final String sizeExpression) { return this.StringArray(null, assertExpressionChars(sizeExpression)); } /** * Add anonymous fixed size string array. * * @param size size of array, if negative then read till stream end * @return the builder instance, must not be null */ public JBBPDslBuilder StringArray(final int size) { return this.StringArray(null, arraySizeToString(size)); } /** * Add named fixed size string array. * * @param name name of field, can be null for anonymous * @param size size of array, if negative then read till stream end * @return the builder instance, must not be null */ public JBBPDslBuilder StringArray(final String name, final int size) { return this.StringArray(name, arraySizeToString(size)); } /** * Add named string array which size calculated through expression. * * @param name name of field, can be null for anonymous * @param sizeExpression expression to calculate size, must not be null * @return the builder instance, must not be null */ public JBBPDslBuilder StringArray(final String name, final String sizeExpression) { final Item item = new Item(BinType.STRING_ARRAY, name, this.byteOrder); item.sizeExpression = assertExpressionChars(sizeExpression); this.addItem(item); return this; } /** * Set byte order for next fields * * @param order order, if null then BIG_ENDIAN * @return the builder instance, must not be null */ public JBBPDslBuilder ByteOrder(final JBBPByteOrder order) { this.byteOrder = order == null ? JBBPByteOrder.BIG_ENDIAN : order; return this; } /** * Build non-formatted script. * * @return script in non-formatted form, must not be null * @throws IllegalStateException if there is an unclosed struct */ public String End() { return this.End(false); } /** * Build a formatted script. * * @param format if true then make some formatting of result, false if non-formatted version allowed * @return script in formatted form, must not be null * @throws IllegalStateException if there is an unclosed struct */ public String End(final boolean format) { if (this.openedStructCounter != 0) { throw new IllegalStateException("Detected unclosed structs: " + this.openedStructCounter); } final StringBuilder buffer = new StringBuilder(128); int structCounter = 0; for (final Item item : this.items) { switch (item.type) { case STRUCT: { doTabs(format, buffer, structCounter).append(item.name == null ? "" : item.name) .append('{'); structCounter++; } break; case STRUCT_ARRAY: { doTabs(format, buffer, structCounter).append(item.name == null ? "" : item.name) .append('[').append(item.sizeExpression).append(']').append('{'); structCounter++; } break; case UNDEFINED: { if (item instanceof ItemStructEnd) { final ItemStructEnd structEnd = (ItemStructEnd) item; if (structEnd.endAll) { while (structCounter > 0) { structCounter--; doTabs(format, buffer, structCounter).append('}'); if (structCounter > 0 && format) { buffer.append('\n'); } } } else { structCounter--; doTabs(format, buffer, structCounter).append('}'); } } else if (item instanceof ItemCustom) { doTabs(format, buffer, structCounter).append(item.toString()); } else if (item instanceof ItemAlign) { doTabs(format, buffer, structCounter).append("align").append( item.sizeExpression == null ? "" : ':' + item.makeExpressionForExtraField(item.sizeExpression)).append(';'); } else if (item instanceof ItemVal) { doTabs(format, buffer, structCounter).append("val").append(':') .append(item.makeExpressionForExtraField(item.sizeExpression)).append(' ') .append(item.name).append(';'); } else if (item instanceof ItemResetCounter) { doTabs(format, buffer, structCounter).append("reset$$;"); } else if (item instanceof ItemSkip) { doTabs(format, buffer, structCounter).append("skip").append( item.sizeExpression == null ? "" : ':' + item.makeExpressionForExtraField(item.sizeExpression)).append(';'); } else if (item instanceof ItemComment) { final ItemComment comment = (ItemComment) item; final String commentText = comment.getComment().replace("\n", " "); if (comment.isNewLine()) { if (buffer.length() > 0) { final int lastNewLine = buffer.lastIndexOf("\n"); if (lastNewLine < 0 || buffer.substring(lastNewLine + 1).length() != 0) { buffer.append('\n'); } } doTabs(format, buffer, structCounter).append("// ").append(commentText); } else { final String current = buffer.toString(); if (current.endsWith(";\n") || current.endsWith("}\n")) { buffer.setLength(buffer.length() - 1); } final int lastCommentIndex = buffer.lastIndexOf("//"); if (lastCommentIndex < 0) { buffer.append("// "); } else if (buffer.lastIndexOf("\n") > lastCommentIndex) { buffer.append("// "); } else { buffer.append(' '); } buffer.append(commentText); } } else { throw new IllegalArgumentException("Unexpected item : " + item.getClass().getName()); } } break; default: { doTabs(format, buffer, structCounter).append(item.toString()); } break; } if (format || item instanceof ItemComment) { buffer.append('\n'); } } return buffer.toString(); } /** * Allows to collect all fields which can be used for scripting. * * @param annotatedClass class to be processed, must not be null * @return container which contains all found items */ protected BinFieldContainer collectAnnotatedFields(final Class annotatedClass) { final Bin defaultBin = annotatedClass.getAnnotation(Bin.class); final BinFieldContainer result; if (defaultBin != null) { result = new BinFieldContainer(annotatedClass, defaultBin, true, null); } else { result = new BinFieldContainer(annotatedClass, null); } final Class superClass = annotatedClass.getSuperclass(); if (superClass != null && superClass != Object.class) { final BinFieldContainer parentFields = collectAnnotatedFields(superClass); if (!parentFields.fields.isEmpty()) { result.addAllFromContainerExcludeEndStruct(parentFields); } } for (final Field f : annotatedClass.getDeclaredFields()) { if ((f.getModifiers() & (Modifier.NATIVE | Modifier.STATIC | Modifier.FINAL | Modifier.PRIVATE | Modifier.TRANSIENT)) == 0) { final Bin foundFieldBin = f.getAnnotation(Bin.class); if (foundFieldBin != null || defaultBin != null) { validateAnnotatedField(defaultBin, foundFieldBin, f); final Class type = f.getType().isArray() ? f.getType().getComponentType() : f.getType(); if (type.isPrimitive() || type == String.class) { if (foundFieldBin != null) { result.addBinField(foundFieldBin, true, f); } else { result.addBinField(defaultBin, false, f); } } else { final BinFieldContainer container = collectAnnotatedFields(type); if (!container.fields.isEmpty()) { if (foundFieldBin != null) { container.bin = foundFieldBin; container.fieldLocalAnnotation = true; } container.field = f; result.addContainer(container); } } } } } result.sort(); if (!result.fields.isEmpty()) { result.addContainer(BinFieldContainer.END_STRUCT); } return result; } private void validateAnnotatedField( final Bin defaultBin, final Bin fieldBin, final Field field ) { //TODO final Bin bin = fieldBin == null ? defaultBin : fieldBin; if ((bin.type() == BinType.UNDEFINED && field.getType().isArray() || bin.type().name().endsWith("_ARRAY")) && bin.arraySizeExpr().isEmpty()) { throw new IllegalArgumentException( field.toString() + ": missing expression in Bin#arraySizeExpression"); } } /** * Convert an annotated class into its JBBP DSL representation. * NB! the method creates structure bases on class name, so that it can't be used for auto-mapping * if you want use auto-mapping, use {@link JBBPDslBuilder#AnnotatedClassFields(Class)} * * @param annotatedClass class to be converted into JBBP script, must not be null * @return the builder instance, must not be null * @see JBBPDslBuilder#AnnotatedClassFields(Class) */ public JBBPDslBuilder AnnotatedClass(final Class annotatedClass) { return addAnnotatedClass(annotatedClass, false); } /** * Add just fields of annotated class, outbound class will not be added as structure. * The Method allows to prepare script which will be able make mapping of field values * * @param annotatedClass class to be converted into JBBP script, must not be null * @return the builder instance, must not be null * @see com.igormaznitsa.jbbp.model.JBBPFieldStruct#mapTo * @see com.igormaznitsa.jbbp.mapper.Bin * @see JBBPDslBuilder#AnnotatedClass(Class) */ public JBBPDslBuilder AnnotatedClassFields(final Class annotatedClass) { return addAnnotatedClass(annotatedClass, true); } protected JBBPDslBuilder addAnnotatedClass(final Class annotatedClass, final boolean onlyFields) { final BinFieldContainer collected = collectAnnotatedFields(annotatedClass); final JBBPByteOrder old = this.byteOrder; this.byteOrder = JBBPByteOrder.BIG_ENDIAN; class Pair { final BinFieldContainer container; final Iterator fieldIterator; Pair(final BinFieldContainer container) { this.container = container; this.fieldIterator = container.fields.iterator(); } } if (onlyFields) { int indexOfLastEndStruct = -1; for (int i = collected.fields.size() - 1; i >= 0; i--) { if (collected.fields.get(i) == BinFieldContainer.END_STRUCT) { indexOfLastEndStruct = i; break; } } if (indexOfLastEndStruct >= 0) { collected.fields.remove(indexOfLastEndStruct); } else { throw new IllegalStateException("Can't find end of structure"); } } else { this.Struct(collected.getName()); } final List stack = new ArrayList<>(); stack.add(new Pair(collected)); while (!stack.isEmpty()) { final Pair pair = stack.remove(0); while (pair.fieldIterator.hasNext()) { final BinField field = pair.fieldIterator.next(); if (field instanceof BinFieldContainer) { final BinFieldContainer binFieldContainer = (BinFieldContainer) field; if (binFieldContainer == BinFieldContainer.END_STRUCT) { this.CloseStruct(); } else { if (field.isArrayField()) { this.StructArray(binFieldContainer.getName(), binFieldContainer.bin.arraySizeExpr()); } else { this.Struct(binFieldContainer.getName()); } stack.add(0, pair); stack.add(0, new Pair(binFieldContainer)); break; } } else { final BinType type = field.findType(); this.ByteOrder(pair.container.getByteOrder(field)); switch (type) { case BIT_ARRAY: { if (field.bin.paramExpr().isEmpty()) { this.BitArray(field.getName(), pair.container.getBitNumber(field), field.bin.arraySizeExpr()); } else { this.CustomArray("bit", field.getName(), field.bin.arraySizeExpr(), field.bin.paramExpr()); } } break; case BIT: { if (field.bin.paramExpr().isEmpty()) { this.Bits(field.getName(), pair.container.getBitNumber(field)); } else { this.Custom("bit", field.getName(), field.bin.paramExpr()); } } break; case BOOL: { this.Bool(field.getName()); } break; case BOOL_ARRAY: { this.BoolArray(field.getName(), field.bin.arraySizeExpr()); } break; case BYTE: { this.Byte(field.getName()); } break; case BYTE_ARRAY: { this.ByteArray(field.getName(), field.bin.arraySizeExpr()); } break; case UBYTE: { this.UByte(field.getName()); } break; case UBYTE_ARRAY: { this.UByteArray(field.getName(), field.bin.arraySizeExpr()); } break; case SHORT: { this.Short(field.getName()); } break; case SHORT_ARRAY: { this.ShortArray(field.getName(), field.bin.arraySizeExpr()); } break; case USHORT: { this.UShort(field.getName()); } break; case USHORT_ARRAY: { this.UShortArray(field.getName(), field.bin.arraySizeExpr()); } break; case INT: { this.Int(field.getName()); } break; case INT_ARRAY: { this.IntArray(field.getName(), field.bin.arraySizeExpr()); } break; case LONG: { this.Long(field.getName()); } break; case LONG_ARRAY: { this.LongArray(field.getName(), field.bin.arraySizeExpr()); } break; case FLOAT: { this.Float(field.getName()); } break; case FLOAT_ARRAY: { this.FloatArray(field.getName(), field.bin.arraySizeExpr()); } break; case DOUBLE: { this.Double(field.getName()); } break; case DOUBLE_ARRAY: { this.DoubleArray(field.getName(), field.bin.arraySizeExpr()); } break; case STRING: { this.String(field.getName()); } break; case STRING_ARRAY: { this.StringArray(field.getName(), field.bin.arraySizeExpr()); } break; case UNDEFINED: { if (!field.getCustomType().isEmpty()) { this.ByteOrder(pair.container.getByteOrder(field)); if (field.isArrayField() || !field.bin.arraySizeExpr().isEmpty()) { this.CustomArray(field.bin.customType(), field.getName(), field.bin.arraySizeExpr(), field.bin.paramExpr()); } else { this.Custom(field.bin.customType(), field.getName(), field.bin.arraySizeExpr()); } } } break; default: { throw new Error("Unexpected type:" + type); } } } final String comment = field.getComment(); if (comment.length() != 0) { this.Comment(comment); } } } this.byteOrder = old; return this; } protected static class ItemCustom extends Item { final String customType; boolean array; ItemCustom(final String customType, final String name, final JBBPByteOrder byteOrder) { super(BinType.UNDEFINED, name, byteOrder); this.customType = customType; } } protected static class Item { final BinType type; final String name; final JBBPByteOrder byteOrder; String sizeExpression; JBBPBitNumber bitNumber; String bitLenExpression; Item(final BinType type, final String name, final JBBPByteOrder byteOrder) { this.type = type; this.name = name == null ? null : assertNameIfNotNull(assertTextNotNullAndTrimmedNotEmpty(name)).trim(); this.byteOrder = byteOrder; } @Override public String toString() { String type; boolean isArray; boolean customType = this instanceof ItemCustom; if (customType) { type = ((ItemCustom) this).customType; isArray = ((ItemCustom) this).array; } else { type = this.type.name().toLowerCase(Locale.ENGLISH); isArray = type.endsWith("_array"); if (isArray) { type = type.substring(0, type.indexOf('_')); } if (type.equals("string") || type.equals("float") || type.equals("double")) { type += 'j'; } } final StringBuilder result = new StringBuilder(); if (this.byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { result.append('<'); } result.append(type); if (customType) { if (this.bitLenExpression != null) { result.append(':').append(makeExpressionForExtraField(this.bitLenExpression)); } } else if (this.type == BinType.BIT || this.type == BinType.BIT_ARRAY) { result.append(':'); if (bitLenExpression == null) { if (this.bitNumber == null) { result.append('1'); } else { result.append(this.bitNumber.getBitNumber()); } } else { result.append(makeExpressionForExtraField(this.bitLenExpression)); } } if (isArray) { result.append('[').append(this.sizeExpression).append(']'); } if (this.name != null && this.name.length() != 0) { result.append(' ').append(this.name); } result.append(';'); return result.toString(); } /** * Auxiliary method to prepare expression which can be placed as extra data in fields. * * @param expression expression to be prepared, must not be null * @return prepared string, must not be null */ protected String makeExpressionForExtraField(final String expression) { try { Long.parseLong(expression); return expression; } catch (NumberFormatException ex) { return '(' + expression + ')'; } } } /** * Internal auxiliary class to keep found annotated fields. */ protected static class BinField implements Comparable { Bin bin; Field field; boolean fieldLocalAnnotation; BinField(final Bin bin, final boolean fieldLocalAnnotation, final Field field) { this.fieldLocalAnnotation = fieldLocalAnnotation; this.bin = bin; this.field = field; } String getComment() { String result = ""; if (this.fieldLocalAnnotation) { if (this.bin != null) { result = this.bin.comment(); } } return result; } boolean isArrayField() { return this.field != null && this.field.getType().isArray(); } BinType findType() { if (this.field == null) { return BinType.STRUCT; } if (this.bin.type() == BinType.UNDEFINED) { return this.bin.customType().isEmpty() ? BinType.findCompatible(this.field.getType()) : this.bin.type(); } else { return this.bin.type(); } } String getName() { String result = null; if (this.field != null) { if (this.fieldLocalAnnotation) { if (this.bin != null) { result = this.bin.name().length() == 0 ? this.field.getName() : this.bin.name(); } else { throw new Error("Unexpected"); } } else { result = field.getName(); } } return result; } @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return true; } if (obj instanceof BinField) { final BinField that = (BinField) obj; return this.field.equals(that.field) && JBBPUtils.equals(this.bin, that.bin); } return false; } @Override public int hashCode() { return this.field.hashCode(); } private int getOrder() { if (this.bin != null) { return this.bin.order(); } return -1; } @Override public int compareTo(final BinField that) { final int thisOrder = this.getOrder(); final int thatOrder = that.getOrder(); if (thisOrder == thatOrder) { return this.getName().compareTo(that.getName()); } else { return thisOrder < thatOrder ? -1 : 1; } } String getCustomType() { return this.bin.customType(); } } protected static class BinFieldContainer extends BinField { static final BinFieldContainer END_STRUCT = new BinFieldContainer(null, null); final List fields = new ArrayList<>(); final Class klazz; BinFieldContainer(final Class klazz, final Bin bin, final boolean fieldLocalAnnotation, final Field field) { super(bin, fieldLocalAnnotation, field); this.klazz = klazz; } BinFieldContainer(final Class klazz, final Field field) { super((Bin) null, false, field); this.klazz = klazz; } void sort() { Collections.sort(this.fields); } void addAllFromContainerExcludeEndStruct(final BinFieldContainer container) { for(final BinField field : container.fields) { if (field != END_STRUCT) { this.fields.add(field); } } } void addContainer(final BinFieldContainer container) { this.fields.add(container); } void addBinField(final Bin bin, final boolean fieldLocalAnnotation, final Field field) { this.fields.add(new BinField(bin, fieldLocalAnnotation, field)); } JBBPByteOrder getByteOrder(final BinField field) { return field.bin.byteOrder(); } String getName() { final String name = super.getName(); return name == null ? this.klazz.getSimpleName() : name; } JBBPBitNumber getBitNumber(final BinField field) { final JBBPBitNumber result; if (field.bin.bitNumber() == JBBPBitNumber.BITS_8) { result = this.bin == null ? JBBPBitNumber.BITS_8 : this.bin.bitNumber(); } else { result = field.bin.bitNumber(); } return result; } } protected static class ItemComment extends Item { private final boolean newLine; private final String comment; ItemComment(final String text, final boolean newLine) { super(BinType.UNDEFINED, "_ignored_because_comment_", JBBPByteOrder.BIG_ENDIAN); this.comment = text; this.newLine = newLine; } String getComment() { return this.comment == null ? "" : this.comment; } boolean isNewLine() { return this.newLine; } } protected static class ItemAlign extends Item { ItemAlign(final String sizeExpression) { super(BinType.UNDEFINED, null, JBBPByteOrder.BIG_ENDIAN); this.sizeExpression = sizeExpression; } } protected static class ItemVal extends Item { ItemVal(final String name, final String sizeExpression) { super(BinType.UNDEFINED, assertTextNotNullAndTrimmedNotEmpty(name), JBBPByteOrder.BIG_ENDIAN); this.sizeExpression = assertExpressionChars(assertTextNotNullAndTrimmedNotEmpty(sizeExpression).trim()); } } protected static class ItemResetCounter extends Item { ItemResetCounter() { super(BinType.UNDEFINED, null, JBBPByteOrder.BIG_ENDIAN); } } protected static class ItemStructEnd extends Item { private final boolean endAll; ItemStructEnd(final boolean endAll) { super(BinType.UNDEFINED, null, JBBPByteOrder.BIG_ENDIAN); this.endAll = endAll; } } protected static class ItemSkip extends Item { ItemSkip(final String sizeExpression) { super(BinType.UNDEFINED, null, JBBPByteOrder.BIG_ENDIAN); this.sizeExpression = sizeExpression; } } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPIntCounter.java000066400000000000000000000060411445000440600326100ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; /** * The Class implements a simple thread unsafe integer counter. * * @since 1.0 */ public final class JBBPIntCounter extends Number { private static final long serialVersionUID = -4457089530231871404L; /** * The Variable contains the counter value. */ private int counter; /** * The Constructor to initialize the counter by 0. */ public JBBPIntCounter() { this(0); } /** * The Constructor to initialize the counter by an integer value. * * @param startValue the start integer value for the counter */ public JBBPIntCounter(final int startValue) { this.counter = startValue; } /** * Set the value for the counter. * * @param value the new value for the counter */ public void set(final int value) { this.counter = value; } /** * Increase the counter. */ public void inc() { this.counter++; } /** * Decrease the counter. */ public void dec() { this.counter--; } /** * Get the counter value and increment the counter after result return. * * @return the value before increment. */ public int getAndIncrement() { return this.counter++; } /** * Increment the counter and get the incremented result. * * @return the value after increment */ public int incrementAndGet() { return ++this.counter; } /** * Get the counter value and decrement the counter after result return. * * @return the value before decrement */ public int getAndDecrement() { return this.counter--; } /** * Decrement the counter and return the decremented value. * * @return the decremented value */ public int decrementAndGet() { return --this.counter; } /** * Get the counter value. * * @return the current counter value */ public int get() { return this.counter; } /** * Get the current counter value and add a value after the return. * * @param value a value to be added to the counter * @return the counter before addition */ public int getAndAdd(final int value) { final int result = this.counter; this.counter += value; return result; } @Override public int intValue() { return this.counter; } @Override public long longValue() { return this.counter; } @Override public float floatValue() { return (float) this.counter; } @Override public double doubleValue() { return this.counter; } } JBBPSystemProperty.java000066400000000000000000000060231445000440600334700ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; /** * The Enum contains all system properties which are used by the JBBP framework. * * @since 1.0 */ public enum JBBPSystemProperty { /** * The Property allows to define which class will work as a class instantiator for the JBBP mapper. */ PROPERTY_INSTANTIATOR_CLASS("jbbp.mapper.instantiator"), /** * The Property allows to define the initial size for array buffer to read whole stream. */ PROPERTY_INPUT_INITIAL_ARRAY_BUFFER_SIZE("jbbp.input.initial.array.buffer"); /** * The name of the property. */ private final String propertyName; /** * The Constructor. * * @param propertyName the property name */ JBBPSystemProperty(final String propertyName) { this.propertyName = propertyName; } /** * Set a value to the property. * * @param value the value to be set to the property, must not be null. */ public void set(final String value) { System.setProperty(this.propertyName, value); } /** * Remove the property. */ public void remove() { System.clearProperty(this.propertyName); } /** * Get the property value as string. * * @param defaultValue the default value which will be returned if there is not defined value for the property. * @return the value as string or the default value if it is not defined */ public String getAsString(final String defaultValue) { final String value = System.getProperty(this.propertyName); return value == null ? defaultValue : value; } /** * Get the property value as integer. * * @param defaultValue the default value which will be returned if there is * not defined value for the property. * @return the value as integer or the default value if it is not defined * @throws Error if the value can't be recognized as integer */ public int getAsInteger(final int defaultValue) { final String value = System.getProperty(this.propertyName); int result = defaultValue; if (value != null) { try { result = Integer.parseInt(value); } catch (NumberFormatException ex) { throw new Error("Can't get the system property '" + this.propertyName + "' as integer value, may be wrong format [" + value + ']', ex); } } return result; } /** * Get the property name. * * @return the property name */ public String getPropertyName() { return this.propertyName; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPTextWriter.java000066400000000000000000001752141445000440600326500ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; import com.igormaznitsa.jbbp.exceptions.JBBPIOException; import com.igormaznitsa.jbbp.io.AbstractMappedClassFieldObserver; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.model.JBBPAbstractArrayField; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBit; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayDouble; import com.igormaznitsa.jbbp.model.JBBPFieldArrayFloat; import com.igormaznitsa.jbbp.model.JBBPFieldArrayInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong; import com.igormaznitsa.jbbp.model.JBBPFieldArrayShort; import com.igormaznitsa.jbbp.model.JBBPFieldArrayString; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUShort; import com.igormaznitsa.jbbp.model.JBBPFieldBit; import com.igormaznitsa.jbbp.model.JBBPFieldBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldByte; import com.igormaznitsa.jbbp.model.JBBPFieldDouble; import com.igormaznitsa.jbbp.model.JBBPFieldFloat; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldShort; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.model.JBBPFieldUByte; import com.igormaznitsa.jbbp.model.JBBPFieldUInt; import com.igormaznitsa.jbbp.model.JBBPFieldUShort; import com.igormaznitsa.jbbp.model.JBBPNumericField; import java.io.FilterWriter; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Field; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; /** * the Writer allows to make text describes some bin data, it supports output of * commentaries and values, also it is possible to register own extras to * process complex data and cases. The Class is not thread safe. * * @since 1.1 */ public class JBBPTextWriter extends FilterWriter { /** * The Default comment prefix. */ private static final String DEFAULT_COMMENT_PREFIX = "; "; /** * The Default value postfix. */ private static final String DEFAULT_VALUE_POSTFIX = ""; /** * The Default horizontal rule prefix. */ private static final String DEFAULT_HR_PREFIX = ";"; /** * The Default first value line. */ private static final String DEFAULT_FIRST_VALUE_LINE_PREFIX = ""; /** * The Default value prefix. */ private static final String DEFAULT_VALUE_PREFIX = ""; /** * The Default value delimiter. */ private static final String DEFAULT_VALUE_DELIMITER = " "; /** * The Default max value number per line. */ private static final int DEFAULT_MAX_VALUES_PER_LINE = -1; /** * The Default radix. */ private static final int DEFAULT_RADIX = 16; /** * The Default length of horizontal rule in chars. */ private static final int DEFAULT_HR_LENGTH = 80; /** * The Default char to draw horizontal rule. */ private static final char DEFAULT_HR_CHAR = '-'; /** * The Mode shows that the start line. */ private static final int MODE_START_LINE = 0; /** * the Mode shows that values are printed. */ private static final int MODE_VALUES = 1; /** * The Mode shows that commentaries printing. */ private static final int MODE_COMMENTS = 2; /** * The Line separator, must not be null. */ private final String lineSeparator; /** * Inside char buffer to be used for converting operations. */ private final char[] CHAR_BUFFER = new char[64]; /** * The List contains all registered extras. */ private final List extras = new ArrayList<>(); /** * Lazy initialized field to keep field observer for Bin marked classes. */ private MappedObjectLogger mappedClassObserver; /** * The Variable contains the previous activity mode. */ private int prevMode = MODE_START_LINE; /** * The Variable contains the current activity mode. */ private int mode = MODE_START_LINE; /** * The Current horizontal rule length. */ private int hrLength = DEFAULT_HR_LENGTH; /** * The Current horizontal rule char. */ private char hrChar = DEFAULT_HR_CHAR; /** * The Current radix. */ private int radix; /** * The Variable contains number of max chars to show byte for current radix. */ private int maxCharsRadixForByte; /** * The Variable contains number of max chars to show short for current radix. */ private int maxCharsRadixForShort; /** * The Variable contains number of max chars to show integer for current * radix. */ private int maxCharsRadixForInt; /** * The Variable contains number of max chars to show long for current radix. */ private int maxCharsRadixForLong; /** * The flag allows print of comments. */ private boolean flagCommentsAllowed; /** * The Current byte order. */ private JBBPByteOrder byteOrder; /** * The Current first value at line prefix. */ private String prefixFirtValueAtLine; /** * The Current value prefix. */ private String prefixValue; /** * The Current value postfix. */ private String postfixValue = DEFAULT_VALUE_POSTFIX; /** * The Current value delimiter. */ private String valueSeparator; /** * The Current comment prefix. */ private String prefixComment; /** * The Current HR prefix. */ private String prefixHR; /** * The Current line position, 0 is first one. */ private int linePosition = 0; /** * The Current line number, 0 is the first one. */ private int lineNumber = 0; /** * Number of space chars representing the tab. */ private int spacesInTab = 4; /** * The Current indent. */ private int indent = 0; /** * Counter of printed values on the current line. */ private int valuesLineCounter; /** * The Max number of values to be presented on single line. */ private int maxValuesPerLine = DEFAULT_MAX_VALUES_PER_LINE; /** * The Value contains the start of commentaries on the previous line. */ private int prevLineCommentsStartPosition = 0; /** * The Default constructor. A StringWriter will be used inside. */ public JBBPTextWriter() { this(new StringWriter(1024), JBBPByteOrder.BIG_ENDIAN, System.getProperty("line.separator"), DEFAULT_RADIX, DEFAULT_VALUE_PREFIX, DEFAULT_FIRST_VALUE_LINE_PREFIX, DEFAULT_COMMENT_PREFIX, DEFAULT_HR_PREFIX, DEFAULT_VALUE_DELIMITER); } /** * Constructor to wrap a writer, * * @param out a writer to be wrapped, must not be null. */ public JBBPTextWriter(final Writer out) { this(out, JBBPByteOrder.BIG_ENDIAN, System.getProperty("line.separator"), DEFAULT_RADIX, DEFAULT_VALUE_PREFIX, DEFAULT_FIRST_VALUE_LINE_PREFIX, DEFAULT_COMMENT_PREFIX, DEFAULT_HR_PREFIX, DEFAULT_VALUE_DELIMITER); } /** * Constructor to wrap a writer with defined byte order. * * @param out a writer to be wrapped, must not be null. * @param byteOrder a byte order to be used, it must not be null. */ public JBBPTextWriter(final Writer out, final JBBPByteOrder byteOrder) { this(out, byteOrder, System.getProperty("line.separator"), DEFAULT_RADIX, DEFAULT_VALUE_PREFIX, DEFAULT_FIRST_VALUE_LINE_PREFIX, DEFAULT_COMMENT_PREFIX, DEFAULT_HR_PREFIX, DEFAULT_VALUE_DELIMITER); } /** * Constructor. * * @param out a writer to be wrapper, must not be null. * @param byteOrder byte order to be used for converting, must not be null. * @param lineSeparator line separator, must not be null. * @param radix radix, must be 2..36. * @param valuePrefix prefix before each value, can be null. * @param startValueLinePrefix prefix before the first value on line, can be * null. * @param commentPrefix prefix before comments, can be null. * @param hrPrefix prefix for horizontal rule * @param valueDelimiter delimiter between values, can be null */ public JBBPTextWriter( final Writer out, final JBBPByteOrder byteOrder, final String lineSeparator, final int radix, final String valuePrefix, final String startValueLinePrefix, final String commentPrefix, final String hrPrefix, final String valueDelimiter) { super(out); JBBPUtils.assertNotNull(lineSeparator, "Line separator must not be null"); this.flagCommentsAllowed = true; this.prefixHR = hrPrefix == null ? "" : hrPrefix; this.lineSeparator = lineSeparator; JBBPUtils.assertNotNull(out, "Writer must not be null"); ByteOrder(byteOrder); SetValueSeparator(valueDelimiter); SetValueLinePrefix(startValueLinePrefix); SetCommentPrefix(commentPrefix); SetValuePrefix(valuePrefix); Radix(radix); } /** * Auxiliary method allows to build writer over StringWriter with system-depended next line and hex radix. * The Method allows fast instance create. * * @return the text writer instance, must not be null * @since 1.4.0 */ public static JBBPTextWriter makeStrWriter() { final String lineSeparator = System.setProperty("line.separator", "\n"); return new JBBPTextWriter(new StringWriter(), JBBPByteOrder.BIG_ENDIAN, lineSeparator, 16, "0x", ".", ";", "~", ","); } protected static String makeFieldComment(final JBBPAbstractField field) { final String path = field.getFieldPath(); final StringBuilder result = new StringBuilder(128); result.append(field.getTypeAsString()).append(' '); if (path == null) { result.append(""); } else { result.append(path); } return result.toString(); } /** * Get the wrapped writer. * * @return the wrapped writer */ public Writer getWrappedWriter() { return this.out; } /** * Get the current byte order. * * @return the current byte order. */ public JBBPByteOrder getByteOrder() { return this.byteOrder; } /** * get the current radix. * * @return the current radix */ public int getRadix() { return this.radix; } /** * Get the current line separator. * * @return the current line separator as string */ public String getLineSeparator() { return this.lineSeparator; } /** * Change current mode. * * @param mode the new mode value */ private void changeMode(final int mode) { this.prevMode = this.mode; this.mode = mode; } /** * Ensure the value mode. * * @throws IOException it will be thrown for transport errors */ private void ensureValueMode() throws IOException { switch (this.mode) { case MODE_START_LINE: { changeMode(MODE_VALUES); for (final Extra e : extras) { e.onBeforeFirstValue(this); } writeIndent(); this.write(this.prefixFirtValueAtLine); } break; case MODE_COMMENTS: { this.BR(); writeIndent(); changeMode(MODE_VALUES); for (final Extra e : extras) { e.onBeforeFirstValue(this); } this.write(this.prefixFirtValueAtLine); } break; case MODE_VALUES: break; default: throw new Error("Unexpected state"); } } /** * Write current indent to line as bunch of spaces. * * @throws IOException it will be thrown for transport errors */ private void writeIndent() throws IOException { if (this.indent > 0) { int i = this.indent; while (i > 0) { this.write(' '); i--; } } } /** * Ensure the comment mode. * * @throws IOException it will be thrown for transport errors */ private void ensureCommentMode() throws IOException { switch (this.mode) { case MODE_START_LINE: writeIndent(); this.prevLineCommentsStartPosition = this.linePosition; this.write(this.prefixComment); changeMode(MODE_COMMENTS); break; case MODE_VALUES: { this.prevLineCommentsStartPosition = this.linePosition; this.write(this.prefixComment); changeMode(MODE_COMMENTS); } break; case MODE_COMMENTS: { BR(); writeIndent(); while (this.linePosition < this.prevLineCommentsStartPosition) { this.write(' '); } this.write(this.prefixComment); } break; default: throw new Error("Unexpected state"); } } /** * Ensure the new line. * * @throws IOException it will be thrown for transport errors */ private void ensureNewLineMode() throws IOException { if (this.mode != MODE_START_LINE) { this.write(this.lineSeparator); this.mode = MODE_START_LINE; } valuesLineCounter = 0; } /** * Print a value represented by its string. * * @param value the value to be printed, must not be null * @throws IOException it will be thrown for transport errors */ private void printValueString(final String value) throws IOException { if (this.valuesLineCounter > 0 && this.valueSeparator.length() > 0) { this.write(this.valueSeparator); } if (this.prefixValue.length() > 0) { this.write(this.prefixValue); } this.write(value); if (this.postfixValue.length() > 0) { this.write(this.postfixValue); } this.valuesLineCounter++; if (this.maxValuesPerLine > 0 && this.valuesLineCounter >= this.maxValuesPerLine) { for (final Extra e : this.extras) { e.onReachedMaxValueNumberForLine(this); } ensureNewLineMode(); } } /** * Enable print comments. * * @return the context */ public JBBPTextWriter EnableComments() { this.flagCommentsAllowed = true; return this; } /** * Disable print comments. * * @return the context */ public JBBPTextWriter DisableComments() { this.flagCommentsAllowed = false; return this; } /** * Print string values. * * @param str array of string values, must not be null but may contain nulls * @return the context * @throws IOException it will be thrown for error */ public JBBPTextWriter Str(final String... str) throws IOException { JBBPUtils.assertNotNull(str, "String must not be null"); final String oldPrefix = this.prefixValue; final String oldPostfix = this.postfixValue; this.prefixValue = ""; this.postfixValue = ""; for (final String s : str) { ensureValueMode(); printValueString(s == null ? "" : s); } this.prefixValue = oldPrefix; this.postfixValue = oldPostfix; return this; } /** * Print byte value. * * @param value a byte value * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Byte(final int value) throws IOException { ensureValueMode(); String convertedByExtras = null; for (final Extra e : this.extras) { convertedByExtras = e.doConvertByteToStr(this, value & 0xFF); if (convertedByExtras != null) { break; } } if (convertedByExtras == null) { printValueString(JBBPUtils .ensureMinTextLength(JBBPUtils.ulong2str(value & 0xFF, this.radix, CHAR_BUFFER), this.maxCharsRadixForByte, '0', 0)); } else { printValueString(convertedByExtras); } return this; } /** * Print byte array defined as string. * * @param value string which codes should be printed as byte array. * @return the context * @throws IOException it will be thrown for transport error */ public JBBPTextWriter Byte(final String value) throws IOException { for (int i = 0; i < value.length(); i++) { this.Byte(value.charAt(i)); } return this; } /** * Print byte array. * * @param values a byte array, must not be null * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Byte(final byte[] values) throws IOException { return Byte(values, 0, values.length); } /** * Print values from byte array. * * @param array source byte array, must not be null * @param off the offset of the first element in array * @param len number of bytes to be printed * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Byte(final byte[] array, int off, int len) throws IOException { ensureValueMode(); while (len-- > 0) { Byte(array[off++]); } return this; } /** * Check that line start mode is active. * * @return true if mode is line start, false otherwise */ public boolean isLineStart() { return this.mode == MODE_START_LINE; } /** * Check that comment mode is active. * * @return true if comment mode is active, false otherwise */ public boolean isComments() { return this.mode == MODE_COMMENTS; } /** * Check that value mode is active. * * @return true if value mode is active, false otherwise */ public boolean isValues() { return this.mode == MODE_VALUES; } /** * Set prefix to be printed before start of values on every line. * * @param text string to be used as value line prefix, can be null * @return the context */ public final JBBPTextWriter SetValueLinePrefix(final String text) { this.prefixFirtValueAtLine = text == null ? "" : text; return this; } /** * Set prefix to be printed before every value. * * @param text string to be used as value prefix, can be null * @return the context */ public final JBBPTextWriter SetValuePrefix(final String text) { this.prefixValue = text == null ? "" : text; return this; } /** * Set postfix to be printed after every value. * * @param text string to be used as value postfix, can be null * @return the context */ public final JBBPTextWriter SetValuePostfix(final String text) { this.postfixValue = text == null ? "" : text; return this; } /** * Set prefix to be printed as comment start. * * @param text string to be used as comment start, can be null * @return the context */ public final JBBPTextWriter SetCommentPrefix(final String text) { this.prefixComment = text == null ? "" : text; return this; } /** * Set delimiter to be printed between values. * * @param text string to be used as separator between values. * @return the context */ public final JBBPTextWriter SetValueSeparator(final String text) { this.valueSeparator = text == null ? "" : text; return this; } /** * Add extras to context. * * @param extras extras to be added to context, must not be null * @return the context */ public JBBPTextWriter AddExtras(final Extra... extras) { JBBPUtils.assertNotNull(extras, "Extras must not be null"); for (final Extra e : extras) { JBBPUtils.assertNotNull(e, "Extras must not be null"); this.extras.add(0, e); } return this; } /** * Remove extras from context * * @param extras extras to be removed, must not be null * @return the context */ public JBBPTextWriter DelExtras(final Extra... extras) { JBBPUtils.assertNotNull(extras, "Extras must not be null"); for (final Extra e : extras) { JBBPUtils.assertNotNull(e, "Extras must not be null"); this.extras.remove(e); } return this; } /** * Set max number of values to be printed in one line. * * @param value max number of values to be presented on line, 0 or negative to * turn off checking * @return the context */ public JBBPTextWriter SetMaxValuesPerLine(final int value) { this.maxValuesPerLine = value; return this; } /** * Set number of spaces per tab. * * @param numberOfSpacesPerTab number of spaces, must be greater than zero * @return the context */ public JBBPTextWriter SetTabSpaces(final int numberOfSpacesPerTab) { if (numberOfSpacesPerTab <= 0) { throw new IllegalArgumentException( "Tab must contains positive number of space chars [" + numberOfSpacesPerTab + ']'); } final int currentIdentSteps = this.indent / this.spacesInTab; this.spacesInTab = numberOfSpacesPerTab; this.indent = currentIdentSteps * this.spacesInTab; return this; } /** * Set byte order. * * @param order new byte order, must not be null * @return the context */ public final JBBPTextWriter ByteOrder(final JBBPByteOrder order) { JBBPUtils.assertNotNull(order, "Byte order must not be null"); this.byteOrder = order; return this; } /** * Set radix. * * @param radix new radix value, must be 2..36 * @return the context */ public final JBBPTextWriter Radix(final int radix) { if (radix < 2 || radix > 36) { throw new IllegalArgumentException("Unsupported radix value [" + radix + ']'); } this.radix = radix; this.maxCharsRadixForByte = JBBPUtils.ulong2str(0xFFL, this.radix, CHAR_BUFFER).length(); this.maxCharsRadixForShort = JBBPUtils.ulong2str(0xFFFFL, this.radix, CHAR_BUFFER).length(); this.maxCharsRadixForInt = JBBPUtils.ulong2str(0xFFFFFFFFL, this.radix, CHAR_BUFFER).length(); this.maxCharsRadixForLong = JBBPUtils.ulong2str(0xFFFFFFFFFFFFFFFFL, this.radix, CHAR_BUFFER).length(); return this; } /** * Print short value. * * @param value short value to be printed * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Short(final int value) throws IOException { ensureValueMode(); String convertedByExtras = null; for (final Extra e : this.extras) { convertedByExtras = e.doConvertShortToStr(this, value & 0xFFFF); if (convertedByExtras != null) { break; } } if (convertedByExtras == null) { final long valueToWrite; if (this.byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { valueToWrite = JBBPUtils.reverseByteOrder(value, 2); } else { valueToWrite = value; } printValueString(JBBPUtils .ensureMinTextLength(JBBPUtils.ulong2str(valueToWrite & 0xFFFFL, this.radix, CHAR_BUFFER), this.maxCharsRadixForShort, '0', 0)); } else { printValueString(convertedByExtras); } return this; } /** * Print float value. * * @param value float value to be printed * @return the context * @throws IOException it will be thrown for transport errors * @since 1.4.0 */ public JBBPTextWriter Float(final float value) throws IOException { ensureValueMode(); String convertedByExtras = null; for (final Extra e : this.extras) { convertedByExtras = e.doConvertFloatToStr(this, value); if (convertedByExtras != null) { break; } } if (convertedByExtras == null) { final float valueToWrite; if (this.byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { valueToWrite = Float.intBitsToFloat((int) JBBPFieldInt.reverseBits(Float.floatToIntBits(value))); } else { valueToWrite = value; } printValueString(JBBPUtils.ensureMinTextLength(JBBPUtils.float2str(valueToWrite, this.radix), this.maxCharsRadixForShort, '0', 0)); } else { printValueString(convertedByExtras); } return this; } /** * Print double value. * * @param value double value to be printed * @return the context * @throws IOException it will be thrown for transport errors * @since 1.4.0 */ public JBBPTextWriter Double(final double value) throws IOException { ensureValueMode(); String convertedByExtras = null; for (final Extra e : this.extras) { convertedByExtras = e.doConvertDoubleToStr(this, value); if (convertedByExtras != null) { break; } } if (convertedByExtras == null) { final double valueToWrite; if (this.byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { valueToWrite = Double.longBitsToDouble(JBBPFieldLong.reverseBits(Double.doubleToLongBits(value))); } else { valueToWrite = value; } printValueString(JBBPUtils.ensureMinTextLength(JBBPUtils.double2str(valueToWrite, this.radix), this.maxCharsRadixForShort, '0', 0)); } else { printValueString(convertedByExtras); } return this; } /** * Print char codes of string as short array. * * @param value string which codes should be printed, must not be null * @return the context * @throws IOException it will be thrown if any transport error */ public JBBPTextWriter Short(final String value) throws IOException { for (int i = 0; i < value.length(); i++) { this.Short(value.charAt(i)); } return this; } /** * Print array of short values. * * @param values array of short values, must not be null * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Short(final short[] values) throws IOException { return this.Short(values, 0, values.length); } /** * Print array of float values. * * @param values array of float values, must not be null * @return the context * @throws IOException it will be thrown for transport errors * @since 1.4.0 */ public JBBPTextWriter Float(final float[] values) throws IOException { return this.Float(values, 0, values.length); } /** * Print array of double values. * * @param values array of double values, must not be null * @return the context * @throws IOException it will be thrown for transport errors * @since 1.4.0 */ public JBBPTextWriter Double(final double[] values) throws IOException { return this.Double(values, 0, values.length); } /** * Print values from short array * * @param values short value array, must not be null * @param off offset to the first element * @param len number of elements to print * @return the context * @throws IOException it will be thrown for transport error */ public JBBPTextWriter Short(final short[] values, int off, int len) throws IOException { while (len-- > 0) { this.Short(values[off++]); } return this; } /** * Print values from float array * * @param values float value array, must not be null * @param off offset to the first element * @param len number of elements to print * @return the context * @throws IOException it will be thrown for transport error * @since 1.4.0 */ public JBBPTextWriter Float(final float[] values, int off, int len) throws IOException { while (len-- > 0) { this.Float(values[off++]); } return this; } /** * Print values from double array * * @param values double value array, must not be null * @param off offset to the first element * @param len number of elements to print * @return the context * @throws IOException it will be thrown for transport error * @since 1.4.0 */ public JBBPTextWriter Double(final double[] values, int off, int len) throws IOException { while (len-- > 0) { this.Double(values[off++]); } return this; } /** * Print integer value * * @param value value to be printed * @return the context * @throws IOException it will be thrown for transport error */ public JBBPTextWriter Int(final int value) throws IOException { ensureValueMode(); String convertedByExtras = null; for (final Extra e : this.extras) { convertedByExtras = e.doConvertIntToStr(this, value); if (convertedByExtras != null) { break; } } if (convertedByExtras == null) { final long valueToWrite; if (this.byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { valueToWrite = JBBPUtils.reverseByteOrder(value, 4); } else { valueToWrite = value; } printValueString(JBBPUtils.ensureMinTextLength( JBBPUtils.ulong2str(valueToWrite & 0xFFFFFFFFL, this.radix, CHAR_BUFFER), this.maxCharsRadixForInt, '0', 0)); } else { printValueString(convertedByExtras); } return this; } /** * Print unsigned integer value * * @param value value to be printed * @return the context * @throws IOException it will be thrown for transport error * @since 2.0.4 */ public JBBPTextWriter UInt(final int value) throws IOException { ensureValueMode(); String convertedByExtras = null; for (final Extra e : this.extras) { convertedByExtras = e.doConvertUIntToStr(this, value); if (convertedByExtras != null) { break; } } if (convertedByExtras == null) { final long valueToWrite; if (this.byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { valueToWrite = JBBPUtils.reverseByteOrder(value, 4); } else { valueToWrite = value; } printValueString(JBBPUtils.ensureMinTextLength( JBBPUtils.ulong2str(valueToWrite & 0xFFFFFFFFL, this.radix, CHAR_BUFFER), this.maxCharsRadixForInt, '0', 0)); } else { printValueString(convertedByExtras); } return this; } /** * Print integer array. * * @param values integer array to be printed, must not be null * @return the context * @throws IOException it will be thrown for transport error */ public JBBPTextWriter Int(final int[] values) throws IOException { return this.Int(values, 0, values.length); } /** * Print unsigned integer array. * * @param values unsigned integer array to be printed, must not be null * @return the context * @throws IOException it will be thrown for transport error * @since 2.0.4 */ public JBBPTextWriter UInt(final int[] values) throws IOException { return this.UInt(values, 0, values.length); } /** * Print values from integer array. * * @param values integer array, must not be null * @param off offset to the first element in array * @param len number of elements to print * @return the context * @throws IOException it will be thrown for transport error */ public JBBPTextWriter Int(final int[] values, int off, int len) throws IOException { while (len-- > 0) { this.Int(values[off++]); } return this; } /** * Print values from unsigned integer array. * * @param values unsigned integer array, must not be null * @param off offset to the first element in array * @param len number of elements to print * @return the context * @throws IOException it will be thrown for transport error * @since 2.0.4 */ public JBBPTextWriter UInt(final int[] values, int off, int len) throws IOException { while (len-- > 0) { this.UInt(values[off++]); } return this; } /** * Increase indent. * * @return the context */ public JBBPTextWriter IndentInc() { this.indent += this.spacesInTab; return this; } /** * Increase indent. * * @param count number of indents * @return the context * @throws IOException it will be thrown for transport error */ public JBBPTextWriter IndentInc(final int count) throws IOException { for (int i = 0; i < count; i++) { IndentInc(); } return this; } /** * Decrease indent. * * @return the context */ public JBBPTextWriter IndentDec() { if (this.indent > 0) { this.indent = Math.max(0, this.indent - this.spacesInTab); } return this; } /** * Decrease indent. * * @param count number of indents * @return the context * @throws IOException it will be thrown for transport error */ public JBBPTextWriter IndentDec(final int count) throws IOException { for (int i = 0; i < count; i++) { IndentDec(); } return this; } /** * Print long value * * @param value value to be printed * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Long(final long value) throws IOException { ensureValueMode(); String convertedByExtras = null; for (final Extra e : this.extras) { convertedByExtras = e.doConvertLongToStr(this, value); if (convertedByExtras != null) { break; } } if (convertedByExtras == null) { final long valueToWrite; if (this.byteOrder == JBBPByteOrder.LITTLE_ENDIAN) { valueToWrite = JBBPUtils.reverseByteOrder(value, 8); } else { valueToWrite = value; } printValueString(JBBPUtils .ensureMinTextLength(JBBPUtils.ulong2str(valueToWrite, this.radix, CHAR_BUFFER), this.maxCharsRadixForLong, '0', 0)); } else { printValueString(convertedByExtras); } return this; } /** * Print values from long array. * * @param values array to be printed, must not be null * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Long(final long[] values) throws IOException { return this.Long(values, 0, values.length); } /** * print values from long array. * * @param values array to be printed, must not be null * @param off offset to the first element to print * @param len number of elements to be printed * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Long(final long[] values, int off, int len) throws IOException { while (len-- > 0) { this.Long(values[off++]); } return this; } /** * Make new line. * * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter BR() throws IOException { this.write(this.lineSeparator); ensureNewLineMode(); return this; } /** * Change parameters for horizontal rule. * * @param prefix the prefix to be printed before rule, it can be null * @param length the length in symbols. * @param ch symbol to draw * @return the context */ public JBBPTextWriter SetHR(final String prefix, final int length, final char ch) { this.prefixHR = prefix == null ? "" : prefix; this.hrChar = ch; this.hrLength = length; return this; } /** * Print horizontal rule. If comments are disabled then only next line will be * added. * * @return the context * @throws IOException it will be thrown for transport errors * @see #EnableComments() * @see #DisableComments() */ public JBBPTextWriter HR() throws IOException { if (this.flagCommentsAllowed) { this.ensureNewLineMode(); this.writeIndent(); this.write(this.prefixHR); for (int i = 0; i < this.hrLength; i++) { this.write(this.hrChar); } } this.BR(); return this; } /** * Print comments. Wilt aligning of line start for multi-line comment. * Comments will be printed only if they are allowed. * * @param comment array of string to be printed as comment lines. * @return the context * @throws IOException it will be thrown for transport errors * @see #EnableComments() * @see #DisableComments() */ public JBBPTextWriter Comment(final String... comment) throws IOException { if (this.flagCommentsAllowed) { if (comment != null) { for (final String c : comment) { if (c == null) { continue; } if (c.indexOf('\n') >= 0) { final String[] split = c.split("\\n", -1); for (final String s : split) { this.ensureCommentMode(); this.write(s); } } else { this.ensureCommentMode(); this.write(c); } } this.prevLineCommentsStartPosition = 0; } } else { ensureNewLineMode(); } return this; } @Override public String toString() { final String result; if (this.out instanceof StringWriter) { result = this.out.toString(); } else { result = JBBPTextWriter.class.getName() + '(' + this.out.getClass().getName() + ")@" + System.identityHashCode(this); } return result; } /** * Print objects. * * @param objId object id which will be provided to a converter as extra info * @param obj objects to be converted and printed, must not be null * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Obj(final int objId, final Object... obj) throws IOException { if (this.extras.isEmpty()) { throw new IllegalStateException("There is not any registered extras"); } for (final Object c : obj) { String str = null; for (final Extra e : this.extras) { str = e.doConvertObjToStr(this, objId, c); if (str != null) { break; } } if (str != null) { ensureValueMode(); printValueString(str); } } return this; } /** * Print objects from array. * * @param objId object id which will be provided to a converter as extra info * @param array array of objects, must not be null * @param off offset to the first element to be printed * @param len number of elements to be printed * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Obj(final int objId, final Object[] array, int off, int len) throws IOException { while (len-- > 0) { this.Obj(objId, array[off++]); } return this; } /** * Print objects which marked by Bin annotation or successors of JBBPAbstractField. * NB! Keep in mind that values of fields will be processed for their attributes before printing * and for instance a bit field with inversion will be shown as inverted one. * * @param objs array of object marked by Bin annotation or successors of * JBBPAbstractField * @return the context * @throws IOException it will be thrown if transport errors */ public JBBPTextWriter Bin(final Object... objs) throws IOException { if (this.mappedClassObserver == null) { this.mappedClassObserver = new MappedObjectLogger(); } ensureNewLineMode(); for (final Object obj : objs) { if (obj == null) { write(""); } else { if (obj instanceof JBBPAbstractField) { printAbstractFieldObject(null, (JBBPAbstractField) obj); } else { this.mappedClassObserver.init(); this.mappedClassObserver.processObject(obj); } } } return this; } protected void printAbstractFieldObject(final String postText, final JBBPAbstractField field) throws IOException { final String postfix = (postText == null ? "" : " " + postText); if (field instanceof JBBPAbstractArrayField || field instanceof JBBPFieldStruct) { HR(); Comment(" Start " + makeFieldComment(field) + postfix); HR(); IndentInc(); if (field instanceof JBBPAbstractArrayField) { final JBBPAbstractArrayField array = (JBBPAbstractArrayField) field; if (array.size() > 0) { if (array instanceof JBBPFieldArrayBit) { Byte(((JBBPFieldArrayBit) array).getArray()); } else if (array instanceof JBBPFieldArrayBoolean) { final boolean[] boolArray = ((JBBPFieldArrayBoolean) array).getArray(); final String[] arrayToPrint = new String[boolArray.length]; for (int i = 0; i < boolArray.length; i++) { arrayToPrint[i] = boolArray[i] ? "T" : "F"; } Str(arrayToPrint); } else if (array instanceof JBBPFieldArrayByte) { Byte(((JBBPFieldArrayByte) array).getArray()); } else if (array instanceof JBBPFieldArrayInt) { Int(((JBBPFieldArrayInt) array).getArray()); } else if (array instanceof JBBPFieldArrayUInt) { UInt(((JBBPFieldArrayUInt) array).getInternalArray()); } else if (array instanceof JBBPFieldArrayLong) { Long(((JBBPFieldArrayLong) array).getArray()); } else if (array instanceof JBBPFieldArrayShort) { Short(((JBBPFieldArrayShort) array).getArray()); } else if (array instanceof JBBPFieldArrayStruct) { final JBBPFieldArrayStruct structArray = (JBBPFieldArrayStruct) array; int index = 0; for (final JBBPFieldStruct s : structArray.getArray()) { printAbstractFieldObject('[' + Integer.toString(index++) + ']', s); } } else if (array instanceof JBBPFieldArrayUByte) { Byte(((JBBPFieldArrayUByte) array).getArray()); } else if (array instanceof JBBPFieldArrayUShort) { Short(((JBBPFieldArrayUShort) array).getArray()); } else if (array instanceof JBBPFieldArrayFloat) { Float(((JBBPFieldArrayFloat) array).getArray()); } else if (array instanceof JBBPFieldArrayDouble) { Double(((JBBPFieldArrayDouble) array).getArray()); } else if (array instanceof JBBPFieldArrayString) { final String[] strArray = ((JBBPFieldArrayString) array).getArray(); final String[] arrayToPrint = new String[strArray.length]; for (int i = 0; i < strArray.length; i++) { arrayToPrint[i] = strArray[i] == null ? "" : '\"' + strArray[i] + '\"'; } Str(arrayToPrint); } else { throw new Error("Unexpected field [" + field.getClass() + ']'); } } } else { final JBBPFieldStruct struct = (JBBPFieldStruct) field; for (final JBBPAbstractField f : struct.getArray()) { printAbstractFieldObject(null, f); } } IndentDec(); HR(); Comment(" End " + makeFieldComment(field) + postfix); HR(); } else { if (field instanceof JBBPNumericField) { final JBBPNumericField numeric = (JBBPNumericField) field; if (numeric instanceof JBBPFieldBit) { Byte(numeric.getAsInt()); } else if (numeric instanceof JBBPFieldBoolean) { Str(numeric.getAsBool() ? "T" : "F"); } else if (numeric instanceof JBBPFieldByte) { Byte(numeric.getAsInt()); } else if (numeric instanceof JBBPFieldInt) { Int(numeric.getAsInt()); } else if (numeric instanceof JBBPFieldUInt) { UInt(numeric.getAsInt()); } else if (numeric instanceof JBBPFieldLong) { Long(numeric.getAsLong()); } else if (numeric instanceof JBBPFieldShort) { Short(numeric.getAsInt()); } else if (numeric instanceof JBBPFieldUByte) { Byte(numeric.getAsInt()); } else if (numeric instanceof JBBPFieldUShort) { Short(numeric.getAsInt()); } else if (numeric instanceof JBBPFieldFloat) { Float(numeric.getAsFloat()); } else if (numeric instanceof JBBPFieldDouble) { Double(numeric.getAsDouble()); } else { throw new Error("Unexpected field [" + field.getClass() + ']'); } Comment(" " + makeFieldComment(field) + postfix); } else if (field instanceof JBBPFieldString) { final String value = ((JBBPFieldString) field).getAsString(); Str(value == null ? "" : '\"' + value + '\"'); Comment(" " + makeFieldComment(field) + postfix); } else { throw new Error("Unexpected field [" + field.getClass() + ']'); } } } /** * Close the wrapped writer. * * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Close() throws IOException { for (final Extra e : extras) { e.onClose(this); } super.close(); return this; } @Override public void close() throws IOException { this.Close(); } /** * Flush buffers in wrapped writer. * * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Flush() throws IOException { super.flush(); return this; } @Override public void flush() throws IOException { this.Flush(); } /** * Print tab as space chars. * * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Tab() throws IOException { this.Space(this.spacesInTab - (this.linePosition % this.spacesInTab)); return this; } /** * Print number of spaces. * * @param numberOfSpaces number of spaces to print * @return the context * @throws IOException it will be thrown for transport errors */ public JBBPTextWriter Space(final int numberOfSpaces) throws IOException { for (int i = 0; i < numberOfSpaces; i++) { writeChar(' '); } return this; } /** * Main method writing a char into wrapped writer. * * @param chr a char to be written. * @throws IOException it will be thrown for transport errors. */ private void writeChar(final char chr) throws IOException { switch (chr) { case '\t': this.Tab(); break; case '\n': { this.out.write(this.lineSeparator); this.lineNumber++; this.prevMode = this.mode; this.mode = MODE_START_LINE; this.linePosition = 0; for (final Extra e : extras) { e.onNewLine(this, this.lineNumber); } } break; case '\r': break; default: { if (!Character.isISOControl(chr)) { this.out.write(chr); this.linePosition++; if (this.mode == MODE_START_LINE) { this.mode = this.prevMode; } } } break; } } /** * Get the current line number. * * @return the current line number, the first one is zero */ public int getLine() { return this.lineNumber; } /** * Get the current line position. * * @return the current line position, the first one is zero */ public int getLinePosition() { return this.linePosition; } @Override public void write(final String str) throws IOException { this.write(str, 0, str.length()); } @Override public void write(final char[] cbuf) throws IOException { this.write(cbuf, 0, cbuf.length); } @Override public void write(final String str, int off, int len) throws IOException { while (len-- > 0) { this.writeChar(str.charAt(off++)); } } @Override public void write(final char[] cbuf, int off, int len) throws IOException { while (len-- > 0) { this.writeChar(cbuf[off++]); } } @Override public void write(final int c) throws IOException { this.writeChar((char) c); } @Override public Writer append(final char c) throws IOException { write(c); return this; } @Override public Writer append(final CharSequence csq, final int start, final int end) throws IOException { final CharSequence cs = (csq == null ? "null" : csq); this.write(cs.subSequence(start, end).toString()); return this; } @Override public Writer append(final CharSequence csq) throws IOException { if (csq == null) { this.write("null"); } else { this.write(csq.toString()); } return this; } /** * The Interface describes some extras for the writer which can make * extra-work. */ public interface Extra { /** * Notification about the start new line. * * @param context the context, must not be null * @param lineNumber the current line number (0 is the first one) * @throws IOException it can be thrown for transport error */ void onNewLine(JBBPTextWriter context, int lineNumber) throws IOException; /** * Notification about print of the first value on the line. * * @param context the context, must not be null * @throws IOException it can be thrown for transport error */ void onBeforeFirstValue(JBBPTextWriter context) throws IOException; /** * Notification that reached defined maximal number of values per string * line. * * @param context the context, must not be null * @throws IOException it can be thrown for transport error */ void onReachedMaxValueNumberForLine(JBBPTextWriter context) throws IOException; /** * Notification about close * * @param context the context, must not be null * @throws IOException it can be thrown for transport error */ void onClose(JBBPTextWriter context) throws IOException; /** * Convert byte to string representation. * * @param context the context, must not be null * @param value the unsigned byte value to be converted * @return string representation of the byte value, must not return null * @throws IOException it can be thrown for transport error */ String doConvertByteToStr(JBBPTextWriter context, int value) throws IOException; /** * Convert short to string representation. * * @param context the context, must not be null * @param value the unsigned short value to be converted * @return string representation of the short value, must not return null * @throws IOException it can be thrown for transport error */ String doConvertShortToStr(JBBPTextWriter context, int value) throws IOException; /** * Convert integer value to string representation. * * @param context the context, must not be null * @param value the integer value to be converted * @return string representation of the integer value, must not return null * @throws IOException it can be thrown for transport error */ String doConvertIntToStr(JBBPTextWriter context, int value) throws IOException; /** * Convert unsigned integer value to string representation. * * @param context the context, must not be null * @param value the unsigned integer value to be converted * @return string representation of the integer value, must not return null * @throws IOException it can be thrown for transport error * @since 2.0.4 */ String doConvertUIntToStr(JBBPTextWriter context, int value) throws IOException; /** * Convert float value to string representation. * * @param context the context, must not be null * @param value the float value to be converted * @return string representation of the float value, must not return null * @throws IOException it can be thrown for transport error * @since 1.4.0 */ String doConvertFloatToStr(JBBPTextWriter context, float value) throws IOException; /** * Convert double value to string representation. * * @param context the context, must not be null * @param value the double value to be converted * @return string representation of the double value, must not return null * @throws IOException it can be thrown for transport error * @since 1.4.0 */ String doConvertDoubleToStr(JBBPTextWriter context, double value) throws IOException; /** * Convert long value to string representation. * * @param context the context, must not be null * @param value the long value to be converted * @return string representation of the long value, must not return null * @throws IOException it can be thrown for transport error */ String doConvertLongToStr(JBBPTextWriter context, long value) throws IOException; /** * Convert an object to its string representation. * * @param context the context, must not be null * @param id an optional object id * @param obj an object to be converted into string, must not be null * @return string representation of the object * @throws IOException it can be thrown for transport error */ String doConvertObjToStr(JBBPTextWriter context, int id, Object obj) throws IOException; /** * Convert a custom field into string. * * @param context the context, must not be null * @param obj an object instance which field must be converted into string, * must not be null * @param field the field of the object which must be converted, must not be * null * @param annotation the bin annotation for the field, must not be null * @return the text representation of the field or null if to not print * anything * @throws IOException it will be thrown for transport error */ String doConvertCustomField(JBBPTextWriter context, final Object obj, final Field field, final Bin annotation) throws IOException; } private final class MappedObjectLogger extends AbstractMappedClassFieldObserver { private final Deque counterStack = new ArrayDeque<>(); private int arrayCounter; protected void init() { arrayCounter = 0; counterStack.clear(); } private String makeFieldDescription(final Field field, final Bin annotation) { final StringBuilder result = new StringBuilder(); if (annotation.name().length() == 0) { result.append(field.getName()); } else { result.append(annotation.name()); } if (annotation.comment().length() != 0) { result.append(", ").append(annotation.comment()); } return result.toString(); } private String makeStructDescription(final Object obj, final Field field, final Bin annotation) { final StringBuilder result = new StringBuilder(); final Class objClass = obj.getClass(); Bin classAnno = objClass.getAnnotation(Bin.class); if (classAnno != null && classAnno.equals(annotation)) { classAnno = null; } final String typeName; if (field == null) { typeName = obj.getClass().getSimpleName(); } else { final Class fieldType = field.getType(); typeName = fieldType.isArray() ? fieldType.getComponentType().getSimpleName() : fieldType.getSimpleName(); } final String name = annotation == null || annotation.name().length() == 0 ? typeName : annotation.name(); final String fieldComment = annotation == null || annotation.comment().length() == 0 ? null : annotation.comment(); final String objectComment = classAnno == null || classAnno.comment().length() == 0 ? null : classAnno.comment(); result.append(name); if (fieldComment != null) { result.append(", ").append(fieldComment); } if (objectComment != null) { if (fieldComment != null) { result.append('\n'); } else { result.append(", "); } result.append(objectComment); } return result.toString(); } private String makeArrayDescription(final Field field, final Bin annotation) { return makeFieldDescription(field, annotation); } @Override protected void onArrayEnd(final Object obj, final Field field, final Bin annotation) { try { IndentDec(); HR(); if (field.getType() == String.class) { Comment("END STRING : " + makeArrayDescription(field, annotation)); } else { Comment("END ARRAY : " + makeArrayDescription(field, annotation)); } HR(); } catch (IOException ex) { throw new JBBPIOException("Can't log array field", ex); } finally { arrayCounter--; } } @Override protected void onArrayStart(final Object obj, final Field field, final Bin annotation, final int length) { try { HR(); if (field.getType() == String.class) { Comment("STRING: " + makeFieldDescription(field, annotation)); } else { Comment("START ARRAY : " + makeArrayDescription(field, annotation) + " OF " + field.getType().getComponentType().getSimpleName() + " [" + length + ']'); } HR(); IndentInc(); } catch (IOException ex) { throw new JBBPIOException("Can't log array field", ex); } finally { arrayCounter++; } } @Override protected void onStructEnd(final Object obj, final Field field, final Bin annotation) { try { IndentDec(); HR(); Comment("END : " + makeStructDescription(obj, field, annotation)); HR(); this.arrayCounter = this.counterStack.pop(); } catch (IOException ex) { throw new JBBPIOException("Can't log struct field", ex); } } @Override protected void onStructStart(final Object obj, final Field field, final Bin annotation) { try { this.counterStack.add(this.arrayCounter); this.arrayCounter = 0; HR(); Comment("START : " + makeStructDescription(obj, field, annotation)); HR(); IndentInc(); } catch (IOException ex) { throw new JBBPIOException("Can't log short field", ex); } } @Override protected void onFieldLong(final Object obj, final Field field, final Bin annotation, final long value) { try { Long(value); if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log short field", ex); } } @Override protected void onFieldInt(final Object obj, final Field field, final Bin annotation, final int value) { try { Int(value); if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log int field", ex); } } @Override protected void onFieldUInt(Object obj, Field field, Bin annotation, int value) { try { UInt(value); if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log uint field", ex); } } @Override protected void onFieldFloat(final Object obj, final Field field, final Bin annotation, final float value) { try { Float(value); if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log float field", ex); } } @Override protected void onFieldString(final Object obj, final Field field, final Bin annotation, final String value) { try { ensureValueMode(); final String prefix = prefixValue; prefixValue = ""; printValueString(value == null ? "" : '\"' + value + '\"'); prefixValue = prefix; if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log string field", ex); } } @Override protected void onFieldDouble(final Object obj, final Field field, final Bin annotation, final double value) { try { Double(value); if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log double field", ex); } } @Override protected void onFieldShort(final Object obj, final Field field, final Bin annotation, final boolean signed, final int value) { try { Short(value); if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log short field", ex); } } @Override protected void onFieldByte(final Object obj, final Field field, final Bin annotation, final boolean signed, final int value) { try { Byte(value); if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log byte field", ex); } } @Override protected void onFieldBool(final Object obj, final Field field, final Bin annotation, final boolean value) { try { Byte(value ? 0x01 : 0x00); if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log boolean field", ex); } } @Override protected void onFieldBits(final Object obj, final Field field, final Bin annotation, final JBBPBitNumber bitNumber, final int value) { try { Byte(value & bitNumber.getMask()); if (this.arrayCounter == 0) { Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log bit field", ex); } } @Override protected void onFieldCustom(final Object obj, final Field field, final Bin annotation, final Object customFieldProcessor, final Object value) { try { if (extras.isEmpty()) { throw new IllegalStateException("There is not any registered extras"); } String str = null; for (final Extra e : extras) { str = e.doConvertCustomField(JBBPTextWriter.this, obj, field, annotation); if (str != null) { break; } } if (str != null) { ensureValueMode(); printValueString(str); Comment(makeFieldDescription(field, annotation)); } } catch (IOException ex) { throw new JBBPIOException("Can't log custom field", ex); } } public void processObject(final Object obj) { super.processObject(obj, null, null, this); } } } JBBPTextWriterExtraAdapter.java000066400000000000000000000052321445000440600350660ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; import com.igormaznitsa.jbbp.mapper.Bin; import java.io.IOException; import java.lang.reflect.Field; /** * Auxiliary adapter for interface JBBPTextWriter.Extra. * * @see com.igormaznitsa.jbbp.utils.JBBPTextWriter.Extra * @since 1.1 */ public abstract class JBBPTextWriterExtraAdapter implements JBBPTextWriter.Extra { @Override public void onNewLine(final JBBPTextWriter context, final int lineNumber) throws IOException { } @Override public void onBeforeFirstValue(final JBBPTextWriter context) throws IOException { } @Override public void onClose(final JBBPTextWriter context) throws IOException { } @Override public String doConvertByteToStr(final JBBPTextWriter context, final int value) throws IOException { return null; } @Override public String doConvertShortToStr(final JBBPTextWriter context, final int value) throws IOException { return null; } @Override public String doConvertFloatToStr(JBBPTextWriter context, float value) throws IOException { return null; } @Override public String doConvertDoubleToStr(JBBPTextWriter context, double value) throws IOException { return null; } @Override public String doConvertIntToStr(final JBBPTextWriter context, final int value) throws IOException { return null; } @Override public String doConvertLongToStr(final JBBPTextWriter context, final long value) throws IOException { return null; } @Override public String doConvertObjToStr(final JBBPTextWriter context, final int id, final Object obj) throws IOException { return null; } @Override public String doConvertCustomField(final JBBPTextWriter context, final Object obj, final Field field, final Bin annotation) throws IOException { return null; } @Override public void onReachedMaxValueNumberForLine(final JBBPTextWriter context) throws IOException { } @Override public String doConvertUIntToStr(JBBPTextWriter context, int value) throws IOException { return null; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPUtils.java000066400000000000000000001044231445000440600316210ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; /** * Misc auxiliary methods to be used in the framework. * * @since 1.0 */ public final class JBBPUtils { public static final String[] ARRAY_STRING_EMPTY = new String[0]; public static final JBBPAbstractField[] ARRAY_FIELD_EMPTY = new JBBPAbstractField[0]; // Don't replace by StandardCharsets for Android compatibility! @SuppressWarnings("CharsetObjectCanBeUsed") private static final Charset CHARSET_UTF8 = Charset.forName("UTF-8"); private JBBPUtils() { } /** * Convert a string into its UTF8 representation. * * @param str string to be converted, must not be null * @return array of chars from the string in utf8 format, must not be null * @since 1.4.0 */ public static byte[] strToUtf8(final String str) { final ByteBuffer buffer = CHARSET_UTF8.encode(str); final byte[] bytesArray = new byte[buffer.remaining()]; buffer.get(bytesArray, 0, bytesArray.length); return bytesArray; } /** * Convert UTF8 byte array into string. * * @param array array to be converted, must not be null * @return result string, must not be nulls * @since 1.4.0 */ public static String utf8ToStr(final byte[] array) { return CHARSET_UTF8.decode(ByteBuffer.wrap(array)).toString(); } /** * Check that a string is a number. * * @param num a string to be checked, it can be null * @return true if the string represents a number, false if it is not number * or it is null */ public static boolean isNumber(final String num) { if (num == null || num.length() == 0) { return false; } final boolean firstIsDigit = Character.isDigit(num.charAt(0)); if (!firstIsDigit && num.charAt(0) != '-') { return false; } boolean dig = firstIsDigit; for (int i = 1; i < num.length(); i++) { if (!Character.isDigit(num.charAt(i))) { return false; } dig = true; } return dig; } /** * Pack an integer value as a byte array. * * @param value a code to be packed * @return a byte array contains the packed code */ public static byte[] packInt(final int value) { if ((value & 0xFFFFFF80) == 0) { return new byte[] {(byte) value}; } else if ((value & 0xFFFF0000) == 0) { return new byte[] {(byte) 0x80, (byte) (value >>> 8), (byte) value}; } else { return new byte[] {(byte) 0x81, (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value}; } } /** * Pack an integer value and save that into a byte array since defined * position. * * @param array a byte array where to write the packed data, it must not be * null * @param position the position of the first byte of the packed value, it must * not be null * @param value the value to be packed * @return number of bytes written into the array, the position will be * increased */ public static int packInt(final byte[] array, final JBBPIntCounter position, final int value) { if ((value & 0xFFFFFF80) == 0) { array[position.getAndIncrement()] = (byte) value; return 1; } else if ((value & 0xFFFF0000) == 0) { array[position.getAndIncrement()] = (byte) 0x80; array[position.getAndIncrement()] = (byte) (value >>> 8); array[position.getAndIncrement()] = (byte) value; return 3; } array[position.getAndIncrement()] = (byte) 0x81; array[position.getAndIncrement()] = (byte) (value >>> 24); array[position.getAndIncrement()] = (byte) (value >>> 16); array[position.getAndIncrement()] = (byte) (value >>> 8); array[position.getAndIncrement()] = (byte) value; return 5; } /** * Unpack an integer value from defined position in a byte array. * * @param array the source byte array * @param position the position of the first byte of packed value * @return the unpacked value, the position will be increased */ public static int unpackInt(final byte[] array, final JBBPIntCounter position) { final int code = array[position.getAndIncrement()] & 0xFF; if (code < 0x80) { return code; } final int result; switch (code) { case 0x80: { result = ((array[position.getAndIncrement()] & 0xFF) << 8) | (array[position.getAndIncrement()] & 0xFF); } break; case 0x81: { result = ((array[position.getAndIncrement()] & 0xFF) << 24) | ((array[position.getAndIncrement()] & 0xFF) << 16) | ((array[position.getAndIncrement()] & 0xFF) << 8) | (array[position.getAndIncrement()] & 0xFF); } break; default: throw new IllegalArgumentException("Unsupported packed integer prefix [0x" + Integer.toHexString(code).toUpperCase(Locale.ENGLISH) + ']'); } return result; } /** * A Byte array into its hex string representation * * @param array an array to be converted * @return a string of hex representations of values from the array */ public static String array2hex(final byte[] array) { return byteArray2String(array, "0x", ", ", true, 16); } /** * A Byte array into its bin string representation * * @param array an array to be converted * @return a string of bin representations of values from the array */ public static String array2bin(final byte[] array) { return byteArray2String(array, "0b", ", ", true, 2); } /** * A Byte array into its octal string representation * * @param array an array to be converted * @return a string of octal representations of values from the array */ public static String array2oct(final byte[] array) { return byteArray2String(array, "0o", ", ", true, 8); } /** * Convert a byte array into string representation * * @param array the array to be converted, it must not be null * @param prefix the prefix for each converted value, it can be null * @param delimiter the delimiter for string representations * @param brackets if true then place the result into square brackets * @param radix the base for conversion * @return the string representation of the byte array */ public static String byteArray2String(final byte[] array, final String prefix, final String delimiter, final boolean brackets, final int radix) { if (array == null) { return null; } final int maxLength = Integer.toString(0xFF, radix).length(); final String zero = "00000000"; final String normDelimiter = delimiter == null ? " " : delimiter; final String normPrefix = prefix == null ? "" : prefix; final StringBuilder result = new StringBuilder(array.length * 4); if (brackets) { result.append('['); } boolean notFirst = false; for (final byte b : array) { if (notFirst) { result.append(normDelimiter); } else { notFirst = true; } result.append(normPrefix); final String v = Integer.toString(b & 0xFF, radix); if (v.length() < maxLength) { result.append(zero, 0, maxLength - v.length()); } result.append(v.toUpperCase(Locale.ENGLISH)); } if (brackets) { result.append(']'); } return result.toString(); } /** * Reverse bits in a byte. * * @param value a byte value which bits must be reversed. * @return the reversed version of the byte * @since 1.1 */ public static byte reverseBitsInByte(final byte value) { final int v = value & 0xFF; return (byte) ((((v * 0x0802 & 0x22110) | (v * 0x8020 & 0x88440)) * 0x10101) >> 16); } /** * Reverse lower part of a byte defined by bits number constant. * * @param bitNumber number of lowest bits to be reversed, must not be null * @param value a byte to be processed * @return value contains reversed number of lowest bits of the byte */ public static byte reverseBitsInByte(final JBBPBitNumber bitNumber, final byte value) { final byte reversed = reverseBitsInByte(value); return (byte) ((reversed >>> (8 - bitNumber.getBitNumber())) & bitNumber.getMask()); } /** * Convert a byte array into string binary representation with LSB0 order. * * @param values a byte array to be converted * @return the string representation of the array */ public static String bin2str(final byte[] values) { return bin2str(values, JBBPBitOrder.LSB0, false); } /** * Convert a byte array into string binary representation with LSB0 order and * possibility to separate bytes. * * @param values a byte array to be converted * @param separateBytes if true then bytes will be separated by spaces * @return the string representation of the array */ public static String bin2str(final byte[] values, final boolean separateBytes) { return bin2str(values, JBBPBitOrder.LSB0, separateBytes); } /** * Convert a byte array into string binary representation with defined bit * order and possibility to separate bytes. * * @param values a byte array to be converted * @param bitOrder the bit order for byte decoding * @param separateBytes if true then bytes will be separated by spaces * @return the string representation of the array */ public static String bin2str(final byte[] values, final JBBPBitOrder bitOrder, final boolean separateBytes) { if (values == null) { return null; } final StringBuilder result = new StringBuilder(values.length * (separateBytes ? 9 : 8)); boolean notFirst = false; for (final byte b : values) { if (separateBytes) { if (notFirst) { result.append(' '); } else { notFirst = true; } } int a = b; if (bitOrder == JBBPBitOrder.MSB0) { for (int i = 0; i < 8; i++) { result.append((a & 0x1) == 0 ? '0' : '1'); a >>= 1; } } else { for (int i = 0; i < 8; i++) { result.append((a & 0x80) == 0 ? '0' : '1'); a <<= 1; } } } return result.toString(); } /** * Convert array of JBBP fields into a list. * * @param fields an array of fields, must not be null * @return a list of JBBP fields */ public static List fieldsAsList(final JBBPAbstractField... fields) { final List result = new ArrayList<>(); Collections.addAll(result, fields); return result; } /** * Convert string representation of binary data into byte array with LSB0 bit * order. * * @param values a string represents binary data * @return a byte array generated from the decoded string, empty array for * null string */ public static byte[] str2bin(final String values) { return str2bin(values, JBBPBitOrder.LSB0); } /** * Convert string representation of binary data into byte array/ * * @param values a string represents binary data * @param bitOrder the bit order to be used for operation * @return a byte array generated from the decoded string, empty array for * null string */ public static byte[] str2bin(final String values, final JBBPBitOrder bitOrder) { if (values == null) { return new byte[0]; } int buff = 0; int cnt = 0; final ByteArrayOutputStream buffer = new ByteArrayOutputStream((values.length() + 7) >> 3); final boolean msb0 = bitOrder == JBBPBitOrder.MSB0; for (final char v : values.toCharArray()) { switch (v) { case '_': case ' ': continue; case '0': case 'X': case 'x': case 'Z': case 'z': { if (msb0) { buff >>= 1; } else { buff <<= 1; } } break; case '1': { if (msb0) { buff = (buff >> 1) | 0x80; } else { buff = (buff << 1) | 1; } } break; default: throw new IllegalArgumentException("Detected unsupported char '" + v + ']'); } cnt++; if (cnt == 8) { buffer.write(buff); cnt = 0; buff = 0; } } if (cnt > 0) { buffer.write(msb0 ? buff >>> (8 - cnt) : buff); } return buffer.toByteArray(); } /** * Split a string for a char used as the delimiter. * * @param str a string to be split * @param splitChar a char to be used as delimeter * @return array contains split string parts without delimeter chars */ public static String[] splitString(final String str, final char splitChar) { final int length = str.length(); final StringBuilder builder = new StringBuilder(Math.max(8, length)); int counter = 1; for (int i = 0; i < length; i++) { if (str.charAt(i) == splitChar) { counter++; } } final String[] result = new String[counter]; int position = 0; for (int i = 0; i < length; i++) { final char chr = str.charAt(i); if (chr == splitChar) { result[position++] = builder.toString(); builder.setLength(0); } else { builder.append(chr); } } if (position < result.length) { result[position] = builder.toString(); } return result; } /** * Check that an object is null and throw NullPointerException in the case. * * @param object an object to be checked * @param message message to be used as the exception message * @throws NullPointerException it will be thrown if the object is null */ public static void assertNotNull(final Object object, final String message) { if (object == null) { throw new NullPointerException(message == null ? "Object is null" : message); } } /** * Convert an integer number into human readable hexadecimal format. * * @param number a number to be converted * @return a string with human readable hexadecimal number representation */ public static String int2msg(final int number) { return number + " (0x" + Long.toHexString((long) number & 0xFFFFFFFFL).toUpperCase(Locale.ENGLISH) + ')'; } /** * Normalize field name or path. * * @param nameOrPath a field name or a path to be normalized, must not be null * @return the normalized version of the name or path */ public static String normalizeFieldNameOrPath(final String nameOrPath) { assertNotNull(nameOrPath, "Name of path must not be null"); return nameOrPath.trim().toLowerCase(Locale.ENGLISH); } /** * Quiet closing of a closeable object. * * @param closeable a closeable object, can be null */ public static void closeQuietly(final Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (IOException ex) { // Keep silence } } /** * Convert chars of a string into a byte array contains the unicode codes. * * @param byteOrder the byte order for the operation, must not be null * @param str the string which chars should be written, must not be null * @return the byte array contains unicodes of the string written as byte * pairs * @since 1.1 */ public static byte[] str2UnicodeByteArray(final JBBPByteOrder byteOrder, final String str) { final byte[] result = new byte[str.length() << 1]; int index = 0; for (int i = 0; i < str.length(); i++) { final int val = str.charAt(i); switch (byteOrder) { case BIG_ENDIAN: { result[index++] = (byte) (val >> 8); result[index++] = (byte) val; } break; case LITTLE_ENDIAN: { result[index++] = (byte) val; result[index++] = (byte) (val >> 8); } break; default: throw new Error("Unexpected byte order [" + byteOrder + ']'); } } return result; } /** * Reverse order of bytes in a byte array. * * @param nullableArrayToBeInverted a byte array which order must be reversed, * it can be null * @return the same array instance but with reversed byte order, null if the * source array is null * @since 1.1 */ public static byte[] reverseArray(final byte[] nullableArrayToBeInverted) { if (nullableArrayToBeInverted != null && nullableArrayToBeInverted.length > 0) { int indexStart = 0; int indexEnd = nullableArrayToBeInverted.length - 1; while (indexStart < indexEnd) { final byte a = nullableArrayToBeInverted[indexStart]; nullableArrayToBeInverted[indexStart] = nullableArrayToBeInverted[indexEnd]; nullableArrayToBeInverted[indexEnd] = a; indexStart++; indexEnd--; } } return nullableArrayToBeInverted; } /** * Split an integer value to bytes and returns as a byte array. * * @param value a value to be split * @param valueInLittleEndian the flag shows that the integer is presented in * the little endian form * @param buffer a buffer array to be used as a storage, if the array is null * or its length is less than 4 then new array will be created * @return the same array filled by parts of the integer value or new array if * the provided buffer is null or has not enough size * @since 1.1 */ public static byte[] splitInteger(final int value, final boolean valueInLittleEndian, final byte[] buffer) { final byte[] result; if (buffer == null || buffer.length < 4) { result = new byte[4]; } else { result = buffer; } int tempValue = value; if (valueInLittleEndian) { for (int i = 0; i < 4; i++) { result[i] = (byte) tempValue; tempValue >>>= 8; } } else { for (int i = 3; i >= 0; i--) { result[i] = (byte) tempValue; tempValue >>>= 8; } } return result; } /** * Split a long value to its bytes and returns the parts as an array. * * @param value the value to be split * @param valueInLittleEndian the flag shows that the long is presented in the * little endian for * @param buffer a buffer array to be used as a storage, if the array is null * or its length is less than 8 then new array will be created * @return the same array filled by parts of the integer value or new array if * the provided buffer is null or has not enough size * @since 1.1 */ public static byte[] splitLong(final long value, final boolean valueInLittleEndian, final byte[] buffer) { final byte[] result; if (buffer == null || buffer.length < 8) { result = new byte[8]; } else { result = buffer; } long tempValue = value; if (valueInLittleEndian) { for (int i = 0; i < 8; i++) { result[i] = (byte) tempValue; tempValue >>>= 8; } } else { for (int i = 7; i >= 0; i--) { result[i] = (byte) tempValue; tempValue >>>= 8; } } return result; } /** * Concatenate byte arrays into one byte array sequentially. * * @param arrays arrays to be concatenated * @return the result byte array contains concatenated source arrays * @since 1.1 */ public static byte[] concat(final byte[]... arrays) { int len = 0; for (final byte[] arr : arrays) { len += arr.length; } final byte[] result = new byte[len]; int pos = 0; for (final byte[] arr : arrays) { System.arraycopy(arr, 0, result, pos, arr.length); pos += arr.length; } return result; } /** * Revert order for defined number of bytes in a value. * * @param value the value which bytes should be reordered * @param numOfLowerBytesToInvert number of lower bytes to be reverted in * their order, must be 1..8 * @return new value which has reverted order for defined number of lower * bytes * @since 1.1 */ public static long reverseByteOrder(long value, int numOfLowerBytesToInvert) { if (numOfLowerBytesToInvert < 1 || numOfLowerBytesToInvert > 8) { throw new IllegalArgumentException("Wrong number of bytes [" + numOfLowerBytesToInvert + ']'); } long result = 0; int offsetInResult = (numOfLowerBytesToInvert - 1) * 8; while (numOfLowerBytesToInvert-- > 0) { final long b = value & 0xFF; value >>>= 8; result |= (b << offsetInResult); offsetInResult -= 8; } return result; } /** * Convert double value into string representation with defined radix * base. * * @param doubleValue value to be converted in string * @param radix radix base to be used for conversion, must be 10 or 16 * @return converted value as upper case string * @throws IllegalArgumentException for wrong radix base * @since 1.4.0 */ public static String double2str(final double doubleValue, final int radix) { if (radix != 10 && radix != 16) { throw new IllegalArgumentException("Illegal radix [" + radix + ']'); } final String result; if (radix == 16) { String converted = Double.toHexString(doubleValue); boolean minus = converted.startsWith("-"); if (minus) { converted = converted.substring(1); } if (converted.startsWith("0x")) { converted = converted.substring(2); } result = (minus ? '-' + converted : converted).toUpperCase(Locale.ENGLISH); } else { result = Double.toString(doubleValue); } return result; } /** * Convert float value into string representation with defined radix * base. * * @param floatValue value to be converted in string * @param radix radix base to be used for conversion, must be 10 or 16 * @return converted value as upper case string * @throws IllegalArgumentException for wrong radix base * @since 1.4.0 */ public static String float2str(final float floatValue, final int radix) { if (radix != 10 && radix != 16) { throw new IllegalArgumentException("Illegal radix [" + radix + ']'); } final String result; if (radix == 16) { String converted = Double.toHexString(floatValue); boolean minus = converted.startsWith("-"); if (minus) { converted = converted.substring(1); } if (converted.startsWith("0x")) { converted = converted.substring(2); } result = (minus ? '-' + converted : converted).toUpperCase(Locale.ENGLISH); } else { result = Double.toString(floatValue); } return result; } /** * Convert unsigned long value into string representation with defined radix * base. * * @param ulongValue value to be converted in string * @param radix radix base to be used for conversion, must be 2..36 * @param charBuffer char buffer to be used for conversion operations, should * be not less than 64 char length, if length is less than 64 or null then new * one will be created * @return converted value as upper case string * @throws IllegalArgumentException for wrong radix base * @since 1.1 */ public static String ulong2str(final long ulongValue, final int radix, final char[] charBuffer) { if (radix < 2 || radix > 36) { throw new IllegalArgumentException("Illegal radix [" + radix + ']'); } if (ulongValue == 0) { return "0"; } else { final String result; if (ulongValue > 0) { result = Long.toString(ulongValue, radix).toUpperCase(Locale.ENGLISH); } else { final char[] buffer = charBuffer == null || charBuffer.length < 64 ? new char[64] : charBuffer; int pos = buffer.length; long topPart = ulongValue >>> 32; long bottomPart = (ulongValue & 0xFFFFFFFFL) + ((topPart % radix) << 32); topPart /= radix; while ((bottomPart | topPart) > 0) { final int val = (int) (bottomPart % radix); buffer[--pos] = (char) (val < 10 ? '0' + val : 'A' + val - 10); bottomPart = (bottomPart / radix) + ((topPart % radix) << 32); topPart /= radix; } result = new String(buffer, pos, buffer.length - pos); } return result; } } /** * Extend text by chars to needed length. * * @param text text to be extended, must not be null. * @param neededLen needed length for text * @param ch char to be used for extending * @param mode 0 to extend left, 1 to extend right, otherwise extends both * sides * @return text extended by chars up to needed length, or non-changed if the * text has equals or greater length. * @since 1.1 */ public static String ensureMinTextLength(final String text, final int neededLen, final char ch, final int mode) { final int number = neededLen - text.length(); if (number <= 0) { return text; } final StringBuilder result = new StringBuilder(neededLen); switch (mode) { case 0: { for (int i = 0; i < number; i++) { result.append(ch); } result.append(text); } break; case 1: { result.append(text); for (int i = 0; i < number; i++) { result.append(ch); } } break; default: { int leftField = number / 2; int rightField = number - leftField; while (leftField-- > 0) { result.append(ch); } result.append(text); while (rightField-- > 0) { result.append(ch); } } break; } return result.toString(); } /** * Remove leading zeros from string. * * @param str the string to be trimmed * @return the result string without left extra zeros, or null if argument is * null * @since 1.1 */ public static String removeLeadingZeros(final String str) { String result = str; if (str != null && str.length() != 0) { int startIndex = 0; while (startIndex < str.length() - 1) { final char ch = str.charAt(startIndex); if (ch != '0') { break; } startIndex++; } if (startIndex > 0) { result = str.substring(startIndex); } } return result; } /** * Remove trailing zeros from string. * * @param str the string to be trimmed * @return the result string without left extra zeros, or null if argument is * null * @since 1.1 */ public static String removeTrailingZeros(final String str) { String result = str; if (str != null && str.length() != 0) { int endIndex = str.length(); while (endIndex > 1) { final char ch = str.charAt(endIndex - 1); if (ch != '0') { break; } endIndex--; } if (endIndex < str.length()) { result = str.substring(0, endIndex); } } return result; } /** * Check that a byte array starts with some byte values. * * @param array array to be checked, must not be null * @param str a byte string which will be checked as the start sequence of the * array, must not be null * @return true if the string is the start sequence of the array, false * otherwise * @throws NullPointerException if any argument is null * @since 1.1 */ public static boolean arrayStartsWith(final byte[] array, final byte[] str) { boolean result = false; if (array.length >= str.length) { result = true; int index = str.length; while (--index >= 0) { if (array[index] != str[index]) { result = false; break; } } } return result; } /** * Check that a byte array ends with some byte values. * * @param array array to be checked, must not be null * @param str a byte string which will be checked as the end sequence of the * array, must not be null * @return true if the string is the end sequence of the array, false * otherwise * @throws NullPointerException if any argument is null * @since 1.1 */ public static boolean arrayEndsWith(final byte[] array, final byte[] str) { boolean result = false; if (array.length >= str.length) { result = true; int index = str.length; int arrayIndex = array.length; while (--index >= 0) { if (array[--arrayIndex] != str[index]) { result = false; break; } } } return result; } /** * Make mask for value. * * @param value a value for which we need to make mask. * @return generated mask to represent the value * @since 1.1 */ public static int makeMask(final int value) { if (value == 0) { return 0; } if ((value & 0x80000000) != 0) { return 0xFFFFFFFF; } int msk = 1; do { msk <<= 1; } while (msk <= value); return msk - 1; } /** * Allows to check two objects even if one of them is null * * @param o1 object one can be null * @param o2 object two can be null * @return true if objects are equal ones, false otherwise * @since 1.4.1 */ public static boolean equals(final Object o1, final Object o2) { if (o1 == o2) { return true; } if (o1 == null || o2 == null) { return false; } return o1.equals(o2); } public static String toHexString(final long value, final int charsNum) { String result = Long.toHexString(value).toUpperCase(Locale.ENGLISH); if (charsNum >= result.length()) { final StringBuilder buffer = new StringBuilder(charsNum); for (int i = 0; i < charsNum - result.length(); i++) { buffer.append('0'); } buffer.append(result); result = buffer.toString(); } return result; } /** * Trace an input stream into a print writer. * * @param inStream input stream to be traced, must not be null * @param out destination print stream, must not be null * @throws IOException thrown if transport error * @see #traceData(InputStream, int, int, String, String, String, String, char, boolean, PrintStream) * @since 2.0.3 */ public static void traceData(final InputStream inStream, final PrintStream out) throws IOException { traceData( inStream, 4, 8, " ", " ", " | ", " ", '.', true, out ); } /** * Trace an input stream into a print writer. * * @param inStream an input stream to be traced, must not be null * @param valuesPerColumn number of value in one shown column * @param columnsNumber number of eight byte columns * @param afterAddressDelimiter string to be written after address section, must not be null * @param interValueDelimiter string to be written after each value, must not be null * @param interColumnDelimiter string to be written to show column, must not be null * @param delimiterBeforeChars string to be written before chars section, must not be null * @param nonPrintableChar char to be used for non-printable chars in chars section * @param printAsChars true if char section is required, false otherwise * @param out destination writer, must not be null * @throws IOException thrown if any transport error * @since 2.0.3 */ public static void traceData(final InputStream inStream, int valuesPerColumn, final int columnsNumber, final String afterAddressDelimiter, final String interValueDelimiter, final String interColumnDelimiter, final String delimiterBeforeChars, final char nonPrintableChar, final boolean printAsChars, final PrintStream out) throws IOException { long address = 0L; valuesPerColumn = valuesPerColumn <= 0 ? 1 : valuesPerColumn; final int bytesPerLine = columnsNumber <= 0 ? 8 : columnsNumber * valuesPerColumn; final StringBuilder charBuffer = printAsChars ? new StringBuilder(bytesPerLine) : null; int lineByteCounter = 0; boolean ending = false; while (!Thread.currentThread().isInterrupted()) { final int nextData; if (ending) { nextData = -1; } else { nextData = inStream.read(); ending = nextData < 0; } if (lineByteCounter == 0) { out.print(toHexString(address, 8)); out.print(afterAddressDelimiter); } if (charBuffer != null) { charBuffer.append(nextData > 0x1F && nextData < 0xFF ? (char) nextData : nonPrintableChar); } out.print(nextData < 0 ? "--" : toHexString(nextData, 2)); lineByteCounter++; if (lineByteCounter == bytesPerLine) { if (charBuffer != null) { out.print(delimiterBeforeChars); out.print(charBuffer.toString()); charBuffer.setLength(0); } lineByteCounter = 0; address += bytesPerLine; out.println(); if (ending) { break; } } else if (lineByteCounter % valuesPerColumn == 0) { out.print(interColumnDelimiter); } else { out.print(interValueDelimiter); } } } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JavaSrcTextBuffer.java000066400000000000000000000124251445000440600334130ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; /** * Text buffer to provide text manipulation operations to form Java style sources. * * @since 1.3.0 */ public class JavaSrcTextBuffer { private final StringBuilder buffer; private int tabCounter = 0; /** * Constructor with default capacity. */ public JavaSrcTextBuffer() { this.buffer = new StringBuilder(); } /** * Constructor with capacity value. * * @param capacity capacity of the created buffer */ public JavaSrcTextBuffer(final int capacity) { this.buffer = new StringBuilder(capacity); } /** * Increase indent * * @return this instance */ public JavaSrcTextBuffer incIndent() { this.tabCounter++; return this; } /** * Decrease indent * * @return this instance */ public JavaSrcTextBuffer decIndent() { if (this.tabCounter > 0) { this.tabCounter--; } return this; } /** * Print integer value * * @param value integer value * @return this instance */ public JavaSrcTextBuffer print(final int value) { this.buffer.append(value); return this; } /** * Print boolean value * * @param value boolean value * @return this instance */ public JavaSrcTextBuffer print(final boolean value) { this.buffer.append(value ? "true" : "false"); return this; } /** * Formatted print. * * @param text format string * @param args arguments for formatted string * @return this instance * @see String#format(String, Object...) */ public JavaSrcTextBuffer printf(final String text, final Object... args) { this.buffer.append(String.format(text, args)); return this; } /** * Print string * * @param text string * @return this instance */ public JavaSrcTextBuffer print(final String text) { this.buffer.append(text); return this; } /** * Clean buffer * * @return this instance */ public JavaSrcTextBuffer clean() { this.buffer.setLength(0); this.buffer.trimToSize(); return this; } /** * Print tab char * * @return this instance */ public JavaSrcTextBuffer tab() { this.buffer.append('\t'); return this; } /** * Print tabs for current indent number * * @return this instance */ public JavaSrcTextBuffer indent() { for (int i = 0; i < this.tabCounter; i++) { this.tab(); } return this; } /** * Print next line char * * @return this instance */ public JavaSrcTextBuffer println() { this.buffer.append(String.format("%n")); return this; } /** * Check that the buffer is empty * * @return true if the buffer is empty, false otherwise */ public boolean isEmpty() { return this.buffer.length() == 0; } /** * Print text and next line char in the end * * @param text the text to be printed * @return this instance */ public JavaSrcTextBuffer println(final String text) { return this.print(text).println(); } /** * Parse string to lines and print each line with current indent * * @param text the text to be printed * @return this instance */ public JavaSrcTextBuffer printLinesWithIndent(final String text) { final String[] split = text.split("\n", -1); for (final String s : split) { this.indent().println(s); } return this; } /** * Parse string to lines and print each line with '//' comment * * @param text text to be printed * @return this instance */ public JavaSrcTextBuffer printCommentLinesWithIndent(final String text) { final String[] split = text.split("\n", -1); for (final String s : split) { this.indent().print("// ").println(s); } return this; } /** * Print string as multiline java comment started with '/*' * * @param text text to be printed as multiline comment * @return this instance */ public JavaSrcTextBuffer printCommentMultiLinesWithIndent(final String text) { final String[] split = text.split("\n", -1); this.indent().println("/*"); for (final String s : split) { this.indent().print(" * ").println(s); } this.indent().println(" */"); return this; } /** * Print string as multiline java comment started with '/**' * * @param text text to be printed as multiline comment * @return this instance */ public JavaSrcTextBuffer printJavaDocLinesWithIndent(final String text) { final String[] split = text.split("\n", -1); this.indent().println("/**"); for (final String s : split) { this.indent().print(" * ").println(s); } this.indent().println(" */"); return this; } @Override public String toString() { return this.buffer.toString(); } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/NullableTriple.java000066400000000000000000000010601445000440600327720ustar00rootroot00000000000000package com.igormaznitsa.jbbp.utils; /** * Auxiliary class to keep three values. Any value can be null. * * @param
first value type * @param second value type * @param third value type * @since 2.0.0 */ public final class NullableTriple { private final A a; private final B b; private final C c; public NullableTriple(A a, B b, C c) { this.a = a; this.b = b; this.c = c; } public A getA() { return this.a; } public B getB() { return this.b; } public C getC() { return this.c; } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/ReflectUtils.java000066400000000000000000000060161445000440600324670ustar00rootroot00000000000000package com.igormaznitsa.jbbp.utils; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; /** * Auxiliary class collects methods for work with reflection. * * @since 1.4.0 */ public final class ReflectUtils { /** * Inside auxiliary queue for privileged processors to avoid mass creation of * processors. */ private static final Queue PROCESSORS_QUEUE = new ArrayBlockingQueue<>(32); private ReflectUtils() { } public static boolean isPotentiallyAccessibleClass(final Class klazz) { if (klazz.isLocalClass() && !isPotentiallyAccessibleClass(klazz.getEnclosingClass())) { return false; } return Modifier.isPublic(klazz.getModifiers()); } public static boolean isPotentiallyAccessibleField(final Field field) { return isPotentiallyAccessibleClass(field.getDeclaringClass()) && Modifier.isPublic(field.getModifiers()); } /** * Make accessible an accessible object, AccessController.doPrivileged will be * called. * * @param type of object * @param obj an object to make accessible, it can be null. * @return the same object * @see AccessController#doPrivileged(java.security.PrivilegedAction) */ public static T makeAccessible(final T obj) { if (obj != null) { PrivilegedProcessor processor = PROCESSORS_QUEUE.poll(); if (processor == null) { processor = new PrivilegedProcessor(); } processor.setAccessibleObject(obj); AccessController.doPrivileged(processor); if (!PROCESSORS_QUEUE.offer(processor)) { throw new Error("Can't place processor into queue"); } } return obj; } /** * Create class instance through default constructor call * * @param klazz class to be instantiated, must not be null * @param type of the class * @return instance of class, must not be null * @throws RuntimeException if can't create instance for an error */ public static T newInstance(final Class klazz) { try { return klazz.getConstructor().newInstance(); } catch (Exception ex) { throw new RuntimeException( String.format("Can't create instance of %s for error %s", klazz, ex.getMessage()), ex); } } /** * Inside auxiliary class to make makeAccessible as a privileged action. */ private static final class PrivilegedProcessor implements PrivilegedAction { private AccessibleObject theObject; public void setAccessibleObject(final AccessibleObject obj) { this.theObject = obj; } @Override public AccessibleObject run() { final AccessibleObject objectToProcess = this.theObject; this.theObject = null; if (objectToProcess != null) { objectToProcess.setAccessible(true); } return objectToProcess; } } } java-binary-block-parser-2.0.6/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/TargetSources.java000066400000000000000000000015521445000440600326540ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; /** * Allowed sources for source generation in JBBPParser * * @see com.igormaznitsa.jbbp.JBBPParser#convertToSrc(TargetSources, String) * @since 1.3.0 */ public enum TargetSources { /** * Generate Java sources. */ JAVA } java-binary-block-parser-2.0.6/jbbp/src/test/000077500000000000000000000000001445000440600207645ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/000077500000000000000000000000001445000440600217055ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/000077500000000000000000000000001445000440600224635ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/000077500000000000000000000000001445000440600251725ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/000077500000000000000000000000001445000440600261075ustar00rootroot00000000000000JBBPCustomFieldTypeProcessorTest.java000066400000000000000000000244371445000440600351630ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldByte; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldShort; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import java.io.IOException; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; public class JBBPCustomFieldTypeProcessorTest { @Test public void testFieldsWithCustomNames() throws Exception { final AtomicInteger callCounter = new AtomicInteger(); final JBBPCustomFieldTypeProcessor testProcessor = new JBBPCustomFieldTypeProcessor() { private final String[] types = new String[] {"some1", "some2", "some3"}; @Override public String[] getCustomFieldTypes() { return this.types; } @Override public boolean isAllowed(final JBBPFieldTypeParameterContainer fieldType, final String fieldName, final int extraData, final boolean isArray) { callCounter.incrementAndGet(); assertNotNull(fieldType); final String type = fieldType.getTypeName(); assertTrue(type.equals("some1") || type.equals("some2") || type.equals("some3")); if (fieldName.equals("b")) { assertEquals("some1", type); } if (fieldName.equals("c")) { assertEquals("some2", type); } if (fieldName.equals("e")) { assertEquals("some3", type); } if (type.equals("some3")) { assertTrue(isArray); } else { assertFalse(isArray); } if (type.equals("some2")) { assertEquals(345, extraData); } else { assertEquals(0, extraData); } return true; } @Override public JBBPAbstractField readCustomFieldType(final JBBPBitInputStream in, final JBBPBitOrder bitOrder, final int parserFlags, final JBBPFieldTypeParameterContainer customFieldTypeInfo, final JBBPNamedFieldInfo fieldName, final int extraData, final boolean readWholeStream, final int arrayLength) throws IOException { final String type = customFieldTypeInfo.getTypeName(); assertEquals(JBBPBitOrder.LSB0, bitOrder); assertEquals(JBBPParser.FLAG_SKIP_REMAINING_FIELDS_IF_EOF, parserFlags); assertEquals(type.equals("some1") ? JBBPByteOrder.LITTLE_ENDIAN : JBBPByteOrder.BIG_ENDIAN, customFieldTypeInfo.getByteOrder()); switch (type) { case "some1": assertEquals(0, extraData); assertEquals("b", fieldName.getFieldName()); assertFalse(readWholeStream); assertEquals(-1, arrayLength); return new JBBPFieldByte(fieldName, (byte) in.readByte()); case "some2": assertEquals(345, extraData); assertEquals("c", fieldName.getFieldName()); assertFalse(readWholeStream); assertEquals(-1, arrayLength); return new JBBPFieldShort(fieldName, (short) in.readUnsignedShort(customFieldTypeInfo.getByteOrder())); case "some3": assertEquals(0, extraData); assertEquals("e", fieldName.getFieldName()); assertFalse(readWholeStream); assertEquals(5, arrayLength); return new JBBPFieldArrayByte(fieldName, in.readByteArray(arrayLength)); default: fail("Unexpected " + type); return null; } } }; final JBBPParser parser = JBBPParser .prepare("int a; 0; final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(provider); assertEquals(0, map.size()); assertSame(provider, map.getExternalValueProvider()); } @Test public void testGetExternalValueProvider() { final JBBPExternalValueProvider provider = (fieldName, numericFieldMap, compiledBlock) -> 0; final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(provider); assertSame(provider, map.getExternalValueProvider()); final JBBPNamedNumericFieldMap map2 = new JBBPNamedNumericFieldMap(null); try { map2.getExternalFieldValue("test", JBBPCompiledBlock.prepare().setSource("").setCompiledData(new byte[] {0}).build(), null); fail("Must throw JBBPEvalException"); } catch (JBBPEvalException ex) { } } @Test public void testSize() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); assertEquals(0, map.size()); map.putField(new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123)); assertEquals(1, map.size()); } @Test public void testIsEmpty() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); assertTrue(map.isEmpty()); map.putField(new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123)); assertFalse(map.isEmpty()); } @Test public void testClear() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); map.clear(); assertTrue(map.isEmpty()); map.putField(new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123)); assertFalse(map.isEmpty()); map.clear(); assertTrue(map.isEmpty()); } @Test public void testFindFieldForName() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); map.putField(field); assertSame(field, map.findFieldForName("tESt")); assertNull(map.findFieldForName("test1")); try { map.findFieldForName(null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } @Test public void testRemove() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field1 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); final JBBPNumericField field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test2", "test2", 0), 123); final JBBPNumericField field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("test.test3", "test3", 0), (byte) 123); map.putField(field1); map.putField(field2); map.putField(field3); assertEquals(3, map.size()); map.remove(field2.getNameInfo()); assertEquals(2, map.size()); assertSame(field1, map.findFieldForName("test")); assertSame(field3, map.findFieldForName("test3")); assertNull(map.findFieldForName("test2")); try { map.remove(null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } @Test public void testFindFieldForType() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field1 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); final JBBPNumericField field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test2", "test2", 0), 123); final JBBPNumericField field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("test.test3", "test3", 0), (byte) 123); map.putField(field1); map.putField(field2); map.putField(field3); assertSame(field3, map.findFieldForType(JBBPFieldByte.class)); assertNull(map.findFieldForType(JBBPFieldLong.class)); try { map.findFieldForType(JBBPFieldInt.class); fail("Must throw exception JBBPTooManyFieldsFoundException"); } catch (JBBPTooManyFieldsFoundException ex) { assertEquals(2, ex.getNumberOfFoundInstances()); } try { map.findFieldForType(null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } @Test public void testFindLastFieldForType() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field1 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); final JBBPNumericField field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test2", "test2", 0), 123); final JBBPNumericField field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("test.test3", "test3", 0), (byte) 123); map.putField(field1); map.putField(field2); map.putField(field3); assertSame(field2, map.findLastFieldForType(JBBPFieldInt.class)); assertNull(map.findLastFieldForType(JBBPFieldLong.class)); try { map.findLastFieldForType(null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } @Test public void testFindFirstFieldForType() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field1 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); final JBBPNumericField field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test2", "test2", 0), 123); final JBBPNumericField field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("test.test3", "test3", 0), (byte) 123); map.putField(field1); map.putField(field2); map.putField(field3); assertSame(field1, map.findFirstFieldForType(JBBPFieldInt.class)); assertNull(map.findFirstFieldForType(JBBPFieldLong.class)); try { map.findFirstFieldForType(null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } @Test public void testFindForFieldOffset() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field1 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 1), 123); final JBBPNumericField field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test2", "test2", 2222), 123); final JBBPNumericField field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("test.test3", "test3", 3000), (byte) 123); map.putField(field1); map.putField(field2); map.putField(field3); assertSame(field1, map.findForFieldOffset(1)); assertSame(field2, map.findForFieldOffset(2222)); assertSame(field3, map.findForFieldOffset(3000)); assertNull(map.findForFieldOffset(123)); } @Test public void testFindFieldForPath() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); map.putField(field); assertSame(field, map.findFieldForPath("test.test")); assertNull(map.findFieldForPath("test.test1")); assertNull(map.findFieldForPath("test")); try { map.findFieldForPath(null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } @Test public void testFindFieldForPathAndType() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); map.putField(field); assertSame(field, map.findFieldForPathAndType("TesT.tESt", JBBPFieldInt.class)); assertNull(map.findFieldForPathAndType("test.test", JBBPFieldByte.class)); assertNull(map.findFieldForPathAndType("test.test1", JBBPFieldInt.class)); try { map.findFieldForPathAndType(null, JBBPFieldInt.class); fail("Must throw NPE"); } catch (NullPointerException ex) { } try { map.findFieldForPathAndType("test.test", null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } @Test public void testFindFieldForNameAndType() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); map.putField(field); assertSame(field, map.findFieldForNameAndType("tESt", JBBPFieldInt.class)); assertNull(map.findFieldForNameAndType("test", JBBPFieldByte.class)); assertNull(map.findFieldForNameAndType("test1", JBBPFieldInt.class)); try { map.findFieldForNameAndType(null, JBBPFieldInt.class); fail("Must throw NPE"); } catch (NullPointerException ex) { } try { map.findFieldForNameAndType("test.test", null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } @Test public void testPathExists() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); map.putField(field); assertTrue(map.pathExists("tESt.teSt")); assertFalse(map.pathExists("test.test1")); assertFalse(map.pathExists("test")); try { map.pathExists(null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } @Test public void testNameExists() { final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNumericField field = new JBBPFieldInt(new JBBPNamedFieldInfo("test.test", "test", 0), 123); map.putField(field); assertTrue(map.nameExists("tESt")); assertFalse(map.nameExists("test1")); assertFalse(map.nameExists("test.test")); try { map.nameExists(null); fail("Must throw NPE"); } catch (NullPointerException ex) { } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/JBBPParserTest.java000066400000000000000000003305751445000440600315210ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.exceptions.JBBPCompilationException; import com.igormaznitsa.jbbp.exceptions.JBBPNumericFieldValueConversionException; import com.igormaznitsa.jbbp.exceptions.JBBPParsingException; import com.igormaznitsa.jbbp.exceptions.JBBPTooManyFieldsFoundException; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractArrayField; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBit; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayDouble; import com.igormaznitsa.jbbp.model.JBBPFieldArrayFloat; import com.igormaznitsa.jbbp.model.JBBPFieldArrayInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong; import com.igormaznitsa.jbbp.model.JBBPFieldArrayShort; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUShort; import com.igormaznitsa.jbbp.model.JBBPFieldBit; import com.igormaznitsa.jbbp.model.JBBPFieldBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldByte; import com.igormaznitsa.jbbp.model.JBBPFieldDouble; import com.igormaznitsa.jbbp.model.JBBPFieldFloat; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldShort; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.model.JBBPFieldUByte; import com.igormaznitsa.jbbp.model.JBBPFieldUInt; import com.igormaznitsa.jbbp.model.JBBPFieldUShort; import com.igormaznitsa.jbbp.utils.JBBPIntCounter; import com.igormaznitsa.jbbp.utils.TargetSources; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.util.List; import org.junit.jupiter.api.Test; public class JBBPParserTest { @Test public void testErrorDuringReadingOfNamedField() throws Exception { try { JBBPParser.prepare("short helloworld;").parse(new byte[] {1}); fail("Must throw JBBPParsingException"); } catch (JBBPParsingException ex) { assertTrue(ex.getMessage().contains("helloworld")); } } @Test public void testErrorDuringReadingOfNonNamedField() throws Exception { try { JBBPParser.prepare("short;").parse(new byte[] {1}); fail("Must throw EOFException"); } catch (EOFException ex) { assertNull(ex.getMessage()); } } @Test public void testFieldNameCaseInsensetive_ExceptionForDuplicationOfFieldNames() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPParser.prepare("bool Field1; byte field1;")); } @Test public void testFieldNameCaseInsensetive() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("bool FiElD1;").parse(new byte[] {1}); assertTrue(parsed.nameExists("fiELD1")); assertTrue(parsed.pathExists("fiELD1")); assertNotNull(parsed.findFieldForName("fiELD1")); assertNotNull(parsed.findFieldForNameAndType("fiELD1", JBBPFieldBoolean.class)); assertNotNull(parsed.findFieldForPathAndType("fiELD1", JBBPFieldBoolean.class)); } @Test public void testParse_Bool_ErrorForEOF() throws Exception { final JBBPParser parser = JBBPParser.prepare("bool;"); assertThrows(EOFException.class, () -> parser.parse(new byte[0])); } @Test public void testParse_SingleDefaultNonamedBool_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("bool;"); JBBPFieldStruct result = parser.parse(new byte[] {(byte) 1}); assertTrue(result.findFieldForType(JBBPFieldBoolean.class).getAsBool()); result = parser.parse(new byte[] {(byte) 0}); assertFalse(result.findFieldForType(JBBPFieldBoolean.class).getAsBool()); } @Test public void testParse_SingleDefaultNonamedBool_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">bool;"); JBBPFieldStruct result = parser.parse(new byte[] {(byte) 1}); assertTrue(result.findFieldForType(JBBPFieldBoolean.class).getAsBool()); result = parser.parse(new byte[] {(byte) 0}); assertFalse(result.findFieldForType(JBBPFieldBoolean.class).getAsBool()); } @Test public void testParse_SingleDefaultNonamedBool_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" parser.parse(new byte[0])); } @Test public void testParse_SingleDefaultNonamedByte_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte;"); final JBBPFieldStruct result = parser.parse(new byte[] {(byte) -42}); assertEquals(-42, result.findFieldForType(JBBPFieldByte.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedByte_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">byte;"); final JBBPFieldStruct result = parser.parse(new byte[] {(byte) -42}); assertEquals(-42, result.findFieldForType(JBBPFieldByte.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedByte_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" JBBPParser.prepare("val;")); assertThrows(JBBPCompilationException.class, () -> JBBPParser.prepare("val:1;")); assertThrows(JBBPCompilationException.class, () -> JBBPParser.prepare("val:(3+5);")); assertThrows(JBBPCompilationException.class, () -> JBBPParser.prepare("val a;")); assertThrows(JBBPCompilationException.class, () -> JBBPParser.prepare("val:3 [_];")); assertThrows(JBBPCompilationException.class, () -> JBBPParser.prepare("val:3 [_] field;")); } @Test public void testParse_Value_Constant() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[0])); assertEquals(34, JBBPParser.prepare("val:34 value;").parse(stream).findFieldForType(JBBPFieldInt.class) .getAsInt()); assertEquals(0L, stream.getCounter()); } @Test public void testParse_Value_NegativeConstant() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[0])); assertEquals(-34, JBBPParser.prepare("val:-34 value;").parse(stream).findFieldForType(JBBPFieldInt.class) .getAsInt()); assertEquals(0L, stream.getCounter()); } @Test public void testParse_Value_Expression() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2})); assertEquals(3, JBBPParser.prepare("ubyte a; ubyte b; val:(a+b) value;").parse(stream) .findFieldForType(JBBPFieldInt.class).getAsInt()); assertEquals(2L, stream.getCounter()); } @Test public void testParse_Value_UseInExpression_NegativeResult() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2})); assertEquals(-2, JBBPParser.prepare("ubyte a; ubyte b; val:(a-b) value; val:(value*2) secondvalue;") .parse(stream).findFieldForNameAndType("secondvalue", JBBPFieldInt.class).getAsInt()); assertEquals(2L, stream.getCounter()); } @Test public void testParse_Value_UseInExpression() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2})); assertEquals(6, JBBPParser.prepare("ubyte a; ubyte b; val:(a+b) value; val:(value*2) secondvalue;") .parse(stream).findFieldForNameAndType("secondvalue", JBBPFieldInt.class).getAsInt()); assertEquals(2L, stream.getCounter()); } @Test public void testParse_String_ErrorForEOF() { final JBBPParser parser = JBBPParser.prepare("stringj;"); assertThrows(EOFException.class, () -> parser.parse(new byte[0])); } @Test public void testParse_String_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("stringj;"); final JBBPFieldStruct result = parser.parse(new byte[] {3, 65, 66, 67}); assertEquals("ABC", result.findFieldForType(JBBPFieldString.class).getAsString()); } @Test public void testParse_String_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">stringj;"); final JBBPFieldStruct result = parser.parse(new byte[] {3, 65, 66, 67}); assertEquals("ABC", result.findFieldForType(JBBPFieldString.class).getAsString()); } @Test public void testParse_String_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" parser.parse(new byte[0])); } @Test public void testParse_SingleDefaultNonamedUByte_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("ubyte;"); final JBBPFieldStruct result = parser.parse(new byte[] {(byte) -42}); assertEquals(-42 & 0xFF, result.findFieldForType(JBBPFieldUByte.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedUByte_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">ubyte;"); final JBBPFieldStruct result = parser.parse(new byte[] {(byte) -42}); assertEquals(-42 & 0xFF, result.findFieldForType(JBBPFieldUByte.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedUByte_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" parser.parse(new byte[0])); } @Test public void testParse_SingleDefaultNonamedShort_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("short;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2}); assertEquals(0x0102, result.findFieldForType(JBBPFieldShort.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedShort_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">short;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2}); assertEquals(0x0102, result.findFieldForType(JBBPFieldShort.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedShort_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" parser.parse(new byte[0])); } @Test public void testParse_SingleDefaultNonamedUShort_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("ushort;"); final JBBPFieldStruct result = parser.parse(new byte[] {-1, -2}); assertEquals(((-1 << 8) | (-2 & 0xFF)) & 0xFFFF, result.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedUShort_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">ushort;"); final JBBPFieldStruct result = parser.parse(new byte[] {-1, -2}); assertEquals(((-1 << 8) | (-2 & 0xFF)) & 0xFFFF, result.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedUShort_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" parser.parse(new byte[0])); } @Test public void testParse_SingleDefaultNonamedInt_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("int;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4}); assertEquals(0x01020304, result.findFieldForType(JBBPFieldInt.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedInt_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">int;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4}); assertEquals(0x01020304, result.findFieldForType(JBBPFieldInt.class).getAsInt()); } @Test public void testParse_SingleDefaultNonamedInt_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" parser.parse(new byte[0])); } @Test public void testParse_SingleDefaultNonamedFloat_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("floatj;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4}); assertEquals(2.3879393E-38f, result.findFieldForType(JBBPFieldFloat.class).getAsFloat(), TestUtils.FLOAT_DELTA); } @Test public void testParse_SingleDefaultNonamedFloat_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">floatj;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4}); assertEquals(2.3879393E-38f, result.findFieldForType(JBBPFieldFloat.class).getAsFloat(), TestUtils.FLOAT_DELTA); } @Test public void testParse_SingleDefaultNonamedFloat_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" parser.parse(new byte[0])); } @Test public void testParse_SingleDefaultNonamedLong_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("long;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(0x0102030405060708L, result.findFieldForType(JBBPFieldLong.class).getAsLong()); } @Test public void testParse_SingleDefaultNonamedLong_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">long;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(0x0102030405060708L, result.findFieldForType(JBBPFieldLong.class).getAsLong()); } @Test public void testParse_SingleDefaultNonamedLong_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" parser.parse(new byte[0])); } @Test public void testParse_SingleDefaultNonamedDouble_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("doublej;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(8.20788039913184E-304d, result.findFieldForType(JBBPFieldDouble.class).getAsDouble(), TestUtils.FLOAT_DELTA); } @Test public void testParse_SingleDefaultNonamedDouble_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">doublej;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(8.20788039913184E-304d, result.findFieldForType(JBBPFieldDouble.class).getAsDouble(), TestUtils.FLOAT_DELTA); } @Test public void testParse_SingleDefaultNonamedDouble_LittleEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(" readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { assertNotNull(inStream); final int value = inStream.readByte(); assertEquals(33, value); assertEquals(0x0908, numericFieldMap.findFieldForType(JBBPFieldShort.class).getAsInt()); assertNull(fieldName); assertEquals(0, extraValue); assertEquals(JBBPByteOrder.BIG_ENDIAN, byteOrder); counter.incrementAndGet(); return new JBBPFieldByte(fieldName, (byte) value); } }, null); assertNotNull(struct); assertEquals(33, struct.findFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(1, counter.get()); } @Test public void testGetFlags() throws Exception { assertEquals(123, JBBPParser.prepare("byte;", 123).getFlags()); } @Test public void testParse_Bit_ExtraNumericFieldAsExpression() throws Exception { final JBBPParser parser = JBBPParser.prepare("ubyte a; bit:(a*4-12) b; bit:(a) c; bit:(a*2) d;"); final JBBPFieldStruct parsed = parser.parse(new byte[] {4, 0x12, 0x34}); assertEquals(4, parsed.findFieldForNameAndType("a", JBBPFieldUByte.class).getAsInt()); assertEquals(2, parsed.findFieldForNameAndType("b", JBBPFieldBit.class).getAsInt()); assertEquals(1, parsed.findFieldForNameAndType("c", JBBPFieldBit.class).getAsInt()); assertEquals(0x34, parsed.findFieldForNameAndType("d", JBBPFieldBit.class).getAsInt()); } @Test public void testParse_BitArray_ExtraNumericFieldAsExpression() throws Exception { final JBBPParser parser = JBBPParser.prepare("ubyte s; ubyte a; bit:(a*4-12) [s] b; bit:(a*2) d;"); final JBBPFieldStruct parsed = parser.parse(new byte[] {2, 4, 0x12, 0x34}); assertEquals(2, parsed.findFieldForNameAndType("s", JBBPFieldUByte.class).getAsInt()); assertEquals(4, parsed.findFieldForNameAndType("a", JBBPFieldUByte.class).getAsInt()); assertArrayEquals(new byte[] {2, 1}, parsed.findFieldForNameAndType("b", JBBPFieldArrayBit.class).getArray()); assertEquals(0x34, parsed.findFieldForNameAndType("d", JBBPFieldBit.class).getAsInt()); } @Test public void testParse_Skip_ExtraNumericFieldAsExpression() throws Exception { final JBBPParser parser = JBBPParser.prepare("ubyte a; skip:(a*2); ubyte b;"); final JBBPFieldStruct parsed = parser.parse(new byte[] {2, 0x12, 0x34, 0x11, 0x22, 0x56}); assertEquals(2, parsed.findFieldForNameAndType("a", JBBPFieldUByte.class).getAsInt()); assertEquals(0x56, parsed.findFieldForNameAndType("b", JBBPFieldUByte.class).getAsInt()); } @Test public void testParse_Align_ExtraNumericFieldAsExpression() throws Exception { final JBBPParser parser = JBBPParser.prepare("ubyte a; align:(a+1); ubyte b;"); final JBBPFieldStruct parsed = parser.parse(new byte[] {2, 0x12, 0x34, 0x11, 0x22, 0x56}); assertEquals(2, parsed.findFieldForNameAndType("a", JBBPFieldUByte.class).getAsInt()); assertEquals(0x11, parsed.findFieldForNameAndType("b", JBBPFieldUByte.class).getAsInt()); } @Test public void testParse_Var_ExtraNumericFieldAsExpression() throws Exception { final JBBPParser parser = JBBPParser.prepare("ubyte a; var:(a/21) vvv; ubyte b;"); final JBBPFieldStruct parsed = parser .parse(new byte[] {(byte) 123, 0x12, 0x34, 0x11, 0x22, 0x56}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( JBBPBitInputStream inStream, int arraySize, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { inStream.skip(3); assertEquals(123 / 21, extraValue); return new JBBPFieldInt(fieldName, 666); } }, null); assertEquals(123, parsed.findFieldForNameAndType("a", JBBPFieldUByte.class).getAsInt()); assertEquals(666, parsed.findFieldForNameAndType("vvv", JBBPFieldInt.class).getAsInt()); assertEquals(0x22, parsed.findFieldForNameAndType("b", JBBPFieldUByte.class).getAsInt()); } @Test public void testParse_VarArray_ExtraNumericFieldAsExpression() throws Exception { final JBBPParser parser = JBBPParser.prepare("ubyte s; ubyte a; var:(a/21) [s*2] vvv; ubyte b;"); final JBBPFieldStruct parsed = parser .parse(new byte[] {4, (byte) 123, 0x12, 0x34, 0x11, 0x22, 0x56}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( JBBPBitInputStream inStream, int arraySize, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { inStream.skip(3); assertEquals(123 / 21, extraValue); assertEquals(8, arraySize); return new JBBPFieldArrayByte(fieldName, new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }, null); assertEquals(123, parsed.findFieldForNameAndType("a", JBBPFieldUByte.class).getAsInt()); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}, parsed.findFieldForNameAndType("vvv", JBBPFieldArrayByte.class).getArray()); assertEquals(0x22, parsed.findFieldForNameAndType("b", JBBPFieldUByte.class).getAsInt()); } @Test public void testParse_NamedVarWithCustomOrder() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { assertNotNull(inStream); final int value = inStream.readByte(); assertEquals(33, value); assertEquals(0x0908, numericFieldMap.findFieldForType(JBBPFieldShort.class).getAsInt()); assertEquals("some", fieldName.getFieldName()); assertEquals(-12345, extraValue); assertEquals(JBBPByteOrder.LITTLE_ENDIAN, byteOrder); counter.incrementAndGet(); return new JBBPFieldByte(fieldName, (byte) value); } }, null); assertEquals(33, struct.findFieldForNameAndType("some", JBBPFieldByte.class).getAsInt()); assertEquals(1, counter.get()); } @Test public void testParse_StringFieldInExpression_NoErrorDuringCompilation() throws Exception { JBBPParser.prepare("byte a; stringj b; byte[a+b];"); JBBPParser.prepare("stringj b; byte[b];"); } @Test public void testParse_StringFieldInArihmeticExpression_ArihmeticException() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte a; stringj b; byte[a+b];"); assertThrows(ArithmeticException.class, () -> parser.parse(new byte[] {1, 3, 65, 66, 67, 0, 1, 2, 3})); } @Test public void testParse_StringFieldAsSingleVariableInExpression_ArihmeticException() throws Exception { final JBBPParser parser = JBBPParser.prepare("stringj b; byte[b];"); assertThrows(ArithmeticException.class, () -> parser.parse(new byte[] {3, 65, 66, 67, 0, 1, 2, 3})); } @Test public void testParse_SingleNonamedVar_ErrorForNullResult() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; var; int;"); assertThrows(NullPointerException.class, () -> { final JBBPFieldStruct struct = parser.parse(new byte[] {9, 8, 33, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { return null; } }, null); }); } @Test public void testParse_SingleNonamedVar_ErrorForArrayResult() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; var; int;"); assertThrows(JBBPParsingException.class, () -> { final JBBPFieldStruct struct = parser.parse(new byte[] {9, 8, 33, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { return new JBBPFieldArrayByte(fieldName, new byte[] {1, 2, 3}); } }, null); }); } @Test public void testParse_SingleNonamedVar_ErrorForDifferentName() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; var name; int;"); assertThrows(JBBPParsingException.class, () -> { final JBBPFieldStruct struct = parser.parse(new byte[] {9, 8, 33, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { assertNotNull(fieldName); return new JBBPFieldByte(new JBBPNamedFieldInfo("jskdjhsd", "dlkjsf", 0), (byte) 1); } }, null); }); } @Test public void testParse_SingleNonamedVarArray() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; var[18]; int;"); final JBBPIntCounter counter = new JBBPIntCounter(); final JBBPFieldStruct struct = parser.parse(new byte[] {9, 8, 33, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { assertNotNull(inStream); final int value = inStream.readByte(); assertEquals(33, value); assertEquals(0x0908, numericFieldMap.findFieldForType(JBBPFieldShort.class).getAsInt()); assertNull(fieldName); assertEquals(0, extraValue); assertEquals(18, arraySize); assertEquals(JBBPByteOrder.BIG_ENDIAN, byteOrder); counter.incrementAndGet(); return new JBBPFieldArrayByte(fieldName, new byte[] {(byte) value}); } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } }, null); assertNotNull(struct); assertArrayEquals(new byte[] {33}, struct.findFieldForType(JBBPFieldArrayByte.class).getArray()); assertEquals(1, counter.get()); } @Test public void testParse_NamedVarArrayWithCustomOrder() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { assertNotNull(inStream); final int value = inStream.readByte(); assertEquals(33, value); assertEquals(0x0908, numericFieldMap.findFieldForType(JBBPFieldShort.class).getAsInt()); assertEquals("some", fieldName.getFieldName()); assertEquals(-12345, extraValue); assertEquals(2334, arraySize); assertEquals(JBBPByteOrder.LITTLE_ENDIAN, byteOrder); counter.incrementAndGet(); return new JBBPFieldArrayByte(fieldName, new byte[] {(byte) value}); } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } }, null); assertNotNull(struct); assertArrayEquals(new byte[] {33}, struct.findFieldForNameAndType("some", JBBPFieldArrayByte.class).getArray()); assertEquals(1, counter.get()); } @Test public void testParse_NamedVarArrayTillEndOfStream() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; var [_] Some;"); final JBBPIntCounter counter = new JBBPIntCounter(); final JBBPFieldStruct struct = parser.parse(new byte[] {9, 8, 33, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { assertNotNull(inStream); assertEquals(0x0908, numericFieldMap.findFieldForType(JBBPFieldShort.class).getAsInt()); assertEquals("some", fieldName.getFieldName()); assertEquals(0, extraValue); assertTrue(arraySize < 0); assertEquals(JBBPByteOrder.BIG_ENDIAN, byteOrder); counter.incrementAndGet(); return new JBBPFieldArrayByte(fieldName, inStream.readByteArray(-1)); } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } }, null); assertNotNull(struct); assertArrayEquals(new byte[] {33, 1, 2, 3, 4}, struct.findFieldForNameAndType("some", JBBPFieldArrayByte.class).getArray()); assertEquals(1, counter.get()); } @Test public void testParse_NamedVarArrayForZeroLength() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; var [k] Some;"); final JBBPFieldStruct struct = parser.parse(new byte[] {0, 0}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { assertEquals(0, arraySize); return new JBBPFieldArrayByte(fieldName, new byte[0]); } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } }, null); assertNotNull(struct); assertEquals(0, struct.findFieldForNameAndType("some", JBBPFieldArrayByte.class).size()); } @Test public void testParse_SingleNonamedVarArray_ErrorForNullResult() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; var [k]; int;"); assertThrows(NullPointerException.class, () -> { final JBBPFieldStruct struct = parser.parse(new byte[] {9, 8, 33, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { assertEquals(0x0908, arraySize); return null; } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } }, null); }); } @Test public void testParse_SingleNonamedVarArray_ErrorForDifferentName() throws Exception { final JBBPParser parser = JBBPParser.prepare("short k; var [234] name; int;"); assertThrows(JBBPParsingException.class, () -> { final JBBPFieldStruct struct = parser.parse(new byte[] {9, 8, 33, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { assertNotNull(fieldName); return new JBBPFieldArrayByte(new JBBPNamedFieldInfo("jskdjhsd", "dlkjsf", 0), new byte[] {1}); } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } }, null); }); } @Test public void testParse_BitFields_EOF() throws Exception { assertThrows(EOFException.class, () -> JBBPParser.prepare("bit:4;").parse(new byte[0])); } @Test public void testParse_BitFields_SizeProvidedThroughExpression() throws Exception { assertEquals(4, JBBPParser.prepare("ubyte a; ubyte b; bit:(a+b) c;").parse(new byte[] {1, 2, (byte) 0xB4}) .findFieldForType(JBBPFieldBit.class).getAsInt()); assertEquals(20, JBBPParser.prepare("ubyte a; ubyte b; bit:(a+b) c;").parse(new byte[] {3, 2, (byte) 0xB4}) .findFieldForType(JBBPFieldBit.class).getAsInt()); } @Test public void testParse_BitFields_ErrorForWrongValueOfBitFieldLength() throws Exception { assertThrows(IllegalArgumentException.class, () -> JBBPParser.prepare("ubyte a; ubyte b; bit:(a+b) c;") .parse(new byte[] {11, 2, (byte) 0xB4})); assertThrows(IllegalArgumentException.class, () -> JBBPParser.prepare("ubyte a; ubyte b; bit:(a-b) c;") .parse(new byte[] {2, 2, (byte) 0xB4})); } @Test public void testParse_BitFieldArray_EOF() throws Exception { assertThrows(EOFException.class, () -> JBBPParser.prepare("bit:4 [1];").parse(new byte[0])); } @Test public void testParse_BitFieldArrayWholeStream_Empty() throws Exception { assertEquals(0, JBBPParser.prepare("bit:4 [_];").parse(new byte[0]) .findFieldForType(JBBPFieldArrayBit.class).size()); } @Test public void testParse_SeveralPrimitiveFields() throws Exception { final JBBPParser parser = JBBPParser.prepare("bit:4; bit:4; bool; byte; ubyte; short; ushort; int; long;"); final JBBPFieldStruct result = parser.parse( new byte[] {0x12, 1, 87, (byte) 0xF3, 1, 2, (byte) 0xFE, 4, 6, 7, 8, 9, (byte) 0xFF, 1, 2, 3, 5, 6, 7, 8, 9}); assertEquals(2, result.findFirstFieldForType(JBBPFieldBit.class).getAsInt()); assertEquals(1, result.findLastFieldForType(JBBPFieldBit.class).getAsInt()); try { result.findFieldForType(JBBPFieldBit.class); fail("Must throw JBBPTooManyFieldsFoundException"); } catch (JBBPTooManyFieldsFoundException ex) { assertEquals(2, ex.getNumberOfFoundInstances()); } assertTrue(result.findFieldForType(JBBPFieldBoolean.class).getAsBool()); assertEquals(87, result.findFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(0xF3, result.findFieldForType(JBBPFieldUByte.class).getAsInt()); assertEquals(0x0102, result.findFieldForType(JBBPFieldShort.class).getAsInt()); assertEquals(0xFE04, result.findFieldForType(JBBPFieldUShort.class).getAsInt()); assertEquals(0x06070809, result.findFieldForType(JBBPFieldInt.class).getAsInt()); assertEquals(0xFF01020305060708L, result.findFieldForType(JBBPFieldLong.class).getAsLong()); } @Test public void testParse_Align_Default_EmptyStream_NoErrors() throws Exception { assertNotNull(JBBPParser.prepare("byte; align;").parse(new byte[] {1})); } @Test public void testParse_Align_ErrorForEOF() throws Exception { assertThrows(EOFException.class, () -> JBBPParser.prepare("byte; align:34;").parse(new byte[] {1})); } @Test public void testParse_Align_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("bit:4; align; bool; byte; ubyte; short; ushort; int; long;"); final JBBPFieldStruct result = parser.parse( new byte[] {0x12, 1, 87, (byte) 0xF3, 1, 2, (byte) 0xFE, 4, 6, 7, 8, 9, (byte) 0xFF, 1, 2, 3, 5, 6, 7, 8, 9}); assertEquals(2, result.findFieldForType(JBBPFieldBit.class).getAsInt()); assertTrue(result.findFieldForType(JBBPFieldBoolean.class).getAsBool()); assertEquals(87, result.findFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(0xF3, result.findFieldForType(JBBPFieldUByte.class).getAsInt()); assertEquals(0x0102, result.findFieldForType(JBBPFieldShort.class).getAsInt()); assertEquals(0xFE04, result.findFieldForType(JBBPFieldUShort.class).getAsInt()); assertEquals(0x06070809, result.findFieldForType(JBBPFieldInt.class).getAsInt()); assertEquals(0xFF01020305060708L, result.findFieldForType(JBBPFieldLong.class).getAsLong()); } @Test public void testParse_Align_LongDistance() throws Exception { final JBBPParser parser = JBBPParser.prepare("ubyte; align:32000; short;"); final byte[] data = new byte[32002]; data[0] = (byte) 0xFE; data[32000] = 0x12; data[32001] = 0x34; final JBBPFieldStruct root = parser.parse(data); assertEquals(0xFE, root.findFieldForType(JBBPFieldUByte.class).getAsInt()); assertEquals(0x1234, root.findFieldForType(JBBPFieldShort.class).getAsInt()); } @Test public void testParse_Skip_Default_ErrorForEOF() throws Exception { assertThrows(EOFException.class, () -> JBBPParser.prepare("byte; skip;").parse(new byte[] {1})); } @Test public void testParse_Skip_ErrorForEOF() throws Exception { assertThrows(EOFException.class, () -> JBBPParser.prepare("byte; skip:34;").parse(new byte[] {1})); } @Test public void testParse_Skip_WithoutArgument() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte; skip; short;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(1, result.findFirstFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(0x0304, result.findFieldForType(JBBPFieldShort.class).getAsInt()); } @Test public void testParse_Skip_ShortDistance() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte; skip:3; short;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(1, result.findFirstFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(0x0506, result.findFieldForType(JBBPFieldShort.class).getAsInt()); } @Test public void testParse_Skip_LongDistance() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte; skip:32000; short;"); final byte[] data = new byte[32003]; data[0] = 1; data[32001] = 0x0A; data[32002] = 0x0B; final JBBPFieldStruct result = parser.parse(data); assertEquals(1, result.findFirstFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(0x0A0B, result.findFieldForType(JBBPFieldShort.class).getAsInt()); } @Test public void testParse_Skip_TooLongDistance() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte; skip:33; short;"); assertThrows(EOFException.class, () -> parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8})); } @Test public void testParse_Align_Int_WithEffect() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte; byte; align:4; int;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(1, result.findFirstFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(2, result.findLastFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(0x05060708, result.findLastFieldForType(JBBPFieldInt.class).getAsInt()); } @Test public void testParse_Align_Int_WithoutEffect() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte; byte; byte; byte; align:4; int;"); final JBBPFieldStruct result = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(1, result.findFirstFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(4, result.findLastFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(0x05060708, result.findLastFieldForType(JBBPFieldInt.class).getAsInt()); } @Test public void testParse_FixedBitArray_EOFException() throws Exception { assertThrows(EOFException.class, () -> JBBPParser.prepare("byte; bit:4[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedBitArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; bit:4[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayBit.class).size()); } @Test public void testParse_ProcessingOfExtraFieldValuesInSkippedStructureFields() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare( "byte len; struct1 [len] { int a; var:23231223 [1024] helloarray; int b; bit:3; bit:7 [10233]; var:-1332 hello; skip:34221223; bit:7; bit:1; align:3445; bit:2; int skippedInt; long lng; insidestruct {bit:1; bit:2; bit:3;} } int end; ") .parse(new byte[] {0, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( JBBPBitInputStream inStream, int arraySize, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } @Override public JBBPAbstractField readVarField(JBBPBitInputStream inStream, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } }, null); assertEquals(0x01020304, parsed.findFieldForNameAndType("end", JBBPFieldInt.class).getAsInt()); } @Test public void testParse_ProcessingOfExtraFieldValuesInSkippedStructureFields1() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; struct1 [len] {var:-1332 hello; align:3445; } int end; ") .parse(new byte[] {0, 1, 2, 3, 4}, new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( JBBPBitInputStream inStream, int arraySize, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } @Override public JBBPAbstractField readVarField(JBBPBitInputStream inStream, JBBPNamedFieldInfo fieldName, int extraValue, JBBPByteOrder byteOrder, JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } }, null); assertEquals(0x01020304, parsed.findFieldForNameAndType("end", JBBPFieldInt.class).getAsInt()); } @Test public void testParse_FixedBitArray() throws Exception { final JBBPFieldArrayBit bits = JBBPParser.prepare("bit:4 [8];").parse(new byte[] {0x21, 0x43, 0x65, (byte) 0x87}) .findFieldForType(JBBPFieldArrayBit.class); assertEquals(8, bits.size()); assertEquals(1, bits.getAsInt(0)); assertEquals(2, bits.getAsInt(1)); assertEquals(3, bits.getAsInt(2)); assertEquals(4, bits.getAsInt(3)); assertEquals(5, bits.getAsInt(4)); assertEquals(6, bits.getAsInt(5)); assertEquals(7, bits.getAsInt(6)); assertEquals(8, bits.getAsInt(7)); } @Test public void testParse_NonFixedBitArray() throws Exception { final JBBPFieldArrayBit bits = JBBPParser.prepare("bit:4 [_];").parse(new byte[] {0x21, 0x43, 0x65, (byte) 0x87}) .findFieldForType(JBBPFieldArrayBit.class); assertEquals(8, bits.size()); assertEquals(1, bits.getAsInt(0)); assertEquals(2, bits.getAsInt(1)); assertEquals(3, bits.getAsInt(2)); assertEquals(4, bits.getAsInt(3)); assertEquals(5, bits.getAsInt(4)); assertEquals(6, bits.getAsInt(5)); assertEquals(7, bits.getAsInt(6)); assertEquals(8, bits.getAsInt(7)); } @Test public void testParse_FixedByteArray_EOFException() throws Exception { assertThrows(EOFException.class, () -> JBBPParser.prepare("byte; byte[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedByteArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; byte[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayByte.class).size()); } @Test public void testParse_FixedByteArray_Default() throws Exception { final JBBPFieldArrayByte bytes = JBBPParser.prepare("byte[4];").parse(new byte[] {0x21, 0x43, 0x65, (byte) 0x87}) .findFieldForType(JBBPFieldArrayByte.class); assertEquals(4, bytes.size()); assertEquals(0x21, bytes.getAsInt(0) & 0xFF); assertEquals(0x43, bytes.getAsInt(1) & 0xFF); assertEquals(0x65, bytes.getAsInt(2) & 0xFF); assertEquals(0x87, bytes.getAsInt(3) & 0xFF); } @Test public void testParse_FixedByteArray_BigEndian() throws Exception { final JBBPFieldArrayByte bytes = JBBPParser.prepare(">byte[4];").parse(new byte[] {0x21, 0x43, 0x65, (byte) 0x87}) .findFieldForType(JBBPFieldArrayByte.class); assertEquals(4, bytes.size()); assertEquals(0x21, bytes.getAsInt(0) & 0xFF); assertEquals(0x43, bytes.getAsInt(1) & 0xFF); assertEquals(0x65, bytes.getAsInt(2) & 0xFF); assertEquals(0x87, bytes.getAsInt(3) & 0xFF); } @Test public void testParse_FixedByteArray_LittleEndian() throws Exception { final JBBPFieldArrayByte bytes = JBBPParser.prepare("byte[_];").parse(new byte[] {0x21, 0x43, 0x65, (byte) 0x87}) .findFieldForType(JBBPFieldArrayByte.class); assertEquals(4, bytes.size()); assertEquals(0x21, bytes.getAsInt(0) & 0xFF); assertEquals(0x43, bytes.getAsInt(1) & 0xFF); assertEquals(0x65, bytes.getAsInt(2) & 0xFF); assertEquals(0x87, bytes.getAsInt(3) & 0xFF); } @Test public void testParse_NonFixedByteArray_LittleEndian() throws Exception { final JBBPFieldArrayByte bytes = JBBPParser.prepare(" JBBPParser.prepare("byte; ubyte[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedUByteArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; ubyte[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayUByte.class).size()); } @Test public void testParse_FixedUByteArray_Default() throws Exception { final JBBPFieldArrayUByte bytes = JBBPParser.prepare("ubyte[4];").parse(new byte[] {0x21, 0x43, 0x65, (byte) 0x87}) .findFieldForType(JBBPFieldArrayUByte.class); assertEquals(4, bytes.size()); assertEquals(0x21, bytes.getAsInt(0)); assertEquals(0x43, bytes.getAsInt(1)); assertEquals(0x65, bytes.getAsInt(2)); assertEquals(0x87, bytes.getAsInt(3)); } @Test public void testParse_FixedUByteArray_BigEndian() throws Exception { final JBBPFieldArrayUByte bytes = JBBPParser.prepare(">ubyte[4];").parse(new byte[] {0x21, 0x43, 0x65, (byte) 0x87}) .findFieldForType(JBBPFieldArrayUByte.class); assertEquals(4, bytes.size()); assertEquals(0x21, bytes.getAsInt(0)); assertEquals(0x43, bytes.getAsInt(1)); assertEquals(0x65, bytes.getAsInt(2)); assertEquals(0x87, bytes.getAsInt(3)); } @Test public void testParse_FixedUByteArray_LittleEndian() throws Exception { final JBBPFieldArrayUByte bytes = JBBPParser.prepare("ubyte[_];").parse(new byte[] {0x21, 0x43, 0x65, (byte) 0x87}) .findFieldForType(JBBPFieldArrayUByte.class); assertEquals(4, bytes.size()); assertEquals(0x21, bytes.getAsInt(0)); assertEquals(0x43, bytes.getAsInt(1)); assertEquals(0x65, bytes.getAsInt(2)); assertEquals(0x87, bytes.getAsInt(3)); } @Test public void testParse_NonFixedUByteArray_LittleEndian() throws Exception { final JBBPFieldArrayUByte bytes = JBBPParser.prepare(" JBBPParser.prepare("byte; bool[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedBoolArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; bool[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayBoolean.class).size()); } @Test public void testParse_FixedBooleanArray_Default() throws Exception { final JBBPFieldArrayBoolean bools = JBBPParser.prepare("bool[4];").parse(new byte[] {0, 0x43, 0x65, 0}) .findFieldForType(JBBPFieldArrayBoolean.class); assertEquals(4, bools.size()); assertFalse(bools.getAsBool(0)); assertTrue(bools.getAsBool(1)); assertTrue(bools.getAsBool(2)); assertFalse(bools.getAsBool(3)); } @Test public void testParse_FixedBooleanArray_BigEndian() throws Exception { final JBBPFieldArrayBoolean bools = JBBPParser.prepare(">bool[4];").parse(new byte[] {0, 0x43, 0x65, 0}) .findFieldForType(JBBPFieldArrayBoolean.class); assertEquals(4, bools.size()); assertFalse(bools.getAsBool(0)); assertTrue(bools.getAsBool(1)); assertTrue(bools.getAsBool(2)); assertFalse(bools.getAsBool(3)); } @Test public void testParse_FixedBooleanArray_LittleEndian() throws Exception { final JBBPFieldArrayBoolean bools = JBBPParser.prepare("bool[_];").parse(new byte[] {0, 0x43, 0x65, 0}) .findFieldForType(JBBPFieldArrayBoolean.class); assertEquals(4, bools.size()); assertFalse(bools.getAsBool(0)); assertTrue(bools.getAsBool(1)); assertTrue(bools.getAsBool(2)); assertFalse(bools.getAsBool(3)); } @Test public void testParse_NonFixedBooleanArray_LittleEndian() throws Exception { final JBBPFieldArrayBoolean bools = JBBPParser.prepare(" JBBPParser.prepare("byte; short[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedShortArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; short[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayShort.class).size()); } @Test public void testParse_FixedShortArray_Default() throws Exception { final JBBPFieldArrayShort shorts = JBBPParser.prepare("short[2];").parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0}) .findFieldForType(JBBPFieldArrayShort.class); assertEquals(2, shorts.size()); assertEquals((short) 0xF743, shorts.getAsInt(0)); assertEquals((short) 0x6500, shorts.getAsInt(1)); } @Test public void testParse_FixedShortArray_BigEndian() throws Exception { final JBBPFieldArrayShort shorts = JBBPParser.prepare(">short[2];").parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0}) .findFieldForType(JBBPFieldArrayShort.class); assertEquals(2, shorts.size()); assertEquals((short) 0xF743, shorts.getAsInt(0)); assertEquals((short) 0x6500, shorts.getAsInt(1)); } @Test public void testParse_FixedShortArray_LittleEndian() throws Exception { final JBBPFieldArrayShort shorts = JBBPParser.prepare("short[_];").parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0}) .findFieldForType(JBBPFieldArrayShort.class); assertEquals(2, shorts.size()); assertEquals((short) 0xF743, shorts.getAsInt(0)); assertEquals((short) 0x6500, shorts.getAsInt(1)); } @Test public void testParse_NonFixedShortArray_LittleEndian() throws Exception { final JBBPFieldArrayShort shorts = JBBPParser.prepare(" JBBPParser.prepare("byte; ushort[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedUShortArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; ushort[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayUShort.class).size()); } @Test public void testParse_FixedUShortArray_Default() throws Exception { final JBBPFieldArrayUShort shorts = JBBPParser.prepare("ushort[2];").parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0}) .findFieldForType(JBBPFieldArrayUShort.class); assertEquals(2, shorts.size()); assertEquals(0xF743, shorts.getAsInt(0)); assertEquals(0x6500, shorts.getAsInt(1)); } @Test public void testParse_FixedUShortArray_BigEndian() throws Exception { final JBBPFieldArrayUShort shorts = JBBPParser.prepare(">ushort[2];").parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0}) .findFieldForType(JBBPFieldArrayUShort.class); assertEquals(2, shorts.size()); assertEquals(0xF743, shorts.getAsInt(0)); assertEquals(0x6500, shorts.getAsInt(1)); } @Test public void testParse_FixedUShortArray_LittleEndian() throws Exception { final JBBPFieldArrayUShort shorts = JBBPParser.prepare("ushort[_];").parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0}) .findFieldForType(JBBPFieldArrayUShort.class); assertEquals(2, shorts.size()); assertEquals(0xF743, shorts.getAsInt(0)); assertEquals(0x6500, shorts.getAsInt(1)); } @Test public void testParse_NonFixedUShortArray_LittleEndian() throws Exception { final JBBPFieldArrayUShort shorts = JBBPParser.prepare(" JBBPParser.prepare("byte; int[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedIntArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; int[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayInt.class).size()); } @Test public void testParse_FixedIntArray_Default() throws Exception { final JBBPFieldArrayInt ints = JBBPParser.prepare("int[2];") .parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0}) .findFieldForType(JBBPFieldArrayInt.class); assertEquals(2, ints.size()); assertEquals(0xF7436510, ints.getAsInt(0)); assertEquals(0x352367A0, ints.getAsInt(1)); } @Test public void testParse_FixedIntArray_BigEndian() throws Exception { final JBBPFieldArrayInt ints = JBBPParser.prepare(">int[2];") .parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0}) .findFieldForType(JBBPFieldArrayInt.class); assertEquals(2, ints.size()); assertEquals(0xF7436510, ints.getAsInt(0)); assertEquals(0x352367A0, ints.getAsInt(1)); } @Test public void testParse_FixedIntArray_LittleEndian() throws Exception { final JBBPFieldArrayInt ints = JBBPParser.prepare("int[_];") .parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0}) .findFieldForType(JBBPFieldArrayInt.class); assertEquals(2, ints.size()); assertEquals(0xF7436510, ints.getAsInt(0)); assertEquals(0x352367A0, ints.getAsInt(1)); } @Test public void testParse_NonFixedIntArray_LittleEndian() throws Exception { final JBBPFieldArrayInt ints = JBBPParser.prepare(" JBBPParser.prepare("byte; floatj[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedFloatArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; floatj[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayFloat.class).size()); } @Test public void testParse_FixedFloatArray_Default() throws Exception { final JBBPFieldArrayFloat ints = JBBPParser.prepare("floatj[2];") .parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0}) .findFieldForType(JBBPFieldArrayFloat.class); assertEquals(2, ints.size()); assertEquals(-3.963077E33f, ints.getAsFloat(0), TestUtils.FLOAT_DELTA); assertEquals(6.0873026E-7f, ints.getAsFloat(1), TestUtils.FLOAT_DELTA); } @Test public void testParse_FixedFloatArray_BigEndian() throws Exception { final JBBPFieldArrayFloat ints = JBBPParser.prepare(">floatj[2];") .parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0}) .findFieldForType(JBBPFieldArrayFloat.class); assertEquals(2, ints.size()); assertEquals(-3.963077E33f, ints.getAsFloat(0), TestUtils.FLOAT_DELTA); assertEquals(6.0873026E-7f, ints.getAsFloat(1), TestUtils.FLOAT_DELTA); } @Test public void testParse_FixedFloatArray_LittleEndian() throws Exception { final JBBPFieldArrayFloat ints = JBBPParser.prepare("floatj[_];") .parse(new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0}) .findFieldForType(JBBPFieldArrayFloat.class); assertEquals(2, ints.size()); assertEquals(-3.963077E33f, ints.getAsFloat(0), TestUtils.FLOAT_DELTA); assertEquals(6.0873026E-7f, ints.getAsFloat(1), TestUtils.FLOAT_DELTA); } @Test public void testParse_NonFixedFloatArray_LittleEndian() throws Exception { final JBBPFieldArrayFloat ints = JBBPParser.prepare(" JBBPParser.prepare("byte; long[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedLongArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; long[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayLong.class).size()); } @Test public void testParse_FixedLongArray_Default() throws Exception { final JBBPFieldArrayLong longs = JBBPParser.prepare("long[2];").parse( new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0, 0x32, 0x33, 0x61, (byte) 0xCA, (byte) 0xBE, 0x22, 0x12, 0x30}).findFieldForType(JBBPFieldArrayLong.class); assertEquals(2, longs.size()); assertEquals(0xF7436510352367A0L, longs.getAsLong(0)); assertEquals(0x323361CABE221230L, longs.getAsLong(1)); } @Test public void testParse_FixedLongArray_BigEndian() throws Exception { final JBBPFieldArrayLong longs = JBBPParser.prepare(">long[2];").parse( new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0, 0x32, 0x33, 0x61, (byte) 0xCA, (byte) 0xBE, 0x22, 0x12, 0x30}).findFieldForType(JBBPFieldArrayLong.class); assertEquals(2, longs.size()); assertEquals(0xF7436510352367A0L, longs.getAsLong(0)); assertEquals(0x323361CABE221230L, longs.getAsLong(1)); } @Test public void testParse_FixedLongArray_LittleEndian() throws Exception { final JBBPFieldArrayLong longs = JBBPParser.prepare("long[_];").parse( new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0, 0x32, 0x33, 0x61, (byte) 0xCA, (byte) 0xBE, 0x22, 0x12, 0x30}).findFieldForType(JBBPFieldArrayLong.class); assertEquals(2, longs.size()); assertEquals(0xF7436510352367A0L, longs.getAsLong(0)); assertEquals(0x323361CABE221230L, longs.getAsLong(1)); } @Test public void testParse_NonFixedLongArray_LittleEndian() throws Exception { final JBBPFieldArrayLong longs = JBBPParser.prepare(" JBBPParser.prepare("byte; doublej[1];").parse(new byte[] {1})); } @Test public void testParse_NonFixedDoubleArray_ParsedAsEmptyArray() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte; doublej[_] array;").parse(new byte[] {1}); assertEquals(0, parsed.findFieldForNameAndType("array", JBBPFieldArrayDouble.class).size()); } @Test public void testParse_FixedDoubleArray_Default() throws Exception { final JBBPFieldArrayDouble longs = JBBPParser.prepare("doublej[2];").parse( new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0, 0x32, 0x33, 0x61, (byte) 0xCA, (byte) 0xBE, 0x22, 0x12, 0x30}) .findFieldForType(JBBPFieldArrayDouble.class); assertEquals(2, longs.size()); assertEquals(-3.126878492655484E266d, longs.getAsDouble(0), TestUtils.FLOAT_DELTA); assertEquals(7.189183308668011E-67d, longs.getAsDouble(1), TestUtils.FLOAT_DELTA); } @Test public void testParse_FixedDoubleArray_BigEndian() throws Exception { final JBBPFieldArrayDouble longs = JBBPParser.prepare(">doublej[2];").parse( new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0, 0x32, 0x33, 0x61, (byte) 0xCA, (byte) 0xBE, 0x22, 0x12, 0x30}) .findFieldForType(JBBPFieldArrayDouble.class); assertEquals(2, longs.size()); assertEquals(-3.126878492655484E266d, longs.getAsDouble(0), TestUtils.FLOAT_DELTA); assertEquals(7.189183308668011E-67d, longs.getAsDouble(1), TestUtils.FLOAT_DELTA); } @Test public void testParse_FixedDoubleArray_LittleEndian() throws Exception { final JBBPFieldArrayDouble longs = JBBPParser.prepare("doublej[_];").parse( new byte[] {(byte) 0xF7, 0x43, 0x65, 0x10, 0x35, 0x23, 0x67, (byte) 0xA0, 0x32, 0x33, 0x61, (byte) 0xCA, (byte) 0xBE, 0x22, 0x12, 0x30}) .findFieldForType(JBBPFieldArrayDouble.class); assertEquals(2, longs.size()); assertEquals(-3.126878492655484E266d, longs.getAsDouble(0), TestUtils.FLOAT_DELTA); assertEquals(7.189183308668011E-67d, longs.getAsDouble(1), TestUtils.FLOAT_DELTA); } @Test public void testParse_NonFixedDoubleArray_LittleEndian() throws Exception { final JBBPFieldArrayDouble longs = JBBPParser.prepare(" JBBPParser.prepare("ubyte len; byte[len-4];").parse(new byte[] {2, 1, 2, 3, 4})); } @Test public void testParse_NegativeArrayLength() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPParser.prepare("ubyte len; byte[-2];").parse(new byte[] {2, 1, 2, 3, 4})); } @Test public void testParse_ErrorForLessDataThanExpected() throws Exception { assertThrows(EOFException.class, () -> JBBPParser.prepare("ubyte len; byte[5];").parse(new byte[] {2, 1, 2, 3, 4})); } @Test public void testParse_WholeStructStream() throws Exception { final JBBPFieldArrayStruct array = JBBPParser.prepare("struct [_] {byte;}").parse(new byte[] {1, 2, 3, 4}) .findFieldForType(JBBPFieldArrayStruct.class); assertEquals(4, array.size()); assertEquals(1, array.getElementAt(0).findFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(2, array.getElementAt(1).findFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(3, array.getElementAt(2).findFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(4, array.getElementAt(3).findFieldForType(JBBPFieldByte.class).getAsInt()); } @Test public void testParse_BitArray_IgnoredForZeroLength() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; bit:4 [len]; ushort;") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); assertEquals(0, parsed.findFieldForType(JBBPFieldArrayBit.class).size()); assertEquals(0x0102, parsed.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_BoolArray_IgnoredForZeroLength() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; bool [len]; ushort;") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); assertEquals(0, parsed.findFieldForType(JBBPFieldArrayBoolean.class).size()); assertEquals(0x0102, parsed.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_ByteArray_IgnoredForZeroLength() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; byte [len]; ushort;") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); assertEquals(0, parsed.findFieldForType(JBBPFieldArrayByte.class).size()); assertEquals(0x0102, parsed.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_UByteArray_IgnoredForZeroLength() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; ubyte [len]; ushort;") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); assertEquals(0, parsed.findFieldForType(JBBPFieldArrayUByte.class).size()); assertEquals(0x0102, parsed.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_ShortArray_IgnoredForZeroLength() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; short [len]; ushort;") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); assertEquals(0, parsed.findFieldForType(JBBPFieldArrayShort.class).size()); assertEquals(0x0102, parsed.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_UShortArray_IgnoredForZeroLength() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; ushort [len]; ushort;") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); assertEquals(0, parsed.findFieldForType(JBBPFieldArrayUShort.class).size()); assertEquals(0x0102, parsed.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_IntArray_IgnoredForZeroLength() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; int [len]; ushort;") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); assertEquals(0, parsed.findFieldForType(JBBPFieldArrayInt.class).size()); assertEquals(0x0102, parsed.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_LongArray_IgnoredForZeroLength() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; long [len]; ushort;") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); assertEquals(0, parsed.findFieldForType(JBBPFieldArrayLong.class).size()); assertEquals(0x0102, parsed.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_StructArray_IgnoredForZeroLength() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte len; sss [len] { byte a; byte b; byte c;} ushort;") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); assertEquals(0, parsed.findFieldForType(JBBPFieldArrayStruct.class).size()); assertEquals(0x0102, parsed.findFieldForType(JBBPFieldUShort.class).getAsInt()); } @Test public void testParse_StructArray_FixedSize() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("sss [1] { byte a; byte b; byte c;}") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); final JBBPFieldArrayStruct struct = parsed.findFieldForPathAndType("sss", JBBPFieldArrayStruct.class); assertEquals(1, struct.size()); final JBBPFieldStruct readStruct = struct.getElementAt(0); assertEquals(0, readStruct.findFieldForNameAndType("a", JBBPFieldByte.class).getAsInt()); assertEquals(1, readStruct.findFieldForNameAndType("b", JBBPFieldByte.class).getAsInt()); assertEquals(2, readStruct.findFieldForNameAndType("c", JBBPFieldByte.class).getAsInt()); } @Test public void testParse_StructArray_WholeStream() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("sss [_] { byte a; byte b; byte c;}") .parse(new byte[] {0x0, 0x01, (byte) 0x02}); final JBBPFieldArrayStruct struct = parsed.findFieldForPathAndType("sss", JBBPFieldArrayStruct.class); assertEquals(1, struct.size()); final JBBPFieldStruct readStruct = struct.getElementAt(0); assertEquals(0, readStruct.findFieldForNameAndType("a", JBBPFieldByte.class).getAsInt()); assertEquals(1, readStruct.findFieldForNameAndType("b", JBBPFieldByte.class).getAsInt()); assertEquals(2, readStruct.findFieldForNameAndType("c", JBBPFieldByte.class).getAsInt()); } @Test public void testParse_EmptyStructArray_WholeStream() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("sss [_] { }").parse(new byte[0]); assertEquals(0, parsed.findFieldForPathAndType("sss", JBBPFieldArrayStruct.class).size()); } @Test public void testParse_EmptyStructArrayInsideStruct_WholeStream() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("sss { sss2[_]{}}").parse(new byte[0]); assertEquals(0, parsed.findFieldForPathAndType("sss.sss2", JBBPFieldArrayStruct.class).size()); } @Test public void testParse_SkipStructureForZeroItems() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare( "byte len; sss [len]{ sss2[10]{ sss3{long;} sss4[45]{ushort; bool [11]; short; bit:4;} byte;}} byte end;") .parse(new byte[] {0x00, 0x1F}); assertEquals(0, parsed.findFieldForPathAndType("len", JBBPFieldByte.class).getAsInt()); assertEquals(0, parsed.findFieldForPathAndType("sss", JBBPFieldArrayStruct.class).size()); assertEquals(0x1F, parsed.findFieldForPathAndType("end", JBBPFieldByte.class).getAsInt()); } @Test public void testParseWithStreamPositionMacros() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("int start; byte [$$] array; int end;") .parse(new byte[] {1, 2, 3, 4, 0x1A, 0x1B, 0x1C, 0x1D, 4, 3, 2, 1}); assertEquals(0x01020304, parsed.findFieldForPathAndType("start", JBBPFieldInt.class).getAsInt()); assertArrayEquals(new byte[] {0x1A, 0x1B, 0x1C, 0x1D}, parsed.findFieldForPathAndType("array", JBBPFieldArrayByte.class).getArray()); assertEquals(0x04030201, parsed.findFieldForPathAndType("end", JBBPFieldInt.class).getAsInt()); } @Test public void testParseWithStreamPositionMacrosInExpressions() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("int start; byte [$$-2] array; byte [$$-4] array2; int end;") .parse(new byte[] {1, 2, 3, 4, 0x1A, 0x1B, 0x1C, 0x1D, 4, 3, 2, 1}); assertEquals(0x01020304, parsed.findFieldForPathAndType("start", JBBPFieldInt.class).getAsInt()); assertArrayEquals(new byte[] {0x1A, 0x1B}, parsed.findFieldForPathAndType("array", JBBPFieldArrayByte.class).getArray()); assertArrayEquals(new byte[] {0x1C, 0x1D}, parsed.findFieldForPathAndType("array2", JBBPFieldArrayByte.class).getArray()); assertEquals(0x04030201, parsed.findFieldForPathAndType("end", JBBPFieldInt.class).getAsInt()); } @Test public void testParseManyFieldsWithTheSameName() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte l; a { byte a; b { byte a; c { byte a;}}} byte [a.b.c.a] aa;") .parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(1, parsed.findFieldForPathAndType("l", JBBPFieldByte.class).getAsInt()); assertEquals(2, parsed.findFieldForPathAndType("a.a", JBBPFieldByte.class).getAsInt()); assertEquals(3, parsed.findFieldForPathAndType("a.b.a", JBBPFieldByte.class).getAsInt()); assertEquals(4, parsed.findFieldForPathAndType("a.b.c.a", JBBPFieldByte.class).getAsInt()); assertArrayEquals(new byte[] {5, 6, 7, 8}, parsed.findFieldForPathAndType("aa", JBBPFieldArrayByte.class).getArray()); } @Test public void testParseScopeOfVisibilityOfFieldIfTheSameInStructBefore() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte a; b { byte a;} byte [a] aa;").parse(new byte[] {1, 2, 3}); assertEquals(1, parsed.findFieldForPathAndType("a", JBBPFieldByte.class).getAsInt()); assertEquals(2, parsed.findFieldForPathAndType("b.a", JBBPFieldByte.class).getAsInt()); assertArrayEquals(new byte[] {3}, parsed.findFieldForPathAndType("aa", JBBPFieldArrayByte.class).getArray()); } @Test public void testParseScopeOfVisibilityOfFieldInsideStructure() throws Exception { final JBBPFieldStruct parsed = JBBPParser.prepare("byte a; b { byte a; byte [a] d; } byte [a] aa;") .parse(new byte[] {1, 2, 3, 4, 5, 6}); assertEquals(1, parsed.findFieldForPathAndType("a", JBBPFieldByte.class).getAsInt()); assertEquals(2, parsed.findFieldForPathAndType("b.a", JBBPFieldByte.class).getAsInt()); assertArrayEquals(new byte[] {3, 4}, parsed.findFieldForPathAndType("b.d", JBBPFieldArrayByte.class).getArray()); assertArrayEquals(new byte[] {5}, parsed.findFieldForPathAndType("aa", JBBPFieldArrayByte.class).getArray()); } @Test public void testParseFixedSizeStructureArray() throws Exception { final JBBPParser parser = JBBPParser.prepare("int val1; inner [2] { byte a; byte b;}"); final JBBPFieldStruct parsed = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}); assertEquals(0x01020304, parsed.findFieldForPathAndType("val1", JBBPFieldInt.class).getAsInt()); final JBBPFieldArrayStruct structArray = parsed.findFieldForNameAndType("inner", JBBPFieldArrayStruct.class); assertEquals(2, structArray.size()); } @Test public void testParseWithResetCounter() throws Exception { final JBBPParser parser = JBBPParser.prepare("struct[_]{reset$$; byte a; align:3; byte b;}"); final JBBPFieldStruct parsed = parser.parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}); final JBBPFieldArrayStruct structArray = parsed.findFieldForNameAndType("struct", JBBPFieldArrayStruct.class); final byte[] etalon = new byte[] {1, 4, 5, 8, 9, 12, 13, 16}; assertEquals(4, structArray.size()); int i = 0; for (final JBBPFieldStruct s : structArray) { final JBBPFieldByte a = s.findFieldForNameAndType("a", JBBPFieldByte.class); final JBBPFieldByte b = s.findFieldForNameAndType("b", JBBPFieldByte.class); assertEquals(etalon[i++] & 0xFF, a.getAsInt()); assertEquals(etalon[i++] & 0xFF, b.getAsInt()); } } @Test public void testParseResetCounterWithCachedBits() throws Exception { final JBBPParser parser = JBBPParser.prepare("bit:4 a; reset$$; byte b;"); final JBBPFieldStruct parsed = parser.parse(new byte[] {0x3, 0x1F}); assertEquals(3, parsed.findFieldForNameAndType("a", JBBPFieldBit.class).getAsInt()); assertEquals(0x1F, parsed.findFieldForNameAndType("b", JBBPFieldByte.class).getAsInt()); assertEquals(1, parser.getFinalStreamByteCounter()); } @Test public void testParseArrayWithZeroLengthForResetCounter() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte; byte [$$] a; reset$$; byte[$$] b; byte [2] c;"); final JBBPFieldStruct parsed = parser.parse(new byte[] {1, 2, 3, 4}); final JBBPFieldArrayByte a = parsed.findFieldForNameAndType("a", JBBPFieldArrayByte.class); final JBBPFieldArrayByte b = parsed.findFieldForNameAndType("b", JBBPFieldArrayByte.class); final JBBPFieldArrayByte c = parsed.findFieldForNameAndType("c", JBBPFieldArrayByte.class); assertArrayEquals(new byte[] {2}, a.getArray()); assertArrayEquals(new byte[0], b.getArray()); assertArrayEquals(new byte[] {3, 4}, c.getArray()); } @Test public void testGetFinalStreamByteCounter_Single_NoError() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte [_];"); assertEquals(0L, parser.getFinalStreamByteCounter()); parser.parse(new byte[2345]); assertEquals(2345L, parser.getFinalStreamByteCounter()); } @Test public void testGetFinalStreamByteCounter_SequentlyFromTheSameStream_WithEOFAtTheEnd() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})); final JBBPParser parser = JBBPParser.prepare("byte [5];"); assertEquals(0L, parser.getFinalStreamByteCounter()); parser.parse(stream); assertEquals(5, parser.getFinalStreamByteCounter()); parser.parse(stream); assertEquals(10, parser.getFinalStreamByteCounter()); parser.parse(stream); assertEquals(15, parser.getFinalStreamByteCounter()); try { parser.parse(stream); fail("Must throw EOF"); } catch (EOFException ex) { assertEquals(16, parser.getFinalStreamByteCounter()); } } @Test public void testParse_ErrorForNotAllReadFields() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4})); final JBBPParser parser = JBBPParser.prepare("int a; int b;"); assertThrows(JBBPParsingException.class, () -> parser.parse(stream)); } @Test public void testParse_NegativeExpressonResult_OneFieldAsExpression_FlagOff() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {(byte) 0xEF, 1, 2, 3})); final JBBPParser parser = JBBPParser.prepare("byte len; byte [len] arr;"); assertThrows(JBBPParsingException.class, () -> parser.parse(stream)); } @Test public void testParse_NegativeExpressonResult_ExpressionWithNegativeResult_FlagOff() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {2, 1, 2, 3})); final JBBPParser parser = JBBPParser.prepare("byte len; byte [len - 8] arr;"); assertThrows(JBBPParsingException.class, () -> parser.parse(stream)); } @Test public void testParse_NegativeExpressonResult_OneFieldAsExpression_FlagOn() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {(byte) 0xEF, 1, 2, 3})); final JBBPParser parser = JBBPParser .prepare("byte len; byte [len] arr;", JBBPParser.FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO); final JBBPFieldStruct result = parser.parse(stream); assertEquals((byte) 0xEF, result.findFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(0, result.findFieldForType(JBBPFieldArrayByte.class).getArray().length); } @Test public void testParse_NegativeExpressonResult_ExpressionWithNegativResult_FlagOn() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {4, 1, 2, 3})); final JBBPParser parser = JBBPParser .prepare("byte len; byte [len-8] arr;", JBBPParser.FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO); final JBBPFieldStruct result = parser.parse(stream); assertEquals(4, result.findFieldForType(JBBPFieldByte.class).getAsInt()); assertEquals(0, result.findFieldForType(JBBPFieldArrayByte.class).getArray().length); } @Test public void testParse_NoErrorForIgnoreRemainingFieldsFlag() throws Exception { final JBBPBitInputStream stream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4})); final JBBPParser parser = JBBPParser.prepare("int a; int b;", JBBPParser.FLAG_SKIP_REMAINING_FIELDS_IF_EOF); final JBBPFieldStruct result = parser.parse(stream); assertEquals(1, result.getArray().length); assertEquals("a", result.getArray()[0].getFieldName()); assertEquals(0x01020304, ((JBBPFieldInt) result.findFieldForName("a")).getAsInt()); } @Test public void testConvertToSrc_Java_NamedPackage() { final JBBPParser parser = JBBPParser.prepare("byte a;"); final List src = parser.convertToSrc(TargetSources.JAVA, "some.package.SomeClass"); assertEquals(1, src.size()); assertEquals("byte a;", src.get(0).getMetadata().getProperty("script")); assertTrue(src.get(0).getResult().get("some/package/SomeClass.java").length() > 128); } @Test public void testConvertToSrc_Java_DefaultPackage() { final JBBPParser parser = JBBPParser.prepare("byte a;"); final List src = parser.convertToSrc(TargetSources.JAVA, "SomeClass"); assertEquals(1, src.size()); assertEquals("byte a;", src.get(0).getMetadata().getProperty("script")); assertTrue(src.get(0).getResult().get("SomeClass.java").length() > 128); } @Test public void testUintUseInExpression() throws Exception { final JBBPParser parser = JBBPParser.prepare("uint length; uint[(length * 2) >> 1] array;"); final JBBPFieldStruct struct = parser.parse( new byte[] {0, 0, 0, 2, (byte) 0xFF, (byte) 0xF0, (byte) 0xE0, (byte) 0x12, 0x01, 0x02, 0x03, 0x04}); final JBBPFieldUInt length = struct.findFieldForPathAndType("length", JBBPFieldUInt.class); final JBBPFieldArrayUInt array = struct.findFieldForPathAndType("array", JBBPFieldArrayUInt.class); assertEquals(2, length.getAsInt()); assertEquals(2, array.size()); JBBPNumericFieldValueConversionException exception = assertThrows(JBBPNumericFieldValueConversionException.class, () -> array.getElementAt(0).getAsInt()); assertTrue(exception.toString().contains("0xFFF0E012")); assertEquals(0xFFF0E012L, array.getElementAt(0).getAsLong()); assertEquals(0x01020304, array.getElementAt(1).getAsInt()); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/TestUtils.java000066400000000000000000000103561445000440600307170ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Locale; import org.apache.commons.codec.digest.PureJavaCrc32; /** * Different useful auxiliary test methods */ public enum TestUtils { ; /** * Delta to be used for double and float compare. */ public static final float FLOAT_DELTA = Float.MIN_VALUE; /** * Read field value, also allows to provide dot-separated chain of fields * * @param expected type of value * @param instance instance of object, must not be null * @param fieldName field name, can be single name or dot-separated one, must * not be null * @param klazz expected value class, must not be null * @return value, can be null * @throws Exception it will be thrown if any error */ public static T getField(final Object instance, final String fieldName, final Class klazz) throws Exception { final String[] fields = fieldName.split("\\."); Object result = instance; for (final String f : fields) { result = result.getClass().getField(f).get(result); } return klazz.cast(result); } /** * Read field value through getters * * @param expected type of value * @param instance instance of object, must not be null * @param fieldName field name, can be single name or dot-separated one, must * not be null * @param klazz expected value class, must not be null * @return value, can be null * @throws Exception it will be thrown if any error */ public static T getFieldThroughGetters(final Object instance, final String fieldName, final Class klazz) throws Exception { final String[] fields = fieldName.split("\\."); Object result = instance; for (final String f : fields) { result = result.getClass().getMethod("get" + f.toUpperCase(Locale.ENGLISH)).invoke(result); } return klazz.cast(result); } /** * Check PNG chunk data. * * @param etalonName etalon type name, must not be null * @param etalonLength etalon length in bytes * @param chunkType chunk type * @param chunkLength chunk data length * @param chunkCrc chunk crc field value * @param chunkData chunk data, must not be null */ public static void assertPngChunk(final String etalonName, final int etalonLength, final int chunkType, final int chunkLength, final int chunkCrc, final byte[] chunkData) { final int chunkEtalonName = (etalonName.charAt(0) << 24) | (etalonName.charAt(1) << 16) | (etalonName.charAt(2) << 8) | etalonName.charAt(3); assertEquals(chunkEtalonName, chunkType, "Chunk must be " + etalonName); assertEquals(etalonLength, chunkLength, "Chunk length must be " + etalonLength); final PureJavaCrc32 crc32 = new PureJavaCrc32(); crc32.update(etalonName.charAt(0)); crc32.update(etalonName.charAt(1)); crc32.update(etalonName.charAt(2)); crc32.update(etalonName.charAt(3)); if (etalonLength != 0) { assertEquals(etalonLength, chunkData.length, "Data array " + etalonName + " must be " + etalonLength); for (final byte b : chunkData) { crc32.update(b & 0xFF); } } final int crc = (int) crc32.getValue(); assertEquals(crc, chunkCrc, "CRC32 for " + etalonName + " must be " + crc); } public static String wavInt2Str(final int value) { return new String(new char[] {(char) (value & 0xFF), (char) ((value >>> 8) & 0xFF), (char) ((value >>> 16) & 0xFF), (char) (value >>> 24)}); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/000077500000000000000000000000001445000440600277215ustar00rootroot00000000000000JBBPCompilerTest.java000066400000000000000000000620551445000440600335650ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPTokenType; import com.igormaznitsa.jbbp.exceptions.JBBPCompilationException; import com.igormaznitsa.jbbp.exceptions.JBBPTokenizerException; import com.igormaznitsa.jbbp.utils.JBBPIntCounter; import com.igormaznitsa.jbbp.utils.JBBPUtils; import org.junit.jupiter.api.Test; public class JBBPCompilerTest { @Test public void testCompile_ErrorForWrongChar() throws Exception { try { JBBPCompiler.compile("align;9").getCompiledData(); fail("Must throw parser exception"); } catch (JBBPTokenizerException ex) { assertEquals(6, ex.getPosition()); assertTrue(ex.getMessage().contains("[9]")); } } @Test public void testCompile_AllowedStructInsideStructWhichShouldBeReadTillEnd() throws Exception { JBBPCompiler.compile("a [_] { byte c;}"); JBBPCompiler.compile("a [_] { b {byte c;}}"); JBBPCompiler.compile("[_] { b [2] {byte c;}}"); } @Test public void testCompile_ErrorSituationsReadTillEnd() { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("ubyte [_]; ubyte [_];")); assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("ubyte [_]; a {byte;};")); assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("byte;test [_] {byte;} int error;")); assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("ubyte [_]; byte;")); assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("a [_] { byte a; } b [_] { byte a;}")); assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("a [_] { b [_] { byte a;}}")); } @Test public void testCompile_StructForWholeStreamAsSecondField() throws Exception { final JBBPCompiledBlock block = JBBPCompiler.compile("byte;test [_] {byte;}"); assertEquals(6, block.getCompiledData().length); assertEquals(JBBPCompiler.CODE_BYTE, block.getCompiledData()[0]); assertEquals(JBBPCompiler.CODE_STRUCT_START | JBBPCompiler.FLAG_WIDE | JBBPCompiler.FLAG_NAMED, block.getCompiledData()[1] & 0xFF); assertEquals(JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM, block.getCompiledData()[2] & 0xFF); assertEquals(JBBPCompiler.CODE_BYTE, block.getCompiledData()[3]); assertEquals(JBBPCompiler.CODE_STRUCT_END, block.getCompiledData()[4]); assertEquals(1, block.getCompiledData()[5]); } @Test public void testCompile_WholeStreamArrayInsideStructure() throws Exception { final JBBPCompiledBlock block = JBBPCompiler.compile("test {byte [_];}"); assertEquals(5, block.getCompiledData().length); assertEquals(JBBPCompiler.CODE_STRUCT_START | JBBPCompiler.FLAG_NAMED, block.getCompiledData()[0]); assertEquals(JBBPCompiler.CODE_BYTE | JBBPCompiler.FLAG_WIDE, block.getCompiledData()[1] & 0xFF); assertEquals(JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM, block.getCompiledData()[2] & 0xFF); assertEquals(JBBPCompiler.CODE_STRUCT_END, block.getCompiledData()[3]); assertEquals(0, block.getCompiledData()[4]); } @Test public void testCompile_WholeStreamStructureArrayInsideStructure() throws Exception { final JBBPCompiledBlock block = JBBPCompiler.compile("test { whole[_]{ byte;}}"); assertEquals(8, block.getCompiledData().length); assertEquals(JBBPCompiler.CODE_STRUCT_START | JBBPCompiler.FLAG_NAMED, block.getCompiledData()[0]); assertEquals(JBBPCompiler.CODE_STRUCT_START | JBBPCompiler.FLAG_NAMED | JBBPCompiler.FLAG_WIDE, block.getCompiledData()[1] & 0xFF); assertEquals(JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM, block.getCompiledData()[2] & 0xFF); assertEquals(JBBPCompiler.CODE_BYTE, block.getCompiledData()[3]); assertEquals(JBBPCompiler.CODE_STRUCT_END, block.getCompiledData()[4]); assertEquals(1, block.getCompiledData()[5]); assertEquals(JBBPCompiler.CODE_STRUCT_END, block.getCompiledData()[6]); assertEquals(0, block.getCompiledData()[7]); } @Test public void testCompile_StructureAndArrayWithLengthFromStructureField() throws Exception { final JBBPCompiledBlock block = JBBPCompiler.compile("str {byte size;} byte[str.size] some;"); assertTrue(block.hasEvaluatedSizeArrays()); assertNotNull(block.findFieldForPath("str.size")); assertNotNull(block.findFieldForPath("some")); } @Test public void testCompile_ErrorForUnknownType() throws Exception { try { JBBPCompiler.compile("somewrong;"); fail("Must throw IAE"); } catch (JBBPCompilationException ex) { assertTrue(ex.getToken().toString().contains("somewrong")); } } @Test public void testCompile_ErrorForNamedAlignField() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("align hello;")); } @Test public void testCompile_ErrorForArrayAlignField() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("align [445] hello;")); } @Test public void testCompile_ErrorForZeroAlignValue() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("align:0;")); } @Test public void testCompile_ErrorForNegativeAlignValue() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("align:-1;")); } @Test public void testCompile_ErrorForNonNumericAlignValue() throws Exception { assertThrows(JBBPTokenizerException.class, () -> JBBPCompiler.compile("align:hhh;")); } @Test public void testCompile_ErrorForNamedSkipField() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("skip hello;")); } @Test public void testCompile_ErrorForArraySkipField() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("skip [445] hello;")); } @Test public void testCompile_ZeroSkipValueIsAllowed() throws Exception { assertArrayEquals(new byte[] {JBBPCompiler.CODE_SKIP, 0}, JBBPCompiler.compile("skip:0;").getCompiledData()); } @Test public void testCompile_ErrorForNegativeSkipValue() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("skip:-1;")); } @Test public void testCompile_ErrorForNonNumericSkipValue() throws Exception { assertThrows(JBBPTokenizerException.class, () -> JBBPCompiler.compile("skip:hhh;")); } @Test public void testCompile_CompiledBlock_getSources() throws Exception { final String src = "byte a; byte b;"; assertEquals(src, JBBPCompiler.compile(src).getSource()); } @Test public void testCompile_ArraySize_ExpressionWithAllOperators_NoExceptions() throws Exception { assertNotNull(JBBPCompiler.compile("byte [(1+2-3)*4/(5<<6>>7)>>>3%1&89|22^34-~45] a;")); } @Test public void testCompile_ExtraFieldData_ExpressionWithAllOperators_NoExceptions() throws Exception { assertNotNull(JBBPCompiler.compile("bit:((1+2-3)*4/(5<<6>>7)>>>3%1&89|22^34-~45) a;")); } @Test public void testCompile_ErrorForUnknownTypeAsSecondField() throws Exception { try { JBBPCompiler.compile("byte; somewrong; int;"); fail("Must throw IAE"); } catch (JBBPCompilationException ex) { assertTrue(ex.getToken().toString().contains("somewrong")); } } @Test public void testCompile_ErrorForNonOpenedStructure() throws Exception { try { JBBPCompiler.compile("byte; int; } "); fail("Must throw IAE"); } catch (JBBPCompilationException ex) { assertEquals(JBBPTokenType.STRUCT_END, ex.getToken().getType()); } } @Test public void testCompile_ErrorForNonClosedStructure() throws Exception { try { JBBPCompiler.compile("{byte; int; "); fail("Must throw IAE"); } catch (JBBPCompilationException ex) { assertNull(ex.getToken()); } } @Test public void testCompile_ErrorForIllegaFieldName() throws Exception { assertThrows(JBBPTokenizerException.class, () -> JBBPCompiler.compile(" byte _;")); assertThrows(JBBPTokenizerException.class, () -> JBBPCompiler.compile(" byte $a;")); assertThrows(JBBPTokenizerException.class, () -> JBBPCompiler.compile(" byte $$;")); assertThrows(JBBPTokenizerException.class, () -> JBBPCompiler.compile(" byte a.b;")); assertThrows(JBBPTokenizerException.class, () -> JBBPCompiler.compile(" byte 1d;")); } @Test public void testCompile_ErrorForIllegalCharInFieldName() throws Exception { try { JBBPCompiler.compile("int; byte in.td;"); fail("Must throw Tokenizer Exception"); } catch (JBBPTokenizerException ex) { assertEquals(6, ex.getPosition()); } } @Test public void testCompile_ErrorForIllegalStartCharInFieldName() throws Exception { try { JBBPCompiler.compile("int; byte 5intd;"); fail("Must throw Tokenizer Exception"); } catch (JBBPTokenizerException ex) { assertEquals(4, ex.getPosition()); } } @Test public void testCompile_ErrorForIllegalCharInOnlyFieldName() throws Exception { try { JBBPCompiler.compile(" byte in.td;"); fail("Must throw Tokenizer Exception"); } catch (JBBPTokenizerException ex) { assertEquals(2, ex.getPosition()); } } @Test public void testCompile_NonamedVarWithoutExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("var;").getCompiledData(); assertArrayEquals(new byte[] {JBBPCompiler.CODE_VAR, 0}, compiled); } @Test public void testCompile_NonamedVarWithPositiveExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("var:12;").getCompiledData(); assertArrayEquals(new byte[] {JBBPCompiler.CODE_VAR, 12}, compiled); } @Test public void testCompile_NonamedVarWithNegativeExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("var:-1;").getCompiledData(); assertArrayEquals( new byte[] {JBBPCompiler.CODE_VAR, (byte) 0x81, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}, compiled); } @Test public void testCompile_NamedVarWithoutExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("var VVV;").getCompiledData(); assertArrayEquals(new byte[] {JBBPCompiler.CODE_VAR | JBBPCompiler.FLAG_NAMED, 0}, compiled); } @Test public void testCompile_NamedVarWithPositiveExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("var:12 VVV;").getCompiledData(); assertArrayEquals(new byte[] {JBBPCompiler.CODE_VAR | JBBPCompiler.FLAG_NAMED, 12}, compiled); } @Test public void testCompile_NamedVarWithNegativeExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("var:-1 VVV;").getCompiledData(); assertArrayEquals( new byte[] {JBBPCompiler.CODE_VAR | JBBPCompiler.FLAG_NAMED, (byte) 0x81, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}, compiled); } @Test public void testCompile_NamedVarArrayWithoutExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("var [98] VVV;").getCompiledData(); assertArrayEquals( new byte[] {JBBPCompiler.CODE_VAR | JBBPCompiler.FLAG_NAMED | JBBPCompiler.FLAG_ARRAY, 98, 0}, compiled); } @Test public void testCompile_NamedVarArrayWithPositiveExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("var:12 [98] VVV;").getCompiledData(); assertArrayEquals( new byte[] {JBBPCompiler.CODE_VAR | JBBPCompiler.FLAG_ARRAY | JBBPCompiler.FLAG_NAMED, 98, 12}, compiled); } @Test public void testCompile_NamedVarArrayWithNegativeExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("var:-1 [98] VVV;").getCompiledData(); assertArrayEquals( new byte[] {JBBPCompiler.CODE_VAR | JBBPCompiler.FLAG_ARRAY | JBBPCompiler.FLAG_NAMED, 98, (byte) 0x81, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}, compiled); } @Test public void testCompile_SingleAlignFieldWithoutExtra() throws Exception { final byte[] compiled = JBBPCompiler.compile("align;").getCompiledData(); assertEquals(2, compiled.length); assertEquals(JBBPCompiler.CODE_ALIGN, compiled[0]); assertEquals(1, compiled[1]); } @Test public void testCompile_SingleAlignFieldWithValue() throws Exception { final byte[] compiled = JBBPCompiler.compile("align:9;").getCompiledData(); assertEquals(2, compiled.length); assertEquals(JBBPCompiler.CODE_ALIGN, compiled[0]); assertEquals(9, compiled[1]); } @Test public void testCompile_SingleNonamedBitDefaultLenField() throws Exception { final byte[] compiled = JBBPCompiler.compile("bit;").getCompiledData(); assertEquals(2, compiled.length); assertEquals(JBBPCompiler.CODE_BIT, compiled[0]); assertEquals(1, compiled[1]); } @Test public void testCompile_SingleNonamedBitDefinedLenField() throws Exception { final byte[] compiled = JBBPCompiler.compile("bit:5;").getCompiledData(); assertEquals(2, compiled.length); assertEquals(JBBPCompiler.CODE_BIT, compiled[0]); assertEquals(5, compiled[1]); } @Test public void testCompile_SingleNonamedBooleanField() throws Exception { final byte[] compiled = JBBPCompiler.compile("bool;").getCompiledData(); assertEquals(1, compiled.length); assertEquals(JBBPCompiler.CODE_BOOL, compiled[0]); } @Test public void testCompile_SingleNonamedByteField() throws Exception { final byte[] compiled = JBBPCompiler.compile("byte;").getCompiledData(); assertEquals(1, compiled.length); assertEquals(JBBPCompiler.CODE_BYTE, compiled[0]); } @Test public void testCompile_SingleNonamedUByteField() throws Exception { final byte[] compiled = JBBPCompiler.compile("ubyte;").getCompiledData(); assertEquals(1, compiled.length); assertEquals(JBBPCompiler.CODE_UBYTE, compiled[0]); } @Test public void testCompile_SingleNonamedShortField() throws Exception { final byte[] compiled = JBBPCompiler.compile("short;").getCompiledData(); assertEquals(1, compiled.length); assertEquals(JBBPCompiler.CODE_SHORT, compiled[0]); } @Test public void testCompile_SingleNonamedUShortField() throws Exception { final byte[] compiled = JBBPCompiler.compile("ushort;").getCompiledData(); assertEquals(1, compiled.length); assertEquals(JBBPCompiler.CODE_USHORT, compiled[0]); } @Test public void testCompile_SingleNonamedIntField() throws Exception { final byte[] compiled = JBBPCompiler.compile("int;").getCompiledData(); assertEquals(1, compiled.length); assertEquals(JBBPCompiler.CODE_INT, compiled[0]); } @Test public void testCompile_SingleNonamedLongField() throws Exception { final byte[] compiled = JBBPCompiler.compile("long;").getCompiledData(); assertEquals(1, compiled.length); assertEquals(JBBPCompiler.CODE_LONG, compiled[0]); } @Test public void testCompile_SingleNamedIntField() throws Exception { final JBBPCompiledBlock block = JBBPCompiler.compile("int HeLLo;"); final byte[] compiled = block.getCompiledData(); assertEquals(1, compiled.length); assertEquals(JBBPCompiler.CODE_INT | JBBPCompiler.FLAG_NAMED, compiled[0]); assertEquals(0, block.findFieldOffsetForPath("hello")); } @Test public void testCompile_SingleNamedIntBigEndianField() throws Exception { final JBBPCompiledBlock block = JBBPCompiler.compile(">int HeLLo;"); final byte[] compiled = block.getCompiledData(); assertEquals(1, compiled.length); assertEquals(JBBPCompiler.CODE_INT | JBBPCompiler.FLAG_NAMED, compiled[0]); assertEquals(0, block.findFieldOffsetForPath("hello")); } @Test public void testCompile_SingleNamedIntLittleEndianField() throws Exception { final JBBPCompiledBlock block = JBBPCompiler.compile(" JBBPCompiler.compile("out { int [4] len; some {byte [len] HeLLo;} }")); } @Test public void testCompile_ErrorForFieldInStructArrayAsVarLength() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("struct [10] {int [4] len;} some {byte [struct.len] HeLLo;} ")); } @Test public void testCompile_ErrorForFieldInStructAsVarLength() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("struct [10] {int len;} some {byte [struct.len] HeLLo;} ")); } @Test public void testCompile_ErrorForUnknownFieldAsArrayLength() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("some {byte [struct.len] HeLLo;} ")); } @Test public void testCompile_ArrayWithUndefinedLength() throws Exception { final JBBPCompiledBlock compiled = JBBPCompiler.compile("byte [_] HeLLo;"); final JBBPNamedFieldInfo field = compiled.findFieldForPath("hello"); assertEquals(2, compiled.getCompiledData().length); assertNotNull(field); assertEquals(0, field.getFieldOffsetInCompiledBlock()); assertEquals(JBBPCompiler.CODE_BYTE | JBBPCompiler.FLAG_NAMED | JBBPCompiler.FLAG_WIDE, compiled.getCompiledData()[0] & 0xFF); assertEquals(JBBPCompiler.EXT_FLAG_EXPRESSION_OR_WHOLESTREAM, compiled.getCompiledData()[1] & 0xFF); } @Test public void testCompile_StructFieldWithNameOfExternalField() throws Exception { final JBBPCompiledBlock compiled = JBBPCompiler.compile("int a; ins{ int a;}"); final byte[] data = compiled.getCompiledData(); assertEquals(5, data.length); } @Test public void testCompile_FixedStructArray() throws Exception { final JBBPCompiledBlock compiled = JBBPCompiler.compile("int value; inner [2] { byte a; byte b;}"); assertArrayEquals(new byte[] { JBBPCompiler.CODE_INT | JBBPCompiler.FLAG_NAMED, JBBPCompiler.CODE_STRUCT_START | JBBPCompiler.FLAG_NAMED | JBBPCompiler.FLAG_ARRAY, 2, JBBPCompiler.CODE_BYTE | JBBPCompiler.FLAG_NAMED, JBBPCompiler.CODE_BYTE | JBBPCompiler.FLAG_NAMED, JBBPCompiler.CODE_STRUCT_END, 1 }, compiled.getCompiledData()); } @Test public void testCompile_correctFieldName() throws Exception { JBBPCompiler.compile("int a;int _a;int a11;int a_f;int a_1;"); assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("int 1a;")); } @Test public void testCompile_ErrorForNonAllowedArguments() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("reset$$:1;")); assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("reset$$ hello;")); assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("reset$$ [445];")); assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("reset$$ [445] hello;")); } @Test public void testCompile_ErrorForResetWithExtraValue() throws Exception { assertThrows(JBBPCompilationException.class, () -> JBBPCompiler.compile("reset$$:1;")); } @Test public void testCompile_Reset() throws Exception { assertArrayEquals(new byte[] {JBBPCompiler.CODE_RESET_COUNTER}, JBBPCompiler.compile("reset$$;").getCompiledData()); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/conversion/000077500000000000000000000000001445000440600321065ustar00rootroot00000000000000JBBPToJBBPToJavaConverterCompilationTest.java000066400000000000000000000510371445000440600424120ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/conversion/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.conversion; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.mapper.JBBPMapper; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.testaux.AbstractJBBPToJavaConverterTest; import java.io.IOException; import org.junit.jupiter.api.Test; class JBBPToJBBPToJavaConverterCompilationTest extends AbstractJBBPToJavaConverterTest { private static String makeSources( final JBBPParser parser, final String classComment, final boolean useSetterGetter, final boolean addBinAnnotations, boolean nonStaticInnerClasses) { final JBBPToJavaConverter.Builder result = JBBPToJavaConverter.makeBuilder(parser) .setMainClassPackage(PACKAGE_NAME) .setMainClassName(CLASS_NAME) .setHeadComment(classComment) .setAddGettersSetters(useSetterGetter); if (nonStaticInnerClasses) { result.doInternalClassesNonStatic(); } if (addBinAnnotations) { result.addBinAnnotations().addNewInstanceMethods(); } return result.build() .convert(); } private void assertCompilation(final String classSrc) throws Exception { System.out.println(classSrc); assertNotNull(saveAndCompile(new JavaClassContent(PACKAGE_NAME + '.' + CLASS_NAME, classSrc))); } @Test void testVarNamesAsJavaTypes() throws Exception { final JBBPParser parser = JBBPParser.prepare( "ubyte;int integer; int number; int try; int byte; int _byte; int _byte_; int char; int short; int long; int double; int float; int [long+double+char] string;"); assertCompilation( makeSources(parser, "some multiline text\nto be added into header", true, true, false)); } @Test void testExpression() throws Exception { final JBBPParser parser = JBBPParser.prepare( "bit:8 bitf; var somevar; bool bbb; long aaa; ubyte kkk; {{int lrn; {int [(lrn/aaa*1*(2*somevar-4)&$joomla)/(100%9>>bitf)&56|~kkk^78&bbb];}}}"); assertCompilation( makeSources(parser, "some multiline text\nto be added into header", false, false, false)); assertCompilation( makeSources(parser, "some multiline text\nto be added into header", true, true, true)); } @Test void testGenerateBinAnnotation() { final JBBPParser parser = JBBPParser .prepare("bit:3 someBit; bit:4 [12] bitArray; some {int a; floatj b; doublej[23] darr;}"); assertFalse( JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME).build().convert() .contains("@Bin")); assertTrue( JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME).addBinAnnotations() .build().convert().contains("@Bin")); } @Test void testGenNewInstanceMethod() { final JBBPParser parser = JBBPParser .prepare("bit:3 someBit; bit:4 [12] bitArray; some {int a; floatj b; doublej[23] darr;}"); assertFalse( JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME).build().convert() .contains("Object " + JBBPMapper.MAKE_CLASS_INSTANCE_METHOD_NAME)); assertTrue( JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME).addNewInstanceMethods() .build().convert().contains("Object " + JBBPMapper.MAKE_CLASS_INSTANCE_METHOD_NAME)); } @Test void testForceAbstract() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte a;"); assertFalse(JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME) .setDoMainClassAbstract(false).build().convert().contains("abstract")); assertTrue(JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME) .setDoMainClassAbstract(true).build().convert().contains("abstract")); } @Test void testMakeInternalClassObjects_StaticClasses() throws Exception { final JBBPParser parser = JBBPParser.prepare("a { b { c [_] { byte d;}} }"); final String text = JBBPToJavaConverter .makeBuilder(parser) .setMainClassName(CLASS_NAME) .setAddGettersSetters(true) .build() .convert(); assertTrue(text.contains("public A makeA(){ this.a = new A(this); return this.a; }")); assertTrue(text.contains("public B makeB(){ this.b = new B(_Root_); return this.b; }")); assertTrue(text.contains( "public C[] makeC(int _Len_){ this.c = new C[_Len_]; for(int i=0;i < _Len_;i++) this.c[i]=new C(_Root_); return this.c; }")); } @Test void testMakeInternalClassObjects_NoMakersWithoutGettersSetters() throws Exception { final JBBPParser parser = JBBPParser.prepare("a { b { c [_] { byte d;}} }"); final String text = JBBPToJavaConverter .makeBuilder(parser) .setMainClassName(CLASS_NAME) .setAddGettersSetters(false) .build() .convert(); assertFalse(text.contains("public A makeA(){ this.a = new A(this); return this.a; }")); assertFalse(text.contains("public B makeB(){ this.b = new B(_Root_); return this.b; }")); assertFalse(text.contains( "public C[] makeC(int _Len_){ this.c = new C[_Len_]; for(int i=0;i < _Len_;i++) this.c[i]=new C(_Root_); return this.c; }")); } @Test void testMakeInternalClassObjects_NonStaticClasses() throws Exception { final JBBPParser parser = JBBPParser.prepare("a { b { c [_] { byte d;}} }"); final String text = JBBPToJavaConverter .makeBuilder(parser) .setMainClassName(CLASS_NAME) .setAddGettersSetters(true) .doInternalClassesNonStatic() .build() .convert(); assertTrue(text.contains("public A makeA(){ this.a = new A(this); return this.a; }")); assertTrue(text.contains("public B makeB(){ this.b = new B(_Root_); return this.b; }")); assertTrue(text.contains( "public C[] makeC(int _Len_){ this.c = new C[_Len_]; for(int i=0;i < _Len_;i++) this.c[i]=new C(_Root_); return this.c; }")); } @Test void testMapSubstructToSuperclassesInterface() throws Exception { final JBBPParser parser = JBBPParser.prepare("a { b { c [_] { byte d;}} }"); final String text = JBBPToJavaConverter.makeBuilder(parser) .setMainClassName(CLASS_NAME) .setAddGettersSetters(true) .setMapSubClassesSuperclasses( makeMap( "a.b", "com.igormaznitsa.Impl", "a.b.c", "com.igormaznitsa.Impl2" ) ) .build() .convert(); assertTrue(text.contains("public static class B extends com.igormaznitsa.Impl")); assertTrue(text.contains("public static class C extends com.igormaznitsa.Impl2")); assertTrue(text.contains("public B getB() { return this.b;}")); assertTrue(text.contains("public C [] getC() { return this.c;}")); System.out.println(text); } @Test void testMapSubstructToInterface() throws Exception { final JBBPParser parser = JBBPParser.prepare("a { b { c [_] { byte d;}} }"); final String text = JBBPToJavaConverter.makeBuilder(parser) .setMainClassName(CLASS_NAME) .setAddGettersSetters(true) .setMapSubClassesInterfaces( makeMap( "a.b", "com.igormaznitsa.Impl", "a.b.c", "com.igormaznitsa.Impl2" ) ) .build() .convert(); assertTrue(text.contains("public static class B implements com.igormaznitsa.Impl")); assertTrue(text.contains("public static class C implements com.igormaznitsa.Impl2")); assertTrue(text.contains("public com.igormaznitsa.Impl getB() { return this.b;}")); assertTrue(text.contains("public com.igormaznitsa.Impl2 [] getC() { return this.c;}")); System.out.println(text); } @Test void testCustomText() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte a;"); assertTrue(JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME) .setMainClassCustomText("public void test(){}").build().convert() .contains("public void test(){}")); } @Test void testSuperclass() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte a;"); assertTrue(JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME) .setSuperClass("com.igormaznitsa.Super").build().convert() .contains("extends com.igormaznitsa.Super ")); } @Test void testInterfaces() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte a;"); assertTrue(JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME) .setMainClassImplements("com.igormaznitsa.InterfaceA", "com.igormaznitsa.InterfaceB") .build().convert() .contains("implements com.igormaznitsa.InterfaceA,com.igormaznitsa.InterfaceB ")); } @Test void testClassPackage() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte a;"); assertFalse( JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME).build().convert() .contains("package ")); assertFalse( JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME).setMainClassPackage("") .build().convert().contains("package ")); assertTrue(JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME) .setMainClassPackage("hello.world").build().convert().contains("package hello.world;")); } @Test void testGettersSetters() throws Exception { final JBBPParser parser = JBBPParser.prepare("byte a;"); String text = JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME).build().convert(); assertTrue(text.contains("public byte a;")); assertFalse(text.contains("public void setA(byte value) {")); assertFalse(text.contains("public byte getA() {")); text = JBBPToJavaConverter.makeBuilder(parser).setAddGettersSetters(true) .setMainClassName(CLASS_NAME).build().convert(); assertFalse(text.contains("public byte a;")); assertTrue(text.contains("protected byte a;")); assertTrue(text.contains("public void setA(byte value) {")); assertTrue(text.contains("public byte getA() {")); } @Test void testZ80snap1() throws Exception { final JBBPParser parser = JBBPParser.prepare( "byte reg_a; byte reg_f; assertCompilation(makeSources(parser, null, false, false, false))); assertThrows(Exception.class, () -> assertCompilation(makeSources(parser, null, true, false, false))); } @Test void testStringFieldInExpression_CompilationErrorForStringFieldInArithmeticException() throws Exception { final JBBPParser parser = JBBPParser.prepare("stringj str; byte a; stringj [str+a] strarr; stringj [_] all;"); assertThrows(Exception.class, () -> assertCompilation(makeSources(parser, null, false, false, false))); assertThrows(Exception.class, () -> assertCompilation(makeSources(parser, null, true, false, false))); } @Test void testPngParsing() throws Exception { final JBBPParser parser = JBBPParser.prepare( "long header;" + "// chunks\n" + "chunk [_]{" + " int length; " + " int type; " + " byte[length] data; " + " int crc;" + "}" ); assertCompilation(makeSources(parser, null, false, false, false)); assertCompilation(makeSources(parser, null, true, false, false)); } @Test void testPrimitiveFieldsInExpression() throws Exception { final JBBPParser parser = JBBPParser.prepare( "long lfield; int ifield; byte bfield; ggg {ubyte ubfield; short shfield;} ushort ushfield; bit:4 bitfield; byte [bfield*ggg.shfield< 0, "Etalon value must not be zero or negative one : " + etalonValue); final Object obj = compileAndMakeInstance(String.format("byte [%s] data;", expression)); callRead(obj, UNLIMITED_STREAM); final int detectedlength = getField(obj, "data", byte[].class).length; if (etalonValue != detectedlength) { System.err.println(JBBPParser.prepare(String.format("byte [%s] data;", expression)) .convertToSrc(TargetSources.JAVA, PACKAGE_NAME + "." + CLASS_NAME).get(0).getResult() .values().iterator().next()); fail(etalonValue + "!=" + detectedlength); } } @Test public void testArithmeticOps() throws Exception { assertExpression(11 * (+8 - 7) % 13 + (-13 - 1) / 2, "11*(+8-7)%13+(-13-1)/2"); assertExpression(11 + 22 * 33 / 44 % 55, "11 + 22 * 33 / 44 % 55"); assertExpression((3 * (5 * 7)) / 11, "(3 * (5 * 7)) / 11"); } @Test public void testBitOps() throws Exception { assertExpression(123 ^ 345 & 767, "123 ^ 345 & 767"); assertExpression((123 | 883) ^ 345 & 767, "(123 | 883) ^ 345 & 767"); assertExpression(123 & 345 | 234 ^ ~123 & 255, "123&345|234^~123&255"); assertExpression(-123 & 345 | 234 ^ ~-123 & 255, "-123&345|234^~-123&255"); assertExpression((-123 & (345 | (234 ^ ~-123))) & 1023, "(-123 & (345 | (234 ^ ~-123))) & 1023"); } @Test public void testShifts() throws Exception { assertExpression(1234 >> 3 << 2 >>> 1, "1234>>3<<2>>>1"); assertExpression((123456 >> (3 << 2)) >>> 1, "(123456>>(3<<2))>>>1"); assertExpression(56 << (3 << 2), "56 << (3 << 2)"); assertExpression(56 << 3 << 2, "56 << 3 << 2"); assertExpression(123456 >> (3 << 2), "123456>>(3<<2)"); } @Test public void testBrackets() throws Exception { assertExpression(3 * (9 / 2), "3*(9/2)"); assertExpression(3 * 9 / 2, "3*9/2"); assertExpression(3 * 9 / 2, "(3*9)/2"); } @Test public void testComplex() throws Exception { assertExpression(3 * 2 + 8 << 4 - 3, "3*2+8<<4-3"); assertExpression(3 * 2 + 8 << 4 - 3 & 7, "3*2+8<<4-3&7"); assertExpression(60 | 7 - ~17 % 1, "60|7-~17%1"); assertExpression((11 * (8 - 7)) % 13 + (1234 >> 3 << 2) >>> 1 + (13 - 1) / 2 + ((11 + 22) * 33 / 44 % 55) - (123 & 345 | 234 ^ ~123) & 255, "(11 * (8 - 7)) % 13 + ( 1234>>3<<2)>>>1 + (13 - 1) / 2 + ((11 + 22) * 33 / 44 % 55) - (123 & 345 | 234 ^ ~123) & 255"); } @Test public void testSynthesidExpression() throws Exception { final Random rnd = new Random(5111975); final String[] operatorsTwo = new String[] {"-", "+", "*", "/", "%", ">>", ">>>", "<<", "^", "|", "&"}; final String[] operatorsOne = new String[] {"-", "+", "~"}; int rightCounter = 0; for (int i = 0; i < 1500; i++) { final StringBuilder buffer = new StringBuilder(); if (rnd.nextInt(100) > 60) { buffer.append(operatorsOne[rnd.nextInt(operatorsOne.length)]); } buffer.append(1 + rnd.nextInt(200)); buffer.append(operatorsTwo[rnd.nextInt(operatorsTwo.length)]); final int totalItems = rnd.nextInt(100) + 1; int brakeCounter = 0; for (int j = 0; j < totalItems; j++) { if (rnd.nextInt(100) > 80) { buffer.append(operatorsOne[rnd.nextInt(operatorsOne.length)]); } buffer.append(1 + rnd.nextInt(100)); buffer.append(operatorsTwo[rnd.nextInt(operatorsTwo.length)]); if (rnd.nextInt(100) > 80) { buffer.append('('); brakeCounter++; } } buffer.append(1 + rnd.nextInt(100)); while (brakeCounter > 0) { buffer.append(')'); brakeCounter--; } String expression = buffer.toString().replace("--", "-").replace("++", "+"); Object theInstance; final StringBuilder src = new StringBuilder(); try { theInstance = compileAndMakeInstanceSrc("byte [" + expression + "] array;", " public static int makeExpressionResult(){ return " + expression + ";}", src); } catch (Exception ex) { fail("Can't compile : " + expression); return; } try { final int etalon = (Integer) theInstance.getClass().getMethod("makeExpressionResult").invoke(null); if (etalon > 0 && etalon < 100000) { System.out.println("Testing expression : " + expression); assertEquals(etalon, getField(callRead(theInstance, new JBBPBitInputStream(UNLIMITED_STREAM)), "array", byte[].class).length, src.toString()); rightCounter++; } } catch (InvocationTargetException ex) { if (!(ex.getCause() instanceof ArithmeticException)) { ex.printStackTrace(); fail("Unexpected exception : " + ex.getCause()); return; } } } System.out.println("Totally generated right expressions : " + rightCounter); } } JBBPToJavaConverterReadWriteTest.java000066400000000000000000001465531445000440600410710ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/conversion/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.conversion; import static com.igormaznitsa.jbbp.TestUtils.assertPngChunk; import static com.igormaznitsa.jbbp.TestUtils.getField; import static com.igormaznitsa.jbbp.TestUtils.getFieldThroughGetters; import static com.igormaznitsa.jbbp.TestUtils.wavInt2Str; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.TestUtils; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayInt; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.testaux.AbstractJBBPToJavaConverterTest; import com.igormaznitsa.jbbp.utils.ReflectUtils; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; /** * Test reading writing with converted classes from parser. */ public class JBBPToJavaConverterReadWriteTest extends AbstractJBBPToJavaConverterTest { private byte[] loadResource(final String name) throws Exception { try (final InputStream result = this.getClass().getClassLoader() .getResourceAsStream("com/igormaznitsa/jbbp/it/" + name)) { return IOUtils.toByteArray(result); } } @Test public void testReadWrite_ExtendsSuperClassAndUseItsFields() throws Exception { final JBBPParser parser = JBBPParser.prepare( "stringj str;" + "stringj [2] strarr;" + "uint uintf;" + "uint [2] uintarr;" + "floatj flt;" + "floatj [2] fltarr;" + "doublej dbl;" + "doublej [2] dblarr;" + "ubyte len;" + "ins [_] {" + " byte [len] a;" + " byte b;" + " byte c;" + " insins {" + " byte a;" + " }" + "}" ); this.printGeneratedClassText = true; final Map superclasses = new HashMap<>(); superclasses.put("ins", TestSuperclass.Ins.class.getCanonicalName()); superclasses.put("ins.insins", TestSuperclass.Ins.InsIns.class.getCanonicalName()); final String thePackage = JBBPToJavaConverterReadWriteTest.class.getPackage().getName(); final String text = JBBPToJavaConverter.makeBuilder(parser) .setMainClassName(CLASS_NAME) .setMainClassPackage(thePackage) .disableGenerateFields() .setSuperClass(TestSuperclass.class.getCanonicalName()) .setMapSubClassesSuperclasses(superclasses) .setAddGettersSetters(false) .doInternalClassesNonStatic() .build() .convert(); final String fullClassName = thePackage + '.' + CLASS_NAME; final ClassLoader classLoader = saveAndCompile(new JavaClassContent(fullClassName, text)); final Object instance = ReflectUtils.newInstance(classLoader.loadClass(fullClassName)); assertTrue(instance instanceof TestSuperclass); final byte[] etalon = new byte[] { 0, 2, 49, 50, 1, 51, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0x1A, (byte) 0x1B, (byte) 0x1C, (byte) 0x1D, (byte) 0x2A, (byte) 0x2B, (byte) 0x2C, (byte) 0x2D, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 2, 1, 2, 3, 4, 5 }; callRead(instance, etalon); final TestSuperclass parsed = (TestSuperclass) instance; assertNull(null, parsed.str); assertArrayEquals(new String[] {"12", "3"}, parsed.strarr); assertEquals(0xFAFBFCFDL, parsed.uintf); assertArrayEquals(new long[] {0x1A1B1C1DL, 0x2A2B2C2DL}, parsed.uintarr); assertEquals(2.3879393E-38f, parsed.flt); assertArrayEquals(new float[] {6.301941E-36f, 1.661634E-33f}, parsed.fltarr); assertEquals(8.20788039913184E-304d, parsed.dbl); assertArrayEquals(new double[] {4.0383818836028145E-265d, 1.9074368412237584E-226d}, parsed.dblarr); assertEquals(1, parsed.ins.length); assertArrayEquals(new byte[] {1, 2}, parsed.ins[0].a); assertEquals(3, parsed.ins[0].b); assertEquals(4, parsed.ins[0].c); assertEquals(5, parsed.ins[0].insins.a); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReaWrite_StructMappedToInterface_Array_GettersSettersOn() throws Exception { final JBBPParser parser = JBBPParser.prepare("z { x { y [_] { byte a;}}}"); final Map interfaceMap = new HashMap<>(); interfaceMap.put("z.x.y", ByteTestInterface.class.getCanonicalName()); final String text = JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME) .setMainClassPackage(PACKAGE_NAME).setAddGettersSetters(true) .setMapSubClassesInterfaces(interfaceMap).build().convert(); final String fullClassName = PACKAGE_NAME + '.' + CLASS_NAME; final ClassLoader classLoader = saveAndCompile(new JavaClassContent(fullClassName, text)); final Object instance = ReflectUtils.newInstance(classLoader.loadClass(fullClassName)); callRead(instance, new byte[] {0, 1, 2, 3, 4, 5}); final ByteTestInterface[] data = getFieldThroughGetters(instance, "z.x.y", ByteTestInterface[].class); for (int i = 0; i < 6; i++) { assertEquals(i, data[i].getA()); } assertArrayEquals(new byte[] {0, 1, 2, 3, 4, 5}, callWrite(instance)); } @Test public void testReaWrite_StructMappedToInterface_NotArray_GettersSettersOn() throws Exception { final JBBPParser parser = JBBPParser.prepare("z { x { y { byte a;}}}"); final Map interfaceMap = new HashMap<>(); interfaceMap.put("z.x.y", ByteTestInterface.class.getCanonicalName()); final String text = JBBPToJavaConverter.makeBuilder(parser).setMainClassName(CLASS_NAME) .setMainClassPackage(PACKAGE_NAME).setAddGettersSetters(true) .setMapSubClassesInterfaces(interfaceMap).build().convert(); final String fullClassName = PACKAGE_NAME + '.' + CLASS_NAME; final ClassLoader classLoader = saveAndCompile(new JavaClassContent(fullClassName, text)); final Object instance = ReflectUtils.newInstance(classLoader.loadClass(fullClassName)); callRead(instance, new byte[] {42}); final ByteTestInterface data = getFieldThroughGetters(instance, "z.x.y", ByteTestInterface.class); assertEquals(42, data.getA()); assertArrayEquals(new byte[] {42}, callWrite(instance)); } @Test public void testReadWrite_BooleanArrayWholeStream() throws Exception { final Object instance = compileAndMakeInstance("bool [_] boolArray;"); assertNull(getField(instance, "boolarray", boolean[].class), "by default must be null"); final byte[] etalon = new byte[] {1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1}; callRead(instance, etalon.clone()); assertArrayEquals( new boolean[] {true, false, true, true, false, true, true, true, false, false, false, true, true, true, true}, getField(instance, "boolarray", boolean[].class)); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_ByteArrayWholeStream() throws Exception { final Object instance = compileAndMakeInstance("byte [_] byteArray;"); assertNull(getField(instance, "bytearray", byte[].class), "by default must be null"); final byte[] etalon = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 22, 33, 44, 55, 66}; callRead(instance, etalon.clone()); assertArrayEquals(etalon, getField(instance, "bytearray", byte[].class)); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_Float_SingleValue() throws Exception { final Object instance = compileAndMakeInstance("floatj value;"); final byte[] etalon = new byte[] {1, 2, 3, 4}; callRead(instance, etalon.clone()); assertEquals(2.3879393E-38f, getField(instance, "value", Float.class).floatValue(), TestUtils.FLOAT_DELTA); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_FloatArrayWholeStream() throws Exception { final Object instance = compileAndMakeInstance("floatj [_] floatArray;"); assertNull(getField(instance, "floatarray", float[].class), "by default must be null"); final byte[] etalon = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 22, 33, 44, 55, 66, 77}; callRead(instance, etalon.clone()); assertArrayEquals(new float[] {2.3879393E-38f, 6.301941E-36f, 1.5417845E-33f, 2.6042668E-12f}, getField(instance, "floatarray", float[].class), TestUtils.FLOAT_DELTA); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_String_SingleValue() throws Exception { final Object instance = compileAndMakeInstance("stringj value;"); final byte[] etalon = new byte[] {3, 65, 66, 67}; callRead(instance, etalon.clone()); assertEquals("ABC", getField(instance, "value", String.class)); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_Val_CalculatedLength() throws Exception { final Object instance = compileAndMakeInstance("ubyte a; ubyte b; val:(a-b) c; val:(c+8) d; byte [d] data;"); final byte[] etalon = new byte[] {2, 8, 33, 44}; callRead(instance, etalon.clone()); assertArrayEquals(new byte[] {33, 44}, getField(instance, "data", byte[].class)); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_Bit_SingleValueWhichLengthCalclatedThrouhExpression() throws Exception { final Object instance = compileAndMakeInstance("ubyte a; ubyte b; bit:(a+b) c;"); final byte[] etalon = new byte[] {1, 2, (byte) 0xB4}; callRead(instance, etalon.clone()); assertEquals(4, getField(instance, "c", Byte.class).intValue()); assertArrayEquals(new byte[] {1, 2, 4}, callWrite(instance)); } @Test public void testReadWite_StringArrayWholeStream() throws Exception { final Object instance = compileAndMakeInstance("stringj [_] strArray;"); assertNull(getField(instance, "strarray", String[].class), "by default must be null"); final byte[] etalon = new byte[] {3, 65, 66, 67, (byte) 0xFF, 3, 49, 50, 51}; callRead(instance, etalon.clone()); assertArrayEquals(new String[] {"ABC", null, "123"}, getField(instance, "strarray", String[].class)); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_Double_SingleValue() throws Exception { final Object instance = compileAndMakeInstance("doublej value;"); final byte[] etalon = new byte[] {1, 2, 3, 4, 5, 6, 7, 8}; callRead(instance, etalon.clone()); assertEquals(8.20788039913184E-304d, getField(instance, "value", Double.class).doubleValue(), TestUtils.FLOAT_DELTA); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_UInt_SingleValue() throws Exception { final Object instance = compileAndMakeInstance("uint value;"); final byte[] etalon = new byte[] {1, 2, 3, 4}; callRead(instance, etalon.clone()); assertEquals(0x01020304L, getField(instance, "value", Long.class).longValue()); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_DoubleArrayWholeStream() throws Exception { final Object instance = compileAndMakeInstance("doublej [_] doubleArray;"); assertNull(getField(instance, "doublearray", double[].class), "by default must be null"); final byte[] etalon = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 22, 33, 44, 55, 66, 77, 3, 5, 9, 11, 33, 12, 10, 45}; callRead(instance, etalon.clone()); assertArrayEquals( new double[] {8.20788039913184E-304d, 2.494444648262547E-265d, 4.117024896955411E-294d}, getField(instance, "doublearray", double[].class), TestUtils.FLOAT_DELTA); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_UIntArrayWholeStream() throws Exception { final Object instance = compileAndMakeInstance("uint [_] uintArray;"); assertNull(getField(instance, "uintarray", long[].class), "by default must be null"); final byte[] etalon = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 22, 33}; callRead(instance, etalon.clone()); assertArrayEquals( new long[] {0x01020304L, 0x05060708L, 0x09001621L}, getField(instance, "uintarray", long[].class)); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_BitArrayWholeStream() throws Exception { final Object instance = compileAndMakeInstance("bit [_] bitArray;"); assertNull(getField(instance, "bitarray", byte[].class), "by default must be null"); final byte[] etalon = new byte[1024]; testRandomGen.nextBytes(etalon); callRead(instance, etalon.clone()); assertEquals(etalon.length * 8, getField(instance, "bitarray", byte[].class).length); assertArrayEquals(etalon, callWrite(instance)); } @Test public void testReadWite_FloatFieldAsCounter() throws Exception { final Object instance = compileAndMakeInstance("floatj len; byte [len] data;"); assertNull(getField(instance, "data", byte[].class), "by default must be null"); final byte[] data = JBBPOut.BeginBin().Float(3.3f).Byte(1, 2, 3).End().toByteArray(); callRead(instance, data.clone()); assertEquals(3.3f, getField(instance, "len", Float.class), TestUtils.FLOAT_DELTA); assertArrayEquals(new byte[] {1, 2, 3}, getField(instance, "data", byte[].class)); assertArrayEquals(data, callWrite(instance)); } @Test public void testReadWite_FloatFieldAsCounter_Expression() throws Exception { final Object instance = compileAndMakeInstance("floatj len; byte [len/2] data;"); assertNull(getField(instance, "data", byte[].class), "by default must be null"); final byte[] data = JBBPOut.BeginBin().Float(4.3f).Byte(1, 2).End().toByteArray(); callRead(instance, data.clone()); assertEquals(4.3f, getField(instance, "len", Float.class).floatValue(), TestUtils.FLOAT_DELTA); assertArrayEquals(new byte[] {1, 2}, getField(instance, "data", byte[].class)); assertArrayEquals(data, callWrite(instance)); } @Test public void testReadWite_DoubleFloatFieldAsCounter() throws Exception { final Object instance = compileAndMakeInstance("doublej len; byte [len] data;"); assertNull(getField(instance, "data", byte[].class), "by default must be null"); final byte[] data = JBBPOut.BeginBin().Double(3.3d).Byte(1, 2, 3).End().toByteArray(); callRead(instance, data.clone()); assertEquals(3.3d, getField(instance, "len", Double.class).doubleValue(), TestUtils.FLOAT_DELTA); assertArrayEquals(new byte[] {1, 2, 3}, getField(instance, "data", byte[].class)); assertArrayEquals(data, callWrite(instance)); } @Test public void testReadWite_DoubleFloatFieldAsCounter_Expression() throws Exception { final Object instance = compileAndMakeInstance("doublej len; byte [len/2] data;"); assertNull(getField(instance, "data", byte[].class), "by default must be null"); final byte[] data = JBBPOut.BeginBin().Double(4.3d).Byte(1, 2).End().toByteArray(); callRead(instance, data.clone()); assertEquals(4.3d, getField(instance, "len", Double.class).doubleValue(), TestUtils.FLOAT_DELTA); assertArrayEquals(new byte[] {1, 2}, getField(instance, "data", byte[].class)); assertArrayEquals(data, callWrite(instance)); } @Test public void testReadWriteWithOptionallyIgnoredStructure() throws Exception { final Object instance = compileAndMakeInstance(PACKAGE_NAME + '.' + CLASS_NAME, "byte a; optional { byte b; }", JBBPParser.FLAG_SKIP_REMAINING_FIELDS_IF_EOF, null); callRead(instance, new byte[] {1}); assertEquals(1, getField(instance, "a", Byte.class).byteValue()); assertNull(getField(instance, "optional", Object.class)); assertArrayEquals(new byte[] {1}, callWrite(instance)); } @Test public void testReadWriteWithOptionallyIgnoredStructureArray() throws Exception { final Object instance = compileAndMakeInstance(PACKAGE_NAME + '.' + CLASS_NAME, "byte a; optional [_] { byte b; }", JBBPParser.FLAG_SKIP_REMAINING_FIELDS_IF_EOF, null); callRead(instance, new byte[] {1}); assertEquals(1, getField(instance, "a", Byte.class).byteValue()); assertNull(getField(instance, "optional", Object[].class)); assertArrayEquals(new byte[] {1}, callWrite(instance)); } @Test public void testReadWite_PNG() throws Exception { final Object instance = compileAndMakeInstance("long header;" + "// chunks\n" + "chunk [_]{" + " int length; " + " int type; " + " byte[length] data; " + " int crc;" + "}"); final byte[] pngEtalon = loadResource("picture.png"); final String[] chunkNames = new String[] {"IHDR", "gAMA", "bKGD", "pHYs", "tIME", "tEXt", "IDAT", "IEND"}; final int[] chunkSizes = new int[] {0x0D, 0x04, 0x06, 0x09, 0x07, 0x19, 0x0E5F, 0x00}; callRead(instance, pngEtalon.clone()); assertEquals(0x89504E470D0A1A0AL, getField(instance, "header", Long.class).longValue()); assertEquals(chunkNames.length, getField(instance, "chunk", Object[].class).length); int i = 0; for (final Object chunk : getField(instance, "chunk", Object[].class)) { assertPngChunk(chunkNames[i], chunkSizes[i], getField(chunk, "type", Integer.class), getField(chunk, "length", Integer.class), getField(chunk, "crc", Integer.class), getField(chunk, "data", byte[].class)); i++; } assertArrayEquals(pngEtalon, callWrite(instance)); } @Test public void testReadWite_WAV() throws Exception { final Object instance = compileAndMakeInstance(">> 3] ColorMapItem; " + " }" + "byte [_] ImageData;"); final byte[] tgaEtalon = loadResource("cbw8.tga"); callRead(instance, tgaEtalon.clone()); assertEquals("Truevision(R) Sample Image".length(), getField(instance, "imageid", byte[].class).length); assertEquals(128, getField(instance, "header.width", Character.class).charValue()); assertEquals(128, getField(instance, "header.height", Character.class).charValue()); assertEquals(8, getField(instance, "header.pixeldepth", Character.class).charValue()); assertEquals(0, getField(instance, "colormap", Object[].class).length); assertEquals(8715, getField(instance, "imagedata", byte[].class).length); assertArrayEquals(tgaEtalon, callWrite(instance)); } @Test public void testReadWrite_TGA_hasColormap() throws Exception { final Object instance = compileAndMakeInstance("Header {" + " ubyte IDLength;" + " ubyte ColorMapType;" + " ubyte ImageType;" + " >> 3] ColorMapItem; " + " }" + "byte [_] ImageData;"); final byte[] tgaEtalon = loadResource("indexedcolor.tga"); callRead(instance, tgaEtalon.clone()); assertEquals("".length(), getField(instance, "imageid", byte[].class).length); assertEquals(640, getField(instance, "header.width", Character.class).charValue()); assertEquals(480, getField(instance, "header.height", Character.class).charValue()); assertEquals(8, getField(instance, "header.pixeldepth", Character.class).charValue()); assertEquals(256, getField(instance, "colormap", Object[].class).length); assertEquals(155403, getField(instance, "imagedata", byte[].class).length); assertArrayEquals(tgaEtalon, callWrite(instance)); } @Test public void testReadWrite_Z80v1() throws Exception { final Object instance = compileAndMakeInstance( "byte reg_a; byte reg_f; > 8) & 0xFF; int a = (value >> 16) & 0xFF;" + " if (byteOrder == JBBPByteOrder.BIG_ENDIAN) {" + " out.write(a); out.write(b); out.write(c);" + " } else {" + " out.write(c); out.write(b); out.write(a);" + " }" + " }" + " public JBBPAbstractField readCustomFieldType(Object sourceStruct, JBBPBitInputStream inStream, JBBPFieldTypeParameterContainer typeParameterContainer, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException{" + " if (sourceStruct == null) throw new Error(\"Struct must not be null\");" + " if (readWholeStream || arraySize>=0) {" + " if (readWholeStream) {" + " if (extraValue!=3) throw new Error(\"must be 3\");" + " com.igormaznitsa.jbbp.utils.DynamicIntBuffer buffer = new com.igormaznitsa.jbbp.utils.DynamicIntBuffer();" + " while(inStream.hasAvailableData()){ buffer.write(readThree(inStream, typeParameterContainer.getByteOrder())); }" + " return new JBBPFieldArrayInt(nullableNamedFieldInfo, buffer.toIntArray());" + " } else {" + " if (extraValue!=2) throw new Error(\"must be 2\");" + " int [] arra = new int[arraySize];" + " for (int i=0;i=0 || wholeArray) {" + " if (wholeArray && extraValue!=3) throw new Error(\"wrong extra\");" + " if (arraySize>=0 && extraValue!=2) throw new Error(\"wrong extra\");" + " int [] arra = ((JBBPFieldArrayInt) fieldValue).getArray();" + " int len = wholeArray ? arra.length : arraySize;" + " for(int i=0;i> 8) & 0xFF; int a = (value >> 16) & 0xFF;" + " if (byteOrder == JBBPByteOrder.BIG_ENDIAN) {" + " out.write(a); out.write(b); out.write(c);" + " } else {" + " out.write(c); out.write(b); out.write(a);" + " }" + " }" + "public JBBPAbstractField readVarField(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue) throws IOException{" + " if (extraValue!=12) throw new Error(\"wrong extra\");" + " return new JBBPFieldInt(nullableNamedFieldInfo, readThree(inStream, byteOrder));" + "}" + "public JBBPAbstractArrayField readVarArray(Object sourceStruct, JBBPBitInputStream inStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, boolean readWholeStream, int arraySize) throws IOException {" + " if (sourceStruct == null) throw new Error(\"Struct must not be null\");" + " if (extraValue!=4) throw new Error(\"wrong extra\");" + " if (readWholeStream) {" + " com.igormaznitsa.jbbp.utils.DynamicIntBuffer buffer = new com.igormaznitsa.jbbp.utils.DynamicIntBuffer();" + " while(inStream.hasAvailableData()){ buffer.write(readThree(inStream, byteOrder)); }" + " return new JBBPFieldArrayInt(nullableNamedFieldInfo, buffer.toIntArray());" + " } else {" + " int [] arra = new int[arraySize];" + " for (int i=0;i array, JBBPBitOutputStream outStream, JBBPByteOrder byteOrder, JBBPNamedFieldInfo nullableNamedFieldInfo, int extraValue, int arraySizeToWrite) throws IOException{" + " if (sourceStruct == null) throw new Error(\"Struct must not be null\");" + " if (extraValue!=4) throw new Error(\"wrong extra\");" + " int [] arra = ((JBBPFieldArrayInt) array).getArray();" + " int len = arraySizeToWrite < 0 ? arra.length : arraySizeToWrite;" + " for(int i=0;i callRead(instance, etalon.clone())); } @Test public void testRead_ExpressionResult_OnlyField_NegativeResultAsZero() throws Exception { final Object instance = compileAndMakeInstance("byte len; byte [len] arr;", JBBPParser.FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO); final byte[] etalon = new byte[] {(byte) 0xFE, 1, 2, 3, 4}; callRead(instance, etalon.clone()); assertEquals((byte) 0xFE, getField(instance, "len", Byte.class).byteValue()); assertEquals(0, getField(instance, "arr", byte[].class).length); } @Test public void testRead_ExpressionResult_NegativeExpression_NegativeResultNotAllowed() throws Exception { final Object instance = compileAndMakeInstance("byte len; byte [len-9] arr;"); final byte[] etalon = new byte[] {8, 1, 2, 3, 4}; assertThrows(IllegalArgumentException.class, () -> callRead(instance, etalon.clone())); } @Test public void testRead_ExpressionResult_NegativeExpression_NegativeResultAsZero() throws Exception { final Object instance = compileAndMakeInstance("byte len; byte [len-9] arr;", JBBPParser.FLAG_NEGATIVE_EXPRESSION_RESULT_AS_ZERO); final byte[] etalon = new byte[] {8, 1, 2, 3, 4}; callRead(instance, etalon.clone()); assertEquals(8, getField(instance, "len", Byte.class).byteValue()); assertEquals(0, getField(instance, "arr", byte[].class).length); } @Test public void testReadWrite_NetPacket() throws Exception { final Object ethernetHeader = compileAndMakeInstance("byte[6] MacDestination;" + "byte[6] MacSource;" + "ushort EtherTypeOrLength;"); final Object ipHeader = compileAndMakeInstance("bit:4 InternetHeaderLength;" + "bit:4 Version;" + "bit:2 ECN;" + "bit:6 DSCP;" + "ushort TotalPacketLength;" + "ushort Identification;" + "ushort IPFlagsAndFragmentOffset;" + "ubyte TTL;" + "ubyte Protocol;" + "ushort HeaderChecksum;" + "int SourceAddress;" + "int DestinationAddress;" + "byte [(InternetHeaderLength-5)*4] Options;"); final Object tcpHeader = compileAndMakeInstance("ushort SourcePort;" + "ushort DestinationPort;" + "int SequenceNumber;" + "int AcknowledgementNumber;" + "bit:1 NONCE;" + "bit:3 RESERVED;" + "bit:4 HLEN;" + "bit:1 FIN;" + "bit:1 SYN;" + "bit:1 RST;" + "bit:1 PSH;" + "bit:1 ACK;" + "bit:1 URG;" + "bit:1 ECNECHO;" + "bit:1 CWR;" + "ushort WindowSize;" + "ushort TCPCheckSum;" + "ushort UrgentPointer;" + "byte [$$-HLEN*4] Option;"); final byte[] netPacketEtalon = loadResource("tcppacket.bin"); final JBBPBitInputStream inStream = new JBBPBitInputStream(new ByteArrayInputStream(netPacketEtalon)); callRead(ethernetHeader, inStream); assertArrayEquals( new byte[] {(byte) 0x60, (byte) 0x67, (byte) 0x20, (byte) 0xE1, (byte) 0xF9, (byte) 0xF8}, getField(ethernetHeader, "macdestination", byte[].class), "Destination MAC"); assertArrayEquals( new byte[] {(byte) 0x00, (byte) 0x26, (byte) 0x44, (byte) 0x74, (byte) 0xFE, (byte) 0x66}, getField(ethernetHeader, "macsource", byte[].class), "Source MAC"); final int etherTypeOrLength = getField(ethernetHeader, "ethertypeorlength", Character.class); assertEquals(0x800, etherTypeOrLength, "Ethernet type or length"); inStream.resetCounter(); callRead(ipHeader, inStream); assertEquals(4, getField(ipHeader, "version", Byte.class).intValue(), "IP Version"); final int internetHeaderLength = getField(ipHeader, "internetheaderlength", Byte.class).intValue(); assertEquals(5, internetHeaderLength, "Length of the IP header (in 4 byte items)"); assertEquals(0, getField(ipHeader, "dscp", Byte.class).intValue(), "Differentiated Services Code Point"); assertEquals(0, getField(ipHeader, "ecn", Byte.class).intValue(), "Explicit Congestion Notification"); final int ipTotalPacketLength = getField(ipHeader, "totalpacketlength", Character.class); assertEquals(159, ipTotalPacketLength, "Entire IP packet size, including header and data, in bytes"); assertEquals(30810, getField(ipHeader, "identification", Character.class).charValue(), "Identification"); final int ipFlagsAndFragmentOffset = getField(ipHeader, "ipflagsandfragmentoffset", Character.class); assertEquals(0x2, ipFlagsAndFragmentOffset >>> 13, "Extracted IP flags"); assertEquals(0x00, ipFlagsAndFragmentOffset & 0x1FFF, "Extracted Fragment offset"); assertEquals(0x39, getField(ipHeader, "ttl", Character.class).charValue(), "Time To Live"); assertEquals(0x06, getField(ipHeader, "protocol", Character.class).charValue(), "Protocol (RFC-790)"); assertEquals(0x7DB6, getField(ipHeader, "headerchecksum", Character.class).charValue(), "IPv4 Header Checksum"); assertEquals(0xD5C7B393, getField(ipHeader, "sourceaddress", Integer.class).intValue(), "Source IP address"); assertEquals(0xC0A80145, getField(ipHeader, "destinationaddress", Integer.class).intValue(), "Destination IP address"); assertEquals(0, getField(ipHeader, "options", byte[].class).length); inStream.resetCounter(); callRead(tcpHeader, inStream); assertEquals(40018, getField(tcpHeader, "sourceport", Character.class).charValue()); assertEquals(56344, getField(tcpHeader, "destinationport", Character.class).charValue()); assertEquals(0xE0084171, getField(tcpHeader, "sequencenumber", Integer.class).intValue()); assertEquals(0xAB616F71, getField(tcpHeader, "acknowledgementnumber", Integer.class).intValue()); assertEquals(0, getField(tcpHeader, "fin", Byte.class).intValue()); assertEquals(0, getField(tcpHeader, "syn", Byte.class).intValue()); assertEquals(0, getField(tcpHeader, "rst", Byte.class).intValue()); assertEquals(1, getField(tcpHeader, "psh", Byte.class).intValue()); assertEquals(1, getField(tcpHeader, "ack", Byte.class).intValue()); assertEquals(0, getField(tcpHeader, "urg", Byte.class).intValue()); assertEquals(0, getField(tcpHeader, "ecnecho", Byte.class).intValue()); assertEquals(0, getField(tcpHeader, "cwr", Byte.class).intValue()); assertEquals(0, getField(tcpHeader, "nonce", Byte.class).intValue()); assertEquals(0, getField(tcpHeader, "reserved", Byte.class).intValue()); assertEquals(5, getField(tcpHeader, "hlen", Byte.class).intValue()); assertEquals(40880, getField(tcpHeader, "windowsize", Character.class).charValue()); assertEquals(0x8BB6, getField(tcpHeader, "tcpchecksum", Character.class).charValue()); assertEquals(0, getField(tcpHeader, "urgentpointer", Character.class).charValue()); assertEquals(0, getField(tcpHeader, "option", byte[].class).length); final int payloadDataLength = ipTotalPacketLength - (internetHeaderLength * 4) - (int) inStream.getCounter(); final byte[] data = inStream.readByteArray(payloadDataLength); assertEquals(119, data.length); assertFalse(inStream.hasAvailableData()); final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final JBBPBitOutputStream outStream = new JBBPBitOutputStream(bos); callWrite(ethernetHeader, outStream); outStream.resetCounter(); callWrite(ipHeader, outStream); outStream.resetCounter(); callWrite(tcpHeader, outStream); outStream.write(data); outStream.close(); assertArrayEquals(netPacketEtalon, bos.toByteArray()); } public interface ByteTestInterface { byte getA(); } public static class TestSuperclass { public long uintf; public long[] uintarr; public String str; public String[] strarr; public float flt; public float[] fltarr; public double dbl; public double[] dblarr; public char len; public Ins[] ins; public class Ins { public byte[] a; public byte b; public byte c; public InsIns insins; public class InsIns { public byte a; } } } } ParserToJBBPToJavaClassConverterJBBPFlagsTest.java000066400000000000000000000034721445000440600433330ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/conversion/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.conversion; import static com.igormaznitsa.jbbp.TestUtils.getField; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.testaux.AbstractJBBPToJavaConverterTest; import java.io.EOFException; import org.junit.jupiter.api.Test; /** * Test parser flags for converted sources. */ public class ParserToJBBPToJavaClassConverterJBBPFlagsTest extends AbstractJBBPToJavaConverterTest { @Test public void testFlag_SkipRemainingFieldsIfEOF() throws Exception { Object instance = compileAndMakeInstance("byte a; byte b;", 0); callRead(instance, new byte[] {1, 2}); try { callRead(instance, new byte[] {1}); fail("Must throw EOF"); } catch (EOFException ex) { } instance = compileAndMakeInstance("byte a; byte b;", JBBPParser.FLAG_SKIP_REMAINING_FIELDS_IF_EOF); try { callRead(instance, new byte[] {11}); assertEquals(11, getField(instance, "a", Byte.class).intValue()); assertEquals(0, getField(instance, "b", Byte.class).intValue()); } catch (EOFException ex) { fail("Must not throw EOF"); } } } RandomAutoTest.java000066400000000000000000000303001445000440600355770ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/conversionpackage com.igormaznitsa.jbbp.compiler.conversion; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.testaux.AbstractJBBPToJavaConverterTest; import com.igormaznitsa.jbbp.utils.JBBPDslBuilder; import java.util.ArrayList; import java.util.List; import java.util.Random; import org.junit.jupiter.api.Test; public class RandomAutoTest extends AbstractJBBPToJavaConverterTest { private final Random RND = new Random(12345); int makeArrayLengthNumber() { return RND.nextInt(16) + 1; } JBBPBitNumber makeRndBitNumber() { return JBBPBitNumber.decode(1 + RND.nextInt(8)); } String generateComment() { final int length = 3 + RND.nextInt(24); final StringBuilder result = new StringBuilder(); for (int i = 0; i < length; i++) { result.append((char) ('a' + RND.nextInt(26))); } return result.toString(); } String makeRndName() { final StringBuilder result = new StringBuilder(); for (int i = 0; i < 3; i++) { result.append((char) ('a' + RND.nextInt(26))); } for (int i = 0; i < 3; i++) { result.append((char) ('0' + RND.nextInt(9))); } for (int i = 0; i < 3; i++) { result.append((char) ('a' + RND.nextInt(26))); } return result.toString(); } String genRandomString(final int length) { final StringBuilder builder = new StringBuilder(length); for (int i = 0; i < length; i++) { builder.append((char) (' ' + this.RND.nextInt(100))); } return builder.toString(); } @Test public void testCompileParseAndWriteArray() throws Exception { int testIndex = 1; long generatedFields = 0L; for (int i = 5; i < 500; i += 3) { Result result; do { result = generate(i, true); } while (result.bitLength > 10000000); generatedFields |= result.typeFlags; System.out.println(String .format("Test %d, data bit length = %d, fields = %d, sructs = %d", testIndex, result.bitLength, result.fieldsNumber, result.structNumber)); final byte[] testData = makeRandomDataArray(result.bitLength); final Object clazzInstance = compileAndMakeInstance(result.script); callRead(clazzInstance, testData); assertEquals(testData.length, callWrite(clazzInstance).length, result.script); testIndex++; } assertEquals(0x1FFFFFFFL, generatedFields, "All field types must be presented"); } private byte[] makeRandomDataArray(final int bitLength) { final int bytelen = (bitLength / 8) + ((bitLength & 7) != 0 ? 1 : 0); assertTrue(bytelen > 0, "Bit length : " + bitLength); final byte[] result = new byte[bytelen]; RND.nextBytes(result); return result; } Result generate(final int items, final boolean generateNames) { final JBBPDslBuilder builder = JBBPDslBuilder.Begin(); final List counterStack = new ArrayList<>(); counterStack.add(new StructLen()); int structsTotal = 0; int fieldsTotal = 0; int booleanDataItems = 0; int activeStructCounter = 0; long typeFlags = 0; for (int i = 0; i < items; i++) { if (RND.nextInt(50) > 48) { builder.ByteOrder(JBBPByteOrder.LITTLE_ENDIAN); } else { builder.ByteOrder(JBBPByteOrder.BIG_ENDIAN); } if (activeStructCounter > 0 && RND.nextInt(100) > 90) { i--; activeStructCounter--; builder.CloseStruct(); final StructLen len = counterStack.remove(0); counterStack.get(0).add(len.make()); } else { final int rndType = RND.nextInt(29); typeFlags |= (1 << rndType); switch (rndType) { case 0: { // STRUCT builder.Struct(generateNames ? makeRndName() : null); counterStack.add(0, new StructLen()); activeStructCounter++; structsTotal++; } break; case 1: { // STRUCT_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.StructArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.add(0, new StructLen(arrayLen)); activeStructCounter++; structsTotal++; } break; case 2: { // BIT final JBBPBitNumber bits = makeRndBitNumber(); builder.Bits(generateNames ? makeRndName() : null, bits); counterStack.get(0).add(bits.getBitNumber()); fieldsTotal++; } break; case 3: { // BIT_ARRAY final JBBPBitNumber bits = makeRndBitNumber(); final int arrayLen = makeArrayLengthNumber(); builder.BitArray(generateNames ? makeRndName() : null, bits, String.valueOf(arrayLen)); counterStack.get(0).add(bits.getBitNumber() * arrayLen); fieldsTotal++; } break; case 4: { // SKIP final int arrayLen = makeArrayLengthNumber(); final int bitlen; if (RND.nextBoolean()) { builder.Skip(); bitlen = 8; } else { final int len = makeArrayLengthNumber(); builder.Skip(String.valueOf(len)); bitlen = len * 8; } counterStack.get(0).add(bitlen); } break; case 5: { // BOOL builder.Bool(generateNames ? makeRndName() : null); counterStack.get(0).add(8); fieldsTotal++; booleanDataItems++; } break; case 6: { // BOOL_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.BoolArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(8 * arrayLen); fieldsTotal++; booleanDataItems += arrayLen; } break; case 7: { // BYTE builder.Byte(generateNames ? makeRndName() : null); counterStack.get(0).add(8); fieldsTotal++; } break; case 8: { // BYTE_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.ByteArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(arrayLen * 8); fieldsTotal++; } break; case 9: { // UBYTE builder.UByte(generateNames ? makeRndName() : null); counterStack.get(0).add(8); fieldsTotal++; } break; case 10: { // UBYTE_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.UByteArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(8 * arrayLen); fieldsTotal++; } break; case 11: { // SHORT builder.Short(generateNames ? makeRndName() : null); counterStack.get(0).add(16); fieldsTotal++; } break; case 12: { // SHORT_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.ShortArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(arrayLen * 16); fieldsTotal++; } break; case 13: { // USHORT builder.UShort(generateNames ? makeRndName() : null); counterStack.get(0).add(16); fieldsTotal++; } break; case 14: { // USHORT_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.UShortArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(16 * arrayLen); fieldsTotal++; } break; case 15: { // INT builder.Int(generateNames ? makeRndName() : null); counterStack.get(0).add(32); fieldsTotal++; } break; case 16: { // INT_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.IntArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(32 * arrayLen); fieldsTotal++; } break; case 17: { // Comment builder.Comment(generateComment()); } break; case 18: { // LONG builder.Long(generateNames ? makeRndName() : null); counterStack.get(0).add(64); fieldsTotal++; } break; case 19: { // LONG_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.LongArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(arrayLen * 64); fieldsTotal++; } break; case 20: { // FLOAT builder.Float(generateNames ? makeRndName() : null); counterStack.get(0).add(32); fieldsTotal++; } break; case 21: { // FLOAT_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.FloatArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(arrayLen * 32); fieldsTotal++; } break; case 22: { // UINT builder.UInt(generateNames ? makeRndName() : null); counterStack.get(0).add(32); fieldsTotal++; } break; case 23: { // UINT_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.UIntArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(arrayLen * 32); fieldsTotal++; } break; case 24: { // DOUBLE builder.Double(generateNames ? makeRndName() : null); counterStack.get(0).add(64); fieldsTotal++; } break; case 25: { // DOUBLE_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.DoubleArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(arrayLen * 64); fieldsTotal++; } break; case 26: { // STRUCT END if (activeStructCounter > 0) { i--; activeStructCounter--; builder.CloseStruct(); final StructLen len = counterStack.remove(0); counterStack.get(0).add(len.make()); } } break; case 27: { // COMMENT builder.Comment(genRandomString(this.RND.nextInt(32))); } break; case 28: { // COMMENT NEW LINE builder.NewLineComment(genRandomString(this.RND.nextInt(32))); } break; } } } while (activeStructCounter > 0) { activeStructCounter--; builder.CloseStruct(); final StructLen len = counterStack.remove(0); counterStack.get(0).add(len.make()); } return new Result(builder.End(), counterStack.get(0).make(), fieldsTotal, structsTotal, booleanDataItems, typeFlags); } private static class StructLen { final int arrayLength; int bitLength = 0; StructLen() { this(1); } StructLen(final int arrayLength) { this.arrayLength = arrayLength; } void add(final int bitLength) { this.bitLength += bitLength; } int make() { return this.arrayLength * bitLength; } } static class Result { final String script; final int bitLength; final int fieldsNumber; final int structNumber; final int booleanDataItemCounter; final long typeFlags; Result(final String script, final int bitLength, final int fieldsNumber, final int structNumber, final int booleanDtaItemCounter, final long typeFlags) { this.script = script; this.bitLength = bitLength; this.fieldsNumber = fieldsNumber; this.structNumber = structNumber; this.booleanDataItemCounter = booleanDtaItemCounter; this.typeFlags = typeFlags; } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/tokenizer/000077500000000000000000000000001445000440600317335ustar00rootroot00000000000000JBBPFieldTypeParameterContainerTest.java000066400000000000000000000036221445000440600414110ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/tokenizer/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.tokenizer; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import org.junit.jupiter.api.Test; public class JBBPFieldTypeParameterContainerTest { @Test public void testConstructorAndGetters() { final String name = "name"; final String extra = "extra"; final JBBPFieldTypeParameterContainer params = new JBBPFieldTypeParameterContainer(JBBPByteOrder.BIG_ENDIAN, name, extra); assertSame(name, params.getTypeName()); assertSame(extra, params.getExtraData()); assertEquals(JBBPByteOrder.BIG_ENDIAN, params.getByteOrder()); } @Test public void testToString() { assertEquals("int hello", new JBBPFieldTypeParameterContainer(JBBPByteOrder.BIG_ENDIAN, "int hello", null) .toString()); assertEquals(" new JBBPTokenizer("int $$;").next()); assertThrows(JBBPTokenizerException.class, () -> new JBBPTokenizer("int $a;").next()); assertThrows(JBBPTokenizerException.class, () -> new JBBPTokenizer("int a%d;").next()); assertThrows(JBBPTokenizerException.class, () -> new JBBPTokenizer("int 1a;").next()); assertThrows(JBBPTokenizerException.class, () -> new JBBPTokenizer("int _;").next()); } @Test public void testError_ForEmptyString() { final JBBPTokenizer parser = new JBBPTokenizer(""); assertTrue(parser.hasNext()); try { parser.next(); fail("Must throw Tokenizer exception"); } catch (JBBPTokenizerException ex) { assertEquals(0, ex.getPosition()); } } @Test public void testError_ForArrayFieldWithoutType() { final JBBPTokenizer parser = new JBBPTokenizer(" [123] hello;"); assertTrue(parser.hasNext()); try { parser.next(); fail("Must throw Tokenizer exception"); } catch (JBBPTokenizerException ex) { assertEquals(1, ex.getPosition()); assertEquals("->[<- 123] hello;", ex.getErrorPart()); } } @Test public void testStructureEndAndErrorForArrayFieldWithoutType() { final JBBPTokenizer parser = new JBBPTokenizer("} [123] hello;"); assertTrue(parser.hasNext()); assertEquals(JBBPTokenType.STRUCT_END, parser.next().getType()); try { parser.next(); fail("Must throw Tokenizer exception"); } catch (JBBPTokenizerException ex) { assertEquals(2, ex.getPosition()); assertEquals("} ->[<- 123] hello;", ex.getErrorPart()); } } @Test public void testParseOnlyCommentLine_WithNextLine() { final JBBPTokenizer parser = new JBBPTokenizer(" // only comment line \n"); int commentCount = 0; int otherCount = 0; for (final JBBPToken item : parser) { if (item.getType() == JBBPTokenType.COMMENT) { assertEquals(3, item.getPosition()); assertEquals("only comment line", item.getFieldName()); assertNull(item.getArraySizeAsString()); assertFalse(item.isArray()); assertTrue(item.isComment()); commentCount++; } else { otherCount++; } } assertEquals(0, otherCount); assertEquals(1, commentCount); } @Test public void testParseOnlyCommentLine_WithoutNextLine() { final JBBPTokenizer parser = new JBBPTokenizer(" // only comment line "); int commentCount = 0; int otherCount = 0; for (final JBBPToken item : parser) { if (item.getType() == JBBPTokenType.COMMENT) { assertEquals(3, item.getPosition()); assertEquals("only comment line", item.getFieldName()); assertNull(item.getArraySizeAsString()); assertFalse(item.isArray()); assertTrue(item.isComment()); commentCount++; } else { otherCount++; } } assertEquals(0, otherCount); assertEquals(1, commentCount); } @Test public void testSingleLine_ErrorForUnsupportedText() { final JBBPTokenizer parser = new JBBPTokenizer(" some unsupported line "); try { parser.iterator().next(); fail("Must throw parser exception"); } catch (JBBPTokenizerException ex) { assertEquals(0, ex.getPosition()); assertEquals("-> <- some", ex.getErrorPart()); } } @Test public void testStructure_EndWithSpaces() { final JBBPTokenizer parser = new JBBPTokenizer(" } "); final Iterator iterator = parser.iterator(); final JBBPToken item = iterator.next(); assertEquals(JBBPTokenType.STRUCT_END, item.getType()); assertNull(item.getFieldName()); assertNull(item.getFieldTypeParameters()); assertNull(item.getArraySizeAsString()); assertEquals(3, item.getPosition()); try { iterator.next(); fail("Must throw NSEE"); } catch (NoSuchElementException ex) { } } @Test public void testStructure_End_WithoutSpaces() { final JBBPTokenizer parser = new JBBPTokenizer("}"); final Iterator iterator = parser.iterator(); final JBBPToken item = iterator.next(); assertEquals(JBBPTokenType.STRUCT_END, item.getType()); assertNull(item.getFieldName()); assertNull(item.getFieldTypeParameters()); assertNull(item.getArraySizeAsString()); assertEquals(0, item.getPosition()); try { iterator.next(); fail("Must throw NSEE"); } catch (NoSuchElementException ex) { } } @Test public void testStructure_End_WithoutSpacesAndWithNextField() { final JBBPTokenizer parser = new JBBPTokenizer("}byte;"); final Iterator iterator = parser.iterator(); final JBBPToken item = iterator.next(); assertEquals(JBBPTokenType.STRUCT_END, item.getType()); assertNull(item.getFieldName()); assertNull(item.getFieldTypeParameters()); assertNull(item.getArraySizeAsString()); assertEquals(0, item.getPosition()); } @Test public void testStructure_Named_Start_WithSpaces() { final JBBPTokenizer parser = new JBBPTokenizer(" struct { "); final Iterator iterator = parser.iterator(); final JBBPToken item = iterator.next(); assertEquals(JBBPTokenType.STRUCT_START, item.getType()); assertEquals("struct", item.getFieldName()); assertNull(item.getFieldTypeParameters()); assertNull(item.getArraySizeAsString()); assertEquals(4, item.getPosition()); try { iterator.next(); fail("Must throw NSEE"); } catch (NoSuchElementException ex) { } } @Test public void testError_ForWrongByteOrderFormatChar() { final JBBPTokenizer parser = new JBBPTokenizer(" !int hello; "); final Iterator iterator = parser.iterator(); try { iterator.next(); fail("Must throw parser exception for wrong symbol of byte order"); } catch (JBBPTokenizerException ex) { assertEquals(3, ex.getPosition()); assertEquals("->!<- int hello;", ex.getErrorPart()); } } @Test public void testErrorForIteratorRemove() { assertThrows(UnsupportedOperationException.class, () -> new JBBPTokenizer("int a;").remove()); } @Test public void testStructure_Named_Start_ErrorForDisabledCharAtName() { final JBBPTokenizer parser = new JBBPTokenizer(" struct.a { "); final Iterator iterator = parser.iterator(); try { iterator.next(); fail("Must throw parser exception for disabled dot char at name"); } catch (JBBPTokenizerException ex) { assertEquals(3, ex.getPosition()); assertEquals("->s<- truct.a {", ex.getErrorPart()); } } @Test public void testRegularField_ErrorForDisabledCharAtName() { final JBBPTokenizer parser = new JBBPTokenizer(" int.a; "); final Iterator iterator = parser.iterator(); try { iterator.next(); fail("Must throw parser exception for disabled dot char at name"); } catch (JBBPTokenizerException ex) { assertEquals(3, ex.getPosition()); assertEquals("->i<- nt.a;", ex.getErrorPart()); } } @Test public void testStructure_Named_Start_ErrorForDollarAsTheFirstChar() { final JBBPTokenizer parser = new JBBPTokenizer(" $struct { "); final Iterator iterator = parser.iterator(); try { iterator.next(); fail("Must throw parser exception for disabled dot char at name"); } catch (JBBPTokenizerException ex) { assertEquals(3, ex.getPosition()); assertEquals("->$<- struct {", ex.getErrorPart()); } } @Test public void testRegularField_Name_ErrorForDollarAsTheFirstChar() { final JBBPTokenizer parser = new JBBPTokenizer(" $int; "); final Iterator iterator = parser.iterator(); try { iterator.next(); fail("Must throw parser exception for disabled dot char at name"); } catch (JBBPTokenizerException ex) { assertEquals(3, ex.getPosition()); assertEquals("->$<- int;", ex.getErrorPart()); } } @Test public void testStructure_Named_WithoutSpacesToBracket() { final JBBPTokenizer parser = new JBBPTokenizer(" struct{ "); final Iterator iterator = parser.iterator(); final JBBPToken item = iterator.next(); assertEquals(JBBPTokenType.STRUCT_START, item.getType()); assertEquals("struct", item.getFieldName()); assertNull(item.getFieldTypeParameters()); assertNull(item.getArraySizeAsString()); assertEquals(2, item.getPosition()); try { iterator.next(); fail("Must throw NSEE"); } catch (NoSuchElementException ex) { } } @Test public void testStructure_NonamedArray_Spaces() { final JBBPTokenizer parser = new JBBPTokenizer(" [ 333 ] { "); final Iterator iterator = parser.iterator(); final JBBPToken item = iterator.next(); assertEquals(JBBPTokenType.STRUCT_START, item.getType()); assertNull(item.getFieldName()); assertNull(item.getFieldTypeParameters()); assertTrue(item.isArray()); assertEquals(333, item.getArraySizeAsInt().intValue()); assertEquals(4, item.getPosition()); try { iterator.next(); fail("Must throw NSEE"); } catch (NoSuchElementException ex) { } } @Test public void testStructure_NonamedArray_WithoutSpacesInSizeAndBeforeBracket() { final JBBPTokenizer parser = new JBBPTokenizer(" [333]{ "); final Iterator iterator = parser.iterator(); final JBBPToken item = iterator.next(); assertEquals(JBBPTokenType.STRUCT_START, item.getType()); assertNull(item.getFieldName()); assertNull(item.getFieldTypeParameters()); assertTrue(item.isArray()); assertEquals(333, item.getArraySizeAsInt().intValue()); assertEquals(4, item.getPosition()); try { iterator.next(); fail("Must throw NSEE"); } catch (NoSuchElementException ex) { } } @Test public void testStructure_Array_ErrorForWrongNameSizeOrder() { final JBBPTokenizer parser = new JBBPTokenizer(" [333] test { "); final Iterator iterator = parser.iterator(); try { iterator.next(); fail("Must throw parser exception"); } catch (JBBPTokenizerException ex) { assertEquals(2, ex.getPosition()); assertEquals("->[<- 333] test", ex.getErrorPart()); } } @Test public void testStructure_Array_SpacesBetweenNameSizeBracket() { final JBBPTokenizer parser = new JBBPTokenizer(" test [333] { "); final Iterator iterator = parser.iterator(); final JBBPToken item = iterator.next(); assertEquals(JBBPTokenType.STRUCT_START, item.getType()); assertEquals("test", item.getFieldName()); assertNull(item.getFieldTypeParameters()); assertTrue(item.isArray()); assertEquals(333, item.getArraySizeAsInt().intValue()); assertEquals(2, item.getPosition()); try { iterator.next(); fail("Must throw NSEE"); } catch (NoSuchElementException ex) { } } @Test public void testStructure_Array_ErrorForTypeDefinition() { final JBBPTokenizer parser = new JBBPTokenizer(" int [] struct{ "); final Iterator iterator = parser.iterator(); try { iterator.next(); fail("Must throw parser exception for wrong struct end definition"); } catch (JBBPTokenizerException ex) { assertEquals(0, ex.getPosition()); assertEquals("-> <- int", ex.getErrorPart()); } } @Test public void testStructure_ErrorForTypeDefinition() { final JBBPTokenizer parser = new JBBPTokenizer(" int struct{ "); final Iterator iterator = parser.iterator(); try { iterator.next(); fail("Must throw parser exception for wrong struct end definition"); } catch (JBBPTokenizerException ex) { assertEquals(4, ex.getPosition()); assertEquals("->i<- nt struct{", ex.getErrorPart()); } } @Test public void testErrorForNonRecognizedStringInMiddleOfText() { final JBBPTokenizer parser = new JBBPTokenizer("// test \n wrong line \n next wrong int ;"); final Iterator iterator = parser.iterator(); JBBPToken comment = iterator.next(); assertTrue(comment.isComment()); try { iterator.next(); fail("Must throw exception"); } catch (JBBPTokenizerException ex) { assertEquals(8, ex.getPosition()); assertEquals("test ->\\u000a<- wrong", ex.getErrorPart()); } } @Test public void testParseScript_WithoutStructures() { final JBBPTokenizer parser = new JBBPTokenizer( "\n// test without structures\n" + " boolean; // nonamed boolean field\n" + " int [123] items;" + " bit:3 bitField; // a bit field"); final Iterator iterator = parser.iterator(); JBBPToken item = iterator.next(); assertTrue(item.isComment()); assertEquals("test without structures", item.getFieldName()); assertEquals(1, item.getPosition()); item = iterator.next(); // boolean; assertFalse(item.isComment()); assertFalse(item.isArray()); assertEquals(JBBPTokenType.ATOM, item.getType()); assertEquals("boolean", item.getFieldTypeParameters().getTypeName()); assertNull(item.getFieldName()); assertEquals(29, item.getPosition()); item = iterator.next(); // comment assertTrue(item.isComment()); assertEquals("nonamed boolean field", item.getFieldName()); assertEquals(38, item.getPosition()); item = iterator.next(); // int [123] items; assertFalse(item.isComment()); assertTrue(item.isArray()); assertEquals(JBBPTokenType.ATOM, item.getType()); assertEquals("int", item.getFieldTypeParameters().getTypeName()); assertEquals(123, item.getArraySizeAsInt().intValue()); assertEquals("items", item.getFieldName()); assertEquals(64, item.getPosition()); item = iterator.next(); // bit:3 bitField; assertFalse(item.isComment()); assertFalse(item.isArray()); assertEquals(JBBPTokenType.ATOM, item.getType()); assertEquals("bit:3", item.getFieldTypeParameters().toString()); assertEquals("bitField", item.getFieldName()); assertEquals(81, item.getPosition()); item = iterator.next(); // comment assertTrue(item.isComment()); assertEquals("a bit field", item.getFieldName()); assertEquals(97, item.getPosition()); assertFalse(iterator.hasNext()); try { iterator.next(); fail("Must throw NSEE"); } catch (NoSuchElementException ex) { } } private void assertParsedItem(final JBBPToken item, final JBBPTokenType itemType, final String fieldType, final String length, final String fieldName) { assertNotNull(item); assertEquals(itemType, item.getType()); if (fieldType == null) { assertNull(item.getFieldTypeParameters()); } else { assertEquals(fieldType, item.getFieldTypeParameters().toString()); } assertEquals(fieldName, item.getFieldName()); assertEquals(length, item.getArraySizeAsString()); } @Test public void testParse_Field_Nonamed_BitBit() { final JBBPTokenizer parser = new JBBPTokenizer(" bit:4; bit:5; "); final Iterator iterator = parser.iterator(); assertParsedItem(iterator.next(), JBBPTokenType.ATOM, "bit:4", null, null); assertParsedItem(iterator.next(), JBBPTokenType.ATOM, "bit:5", null, null); assertFalse(iterator.hasNext()); } @Test public void testParse_Field_Nonamed_Align() { final JBBPTokenizer parser = new JBBPTokenizer(" align:8; "); final Iterator iterator = parser.iterator(); assertParsedItem(iterator.next(), JBBPTokenType.ATOM, "align:8", null, null); assertFalse(iterator.hasNext()); } @Test public void testParse_StructureArrayStartWithComplexExpressionAsSize() { final JBBPTokenizer parser = new JBBPTokenizer("ColorMap [ (Header.ColorMapType & 1) * Header.CMapLength] {"); final Iterator iterator = parser.iterator(); assertParsedItem(iterator.next(), JBBPTokenType.STRUCT_START, null, "(Header.ColorMapType & 1) * Header.CMapLength", "ColorMap"); assertFalse(iterator.hasNext()); } @Test public void testParse_wrongEndOfStruct() { try { JBBPParser.prepare( "byte[5] blob5; " + "accountFlags {" + " byte[32] address; " + "}; " + "byte[7] blob7;" ); } catch (JBBPTokenizerException ex) { assertEquals(49, ex.getPosition()); assertEquals("address; } ->;<- byte[7]", ex.getErrorPart()); } } @Test public void testParse_VarFieldArrayWithNegativeExtra() { final JBBPTokenizer parser = new JBBPTokenizer("var:-123 [size] name;"); final Iterator iterator = parser.iterator(); assertParsedItem(iterator.next(), JBBPTokenType.ATOM, "var:-123", "size", "name"); assertFalse(iterator.hasNext()); } @Test public void testParseScript_WithStructure() { final JBBPTokenizer parser = new JBBPTokenizer( " // test\n" + " int; \n" + " boolean a;\n" + " byte [1024] array ; \n" + " header {\n" + " long id;\n" + " }\n" + " byte [header.long] data;"); final Iterator iterator = parser.iterator(); assertParsedItem(iterator.next(), JBBPTokenType.COMMENT, null, null, "test"); assertParsedItem(iterator.next(), JBBPTokenType.ATOM, "int", null, null); assertParsedItem(iterator.next(), JBBPTokenType.ATOM, "boolean", null, "a"); assertParsedItem(iterator.next(), JBBPTokenType.ATOM, "byte", "1024", "array"); assertParsedItem(iterator.next(), JBBPTokenType.STRUCT_START, null, null, "header"); assertParsedItem(iterator.next(), JBBPTokenType.ATOM, "long", null, "id"); assertParsedItem(iterator.next(), JBBPTokenType.STRUCT_END, null, null, null); assertParsedItem(iterator.next(), JBBPTokenType.ATOM, "byte", "header.long", "data"); assertFalse(iterator.hasNext()); try { iterator.next(); fail("Must throw exception"); } catch (NoSuchElementException ex) { } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/varlen/000077500000000000000000000000001445000440600312105ustar00rootroot00000000000000JBBPExpressionEvaluatorTest.java000066400000000000000000000520531445000440600373210ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/varlen/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.varlen; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPNamedNumericFieldMap; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.JBBPCompiler; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.exceptions.JBBPCompilationException; import com.igormaznitsa.jbbp.exceptions.JBBPEvalException; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Test; public class JBBPExpressionEvaluatorTest { @Test public void testExpression_Error_ConstantsWithoutOperators() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator(" 1 334", null, null)); } @Test public void testExpression_Error_OnlyBrackets() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator(" ( ( ( ( ) )) ) ", null, null)); } @Test public void testExpression_Error_NonClosedBracket() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator("(34+45 ", null, null)); } @Test public void testExpression_Error_NonOpenedBracket() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator("34+45) ", null, null)); } @Test public void testExpression_Error_Spaces() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator(" ", null, null)); } @Test public void testExpression_Error_Empty() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator("", null, null)); } @Test public void testExpression_Error_OnlyUnaryPlus() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator("+", null, null)); } @Test public void testExpression_Error_OnlyUnaryMinus() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator("-", null, null)); } @Test public void testExpression_Error_OnlyNot() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator("~", null, null)); } @Test public void testExpression_Error_WrongFirstUnaryOperator() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator("%345", null, null)); } @Test public void testExpression_Error_WrongUnaryOperator() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator("222%%345", null, null)); } @Test public void testExpression_Error_OnlyNonUnaryOperator() { assertThrows(JBBPCompilationException.class, () -> new JBBPExpressionEvaluator("*", null, null)); } @Test public void testExpression_Error_MulWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("123*", null, null).eval(null, 0, null, null)); } @Test public void testExpression_Error_SubWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("123-", null, null).eval(null, 0, null, null)); } @Test public void testExpression_Error_AddWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("123+", null, null).eval(null, 0, null, null)); } @Test public void testExpression_Error_ModWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("123%", null, null).eval(null, 0, null, null)); } @Test public void testExpression_Error_DivWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("123/", null, null).eval(null, 0, null, null)); } @Test public void testExpression_Error_AndWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("123&", null, null).eval(null, 0, null, null)); } @Test public void testExpression_Error_OrWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("123|", null, null).eval(null, 0, null, null)); } @Test public void testExpression_Error_XorWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("123^", null, null).eval(null, 0, null, null)); } @Test public void testExpression_Constant() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("5678", null, null); assertEquals(5678, expr.eval(null, 0, null, null)); } @Test public void testExpression_ErrorForTooBigConstant() { try { new JBBPExpressionEvaluator("5678892374982347", null, null); fail("Must throw compilation exception"); } catch (JBBPCompilationException ex) { assertTrue(ex.getCause() instanceof NumberFormatException); } } @Test public void testExpression_Variable() { final JBBPNamedFieldInfo info = new JBBPNamedFieldInfo("hello", "hello", 0); final byte[] compiled = new byte[] {JBBPCompiler.CODE_INT | JBBPCompiler.FLAG_NAMED}; final List list = Collections.singletonList(info); JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("hello", list, compiled); final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); map.putField(new JBBPFieldInt(info, 1234)); assertEquals(1234, expr.eval(null, 0, JBBPCompiledBlock.prepare().setCompiledData(compiled).setNamedFieldData(list) .setSource("no source").build(), map)); } @Test public void testExpression_ExpressionWithVariable() { final JBBPNamedFieldInfo info = new JBBPNamedFieldInfo("hello", "hello", 0); final byte[] compiled = new byte[] {JBBPCompiler.CODE_INT | JBBPCompiler.FLAG_NAMED}; final List list = Collections.singletonList(info); JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("2*hello*6/4+3*2-11%3&hello-~hello", list, compiled); final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); map.putField(new JBBPFieldInt(info, 8)); assertEquals(2 * 8 * 6 / 4 + 3 * 2 - 11 % 3 & 8 - ~8, expr.eval(null, 0, JBBPCompiledBlock.prepare().setCompiledData(compiled).setNamedFieldData(list) .setSource("no source").build(), map)); } @Test public void testExpression_MustNotThrowStackOverfow() throws Exception { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator( "1+(2+(3+(4+(5+(6+(7+(8+(9+(10+(11+(12+(13+(14+(15+(16+(17+(18+(19+(20*2)))))))))))))))))))", null, null); assertEquals(21, expr.getMaxStackDepth()); assertEquals(1 + (2 + (3 + (4 + (5 + (6 + (7 + (8 + (9 + (10 + (11 + (12 + (13 + (14 + (15 + (16 + (17 + (18 + (19 + (20 * 2))))))))))))))))))), expr.eval(null, 0, null, null)); } @Test public void testExpression_NegativeConstant() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-5678", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(-5678, expr.eval(null, 0, null, null)); } @Test public void testExpression_UnaryMinusWithSingleConstantInBrackets() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-(5678)", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(-5678, expr.eval(null, 0, null, null)); } @Test public void testExpression_UnaryNotWithSingleConstantInBrackets() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("~(5678)", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(~5678, expr.eval(null, 0, null, null)); } @Test public void testExpression_UnaryOperatorsInEasyExpression() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-6*-7*3*+6*-32", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(-6 * -7 * 3 * +6 * -32, expr.eval(null, 0, null, null)); } @Test public void testExpression_UnaryMinusInExpression() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("8* - 5678", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(8 * -5678, expr.eval(null, 0, null, null)); } @Test public void testExpression_UnaryMinusInExpressionAsFirstArgument() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-5678 * 8", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(-5678 * 8, expr.eval(null, 0, null, null)); } @Test public void testExpression_WithoutVariablesAndBrackets() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("10+2*42/3-8%2", null, null); assertEquals(3, expr.getMaxStackDepth()); assertEquals(10 + 2 * 42 / 3 - 8 % 2, expr.eval(null, 0, null, null)); } @Test public void testExpression_WithoutVariablesAndWithBrackets() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("(10+2)*42/(3-8)%2", null, null); assertEquals(3, expr.getMaxStackDepth()); assertEquals((10 + 2) * 42 / (3 - 8) % 2, expr.eval(null, 0, null, null)); } @Test public void testExpression_unaryMinus() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-567", null, null); assertEquals(-567, expr.eval(null, 0, null, null)); } @Test public void testExpression_unaryPlus() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("+567", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(567, expr.eval(null, 0, null, null)); } @Test public void testExpression_Mul() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("5623*567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(5623 * 567, expr.eval(null, 0, null, null)); } @Test public void testExpression_SingleCharNamedVar_Mul() { final JBBPNamedFieldInfo varA = new JBBPNamedFieldInfo("a", "a", 0); final byte[] compiled = new byte[] {JBBPCompiler.CODE_INT | JBBPCompiler.FLAG_NAMED}; final List list = Collections.singletonList(varA); JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("a*2", list, compiled); assertEquals(2, expr.getMaxStackDepth()); final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); map.putField(new JBBPFieldInt(varA, 123)); assertEquals(123 * 2, expr.eval(null, 0, JBBPCompiledBlock.prepare().setCompiledData(compiled).setNamedFieldData(list) .setSource("no source").build(), map)); } @Test public void testExpression_Div() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("5623/567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(5623 / 567, expr.eval(null, 0, null, null)); } @Test public void testExpression_Mod() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("5623%567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(5623 % 567, expr.eval(null, 0, null, null)); } @Test public void testExpression_Sub() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("5623-567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(5623 - 567, expr.eval(null, 0, null, null)); } @Test public void testExpression_Add() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("5623+567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(5623 + 567, expr.eval(null, 0, null, null)); } @Test public void testExpression_And() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("5623&567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(5623 & 567, expr.eval(null, 0, null, null)); } @Test public void testExpression_Or() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("5623|567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(5623 | 567, expr.eval(null, 0, null, null)); } @Test public void testExpression_Xor() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("5623^567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(5623 ^ 567, expr.eval(null, 0, null, null)); } @Test public void testExpression_Not() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("~567", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(~567, expr.eval(null, 0, null, null)); } @Test public void testExpression_TestUnaryTwoOperators() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-~567", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(-~567, expr.eval(null, 0, null, null)); } @Test public void testExpression_TestUnaryThreeOperators() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("~-~567", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(~-~567, expr.eval(null, 0, null, null)); } @Test public void testExpression_TestUnaryTwoOperatorsInExpression() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("~-567-~567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(~-567 - ~567, expr.eval(null, 0, null, null)); } @Test public void testExpression_TestUnaryThreeOperatorsInExpression() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-~-567-~-567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(-~-567 - ~-567, expr.eval(null, 0, null, null)); } @Test public void testExpression_TestComplexUnaryWithConstant() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-~-~~567", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(-~-~~567, expr.eval(null, 0, null, null)); } @Test public void testExpression_TestComplexUnaryWithConstantAndBrackets() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-(~-(~~567))", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(-(~-(~~567)), expr.eval(null, 0, null, null)); } @Test public void testExpression_TestComplexUnaryWithExpressionAndBrackets() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-(~+-(~+~567*-(+34)))", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(-(~+-(~+~567 * -(+34))), expr.eval(null, 0, null, null)); } @Test public void testExpression_MultipleUnaryPlusOutsideBracketsAndOneInBrackets() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("++++++(+54)", null, null); assertEquals(1, expr.getMaxStackDepth()); assertEquals(+(+54), expr.eval(null, 0, null, null)); } @Test public void testExpression_ConstantAndMultipleUnaryPlusOutsideBracketsAndOneInBrackets() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("112++++++(+54)", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(112 + (+54), expr.eval(null, 0, null, null)); } @Test public void testExpression_TestComplexUnaryInConstantExpression() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("445*-~-~+~567", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(445 * -~-~+~567, expr.eval(null, 0, null, null)); } @Test public void testExpression_ComplexLogicalWithConstants() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("~23*-1234&~123/(34+89)|3232%56^~2234", null, null); assertEquals(4, expr.getMaxStackDepth()); assertEquals((~23 * -1234 & ~123 / (34 + 89)) | (3232 % 56 ^ ~2234), expr.eval(null, 0, null, null)); } @Test public void testExpression_LeftShift() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("1234<<3", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(1234 << 3, expr.eval(null, 0, null, null)); } @Test public void testExpression_LeftShift_ErrorWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("1234<<", null, null).eval(null, 0, null, null)); } @Test public void testExpression_RightShift() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("1234>>3", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(1234 >> 3, expr.eval(null, 0, null, null)); } @Test public void testExpression_RightShift_ErrorWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("1234 >>", null, null).eval(null, 0, null, null)); } @Test public void testExpression_RightSignShift() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-1>>>3", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(-1 >>> 3, expr.eval(null, 0, null, null)); } @Test public void testExpression_RightSignShiftWithInversion() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("-1>>>~3&7", null, null); assertEquals(2, expr.getMaxStackDepth()); assertEquals(-1 >>> ~3 & 7, expr.eval(null, 0, null, null)); } @Test public void testExpression_RightSignShift_ErrorWithoutSecondArgument() { assertThrows(JBBPEvalException.class, () -> new JBBPExpressionEvaluator("1234 >>>", null, null).eval(null, 0, null, null)); } @Test public void testExpression_ReverseByte() { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("((($v*2050&139536)|($v*32800&558144))*65793>>16)&255", null, null); assertEquals(3, expr.getMaxStackDepth()); assertEquals(JBBPUtils.reverseBitsInByte((byte) 123) & 0xFF, expr.eval(null, 0, null, new JBBPNamedNumericFieldMap((fieldName, numericFieldMap, compiledBlock) -> { if ("v".equals(fieldName)) { return 123; } else { fail("Unexpected field [" + fieldName + ']'); return 0; } }))); } @Test public void testExpression_CheckExternalField() { final int value = 1234; final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap((fieldName, numericFieldMap, compiledBlock) -> { if (fieldName.equals("value")) { return value; } assertNotNull(numericFieldMap); assertNotNull(compiledBlock); fail("Unexpected request for value [" + fieldName + ']'); return -1; }); final List list = new ArrayList<>(); final byte[] compiled = new byte[] {0}; final JBBPCompiledBlock compiledBlock = JBBPCompiledBlock.prepare().setCompiledData(compiled).setSource("none") .setNamedFieldData(list).build(); JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("123*($value-45/3)", list, compiled); assertEquals(4, expr.getMaxStackDepth()); assertEquals(123 * (value - 45 / 3), expr.eval(null, 0, compiledBlock, map)); } @Test public void testExpression_NotAfterMinus() throws Exception { JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("60|7-~17%1", null, null); assertEquals(60 | 7 - ~17 % 1, expr.eval(null, 0, null, null)); } @Test public void testExpression_CheckExternalFieldAndStreamOffset() throws Exception { final int value = 1234; final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap((fieldName, numericFieldMap, compiledBlock) -> { if (fieldName.equals("value")) { return value; } assertNotNull(numericFieldMap); assertNotNull(compiledBlock); fail("Unexpected request for value [" + fieldName + ']'); return -1; }); final List list = new ArrayList<>(); final byte[] compiled = new byte[] {0}; final JBBPCompiledBlock compiledBlock = JBBPCompiledBlock.prepare().setCompiledData(compiled).setSource("none") .setNamedFieldData(list).build(); JBBPExpressionEvaluator expr = new JBBPExpressionEvaluator("123*($value-45/3)*$$", list, compiled); assertEquals(4, expr.getMaxStackDepth()); final JBBPBitInputStream inStream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5})); inStream.read(); inStream.read(); inStream.read(); assertEquals(123 * (value - 45 / 3) * 3, expr.eval(inStream, 0, compiledBlock, map)); } } JBBPOnlyFieldEvaluatorTest.java000066400000000000000000000105741445000440600370510ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/varlen/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.compiler.varlen; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPNamedNumericFieldMap; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; public class JBBPOnlyFieldEvaluatorTest { @Test public void testNumericValue() { final int value = 1234; final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap(); final JBBPNamedFieldInfo nameInfo = new JBBPNamedFieldInfo("value", "value", value); map.putField(new JBBPFieldInt(nameInfo, value)); final List list = new ArrayList<>(); list.add(nameInfo); final byte[] compiled = new byte[] {0}; final JBBPCompiledBlock compiledBlock = JBBPCompiledBlock.prepare().setCompiledData(compiled).setSource("none") .setNamedFieldData(list).build(); JBBPOnlyFieldEvaluator expr = new JBBPOnlyFieldEvaluator(null, 0); assertEquals(value, expr.eval(null, 0, compiledBlock, map)); } @Test public void testExternalValue() { final int value = 1234; final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap((fieldName, numericFieldMap, compiledBlock) -> { if (fieldName.equals("value")) { return value; } assertNotNull(numericFieldMap); assertNotNull(compiledBlock); fail("Unexpected request for value [" + fieldName + ']'); return -1; }); final List list = new ArrayList<>(); final byte[] compiled = new byte[] {0}; final JBBPCompiledBlock compiledBlock = JBBPCompiledBlock.prepare().setCompiledData(compiled).setSource("none") .setNamedFieldData(list).build(); JBBPOnlyFieldEvaluator expr = new JBBPOnlyFieldEvaluator("value", -1); assertEquals(value, expr.eval(null, 0, compiledBlock, map)); } @Test public void testExternalValueNamedAsFirstCharDollar() { final int value = 1234; final JBBPNamedNumericFieldMap map = new JBBPNamedNumericFieldMap((fieldName, numericFieldMap, compiledBlock) -> { if (fieldName.equals("$value")) { return value; } assertNotNull(numericFieldMap); assertNotNull(compiledBlock); fail("Unexpected request for value [" + fieldName + ']'); return -1; }); final List list = new ArrayList<>(); final byte[] compiled = new byte[] {0}; final JBBPCompiledBlock compiledBlock = JBBPCompiledBlock.prepare().setCompiledData(compiled).setSource("none") .setNamedFieldData(list).build(); JBBPOnlyFieldEvaluator expr = new JBBPOnlyFieldEvaluator("$value", -1); assertEquals(value, expr.eval(null, 0, compiledBlock, map)); } @Test public void testCounterOfStreamAsParameter() throws Exception { final List list = new ArrayList<>(); final byte[] compiled = new byte[] {0}; final JBBPCompiledBlock compiledBlock = JBBPCompiledBlock.prepare().setCompiledData(compiled).setSource("none") .setNamedFieldData(list).build(); JBBPOnlyFieldEvaluator expr = new JBBPOnlyFieldEvaluator("$", -1); final JBBPBitInputStream inStream = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5})); inStream.read(); inStream.read(); inStream.read(); assertEquals(3, expr.eval(inStream, 0, compiledBlock, null)); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/exceptions/000077500000000000000000000000001445000440600302705ustar00rootroot00000000000000JBBPMapperExceptionTest.java000066400000000000000000000042751445000440600354650ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/exceptions/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.exceptions; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldByte; import java.lang.reflect.Field; import org.junit.jupiter.api.Test; public class JBBPMapperExceptionTest { private final JBBPAbstractField field = new JBBPFieldByte(new JBBPNamedFieldInfo("test.test", "test", 0), (byte) 1); private final Exception cause = new Exception(); private final String message = "Message"; @Test public void testConstructorAndGetters() throws Exception { final Class clazz = Integer.class; final Field clazzField = Integer.class.getDeclaredFields()[0]; assertNotNull(clazzField); final JBBPMapperException ex = new JBBPMapperException(this.message, this.field, clazz, clazzField, this.cause); assertSame(this.message, ex.getMessage()); assertSame(this.field, ex.getField()); assertSame(clazz, ex.getMappingClass()); assertSame(clazzField, ex.getMappingClassField()); } @Test public void testToString() throws Exception { final Class clazz = Integer.class; final Field clazzField = Integer.class.getDeclaredFields()[0]; assertNotNull(clazzField); final JBBPMapperException ex = new JBBPMapperException(this.message, this.field, clazz, clazzField, this.cause); assertTrue(ex.toString().contains(clazzField.toString())); } } JBBPTokenizerExceptionTest.java000066400000000000000000000033741445000440600362120ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/exceptionspackage com.igormaznitsa.jbbp.exceptions; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; class JBBPTokenizerExceptionTest { @Test public void testNullAsArg() { assertEquals("null (pos=0, errorPart=\"\")", new JBBPTokenizerException(null, null, 0).toString()); assertEquals(" (pos=0, errorPart=\"\")", new JBBPTokenizerException("", null, 0).toString()); assertEquals("null (pos=0, errorPart=\"\")", new JBBPTokenizerException(null, "", 0).toString()); assertEquals("null (pos=5, errorPart=\"\")", new JBBPTokenizerException(null, null, 5).toString()); assertEquals(" (pos=5, errorPart=\"\")", new JBBPTokenizerException("", null, 5).toString()); assertEquals("null (pos=5, errorPart=\"\")", new JBBPTokenizerException(null, "", 5).toString()); assertEquals("null (pos=-5, errorPart=\"\")", new JBBPTokenizerException(null, null, -5).toString()); assertEquals(" (pos=-5, errorPart=\"\")", new JBBPTokenizerException("", null, -5).toString()); assertEquals("null (pos=-5, errorPart=\"\")", new JBBPTokenizerException(null, "", -5).toString()); } @Test public void testWrongErrorPos() { assertEquals("hello (pos=23, errorPart=\"\")", new JBBPTokenizerException("hello", "world", 23).toString()); assertEquals("hello (pos=-1, errorPart=\"\")", new JBBPTokenizerException("hello", "world", -1).toString()); } @Test public void testCornerPositions() { assertEquals("hello (pos=0, errorPart=\"->w<- orld\")", new JBBPTokenizerException("hello", "world", 0).toString()); assertEquals("hello (pos=4, errorPart=\"worl ->d<-\")", new JBBPTokenizerException("hello", "world", 4).toString()); } }java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/io/000077500000000000000000000000001445000440600265165ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/io/BitIOCommonTest.java000066400000000000000000000152001445000440600323360ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Random; import org.junit.jupiter.api.Test; public class BitIOCommonTest { private final Random rnd = new Random(1234); private final int[] BYTE_MASK = new int[] {0, 1, 3, 7, 15, 31, 63, 127, 255}; @Test public void testWriteReadString() throws Exception { final StringBuilder buffer = new StringBuilder("ФgЯ"); final Random rnd = new Random(112233); for (int i = 0; i < 18231; i++) { final ByteArrayOutputStream buffBig = new ByteArrayOutputStream(16384); final JBBPBitOutputStream outBig = new JBBPBitOutputStream(buffBig); final ByteArrayOutputStream buffLittl = new ByteArrayOutputStream(16384); final JBBPBitOutputStream outLittl = new JBBPBitOutputStream(buffLittl); final String text = buffer.toString(); outBig.writeString(text, JBBPByteOrder.BIG_ENDIAN); outLittl.writeString(text, JBBPByteOrder.LITTLE_ENDIAN); outBig.close(); outLittl.close(); final byte[] writtenBig = buffBig.toByteArray(); final byte[] writtenLittl = buffLittl.toByteArray(); final String restoredBig = new JBBPBitInputStream(new ByteArrayInputStream(writtenBig)) .readString(JBBPByteOrder.BIG_ENDIAN); final String restoredLittl = new JBBPBitInputStream(new ByteArrayInputStream(writtenLittl)) .readString(JBBPByteOrder.LITTLE_ENDIAN); assertEquals(text, restoredBig, "Iteration#" + i); assertEquals(text, restoredLittl, "Iteration#" + i); buffer.append((char) ('A' + rnd.nextInt(11))); buffer.append((char) ('Б' + rnd.nextInt(22))); } } @Test public void testWriteRead() throws Exception { final int LEN = 10000; final byte[] array = new byte[LEN]; final int[] len = new int[LEN]; rnd.nextBytes(array); for (int i = 0; i < LEN; i++) { final int l = rnd.nextInt(8) + 1; len[i] = l; array[i] = (byte) (array[i] & BYTE_MASK[l]); } final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); int writtenBits = 0; for (int i = 0; i < LEN; i++) { writtenBits += len[i]; out.writeBits(array[i], JBBPBitNumber.decode(len[i])); } out.close(); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(buff.toByteArray())); for (int i = 0; i < LEN; i++) { assertEquals(array[i] & 0xFF, in.readBits(JBBPBitNumber.decode(len[i])), "Index i=" + i); } if (writtenBits % 8 == 0) { assertEquals(-1, in.read()); } else { assertEquals(0, in.read()); assertEquals(-1, in.read()); } } @Test public void testWriteRead_MSB0() throws Exception { final int LEN = 10000; final byte[] array = new byte[LEN]; final int[] len = new int[LEN]; rnd.nextBytes(array); for (int i = 0; i < LEN; i++) { final int l = rnd.nextInt(8) + 1; len[i] = l; array[i] = (byte) (array[i] & BYTE_MASK[l]); } final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff, JBBPBitOrder.MSB0); int writtenBits = 0; for (int i = 0; i < LEN; i++) { writtenBits += len[i]; out.writeBits(array[i], JBBPBitNumber.decode(len[i])); } out.close(); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(buff.toByteArray()), JBBPBitOrder.MSB0); for (int i = 0; i < LEN; i++) { assertEquals(array[i] & 0xFF, in.readBits(JBBPBitNumber.decode(len[i])), "Index i=" + i); } if (writtenBits % 8 == 0) { assertEquals(-1, in.read()); } else { assertEquals(0, in.read()); assertEquals(-1, in.read()); } } @Test public void testWriteRead_NotFullByteAsLSB0AndReadAsMSB0() throws Exception { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buffer, JBBPBitOrder.LSB0); out.writeBits(1, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(1, JBBPBitNumber.BITS_1); out.writeBits(1, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.flush(); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(buffer.toByteArray()), JBBPBitOrder.MSB0); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(1, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(1, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(1, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(-1, in.readBits(JBBPBitNumber.BITS_1)); } @Test public void testWriteRead_LSB0() throws Exception { final int LEN = 10000; final byte[] array = new byte[LEN]; final int[] len = new int[LEN]; rnd.nextBytes(array); for (int i = 0; i < LEN; i++) { final int l = rnd.nextInt(8) + 1; len[i] = l; array[i] = (byte) (array[i] & BYTE_MASK[l]); } final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff, JBBPBitOrder.LSB0); int writtenBits = 0; for (int i = 0; i < LEN; i++) { writtenBits += len[i]; out.writeBits(array[i], JBBPBitNumber.decode(len[i])); } out.close(); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(buff.toByteArray()), JBBPBitOrder.LSB0); for (int i = 0; i < LEN; i++) { assertEquals(array[i] & 0xFF, in.readBits(JBBPBitNumber.decode(len[i])), "Index i=" + i); } if (writtenBits % 8 == 0) { assertEquals(-1, in.read()); } else { assertEquals(0, in.read()); assertEquals(-1, in.read()); } } } JBBPBitInputStreamTest.java000066400000000000000000001447311445000440600335240ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/io/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.TestUtils; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.util.Random; import org.junit.jupiter.api.Test; public class JBBPBitInputStreamTest { private static final String TEST_BYTES = "01001100_01110000_11110000_01111100_00001111_11000000_01100111_00000000_10011111"; private static final String TEST_BYTES_EXTRABIT = "0 01001100_01110000_11110000_01111100_00001111_11000000_01100111_00000000_10011111"; private static byte[] intArrayToByteArray(final int... array) { final byte[] bytearray = new byte[array.length]; for (int i = 0; i < array.length; i++) { if ((array[i] & 0xFFFFFF00) != 0) { fail("Non-convertible byte value [" + array[i] + ']'); } bytearray[i] = (byte) array[i]; } return bytearray; } private static JBBPBitInputStream asInputStream(final int... array) { return new JBBPBitInputStream(new ByteArrayInputStream(intArrayToByteArray(array))); } private static JBBPBitInputStream asInputStreamMSB0(final int... array) { return new JBBPBitInputStream(new ByteArrayInputStream(intArrayToByteArray(array)), JBBPBitOrder.MSB0); } @Test public void testReadUnsignedShort_EOFforEmpty_BigEndian_EOFException() throws Exception { assertThrows(EOFException.class, () -> asInputStream().readUnsignedShort(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadUnsignedShort_EOFforEmpty_LittleEndian_EOFException() throws Exception { assertThrows(EOFException.class, () -> asInputStream().readUnsignedShort(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadUnsignedShort_EOFforOneByte_BigEndian_EOFException() throws Exception { assertThrows(EOFException.class, () -> asInputStream((byte) 1).readUnsignedShort(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadUnsignedShort_EOFforOneByte_LittleEndian_EOFException() throws Exception { assertThrows(EOFException.class, () -> asInputStream((byte) 1).readUnsignedShort(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadUnsignedShort_BigEndian() throws Exception { assertEquals(0x1234, asInputStream(0x12, 0x34).readUnsignedShort(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadUnsignedShort_BigEndian_MSB0() throws Exception { assertEquals(0x482C, asInputStreamMSB0(0x12, 0x34).readUnsignedShort(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadUnsignedShort_LittleEndian() throws Exception { assertEquals(0x3412, asInputStream(0x12, 0x34).readUnsignedShort(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadUnsignedShort_LittleEndian_MSB0() throws Exception { assertEquals(0x2C48, asInputStreamMSB0(0x12, 0x34).readUnsignedShort(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadInt_BigEndian() throws Exception { assertEquals(0x12345678, asInputStream(0x12, 0x34, 0x56, 0x78).readInt(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadInt_BigEndian_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStream(0x12, 0x34, 0x56).readInt(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadInt_BigEndian_MSB0() throws Exception { assertEquals(0x482C6A1E, asInputStreamMSB0(0x12, 0x34, 0x56, 0x78).readInt(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadStringArray_BigEndan_FixedSize() throws Exception { assertArrayEquals(new String[] {null, "", "ABC"}, asInputStream(0xFF, 0x00, 3, 65, 66, 67, 0, 0) .readStringArray(3, JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadStringArray_ErrorForEOF() { assertThrows(IOException.class, () -> asInputStream(0xFF, 0x00, 3, 65, 66, 67, 0, 0) .readStringArray(8, JBBPByteOrder.BIG_ENDIAN)); assertThrows(IOException.class, () -> asInputStream().readStringArray(8, JBBPByteOrder.BIG_ENDIAN)); assertThrows(IOException.class, () -> asInputStream().readStringArray(8, JBBPByteOrder.LITTLE_ENDIAN)); assertThrows(IOException.class, () -> asInputStream(0xFF, 0x00, 3, 65, 66, 67, 0, 0) .readStringArray(8, JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadStringArray_ErrorForWrongPrefix() { assertThrows(IOException.class, () -> asInputStream(0x80, 0x00, 3, 65, 66, 67, 0, 0) .readStringArray(3, JBBPByteOrder.BIG_ENDIAN)); assertThrows(IOException.class, () -> asInputStream(0x91, 0x00, 3, 65, 66, 67, 0, 0) .readStringArray(3, JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadStringArray_LittleEndan_FixedSize() throws Exception { assertArrayEquals(new String[] {null, "", "ABC"}, asInputStream(0xFF, 0x00, 3, 65, 66, 67, 0, 0) .readStringArray(3, JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadStringArray_WholeStream_ResultEmptyArray() throws Exception { assertArrayEquals(new String[0], asInputStream().readStringArray(-1, JBBPByteOrder.LITTLE_ENDIAN)); assertArrayEquals(new String[0], asInputStream().readStringArray(-1, JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadStringArray_BigEndan_WholeStream() throws Exception { assertArrayEquals(new String[] {null, "", "ABC", "", ""}, asInputStream(0xFF, 0x00, 3, 65, 66, 67, 0, 0) .readStringArray(-1, JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadStringArray_LittleEndan_WholeStream() throws Exception { assertArrayEquals(new String[] {null, "", "ABC", "", ""}, asInputStream(0xFF, 0x00, 3, 65, 66, 67, 0, 0) .readStringArray(-1, JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadString_BigEndan_Null() throws Exception { assertNull(asInputStream(0xFF).readString(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadString_BigEndan_Empty() throws Exception { assertEquals("", asInputStream(0x00).readString(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadString_BigEndan_ShortString() throws Exception { assertEquals("ABC", asInputStream(0x03, 65, 66, 67).readString(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadString_BigEndan_Msb0_ShortString() throws Exception { assertEquals("zzzz", asInputStreamMSB0(0x20, '^', '^', '^', '^').readString(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadString_LittleEndan_Null() throws Exception { assertNull(asInputStream(0xFF).readString(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadString_LittleEndian_Empty() throws Exception { assertEquals("", asInputStream(0x00).readString(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadString_LittleEndian_ShortString() throws Exception { assertEquals("ABC", asInputStream(0x03, 65, 66, 67).readString(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadFloat_BigEndian_MSB0() throws Exception { assertEquals(176552.47f, asInputStreamMSB0(0x12, 0x34, 0x56, 0x78).readFloat(JBBPByteOrder.BIG_ENDIAN), TestUtils.FLOAT_DELTA); } @Test public void testReadInt_BigEndian_MSB0_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStreamMSB0(0x12, 0x34, 0x56).readInt(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadFloat_BigEndian_MSB0_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStreamMSB0(0x12, 0x34, 0x56).readFloat(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadInt_LittleEndian() throws Exception { assertEquals(0x78563412, asInputStream(0x12, 0x34, 0x56, 0x78).readInt(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadFloat_LittleEndian() throws Exception { assertEquals(1.7378244E34f, asInputStream(0x12, 0x34, 0x56, 0x78).readFloat(JBBPByteOrder.LITTLE_ENDIAN), TestUtils.FLOAT_DELTA); } @Test public void testReadInt_LittleEndian_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStream(0x12, 0x34, 0x56).readInt(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadFloat_LittleEndian_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStream(0x12, 0x34, 0x56).readFloat(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadInt_LittleEndian_MSB0() throws Exception { assertEquals(0x1E6A2C48, asInputStreamMSB0(0x12, 0x34, 0x56, 0x78).readInt(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadFloat_LittleEndian_MSB0() throws Exception { assertEquals(1.2397014E-20f, asInputStreamMSB0(0x12, 0x34, 0x56, 0x78).readFloat(JBBPByteOrder.LITTLE_ENDIAN), TestUtils.FLOAT_DELTA); } @Test public void testReadInt_LittleEndian_MSB0_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStreamMSB0(0x12, 0x34, 0x56).readInt(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadFloat_LittleEndian_MSB0_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStreamMSB0(0x12, 0x34, 0x56).readFloat(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadLong_BigEndian() throws Exception { assertEquals(0x12345678AABBCCDDL, asInputStream(0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD) .readLong(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadDouble_BigEndian() throws Exception { assertEquals(5.626349538661693E-221d, asInputStream(0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD) .readDouble(JBBPByteOrder.BIG_ENDIAN), TestUtils.FLOAT_DELTA); } @Test public void testReadLong_BigEndian_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStream(0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC) .readLong(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadDouble_BigEndian_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStream(0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC) .readDouble(JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadLong_LittleEndian() throws Exception { assertEquals(0xDDCCBBAA78563412L, asInputStream(0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD) .readLong(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadDouble_LittleEndian() throws Exception { assertEquals(-7.00761088740633E143d, asInputStream(0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC, 0xDD) .readDouble(JBBPByteOrder.LITTLE_ENDIAN), TestUtils.FLOAT_DELTA); } @Test public void testReadLong_LittleEndian_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStream(0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC) .readLong(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadDouble_LittleEndian_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStream(0x12, 0x34, 0x56, 0x78, 0xAA, 0xBB, 0xCC) .readDouble(JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testRead9bit() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {(byte) 0xDA, 1})); assertEquals(0xA, in.readBits(JBBPBitNumber.BITS_4)); assertEquals(0x1D, in.readBits(JBBPBitNumber.BITS_5)); assertEquals(0, in.read()); assertEquals(-1, in.read()); } @Test public void testRead9bit_MSB0() throws Exception { final JBBPBitInputStream in = asInputStreamMSB0(0xD9, 1); assertEquals(0x0B, in.readBits(JBBPBitNumber.BITS_4)); assertEquals(0x09, in.readBits(JBBPBitNumber.BITS_5)); assertEquals(0x40, in.read()); assertEquals(-1, in.read()); } @Test public void testGetBitBuffer() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {(byte) 0xAA})); assertEquals(0, in.getBitBuffer()); assertEquals(0xA, in.readBits(JBBPBitNumber.BITS_4)); assertEquals(0xA, in.getBitBuffer()); } @Test public void testGetOrder() throws Exception { assertEquals(JBBPBitOrder.MSB0, new JBBPBitInputStream(null, JBBPBitOrder.MSB0).getBitOrder()); assertEquals(JBBPBitOrder.LSB0, new JBBPBitInputStream(null, JBBPBitOrder.LSB0).getBitOrder()); assertEquals(JBBPBitOrder.LSB0, new JBBPBitInputStream(null).getBitOrder()); } @Test public void testAlignByte() throws Exception { final byte[] testarray = JBBPUtils.str2bin("01111001 10111000"); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(testarray)); in.alignByte(); assertEquals(0, in.getCounter()); assertEquals(0x19, in.readBits(JBBPBitNumber.BITS_5)); assertEquals(0, in.getCounter()); in.alignByte(); assertEquals(1, in.getCounter()); assertEquals(0x8, in.readBits(JBBPBitNumber.BITS_4)); assertEquals(1, in.getCounter()); assertEquals(0xB, in.readBits(JBBPBitNumber.BITS_4)); assertEquals(2, in.getCounter()); assertEquals(-1, in.read()); assertEquals(2, in.getCounter()); } @Test public void testAlignByte_IfWeHaveBufferedByteInBitBuffer() throws Exception { final byte[] testarray = JBBPUtils.str2bin("01111001 10111000"); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(testarray)); assertEquals(0x19, in.readBits(JBBPBitNumber.BITS_5)); assertEquals(0, in.getCounter()); assertEquals(0x03, in.readBits(JBBPBitNumber.BITS_3)); assertEquals(1, in.getCounter()); assertTrue(in.hasAvailableData()); assertEquals(1, in.getCounter()); in.alignByte(); assertEquals(1, in.getCounter()); assertEquals(0xB8, in.read()); assertEquals(-1, in.read()); assertEquals(2, in.getCounter()); } @Test public void testReadStream_AsBits() throws IOException { final byte[] testarray = JBBPUtils.str2bin(TEST_BYTES, JBBPBitOrder.MSB0); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(testarray)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1));//0 assertEquals(1, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.getCounter()); assertEquals(6, in.getBufferedBitsNumber()); assertEquals(0, in.readBits(JBBPBitNumber.BITS_2));//2 assertEquals(3, in.readBits(JBBPBitNumber.BITS_2)); assertEquals(0, in.getCounter()); assertEquals(2, in.getBufferedBitsNumber()); assertEquals(0, in.readBits(JBBPBitNumber.BITS_3));//6 assertEquals(7, in.readBits(JBBPBitNumber.BITS_3)); assertEquals(1, in.getCounter()); assertEquals(4, in.getBufferedBitsNumber()); assertEquals(0, in.readBits(JBBPBitNumber.BITS_4));//12 assertEquals(2, in.getCounter()); assertEquals(15, in.readBits(JBBPBitNumber.BITS_4)); assertEquals(2, in.getCounter()); assertEquals(4, in.getBufferedBitsNumber()); assertEquals(0, in.readBits(JBBPBitNumber.BITS_5));//20 assertEquals(31, in.readBits(JBBPBitNumber.BITS_5)); assertEquals(3, in.getCounter()); assertEquals(2, in.getBufferedBitsNumber()); assertEquals(0, in.readBits(JBBPBitNumber.BITS_6));//30 assertEquals(63, in.readBits(JBBPBitNumber.BITS_6)); assertEquals(5, in.getCounter()); assertEquals(6, in.getBufferedBitsNumber()); assertEquals(0, in.readBits(JBBPBitNumber.BITS_7));//42 assertEquals(0x73, in.readBits(JBBPBitNumber.BITS_7)); assertEquals(7, in.getCounter()); assertEquals(0, in.getBufferedBitsNumber()); assertEquals(0, in.readBits(JBBPBitNumber.BITS_8));//56 assertEquals(8, in.getCounter()); assertEquals(0, in.getBufferedBitsNumber()); assertEquals(0xF9, in.readBits(JBBPBitNumber.BITS_8));//64 assertEquals(9, in.getCounter()); assertEquals(0, in.getBufferedBitsNumber()); assertEquals(-1, in.read()); assertEquals(9, in.getCounter()); } @Test public void testReadStream_BitByBit() throws IOException { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {0x01})); assertEquals(1, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.getCounter()); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(-1, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(1, in.getCounter()); } @Test public void testReadStream_7bits_Default() throws IOException { final JBBPBitInputStream in = new JBBPBitInputStream( new ByteArrayInputStream(JBBPUtils.str2bin("11011100", JBBPBitOrder.MSB0))); assertEquals(0x3B, in.readBits(JBBPBitNumber.BITS_7)); assertEquals(0, in.getCounter()); } @Test public void testReadStream_AsBytes() throws IOException { final byte[] testarray = JBBPUtils.str2bin(TEST_BYTES, JBBPBitOrder.MSB0); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(testarray)); for (int i = 0; i < testarray.length; i++) { assertEquals(testarray[i] & 0xFF, in.read(), "Byte " + i); } assertEquals(9, in.getCounter()); assertEquals(-1, in.read()); } @Test public void testReadStream_AsArray() throws IOException { final byte[] testarray = JBBPUtils.str2bin(TEST_BYTES, JBBPBitOrder.MSB0); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(testarray)); final byte[] read = new byte[9]; assertEquals(9, in.read(read)); assertEquals(-1, in.read()); assertEquals(9, in.getCounter()); assertArrayEquals(testarray, read); } @Test public void testReadStream_AsPartOfArray() throws IOException { final byte[] testarray = JBBPUtils.str2bin(TEST_BYTES, JBBPBitOrder.MSB0); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(testarray)); final byte[] buff = new byte[27]; assertEquals(5, in.read(buff, 9, 5)); assertEquals(5, in.getCounter()); assertEquals(3, in.read()); for (int i = 0; i < 9; i++) { assertEquals(0, buff[i]); } for (int i = 9; i < 14; i++) { assertEquals(testarray[i - 9], buff[i]); } for (int i = 14; i < 27; i++) { assertEquals(0, buff[i]); } } @Test public void testReadStream_AsPartOfArray_MSB0() throws IOException { final byte[] testarray = JBBPUtils.str2bin(TEST_BYTES, JBBPBitOrder.MSB0); final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(testarray), JBBPBitOrder.MSB0); final byte[] buff = new byte[27]; assertEquals(5, in.read(buff, 9, 5)); assertEquals(5, in.getCounter()); assertEquals(0xC0, in.read()); for (int i = 0; i < 9; i++) { assertEquals(0, buff[i]); } for (int i = 9; i < 14; i++) { assertEquals(JBBPUtils.reverseBitsInByte(testarray[i - 9]), buff[i]); } for (int i = 14; i < 27; i++) { assertEquals(0, buff[i]); } } @Test public void testReadStream_AsPartOfArray_1bitOffset() throws IOException { final byte[] testarray = JBBPUtils.str2bin(TEST_BYTES, JBBPBitOrder.MSB0); final JBBPBitInputStream in = new JBBPBitInputStream( new ByteArrayInputStream(JBBPUtils.str2bin(TEST_BYTES_EXTRABIT, JBBPBitOrder.MSB0))); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); final byte[] read = new byte[27]; assertEquals(5, in.read(read, 9, 5)); assertEquals(5, in.getCounter()); assertEquals(3, in.read()); for (int i = 0; i < 9; i++) { assertEquals(0, read[i]); } for (int i = 9; i < 14; i++) { assertEquals(testarray[i - 9], read[i]); } for (int i = 14; i < 27; i++) { assertEquals(0, read[i]); } } @Test public void testMarkForReadBits() throws IOException { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( JBBPUtils.str2bin("10010110_00101000_10101010", JBBPBitOrder.MSB0))); assertEquals(0x9, in.readBits(JBBPBitNumber.BITS_4)); assertEquals(0x6, in.readBits(JBBPBitNumber.BITS_6)); assertTrue(in.markSupported()); in.mark(1024); assertEquals(5, in.readBits(JBBPBitNumber.BITS_3)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_3)); assertEquals(0x55, in.read()); in.reset(); assertEquals(5, in.readBits(JBBPBitNumber.BITS_3)); assertEquals(0, in.readBits(JBBPBitNumber.BITS_3)); assertEquals(0x55, in.read()); assertEquals(-1, in.read()); } @Test public void testReadBits_ExceptionForWrongArgument() throws Exception { final JBBPBitInputStream inLe = new JBBPBitInputStream( new ByteArrayInputStream(JBBPUtils.str2bin(TEST_BYTES, JBBPBitOrder.LSB0))); try { inLe.readBits(JBBPBitNumber.decode(0)); fail("Must throw IAE"); } catch (IllegalArgumentException ex) { } try { inLe.readBits(JBBPBitNumber.decode(-5)); fail("Must throw IAE"); } catch (IllegalArgumentException ex) { } try { inLe.readBits(JBBPBitNumber.decode(9)); fail("Must throw IAE"); } catch (IllegalArgumentException ex) { } } @Test public void testSkipBytes() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(JBBPUtils .str2bin("01010101_01010101_01010101_00011000_01010101_01010101_00000001", JBBPBitOrder.MSB0))); assertEquals(3, in.skip(3)); assertEquals(0x18, in.read()); assertEquals(2, in.skip(2)); assertEquals(0x80, in.read()); assertEquals(-1, in.read()); } @Test public void testAlignBytes() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(JBBPUtils .str2bin("01010101_01010101_01011101_00011000_01010101_01010101_00000001", JBBPBitOrder.MSB0))); assertEquals(0xAA, in.read()); in.align(3); assertEquals(0x18, in.read()); in.align(6); assertEquals(0x80, in.read()); assertEquals(-1, in.read()); try { in.align(10); fail("Must throw EOF"); } catch (EOFException ex) { } } @Test public void testRead_WithoutOffset() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(JBBPUtils .str2bin("01010111_01010111_01010111_00011000_01010101_01100011_00000001", JBBPBitOrder.MSB0))); assertEquals(0xEA, in.read()); assertEquals(0xEA, in.read()); assertEquals(0xEA, in.read()); assertEquals(0x18, in.read()); assertEquals(0xAA, in.read()); assertEquals(0xC6, in.read()); assertEquals(0x80, in.read()); assertEquals(-1, in.read()); } @Test public void testRead_1bitOffset() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(JBBPUtils .str2bin("1 01010111_01010111_01010111_00011000_01010111_01100011_00101101", JBBPBitOrder.MSB0))); assertEquals(1, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0xEA, in.read()); assertEquals(0xEA, in.read()); assertEquals(0xEA, in.read()); assertEquals(0x18, in.read()); assertEquals(0xEA, in.read()); assertEquals(0xC6, in.read()); assertEquals(0xB4, in.read()); assertEquals(0, in.read()); assertEquals(-1, in.read()); } @Test public void testSkipBytes_1bitOffset() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(JBBPUtils .str2bin("0 01010101_01010101_01010101 00011000_01010101_010110110_0000001", JBBPBitOrder.MSB0))); assertEquals(0, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(0, in.getCounter()); assertEquals(3, in.skip(3)); assertEquals(3, in.getCounter()); assertEquals(0x18, in.read()); assertEquals(4, in.getCounter()); assertEquals(1, in.skip(1)); assertEquals(5, in.getCounter()); assertEquals(0xDA, in.read()); assertEquals(6, in.getCounter()); assertEquals(0x80, in.read()); assertEquals(7, in.getCounter()); assertEquals(0, in.read()); assertEquals(8, in.getCounter()); assertEquals(-1, in.read()); assertEquals(8, in.getCounter()); } @Test public void testReadArray_Bits_WholeStream() throws Exception { JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( JBBPUtils.str2bin("00101001_11001011_10111110", JBBPBitOrder.LSB0))); assertArrayEquals(new byte[] {1, 2, 2, 0, 3, 2, 0, 3, 2, 3, 3, 2}, in.readBitsArray(-1, JBBPBitNumber.BITS_2)); assertEquals(3, in.getCounter()); final Random rnd = new Random(1234); final byte[] buff = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE]; rnd.nextBytes(buff); in = new JBBPBitInputStream(new ByteArrayInputStream(buff)); final byte[] read = in.readBitsArray(-1, JBBPBitNumber.BITS_8); assertEquals(buff.length, in.getCounter()); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE, read.length); for (int i = 0; i < read.length; i++) { assertEquals(buff[i], read[i]); } final byte[] big = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128]; rnd.nextBytes(big); in = new JBBPBitInputStream(new ByteArrayInputStream(big)); final byte[] readbig = in.readBitsArray(-1, JBBPBitNumber.BITS_8); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128, readbig.length); for (int i = 0; i < readbig.length; i++) { assertEquals(big[i], readbig[i]); } } @Test public void testReadArray_Bits_ThreeItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( JBBPUtils.str2bin("00101001_11001011_10111110", JBBPBitOrder.LSB0))); assertArrayEquals(new byte[] {1, 2, 2}, in.readBitsArray(3, JBBPBitNumber.BITS_2)); } @Test public void testReadArray_Bits_EOF() throws Exception { assertThrows(EOFException.class, () -> { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( JBBPUtils.str2bin("00101001_11001011_10111110", JBBPBitOrder.LSB0))); in.readBitsArray(58, JBBPBitNumber.BITS_2); }); } @Test public void testReadArray_Bytes_WholeStream() throws Exception { JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 0}, in.readByteArray(-1)); assertEquals(8, in.getCounter()); final Random rnd = new Random(1234); final byte[] buff = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE]; rnd.nextBytes(buff); in = new JBBPBitInputStream(new ByteArrayInputStream(buff)); final byte[] read = in.readByteArray(-1); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE, read.length); for (int i = 0; i < read.length; i++) { assertEquals(buff[i], read[i]); } final byte[] big = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128]; rnd.nextBytes(big); in = new JBBPBitInputStream(new ByteArrayInputStream(big)); final byte[] readbig = in.readByteArray(-1); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128, readbig.length); for (int i = 0; i < readbig.length; i++) { assertEquals(big[i], readbig[i]); } } @Test public void testReadArray_Bytes_ThreeItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); assertArrayEquals(new byte[] {1, 2, 3,}, in.readByteArray(3)); } @Test public void testReadArray_Bytes_BIG_ENDIAN_ThreeItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); assertArrayEquals(new byte[] {1, 2, 3,}, in.readByteArray(3, JBBPByteOrder.BIG_ENDIAN)); } @Test public void testReadArray_Bytes_LITTLE_ENDIAN_ThreeItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); assertArrayEquals(new byte[] {3, 2, 1,}, in.readByteArray(3, JBBPByteOrder.LITTLE_ENDIAN)); } @Test public void testReadArray_Bytes_EOF() throws Exception { assertThrows(EOFException.class, () -> { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); in.readByteArray(259); }); } @Test public void testReadArray_Short_WholeStream() throws Exception { JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); assertArrayEquals(new short[] {0x0102, 0x0304, 0x0506, 0x0700}, in.readShortArray(-1, JBBPByteOrder.BIG_ENDIAN)); assertEquals(8, in.getCounter()); final Random rnd = new Random(1234); final byte[] buff = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 2]; rnd.nextBytes(buff); in = new JBBPBitInputStream(new ByteArrayInputStream(buff)); final short[] read = in.readShortArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(buff.length, in.getCounter()); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE, read.length); for (int i = 0; i < read.length; i++) { final int val = read[i]; final int j = i * 2; assertEquals(val, ((buff[j] << 8) | ((buff[j + 1] & 0xFF)))); } final byte[] big = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128]; rnd.nextBytes(big); in = new JBBPBitInputStream(new ByteArrayInputStream(big)); final short[] readbig = in.readShortArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 64, readbig.length); for (int i = 0; i < readbig.length; i++) { final int val = readbig[i]; final int j = i * 2; assertEquals(val, ((big[j] << 8) | ((big[j + 1] & 0xFF)))); } } @Test public void testReadArray_UShort_WholeStream() throws Exception { JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); assertArrayEquals(new char[] {0x0102, 0x0304, 0x0506, 0x0700}, in.readUShortArray(-1, JBBPByteOrder.BIG_ENDIAN)); assertEquals(8, in.getCounter()); final Random rnd = new Random(1234); final byte[] buff = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 2]; rnd.nextBytes(buff); in = new JBBPBitInputStream(new ByteArrayInputStream(buff)); final char[] read = in.readUShortArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(buff.length, in.getCounter()); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE, read.length); for (int i = 0; i < read.length; i++) { final int val = read[i]; final int j = i * 2; assertEquals(val, ((buff[j] << 8) | ((buff[j + 1] & 0xFF))) & 0xFFFF); } final byte[] big = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128]; rnd.nextBytes(big); in = new JBBPBitInputStream(new ByteArrayInputStream(big)); final char[] readbig = in.readUShortArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 64, readbig.length); for (int i = 0; i < readbig.length; i++) { final int val = readbig[i]; final int j = i * 2; assertEquals(val, ((big[j] << 8) | ((big[j + 1] & 0xFF))) & 0xFFFF); } } @Test public void testReadArray_Short_TwoItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); assertArrayEquals(new short[] {0x0102, 0x0304}, in.readShortArray(2, JBBPByteOrder.BIG_ENDIAN)); assertEquals(4, in.getCounter()); } @Test public void testReadArray_UShort_TwoItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); assertArrayEquals(new char[] {0x0102, 0x0304}, in.readUShortArray(2, JBBPByteOrder.BIG_ENDIAN)); assertEquals(4, in.getCounter()); } @Test public void testReadArray_Short_EOF() throws Exception { assertThrows(EOFException.class, () -> { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 2, 3, 4, 5, 6, 7, 0})); in.readShortArray(259, JBBPByteOrder.BIG_ENDIAN); }); } @Test public void testReadArray_Int_WholeStream() throws Exception { JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01})); assertArrayEquals(new int[] {0x01020304, 0x05060700, 0xFECABE01}, in.readIntArray(-1, JBBPByteOrder.BIG_ENDIAN)); assertEquals(12, in.getCounter()); final Random rnd = new Random(1234); final byte[] buff = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 4]; rnd.nextBytes(buff); in = new JBBPBitInputStream(new ByteArrayInputStream(buff)); final int[] read = in.readIntArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(buff.length, in.getCounter()); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE, read.length); for (int i = 0; i < read.length; i++) { final int val = read[i]; final int j = i * 4; assertEquals(val, ((buff[j] << 24) | ((buff[j + 1] & 0xFF) << 16) | ((buff[j + 2] & 0xFF) << 8) | (buff[j + 3] & 0xFF))); } final byte[] big = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128]; rnd.nextBytes(big); in = new JBBPBitInputStream(new ByteArrayInputStream(big)); final int[] readbig = in.readIntArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 32, readbig.length); for (int i = 0; i < readbig.length; i++) { final int val = readbig[i]; final int j = i * 4; assertEquals(val, ((big[j] << 24) | ((big[j + 1] & 0xFF) << 16) | ((big[j + 2] & 0xFF) << 8) | (big[j + 3] & 0xFF))); } } @Test public void testReadArray_Float_WholeStream() throws Exception { JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01})); assertArrayEquals(new float[] {2.3879393E-38f, 6.3019354E-36f, -1.3474531E38f}, in.readFloatArray(-1, JBBPByteOrder.BIG_ENDIAN), TestUtils.FLOAT_DELTA); assertEquals(12, in.getCounter()); final Random rnd = new Random(1234); final byte[] buff = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 4]; rnd.nextBytes(buff); in = new JBBPBitInputStream(new ByteArrayInputStream(buff)); final float[] read = in.readFloatArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(buff.length, in.getCounter()); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE, read.length); for (int i = 0; i < read.length; i++) { final float val = read[i]; final int j = i * 4; assertEquals(val, Float.intBitsToFloat( (buff[j] << 24) | ((buff[j + 1] & 0xFF) << 16) | ((buff[j + 2] & 0xFF) << 8) | (buff[j + 3] & 0xFF)), TestUtils.FLOAT_DELTA); } final byte[] big = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128]; rnd.nextBytes(big); in = new JBBPBitInputStream(new ByteArrayInputStream(big)); final float[] readbig = in.readFloatArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 32, readbig.length); for (int i = 0; i < readbig.length; i++) { final float val = readbig[i]; final int j = i * 4; assertEquals(val, Float.intBitsToFloat( (big[j] << 24) | ((big[j + 1] & 0xFF) << 16) | ((big[j + 2] & 0xFF) << 8) | (big[j + 3] & 0xFF)), TestUtils.FLOAT_DELTA); } } @Test public void testReadArray_Int_TwoItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01})); assertArrayEquals(new int[] {0x01020304, 0x05060700}, in.readIntArray(2, JBBPByteOrder.BIG_ENDIAN)); assertEquals(8, in.getCounter()); } @Test public void testReadArray_Float_TwoItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01})); assertArrayEquals(new float[] {2.3879393E-38f, 6.3019354E-36f}, in.readFloatArray(2, JBBPByteOrder.BIG_ENDIAN), TestUtils.FLOAT_DELTA); assertEquals(8, in.getCounter()); } @Test public void testReadArray_Int_EOF() throws Exception { assertThrows(EOFException.class, () -> { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01})); in.readIntArray(259, JBBPByteOrder.BIG_ENDIAN); }); } @Test public void testReadArray_DoubleInt_EOF() throws Exception { assertThrows(EOFException.class, () -> { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01})); in.readFloatArray(259, JBBPByteOrder.BIG_ENDIAN); }); } @Test public void testReadArray_Long_WholeStream() throws Exception { JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4})); assertArrayEquals(new long[] {0x0102030405060700L, 0xFECABE0102030405L, 0x0607080901020304L}, in.readLongArray(-1, JBBPByteOrder.BIG_ENDIAN)); assertEquals(24, in.getCounter()); final Random rnd = new Random(1234); final byte[] buff = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 8]; rnd.nextBytes(buff); in = new JBBPBitInputStream(new ByteArrayInputStream(buff)); final long[] read = in.readLongArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(buff.length, in.getCounter()); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE, read.length); for (int i = 0; i < read.length; i++) { final long val = read[i]; final int j = i * 8; assertEquals(val, (((long) buff[j] << 56) | (((long) buff[j + 1] & 0xFFL) << 48) | (((long) buff[j + 2] & 0xFFL) << 40) | (((long) buff[j + 3] & 0xFFL) << 32) | (((long) buff[j + 4] & 0xFFL) << 24) | (((long) buff[j + 5] & 0xFFL) << 16) | (((long) buff[j + 6] & 0xFFL) << 8) | ((long) buff[j + 7] & 0xFF))); } final byte[] big = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128]; rnd.nextBytes(big); in = new JBBPBitInputStream(new ByteArrayInputStream(big)); final long[] readbig = in.readLongArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 16, readbig.length); for (int i = 0; i < readbig.length; i++) { final long val = readbig[i]; final int j = i * 8; assertEquals(val, (((long) big[j] << 56) | (((long) big[j + 1] & 0xFFL) << 48) | (((long) big[j + 2] & 0xFFL) << 40) | (((long) big[j + 3] & 0xFFL) << 32) | (((long) big[j + 4] & 0xFFL) << 24) | (((long) big[j + 5] & 0xFFL) << 16) | (((long) big[j + 6] & 0xFFL) << 8) | ((long) big[j + 7] & 0xFF))); } } @Test public void testReadArray_Double_WholeStream() throws Exception { JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4})); assertArrayEquals( new double[] {8.207880399131826E-304d, -5.730900111929792E302d, 1.268802825418157E-279d}, in.readDoubleArray(-1, JBBPByteOrder.BIG_ENDIAN), TestUtils.FLOAT_DELTA); assertEquals(24, in.getCounter()); final Random rnd = new Random(1234); final byte[] buff = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 8]; rnd.nextBytes(buff); in = new JBBPBitInputStream(new ByteArrayInputStream(buff)); final double[] read = in.readDoubleArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(buff.length, in.getCounter()); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE, read.length); for (int i = 0; i < read.length; i++) { final double val = read[i]; final int j = i * 8; assertEquals(val, Double.longBitsToDouble( ((long) buff[j] << 56) | (((long) buff[j + 1] & 0xFFL) << 48) | (((long) buff[j + 2] & 0xFFL) << 40) | (((long) buff[j + 3] & 0xFFL) << 32) | (((long) buff[j + 4] & 0xFFL) << 24) | (((long) buff[j + 5] & 0xFFL) << 16) | (((long) buff[j + 6] & 0xFFL) << 8) | ((long) buff[j + 7] & 0xFF)), TestUtils.FLOAT_DELTA); } final byte[] big = new byte[JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 128]; rnd.nextBytes(big); in = new JBBPBitInputStream(new ByteArrayInputStream(big)); final double[] readbig = in.readDoubleArray(-1, JBBPByteOrder.BIG_ENDIAN); assertEquals(JBBPBitInputStream.INITIAL_ARRAY_BUFFER_SIZE * 16, readbig.length); for (int i = 0; i < readbig.length; i++) { final double val = readbig[i]; final int j = i * 8; assertEquals(val, Double.longBitsToDouble( ((long) big[j] << 56) | (((long) big[j + 1] & 0xFFL) << 48) | (((long) big[j + 2] & 0xFFL) << 40) | (((long) big[j + 3] & 0xFFL) << 32) | (((long) big[j + 4] & 0xFFL) << 24) | (((long) big[j + 5] & 0xFFL) << 16) | (((long) big[j + 6] & 0xFFL) << 8) | ((long) big[j + 7] & 0xFF)), TestUtils.FLOAT_DELTA); } } @Test public void testReadArray_Long_TwoItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4})); assertArrayEquals(new long[] {0x0102030405060700L, 0xFECABE0102030405L}, in.readLongArray(2, JBBPByteOrder.BIG_ENDIAN)); assertEquals(16, in.getCounter()); } @Test public void testReadArray_Double_TwoItems() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4})); assertArrayEquals(new double[] {8.207880399131826E-304d, -5.730900111929792E302d}, in.readDoubleArray(2, JBBPByteOrder.BIG_ENDIAN), TestUtils.FLOAT_DELTA); assertEquals(16, in.getCounter()); } @Test public void testReadArray_Long_EOF() throws Exception { assertThrows(EOFException.class, () -> { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4})); in.readLongArray(259, JBBPByteOrder.BIG_ENDIAN); }); } @Test public void testReadArray_Double_EOF() throws Exception { assertThrows(EOFException.class, () -> { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4})); in.readDoubleArray(259, JBBPByteOrder.BIG_ENDIAN); }); } @Test public void testResetCounter_ForStartOfStream() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4})); in.resetCounter(); assertEquals(1, in.readByte()); assertEquals(1, in.getCounter()); } @Test public void testResetCounter_ForCachedBits() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 0, (byte) 0xFE, (byte) 0xCA, (byte) 0xBE, (byte) 0x01, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4})); assertEquals(1, in.readBits(JBBPBitNumber.BITS_3)); assertEquals(0, in.getCounter()); assertTrue(in.getBufferedBitsNumber() != 0); in.resetCounter(); assertEquals(0, in.getCounter()); assertEquals(0, in.getBufferedBitsNumber()); assertEquals(2, in.readByte()); } @Test public void testReadBooleanArray_EOF() throws Exception { assertThrows(EOFException.class, () -> asInputStream(1, 2, 3, 4, 5, 6).readBoolArray(256)); } @Test public void testReadBooleanArray_WholeStream() throws Exception { final byte[] testarray = new byte[16384]; final Random rnd = new Random(1234); for (int i = 0; i < testarray.length; i++) { testarray[i] = rnd.nextInt(100) > 50 ? 0 : (byte) rnd.nextInt(0x100); } final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(testarray)); final boolean[] read = in.readBoolArray(-1); assertEquals(16384, in.getCounter()); assertEquals(testarray.length, read.length); for (int i = 0; i < read.length; i++) { assertEquals(read[i], (testarray[i] != 0)); } } @Test public void testReadNotFullByteArrayAfterBitReading() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream( new ByteArrayInputStream(new byte[] {(byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0xDD})); assertEquals(0x2, in.readBits(JBBPBitNumber.BITS_4)); assertEquals(0, in.getCounter()); final byte[] readarray = new byte[6]; final int read = in.read(readarray, 0, readarray.length); assertEquals(4, read); assertEquals(4, in.getCounter()); assertArrayEquals(new byte[] {(byte) 0x41, (byte) 0x63, (byte) 0xD5, (byte) 0x0D, 0, 0}, readarray); } @Test public void testReadNotFullByteArrayAfterBitReading_MSB0() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream( new ByteArrayInputStream(new byte[] {(byte) 0x12, (byte) 0x34, (byte) 0x56, (byte) 0xDD}), JBBPBitOrder.MSB0); assertEquals(0x8, in.readBits(JBBPBitNumber.BITS_4)); final byte[] readarray = new byte[6]; final int read = in.read(readarray, 0, readarray.length); assertEquals(4, read); assertEquals(4, in.getCounter()); assertArrayEquals(new byte[] {(byte) 0xC4, (byte) 0xA2, (byte) 0xB6, (byte) 0x0B, 0, 0}, readarray); } @Test public void testCheckThatCounterResetDoesntResetFullBitBuffer() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream(new byte[] {1, 0x7F})); assertEquals(0, in.getBufferedBitsNumber()); assertTrue(in.hasAvailableData()); assertEquals(0, in.getCounter()); assertEquals(8, in.getBufferedBitsNumber()); in.resetCounter(); assertEquals(0, in.getCounter()); assertEquals(8, in.getBufferedBitsNumber()); assertEquals(1, in.readBits(JBBPBitNumber.BITS_1)); assertEquals(7, in.getBufferedBitsNumber()); assertEquals(0, in.getCounter()); in.resetCounter(); assertEquals(0, in.getBufferedBitsNumber()); assertEquals(0x7F, in.read()); assertEquals(1, in.getCounter()); assertEquals(-1, in.read()); assertEquals(1, in.getCounter()); } @Test public void testByteCounterWithHasAvailableData() throws Exception { final JBBPBitInputStream in = new JBBPBitInputStream(new ByteArrayInputStream( new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})); assertTrue(in.hasAvailableData()); assertEquals(0, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(0, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(0, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(0, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(0, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(0, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(0, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(0, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(1, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(1, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(1, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(1, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(1, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(1, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(1, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(1, in.getCounter()); assertTrue(in.readBits(JBBPBitNumber.BITS_1) >= 0); assertTrue(in.hasAvailableData()); assertEquals(2, in.getCounter()); } } JBBPBitOutputStreamTest.java000066400000000000000000000513071445000440600337210ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/io/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.utils.JBBPUtils; import com.igormaznitsa.jbbp.utils.SpecialTestUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Random; import org.junit.jupiter.api.Test; public class JBBPBitOutputStreamTest { private static byte[] writeString(final JBBPByteOrder order, final String str) throws IOException { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(bos); out.writeString(str, order); out.close(); return bos.toByteArray(); } private static byte[] writeStrings(final JBBPByteOrder order, final String... array) throws IOException { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(bos); out.writeStringArray(array, order); out.close(); return bos.toByteArray(); } @Test public void testResetCounter_BitBufferEmpty() throws Exception { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); assertEquals(0L, out.getCounter()); out.write(1); assertEquals(0, out.getBufferedBitsNumber()); assertEquals(1L, out.getCounter()); out.resetCounter(); assertEquals(0, out.getBufferedBitsNumber()); assertEquals(0L, out.getCounter()); } @Test public void testResetCounter_BitBufferNotEmpty() throws Exception { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); assertEquals(0L, out.getCounter()); out.write(1); out.writeBits(3, JBBPBitNumber.BITS_7); assertTrue(out.getBufferedBitsNumber() > 0); assertEquals(1L, out.getCounter()); out.resetCounter(); assertEquals(0, out.getBufferedBitsNumber()); assertEquals(0L, out.getCounter()); out.write(2); out.close(); assertArrayEquals(new byte[] {1, 2}, buff.toByteArray()); } @Test public void testWrite9bit_MSB() throws Exception { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); out.writeBits(0x9, JBBPBitNumber.BITS_4); out.writeBits(0x1D, JBBPBitNumber.BITS_5); out.close(); assertArrayEquals(new byte[] {(byte) 0xD9, 1}, buff.toByteArray()); } @Test public void testGetOrder() throws Exception { assertEquals(JBBPBitOrder.MSB0, new JBBPBitOutputStream(null, JBBPBitOrder.MSB0).getBitOrder()); assertEquals(JBBPBitOrder.LSB0, new JBBPBitOutputStream(null, JBBPBitOrder.LSB0).getBitOrder()); assertEquals(JBBPBitOrder.LSB0, new JBBPBitOutputStream(null).getBitOrder()); } @Test public void testWriteBytes_BIG_ENDIAN_wholeArray() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeBytes(new byte[] {1, 2, 3, 4, 5}, -1, JBBPByteOrder.BIG_ENDIAN); out.flush(); assertEquals(5, out.getCounter()); assertArrayEquals(new byte[] {1, 2, 3, 4, 5}, outBuffer.toByteArray()); } @Test public void testWriteBytes_BIG_ENDIAN_zeroArray() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeBytes(new byte[] {1, 2, 3, 4, 5}, 0, JBBPByteOrder.BIG_ENDIAN); out.flush(); assertEquals(0, out.getCounter()); assertArrayEquals(new byte[0], outBuffer.toByteArray()); } @Test public void testWriteBytes_BIG_ENDIAN_threeItemsOfArray() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeBytes(new byte[] {1, 2, 3, 4, 5}, 3, JBBPByteOrder.BIG_ENDIAN); out.flush(); assertEquals(3, out.getCounter()); assertArrayEquals(new byte[] {1, 2, 3}, outBuffer.toByteArray()); } @Test public void testWriteBytes_LITTLE_ENDIAN_threeItemsOfArray() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeBytes(new byte[] {1, 2, 3, 4, 5}, 3, JBBPByteOrder.LITTLE_ENDIAN); out.flush(); assertEquals(3, out.getCounter()); assertArrayEquals(new byte[] {3, 2, 1}, outBuffer.toByteArray()); } @Test public void testWriteBytes_LITTLE_ENDIAN_zeroArray() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeBytes(new byte[] {1, 2, 3, 4, 5}, 0, JBBPByteOrder.LITTLE_ENDIAN); out.flush(); assertEquals(0, out.getCounter()); assertArrayEquals(new byte[0], outBuffer.toByteArray()); } @Test public void testWriteBytes_LITTLE_ENDIAN_wholeArray() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeBytes(new byte[] {1, 2, 3, 4, 5}, -1, JBBPByteOrder.LITTLE_ENDIAN); out.flush(); assertEquals(5, out.getCounter()); assertArrayEquals(new byte[] {5, 4, 3, 2, 1}, outBuffer.toByteArray()); } @Test public void testWriteByte() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.write(0x12); assertEquals(1, out.getCounter()); out.flush(); assertEquals(1, out.getCounter()); assertArrayEquals(new byte[] {0x12}, outBuffer.toByteArray()); } @Test public void testWriteByte_MSB0() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer, JBBPBitOrder.MSB0); assertEquals(0, out.getCounter()); out.write(0x12); assertEquals(1, out.getCounter()); out.flush(); assertEquals(1, out.getCounter()); assertArrayEquals(new byte[] {0x48}, outBuffer.toByteArray()); } @Test public void testWriteShort_BigEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeShort(0x1234, JBBPByteOrder.BIG_ENDIAN); assertEquals(2, out.getCounter()); out.flush(); assertEquals(2, out.getCounter()); assertArrayEquals(new byte[] {0x12, 0x34}, outBuffer.toByteArray()); } @Test public void testWriteShort_BigEndian_MSB0() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer, JBBPBitOrder.MSB0); assertEquals(0, out.getCounter()); out.writeShort(0x1234, JBBPByteOrder.BIG_ENDIAN); assertEquals(2, out.getCounter()); out.flush(); assertEquals(2, out.getCounter()); assertArrayEquals(new byte[] {0x48, 0x2C}, outBuffer.toByteArray()); } @Test public void testWriteShort_LittleEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeShort(0x1234, JBBPByteOrder.LITTLE_ENDIAN); assertEquals(2, out.getCounter()); out.flush(); assertEquals(2, out.getCounter()); assertArrayEquals(new byte[] {0x34, 0x12}, outBuffer.toByteArray()); } @Test public void testWriteShort_LittleEndian_MSB0() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer, JBBPBitOrder.MSB0); assertEquals(0, out.getCounter()); out.writeShort(0x1234, JBBPByteOrder.LITTLE_ENDIAN); assertEquals(2, out.getCounter()); out.flush(); assertEquals(2, out.getCounter()); assertArrayEquals(new byte[] {0x2C, 0x48}, outBuffer.toByteArray()); } @Test public void testWriteInt_BigEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeInt(0x12345678, JBBPByteOrder.BIG_ENDIAN); assertEquals(4, out.getCounter()); out.flush(); assertEquals(4, out.getCounter()); assertArrayEquals(new byte[] {0x12, 0x34, 0x56, 0x78}, outBuffer.toByteArray()); } @Test public void testWriteFloat_BigEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeFloat(9.2345f, JBBPByteOrder.BIG_ENDIAN); assertEquals(4, out.getCounter()); out.flush(); assertEquals(4, out.getCounter()); assertArrayEquals(new byte[] {(byte) 65, (byte) 19, (byte) -64, (byte) -125}, outBuffer.toByteArray()); } @Test public void testWriteInt_LittleEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeInt(0x12345678, JBBPByteOrder.LITTLE_ENDIAN); assertEquals(4, out.getCounter()); out.flush(); assertEquals(4, out.getCounter()); assertArrayEquals(new byte[] {0x78, 0x56, 0x34, 0x12}, outBuffer.toByteArray()); } @Test public void testWriteFloat_LittleEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeFloat(9.2345f, JBBPByteOrder.LITTLE_ENDIAN); assertEquals(4, out.getCounter()); out.flush(); assertEquals(4, out.getCounter()); assertArrayEquals(new byte[] {(byte) -125, (byte) -64, (byte) 19, (byte) 65}, outBuffer.toByteArray()); } @Test public void testWriteStringArray_BigEndian() throws Exception { assertArrayEquals(new byte[] {(byte) 0xFF, 0, 0x03, 65, 66, 67}, writeStrings(JBBPByteOrder.BIG_ENDIAN, null, "", "ABC")); } @Test public void testWriteStringArray_LittleEndian() throws Exception { assertArrayEquals(new byte[] {(byte) 0xFF, 0, 0x03, 65, 66, 67}, writeStrings(JBBPByteOrder.LITTLE_ENDIAN, null, "", "ABC")); } @Test public void testWriteString_BigEndian_Null() throws Exception { assertArrayEquals(new byte[] {(byte) 0xFF}, writeString(JBBPByteOrder.BIG_ENDIAN, null)); } @Test public void testWriteString_BigEndian_Empty() throws Exception { assertArrayEquals(new byte[] {0}, writeString(JBBPByteOrder.BIG_ENDIAN, "")); } @Test public void testWriteString_BigEndian_ShortString() throws Exception { assertArrayEquals(new byte[] {0x03, 65, 66, 67}, writeString(JBBPByteOrder.BIG_ENDIAN, "ABC")); } @Test public void testWriteString_LittleEndian_Null() throws Exception { assertArrayEquals(new byte[] {(byte) 0xFF}, writeString(JBBPByteOrder.LITTLE_ENDIAN, null)); } @Test public void testWriteString_LittleEndian_Empty() throws Exception { assertArrayEquals(new byte[] {0}, writeString(JBBPByteOrder.LITTLE_ENDIAN, "")); } @Test public void testWriteString_LittleEndian_ShortString() throws Exception { assertArrayEquals(new byte[] {0x03, 65, 66, 67}, writeString(JBBPByteOrder.LITTLE_ENDIAN, "ABC")); } @Test public void testWriteLong_BigEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeLong(0x12345678AABBCCDDL, JBBPByteOrder.BIG_ENDIAN); assertEquals(8, out.getCounter()); out.flush(); assertEquals(8, out.getCounter()); assertArrayEquals( new byte[] {0x12, 0x34, 0x56, 0x78, (byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD}, outBuffer.toByteArray()); } @Test public void testWriteDouble_BigEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeDouble(1.12345678234324d, JBBPByteOrder.BIG_ENDIAN); assertEquals(8, out.getCounter()); out.flush(); assertEquals(8, out.getCounter()); assertArrayEquals( new byte[] {(byte) 63, (byte) -15, (byte) -7, (byte) -83, (byte) -47, (byte) -86, (byte) 35, (byte) 64}, outBuffer.toByteArray()); } @Test public void testWriteLong_LittleEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeLong(0x12345678AABBCCDDL, JBBPByteOrder.LITTLE_ENDIAN); assertEquals(8, out.getCounter()); out.flush(); assertEquals(8, out.getCounter()); assertArrayEquals( new byte[] {(byte) 0xDD, (byte) 0xCC, (byte) 0XBB, (byte) 0xAA, 0x78, 0x56, 0x34, 0x12}, outBuffer.toByteArray()); } @Test public void testWriteDouble_LittleEndian() throws Exception { final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBuffer); assertEquals(0, out.getCounter()); out.writeDouble(-23.345213455d, JBBPByteOrder.LITTLE_ENDIAN); assertEquals(8, out.getCounter()); out.flush(); assertEquals(8, out.getCounter()); assertArrayEquals( new byte[] {(byte) 58, (byte) 93, (byte) -77, (byte) -24, (byte) 95, (byte) 88, (byte) 55, (byte) -64}, outBuffer.toByteArray()); } @Test public void testWriteArrayPartly() throws Exception { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); assertEquals(0, out.getCounter()); final Random rnd = new Random(12345); final byte[] array = new byte[1024]; rnd.nextBytes(array); out.write(array, 100, 572); assertEquals(572, out.getCounter()); out.close(); assertArrayEquals(SpecialTestUtils.copyOfRange(array, 100, 672), buff.toByteArray()); } @Test public void testWriteArrayPartlyWithOffset1Bit() throws Exception { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); assertEquals(0, out.getCounter()); final byte[] ORIG_ARRAY = JBBPUtils .str2bin("10101001 01100100 10000101 01000010 10010100 10010010 00100100 10001010", JBBPBitOrder.MSB0); final byte[] ARRAY_1BIT_OFFSET = JBBPUtils.str2bin("1 10000101 01000010 10010100 10010010 00100100", JBBPBitOrder.MSB0); out.writeBits(1, JBBPBitNumber.BITS_1); assertEquals(0, out.getCounter()); out.write(ORIG_ARRAY, 2, 5); assertEquals(5, out.getCounter()); out.close(); assertArrayEquals(ARRAY_1BIT_OFFSET, buff.toByteArray()); } @Test public void testWriteWholeArray() throws Exception { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); final Random rnd = new Random(12345); final byte[] array = new byte[1024]; rnd.nextBytes(array); out.write(array); assertEquals(1024, out.getCounter()); out.close(); assertArrayEquals(array, buff.toByteArray()); } @Test public void testGetBufferedBitsNumber() throws Exception { final JBBPBitOutputStream out = new JBBPBitOutputStream(new ByteArrayOutputStream()); out.writeBits(1, JBBPBitNumber.BITS_1); out.writeBits(3, JBBPBitNumber.BITS_2); assertEquals(3, out.getBufferedBitsNumber()); assertEquals(0, out.getCounter()); } @Test public void testGetBitBuffer() throws Exception { final JBBPBitOutputStream out = new JBBPBitOutputStream(new ByteArrayOutputStream()); out.writeBits(1, JBBPBitNumber.BITS_1); out.writeBits(3, JBBPBitNumber.BITS_2); assertEquals(7, out.getBitBuffer()); assertEquals(0, out.getCounter()); } @Test public void testWriteBit_ErrorForZeroSize() throws Exception { assertThrows(IllegalArgumentException.class, () -> new JBBPBitOutputStream(new ByteArrayOutputStream()) .writeBits(4, JBBPBitNumber.decode(0))); } @Test public void testWriteBit_ErrorForNegativeSize() throws Exception { assertThrows(IllegalArgumentException.class, () -> new JBBPBitOutputStream(new ByteArrayOutputStream()) .writeBits(4, JBBPBitNumber.decode(-1))); } @Test public void testWriteBit_ErrorForTooBigSize() throws Exception { assertThrows(IllegalArgumentException.class, () -> new JBBPBitOutputStream(new ByteArrayOutputStream()) .writeBits(4, JBBPBitNumber.decode(9))); } @Test public void testWriteWholeArrayWith1Bit() throws Exception { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); final byte[] ORIG_ARRAY = JBBPUtils .str2bin("10101001 01100100 10000101 01000010 10010100 10010010 00100100 10001010", JBBPBitOrder.MSB0); final byte[] ORIG_ARRAY_1BIT_OFFSET = JBBPUtils .str2bin("1 10101001 01100100 10000101 01000010 10010100 10010010 00100100 10001010", JBBPBitOrder.MSB0); out.writeBits(1, JBBPBitNumber.BITS_1); out.write(ORIG_ARRAY); assertEquals(8, out.getCounter()); out.close(); assertArrayEquals(ORIG_ARRAY_1BIT_OFFSET, buff.toByteArray()); } @Test public void testWriteBit_BitByBit() throws IOException { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); assertEquals(0, out.getCounter()); out.writeBits(1, JBBPBitNumber.BITS_1); assertEquals(1, out.getCounter()); out.close(); assertArrayEquals(new byte[] {(byte) 0x80}, buff.toByteArray()); } @Test public void testWriteBit_BitByBit_MSB0() throws IOException { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff, JBBPBitOrder.MSB0); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); out.writeBits(0, JBBPBitNumber.BITS_1); assertEquals(0, out.getCounter()); out.writeBits(1, JBBPBitNumber.BITS_1); assertEquals(1, out.getCounter()); out.close(); assertArrayEquals(new byte[] {(byte) 0x01}, buff.toByteArray()); } @Test public void testWrite() throws Exception { final ByteArrayOutputStream buff = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(buff); for (int i = 0; i < 256; ) { out.write(i++); out.writeBits(i++, JBBPBitNumber.BITS_8); } assertEquals(256, out.getCounter()); final byte[] written = buff.toByteArray(); assertEquals(256, written.length); for (int i = 0; i < 256; i++) { assertEquals(i, written[i] & 0xFF, "Pos " + i); } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/io/JBBPOutTest.java000066400000000000000000001474331445000440600314420ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.io; import static com.igormaznitsa.jbbp.io.JBBPOut.BeginBin; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.exceptions.JBBPIllegalArgumentException; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.utils.BinAnnotationWrapper; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Field; import org.junit.jupiter.api.Test; public class JBBPOutTest { @Test public void testString() throws Exception { assertArrayEquals(new byte[] {(byte) 0xFF}, BeginBin().String(null).End().toByteArray()); assertArrayEquals(new byte[] {0}, BeginBin().String("").End().toByteArray()); assertArrayEquals(new byte[] {3, 65, 66, 67}, BeginBin().String("ABC").End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0xFF}, BeginBin(JBBPBitOrder.LSB0).String(null).End().toByteArray()); assertArrayEquals(new byte[] {0}, BeginBin(JBBPBitOrder.LSB0).String("").End().toByteArray()); assertArrayEquals(new byte[] {3, 65, 66, 67}, BeginBin(JBBPBitOrder.LSB0).String("ABC").End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0xFF}, BeginBin(JBBPBitOrder.MSB0).String(null).End().toByteArray()); assertArrayEquals(new byte[] {0}, BeginBin(JBBPBitOrder.MSB0).String("").End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0xc0, (byte) 0x82, (byte) 0x42, (byte) 0xC2}, BeginBin(JBBPBitOrder.MSB0).String("ABC").End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0xFF}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).String(null).End().toByteArray()); assertArrayEquals(new byte[] {0}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).String("").End().toByteArray()); assertArrayEquals(new byte[] {3, 65, 66, 67}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).String("ABC").End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0xFF}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).String(null).End().toByteArray()); assertArrayEquals(new byte[] {0}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).String("").End().toByteArray()); assertArrayEquals(new byte[] {3, 65, 66, 67}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).String("ABC").End().toByteArray()); } @Test public void testStrings() throws Exception { assertArrayEquals(new byte[] {(byte) 0x03, 65, 66, 67, (byte) 0x00, (byte) 0xFF, 3, 48, 49, 50}, BeginBin().Strings("ABC", "", null, "012").End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0x03, 65, 66, 67, (byte) 0x00, (byte) 0xFF, 3, 48, 49, 50}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Strings("ABC", "", null, "012").End() .toByteArray()); assertArrayEquals(new byte[] {(byte) 0x03, 65, 66, 67, (byte) 0x00, (byte) 0xFF, 3, 48, 49, 50}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).Strings("ABC", "", null, "012").End() .toByteArray()); } @Test public void testBeginBin() throws Exception { assertArrayEquals(new byte[] {1}, BeginBin().Byte(1).End().toByteArray()); assertArrayEquals(new byte[] {0x02, 0x01}, BeginBin(JBBPByteOrder.LITTLE_ENDIAN).Short(0x0102).End().toByteArray()); assertArrayEquals(new byte[] {0x40, (byte) 0x80}, BeginBin(JBBPByteOrder.LITTLE_ENDIAN, JBBPBitOrder.MSB0).Short(0x0102).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0x80}, BeginBin(JBBPBitOrder.MSB0).Byte(1).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0x80}, BeginBin(1).Byte(0x80).End().toByteArray()); final ByteArrayOutputStream buffer1 = new ByteArrayOutputStream(); assertSame(buffer1, BeginBin(buffer1).End()); final ByteArrayOutputStream buffer2 = new ByteArrayOutputStream(); BeginBin(buffer2, JBBPByteOrder.LITTLE_ENDIAN, JBBPBitOrder.MSB0).Short(1234).End(); assertArrayEquals(new byte[] {(byte) 0x4b, (byte) 0x20}, buffer2.toByteArray()); } @Test public void testSkip() throws Exception { assertArrayEquals(new byte[] {(byte) 0x01, (byte) 0xFF}, BeginBin().Bit(1).Skip(0).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0x01, 0x00, (byte) 0xFF}, BeginBin().Bit(1).Skip(1).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0x01, 0x00, 0x00, (byte) 0xFF}, BeginBin().Bit(1).Skip(2).Byte(0xFF).End().toByteArray()); } @Test public void testSkip_ErrorForNegativeValue() throws Exception { assertThrows(IllegalArgumentException.class, () -> BeginBin().Skip(-1)); } @Test public void testAlign() throws Exception { assertArrayEquals(new byte[] {(byte) 0x01, (byte) 0xFF}, BeginBin().Bit(1).Align().Byte(0xFF).End().toByteArray()); } @Test public void testResetCounter() throws Exception { assertArrayEquals(new byte[] {1, 2, 0, 0, (byte) 0xFF}, BeginBin().Byte(1).ResetCounter().Byte(2).Align(3).Byte(0xFF).End().toByteArray()); } @Test public void testGetByteCounter() throws Exception { final JBBPOut out = BeginBin(); assertEquals(0, out.getByteCounter()); out.Bit(true); out.Bit(true); out.Bit(true); out.Bit(true); out.Bit(true); out.Bit(true); out.Bit(true); assertEquals(0, out.getByteCounter()); out.Bit(true); assertEquals(1, out.getByteCounter()); out.Bit(true); out.Byte(new byte[1234]); assertEquals(1235, out.getByteCounter()); out.ResetCounter(); assertEquals(0, out.getByteCounter()); out.Bit(true); assertEquals(0, out.getByteCounter()); out.End(); assertEquals(1, out.getByteCounter()); } @Test public void testAlignWithArgument() throws Exception { assertEquals(0, BeginBin().Align(2).End().toByteArray().length); assertArrayEquals(new byte[] {(byte) 0x01, (byte) 0xFF}, BeginBin().Bit(1).Align(1).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0xFF}, BeginBin().Align(3).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {0x01, (byte) 0xFF}, BeginBin().Bit(1).Align(1).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {0x01, 0x00, (byte) 0xFF}, BeginBin().Bit(1).Align(2).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {0x01, 0x00, 0x00, 0x00, (byte) 0xFF}, BeginBin().Bit(1).Align(4).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {0x01, 0x02, 0x00, 0x00, (byte) 0xFF}, BeginBin().Byte(1, 2).Align(4).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x00, 0x00, (byte) 0xFF}, BeginBin().Byte(1, 2, 3).Align(5).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x00, (byte) 0xFF}, BeginBin().Byte(1, 2, 3, 4).Align(5).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, (byte) 0xFF}, BeginBin().Byte(1, 2, 3, 4, 5).Align(5).Byte(0xFF).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0x01, 0x00, 0x02, 0x00, (byte) 0x03}, BeginBin().Align(2).Byte(1).Align(2).Byte(2).Align(2).Byte(3).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0xF1, 0x00, (byte) 0x01, 0x00, 0x02, 0x00, (byte) 0x03}, BeginBin().Byte(0xF1).Align(2).Byte(1).Align(2).Byte(2).Align(2).Byte(3).End() .toByteArray()); assertArrayEquals( new byte[] {(byte) 0xF1, 0x00, 0x00, (byte) 0x01, 0x00, 0x00, 0x02, 0x00, 00, (byte) 0x03}, BeginBin().Byte(0xF1).Align(3).Byte(1).Align(3).Byte(2).Align(3).Byte(3).End() .toByteArray()); assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x00, (byte) 0xF1}, BeginBin().Int(0x01020304).Align(5).Byte(0xF1).End().toByteArray()); assertArrayEquals(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, (byte) 0xF1}, BeginBin().Bit(1).Align(5).Byte(0xF1).End().toByteArray()); } @Test public void testEmptyArray() throws Exception { assertEquals(0, BeginBin().End().toByteArray().length); } @Test public void testByte() throws Exception { assertArrayEquals(new byte[] {-34}, BeginBin().Byte(-34).End().toByteArray()); } @Test public void testByteArrayAsInts() throws Exception { assertArrayEquals(new byte[] {1, 3, 0, 2, 4, 1, 3, 7}, BeginBin().Byte(1, 3, 0, 2, 4, 1, 3, 7).End().toByteArray()); } @Test public void testByteArrayAsByteArray() throws Exception { assertArrayEquals(new byte[] {1, 3, 0, 2, 4, 1, 3, 7}, BeginBin().Byte(new byte[] {1, 3, 0, 2, 4, 1, 3, 7}).End().toByteArray()); } @Test public void testByteArrayAsString() throws Exception { assertArrayEquals(new byte[] {(byte) 'a', (byte) 'b', (byte) 'c'}, BeginBin().Byte("abc").End().toByteArray()); } @Test public void testByteArrayAsString_RussianChars() throws Exception { assertArrayEquals(new byte[] {(byte) 0x20, (byte) 0x43, (byte) 0x41}, BeginBin().Byte("Рус").End().toByteArray()); } @Test public void testUtf8_OnlyLatinChars() throws Exception { assertArrayEquals(new byte[] {(byte) 'a', (byte) 'b', (byte) 'c'}, BeginBin().Utf8("abc").End().toByteArray()); } @Test public void testUtf8_RussianChars() throws Exception { assertArrayEquals( new byte[] {(byte) 0xD0, (byte) 0xA0, (byte) 0xD1, (byte) 0x83, (byte) 0xD1, (byte) 0x81}, BeginBin().Utf8("Рус").End().toByteArray()); } @Test public void testBit() throws Exception { assertArrayEquals(new byte[] {1}, BeginBin().Bit(1).End().toByteArray()); } @Test public void testBit_MSB0() throws Exception { assertArrayEquals(new byte[] {(byte) 0x80}, BeginBin(JBBPByteOrder.BIG_ENDIAN, JBBPBitOrder.MSB0).Bit(1).End().toByteArray()); } @Test public void testBit_LSB0() throws Exception { assertArrayEquals(new byte[] {(byte) 0x01}, BeginBin(JBBPByteOrder.BIG_ENDIAN, JBBPBitOrder.LSB0).Bit(1).End().toByteArray()); } @Test public void testBits_Int() throws Exception { assertArrayEquals(new byte[] {0xD}, BeginBin().Bits(JBBPBitNumber.BITS_4, 0xFD).End().toByteArray()); } @Test public void testBits_IntArray() throws Exception { assertArrayEquals(new byte[] {(byte) 0xED}, BeginBin().Bits(JBBPBitNumber.BITS_4, 0xFD, 0xFE).End().toByteArray()); } @Test public void testBits_ByteArray() throws Exception { assertArrayEquals(new byte[] {(byte) 0xED}, BeginBin().Bits(JBBPBitNumber.BITS_4, new byte[] {(byte) 0xFD, (byte) 0x8E}).End() .toByteArray()); } @Test public void testBitArrayAsInts() throws Exception { assertArrayEquals(new byte[] {(byte) 0xE3}, BeginBin().Bit(1, 3, 0, 2, 4, 1, 3, 7).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0x0B}, BeginBin().Bit(1, 3, 0, 7).End().toByteArray()); } @Test public void testBitArrayAsBytes() throws Exception { assertArrayEquals(new byte[] {(byte) 0xE3}, BeginBin().Bit( new byte[] {(byte) 1, (byte) 3, (byte) 0, (byte) 2, (byte) 4, (byte) 1, (byte) 3, (byte) 7}) .End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0x0B}, BeginBin().Bit(new byte[] {(byte) 1, (byte) 3, (byte) 0, (byte) 7}).End().toByteArray()); } @Test public void testBitArrayAsBooleans() throws Exception { assertArrayEquals(new byte[] {(byte) 0xE3}, BeginBin().Bit(true, true, false, false, false, true, true, true).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0x0B}, BeginBin().Bit(true, true, false, true).End().toByteArray()); } @Test public void testShort() throws Exception { assertArrayEquals(new byte[] {0x01, 0x02}, BeginBin().Short(0x0102).End().toByteArray()); } @Test public void testShort_BigEndian() throws Exception { assertArrayEquals(new byte[] {0x01, 0x02}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).Short(0x0102).End().toByteArray()); } @Test public void testShort_String_NPEForNullString() throws Exception { assertThrows(NullPointerException.class, () -> BeginBin().Short((String) null).End()); } @Test public void testShort_String_BigEndian() throws Exception { assertArrayEquals(JBBPUtils.str2UnicodeByteArray(JBBPByteOrder.BIG_ENDIAN, "Hello"), BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).Short("Hello").End().toByteArray()); } @Test public void testShort_String_LittleEndian() throws Exception { assertArrayEquals(JBBPUtils.str2UnicodeByteArray(JBBPByteOrder.LITTLE_ENDIAN, "Hello"), BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Short("Hello").End().toByteArray()); } @Test public void testShort_LittleEndian() throws Exception { assertArrayEquals(new byte[] {0x02, 0x01}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Short(0x0102).End().toByteArray()); } @Test public void testShortArray_AsIntegers() throws Exception { assertArrayEquals(new byte[] {1, 2, 3, 4}, BeginBin().Short(0x0102, 0x0304).End().toByteArray()); } @Test public void testShortArray_AsIntegers_BigEndian() throws Exception { assertArrayEquals(new byte[] {1, 2, 3, 4}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).Short(0x0102, 0x0304).End().toByteArray()); } @Test public void testShortArray_AsChars_BigEndian() throws Exception { assertArrayEquals(new byte[] {1, 2, 3, 4}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).Short(new char[] {0x0102, 0x0304}).End() .toByteArray()); } @Test public void testShortArray_AsIntegers_LittleEndian() throws Exception { assertArrayEquals(new byte[] {2, 1, 4, 3}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Short(0x0102, 0x0304).End() .toByteArray()); } @Test public void testShortArray_AsShorts() throws Exception { assertArrayEquals(new byte[] {1, 2, 3, 4}, BeginBin().Short(new short[] {(short) 0x0102, (short) 0x0304}).End().toByteArray()); } @Test public void testShortArray_AsShortArray() throws Exception { assertArrayEquals(new byte[] {1, 2, 3, 4}, BeginBin().Short(new short[] {(short) 0x0102, (short) 0x0304}).End().toByteArray()); } @Test public void testInt() throws Exception { assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04}, BeginBin().Int(0x01020304).End().toByteArray()); } @Test public void testUInt() throws Exception { assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04}, BeginBin().UInt(0x01020304).End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xF1}, BeginBin().UInt(0xFFFFFFFF1L).End().toByteArray()); } @Test public void testIntArray() throws Exception { assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, BeginBin().Int(0x01020304, 0x05060708).End().toByteArray()); } @Test public void testUIntArray() throws Exception { assertArrayEquals(new byte[] {(byte) 0xF1, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, BeginBin().UInt(0x1F1020304L, 0x05060708L).End().toByteArray()); } @Test public void testInt_BigEndian() throws Exception { assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).Int(0x01020304).End().toByteArray()); } @Test public void testUInt_BigEndian() throws Exception { assertArrayEquals(new byte[] {(byte) 0xF1, 0x02, 0x03, 0x04}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).UInt(0x1F1020304L).End().toByteArray()); } @Test public void testInt_LittleEndian() throws Exception { assertArrayEquals(new byte[] {0x04, 0x03, 0x02, 0x01}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Int(0x01020304).End().toByteArray()); } @Test public void testUInt_LittleEndian() throws Exception { assertArrayEquals(new byte[] {0x04, 0x03, 0x02, (byte) 0xF1}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).UInt(0x1F1020304L).End().toByteArray()); } @Test public void testFloat_BigEndian() throws Exception { final int flt = Float.floatToIntBits(Float.MAX_VALUE); assertArrayEquals( new byte[] {(byte) (flt >>> 24), (byte) (flt >>> 16), (byte) (flt >>> 8), (byte) flt}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).Float(Float.MAX_VALUE).End().toByteArray()); } @Test public void testFloat_LittleEndian() throws Exception { final int flt = Float.floatToIntBits(Float.MAX_VALUE); assertArrayEquals( new byte[] {(byte) flt, (byte) (flt >>> 8), (byte) (flt >>> 16), (byte) (flt >>> 24)}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Float(Float.MAX_VALUE).End() .toByteArray()); } @Test public void testLong() throws Exception { assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, BeginBin().Long(0x0102030405060708L).End().toByteArray()); } @Test public void testLongArray() throws Exception { assertArrayEquals( new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}, BeginBin().Long(0x0102030405060708L, 0x1112131415161718L).End().toByteArray()); } @Test public void testLong_BigEndian() throws Exception { assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).Long(0x0102030405060708L).End() .toByteArray()); } @Test public void testLong_LittleEndian() throws Exception { assertArrayEquals(new byte[] {0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Long(0x0102030405060708L).End() .toByteArray()); } @Test public void testDouble_BigEndian() throws Exception { final long dbl = Double.doubleToLongBits(Double.MAX_VALUE); final byte[] array = BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN).Double(Double.MAX_VALUE).End().toByteArray(); assertArrayEquals(new byte[] {(byte) (dbl >>> 56), (byte) (dbl >>> 48), (byte) (dbl >>> 40), (byte) (dbl >>> 32), (byte) (dbl >>> 24), (byte) (dbl >>> 16), (byte) (dbl >>> 8), (byte) dbl}, array); } @Test public void testDouble_LittleEndian() throws Exception { final long dbl = Double.doubleToLongBits(Double.MAX_VALUE); assertArrayEquals( new byte[] {(byte) dbl, (byte) (dbl >>> 8), (byte) (dbl >>> 16), (byte) (dbl >>> 24), (byte) (dbl >>> 32), (byte) (dbl >>> 40), (byte) (dbl >>> 48), (byte) (dbl >>> 56)}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Double(Double.MAX_VALUE).End() .toByteArray()); } @Test public void testFlush() throws Exception { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final JBBPOut out = BeginBin(buffer); out.Bit(true); assertEquals(0, buffer.size()); out.Flush(); assertEquals(1, buffer.size()); } @Test public void testExceptionForOperatioOverEndedProcess() throws Exception { final JBBPOut out = BeginBin(); out.ByteOrder(JBBPByteOrder.BIG_ENDIAN).Long(0x0102030405060708L).End(); try { out.Align(); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Align(3); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bit(true); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bit(true, false); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bit((byte) 34); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bit(new byte[] {(byte) 34, (byte) 12}); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bit(34, 12); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bits(JBBPBitNumber.BITS_3, 12); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bits(JBBPBitNumber.BITS_3, 12, 13, 14); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bits(JBBPBitNumber.BITS_3, new byte[] {(byte) 1, (byte) 2, (byte) 3}); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bool(true); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Bool(true, false); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Byte(new byte[] {(byte) 1, (byte) 2, (byte) 3}); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Byte(1); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Byte((String) null); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Utf8(null); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Byte(1, 2, 3); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.ByteOrder(JBBPByteOrder.BIG_ENDIAN); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Flush(); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Int(1); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Int(1, 2); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Long(1L); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Long(1L, 2L); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Short(1); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Short(1, 2, 3); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.Short(new short[] {(short) 1, (short) 2, (short) 3}); fail("Must throw ISE"); } catch (IllegalStateException ex) { } try { out.End(); fail("Must throw ISE"); } catch (IllegalStateException ex) { } } @Test public void testExternalStreamButNoByteArrayOutputStream() throws Exception { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final DataOutputStream dout = new DataOutputStream(buffer); assertNull(BeginBin(dout).Byte(1, 2, 3).End()); assertArrayEquals(new byte[] {1, 2, 3}, buffer.toByteArray()); } @Test public void testExceptionForBitOrderConfilctInCaseOfUsageBitOutputStream() throws Exception { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final JBBPBitOutputStream bitstream = new JBBPBitOutputStream(buffer, JBBPBitOrder.LSB0); assertThrows(IllegalArgumentException.class, () -> BeginBin(bitstream, JBBPByteOrder.BIG_ENDIAN, JBBPBitOrder.MSB0)); } @Test public void testComplexWriting_1() throws Exception { final byte[] array = BeginBin(). Bit(1, 2, 3, 0). Bit(true, false, true). Align(). Byte(5). Short(1, 2, 3, 4, 5). Bool(true, false, true, true). Int(0xABCDEF23, 0xCAFEBABE). Long(0x123456789ABCDEF1L, 0x212356239091AB32L). Utf8("JFIF"). Byte("Рус"). End().toByteArray(); assertEquals(47, array.length); assertArrayEquals(new byte[] { (byte) 0x55, 5, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 1, 0, 1, 1, (byte) 0xAB, (byte) 0xCD, (byte) 0xEF, 0x23, (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE, 0x12, 0x34, 0x56, 0x78, (byte) 0x9A, (byte) 0xBC, (byte) 0xDE, (byte) 0xF1, 0x21, 0x23, 0x56, 0x23, (byte) 0x90, (byte) 0x91, (byte) 0xAB, 0x32, 0x4A, 0x46, 0x49, 0x46, (byte) 0x20, (byte) 0x43, (byte) 0x41 }, array); } @Test public void testVar_NPEForNullProcessor() throws Exception { assertThrows(NullPointerException.class, () -> BeginBin().Var(null).End()); } @Test public void testVar_ProcessRest() throws Exception { class Test { @Bin(order = 1) byte a; @Bin(order = 2) byte b; Test(int a, int b) { this.a = (byte) a; this.b = (byte) b; } } final byte[] array = BeginBin(). Byte(0xCC). Var((context, outStream, args) -> { assertNotNull(context); assertNotNull(outStream); assertEquals(0, args.length); outStream.write(0xDD); return true; }). Byte(0xAA). Bin(new Test(0x12, 0x13)). End().toByteArray(); assertArrayEquals(new byte[] {(byte) 0xCC, (byte) 0xDD, (byte) 0xAA, (byte) 0x12, (byte) 0x13}, array); } @Test public void testVar_SkipRest() throws Exception { class Test { @Bin(order = 1) byte a; @Bin(order = 2) byte b; Test(int a, int b) { this.a = (byte) a; this.b = (byte) b; } } final byte[] array = BeginBin(). Byte(0xCC). Var((context, outStream, args) -> { assertNotNull(context); assertNotNull(outStream); assertEquals(0, args.length); outStream.write(0xDD); return false; }). Byte(0xAA). Align(15). Align(). Bit(true). Bit(34). Bit(true, false). Bit((byte) 11). Bit(new byte[] {(byte) 11, (byte) 45}). Bit(111, 222). Bits(JBBPBitNumber.BITS_5, 0xFF). Bits(JBBPBitNumber.BITS_5, 0xFF, 0xAB). Bits(JBBPBitNumber.BITS_5, new byte[] {(byte) 0xFF, (byte) 0xAB}). Bool(true). Bool(false, false). Byte("HURRAAA"). Byte(new byte[] {(byte) 1, (byte) 2, (byte) 3}). Byte(232324). Byte(2322342, 2323232). ByteOrder(JBBPByteOrder.LITTLE_ENDIAN). Int(23432432). Int(234234234, 234234234). Long(234823948234L). Long(234823948234L, 234233243243L). Short(234). Short(234, 233). Short(new short[] {(short) 234, (short) 233}). Skip(332). Bin(new Test(12, 34)). Utf8("werwerew"). Var((context, outStream, args) -> { fail("Must not be called"); return false; }). End().toByteArray(); assertArrayEquals(new byte[] {(byte) 0xCC, (byte) 0xDD}, array); } @Test public void testVar_VariableContent() throws Exception { final JBBPOutVarProcessor var = (context, outStream, args) -> { final int type = (Integer) args[0]; switch (type) { case 0: { context.Int(0x01020304); } break; case 1: { context.Int(0x05060708); } break; default: { fail("Unexpected parameter [" + type + ']'); } break; } return true; }; final byte[] array = BeginBin(). Var(var, 0). Var(var, 1). End().toByteArray(); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}, array); } @Test public void testBin_UndefinedType_Byte() throws Exception { class Test { @Bin(order = 3) byte c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) byte b; @Bin(order = 1) byte a; Test(byte a, byte b, byte c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(new byte[]{1, (byte) 0x40, 3}, BeginBin().Bin(new Test((byte) 1, (byte) 2, (byte) 3)).End().toByteArray()); } @Test public void testBin_UndefinedType_Byte_WithFilter() throws Exception { class Test { @Bin(order = 3) byte c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) byte b; @Bin(order = 1) byte a; Test(byte a, byte b, byte c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(new byte[]{1, 3}, BeginBin().Bin(new Test((byte) 1, (byte) 2, (byte) 3), (b, f) -> f == null || !f.getName().equals("b")).End().toByteArray()); } @Test public void testBin_Byte_StringAsByteArray() throws Exception { assertArrayEquals(new byte[0], BeginBin().Byte("", JBBPBitOrder.LSB0).End().toByteArray()); assertArrayEquals(new byte[0], BeginBin().Byte("", JBBPBitOrder.MSB0).End().toByteArray()); assertArrayEquals(new byte[]{65, 66, 67, 68}, BeginBin().Byte("ABCD", JBBPBitOrder.LSB0).End().toByteArray()); assertArrayEquals(new byte[]{(byte) 130, 66, (byte) 194, 34}, BeginBin().Byte("ABCD", JBBPBitOrder.MSB0).End().toByteArray()); } @Test public void testBin_Byte_StringAsShortArray() throws Exception { assertArrayEquals(new byte[0], BeginBin().Short("", JBBPBitOrder.LSB0).End().toByteArray()); assertArrayEquals(new byte[0], BeginBin().Short("", JBBPBitOrder.MSB0).End().toByteArray()); assertArrayEquals(new byte[] {0x04, 0x10, 0x04, 0x11, 0x04, 0x12, 0x04, 0x13, 0x04, 0x14}, BeginBin().Short("АБВГД", JBBPBitOrder.LSB0).End().toByteArray()); assertArrayEquals( new byte[] {0x08, 0x20, (byte) 0x88, 0x20, (byte) 0x48, 0x20, (byte) 0xC8, 0x20, (byte) 0x28, 0x20}, BeginBin().Short("АБВГД", JBBPBitOrder.MSB0).End().toByteArray()); } @Test public void testBin_StaticField() throws Exception { assertArrayEquals(new byte[] {1, (byte) 0x40, 3}, BeginBin().Bin(new TestWithStaticField((byte) 1, (byte) 2, (byte) 3)).End().toByteArray()); } @Test public void testBin_LongUintField() throws Exception { assertArrayEquals(new byte[] {(byte) 0xFA, (byte) 0xBC, (byte) 0xDE, (byte) 0x01}, BeginBin().Bin(new TestWithLongUintField(0x1FABCDE01L)).End().toByteArray()); } @Test public void testBin_IntUintField() throws Exception { assertArrayEquals(new byte[] {(byte) 0xFA, (byte) 0xBC, (byte) 0xDE, (byte) 0x01}, BeginBin().Bin(new TestWithIntUintField(0xFABCDE01)).End().toByteArray()); } @Test public void testBin_LongUintArrayField() throws Exception { assertArrayEquals( new byte[] {(byte) 0xFA, (byte) 0xBC, (byte) 0xDE, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05}, BeginBin().Bin(new TestWithLongUintArrayField(new long[] {0x1FABCDE01L, 0x0102030405L})) .End().toByteArray()); } @Test public void testBin_IntUintArrayField() throws Exception { assertArrayEquals( new byte[] {(byte) 0xFA, (byte) 0xBC, (byte) 0xDE, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05}, BeginBin().Bin(new TestWithIntUintArrayField(new int[] {0xFABCDE01, 0x02030405})).End() .toByteArray()); } @Test public void testBin_UndefinedType_Boolean() throws Exception { class Test { @Bin(order = 3) boolean c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) boolean b; @Bin(order = 1) boolean a; Test(boolean a, boolean b, boolean c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(new byte[] {1, (byte) 0x80, 0}, BeginBin().Bin(new Test(true, true, false)).End().toByteArray()); } @Test public void testBin_ForceByteOrder() throws Exception { class DefaultByteOrder { @Bin int value = 0x01020304; } class BigEndianByteOrder { @Bin(byteOrder = JBBPByteOrder.BIG_ENDIAN) int value = 0x01020304; } class LittleEndianByteOrder { @Bin(byteOrder = JBBPByteOrder.LITTLE_ENDIAN) int value = 0x01020304; } class LittleEndianByteOrderWithStructure { @Bin(byteOrder = JBBPByteOrder.LITTLE_ENDIAN, order = 1) int value = 0x01020304; @Bin(order = 2) Internal internal = new Internal(); class Internal { @Bin(byteOrder = JBBPByteOrder.BIG_ENDIAN) int value = 0x05060708; } } final byte[] defaultOrder = JBBPOut.BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN) .BinForceByteOrder(new DefaultByteOrder()).End().toByteArray(); assertArrayEquals(new byte[] {4, 3, 2, 1}, defaultOrder); final byte[] bigEndianOrder = JBBPOut.BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN) .BinForceByteOrder(new BigEndianByteOrder()).End().toByteArray(); assertArrayEquals(new byte[] {4, 3, 2, 1}, bigEndianOrder); final byte[] littleEndianOrder = JBBPOut.BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN) .BinForceByteOrder(new LittleEndianByteOrder()).End().toByteArray(); assertArrayEquals(new byte[] {1, 2, 3, 4}, littleEndianOrder); final byte[] littleEndianOrderWithStruct = JBBPOut.BeginBin().ByteOrder(JBBPByteOrder.BIG_ENDIAN) .BinForceByteOrder(new LittleEndianByteOrderWithStructure()).End().toByteArray(); assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}, littleEndianOrderWithStruct); final byte[] littleEndianOrderWithStructBin = JBBPOut.BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN) .Bin(new LittleEndianByteOrderWithStructure()).End().toByteArray(); assertArrayEquals(new byte[] {4, 3, 2, 1, 5, 6, 7, 8}, littleEndianOrderWithStructBin); final byte[] littleEndianOrderWithStructLe = JBBPOut.BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN) .BinForceByteOrder(new LittleEndianByteOrderWithStructure()).End().toByteArray(); assertArrayEquals(new byte[] {4, 3, 2, 1, 8, 7, 6, 5}, littleEndianOrderWithStructLe); } @Test public void testBin_BitType_Bits() throws Exception { class Test { @Bin(bitNumber = JBBPBitNumber.BITS_4, order = 3, type = BinType.BIT) byte c; @Bin(bitNumber = JBBPBitNumber.BITS_4, order = 2, type = BinType.BIT, bitOrder = JBBPBitOrder.MSB0) byte b; @Bin(bitNumber = JBBPBitNumber.BITS_4, type = BinType.BIT, order = 1) byte a; Test(byte a, byte b, byte c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(new byte[] {(byte) 0x55, 0x0C}, BeginBin().Bin(new Test((byte) 0x05, (byte) 0x0A, (byte) 0x0C)).End().toByteArray()); } @Test public void testBin_OverrideAnnotationValues() throws Exception { class Test { @Bin(bitNumber = JBBPBitNumber.BITS_4, type = BinType.BIT) byte a = (byte) 0b10101010; } assertArrayEquals(new byte[]{(byte) 0b00001010}, BeginBin().Bin(new Test(), null, null, null).End().toByteArray()); assertArrayEquals(new byte[]{(byte) 0b10101010}, BeginBin() .Bin(new Test(), new BinAnnotationWrapper().setBitNumber(JBBPBitNumber.BITS_8), null).End() .toByteArray()); assertArrayEquals(new byte[] {(byte) 0b00000101}, BeginBin().Bin(new Test(), new BinAnnotationWrapper().setBitOrder(JBBPBitOrder.MSB0), null) .End().toByteArray()); assertArrayEquals(new byte[] {(byte) 0b10101010}, BeginBin().Bin(new Test(), new BinAnnotationWrapper().setType(BinType.BYTE), null).End() .toByteArray()); final JBBPCustomFieldWriter customFieldWriter = new JBBPCustomFieldWriter() { @Override public void writeCustomField(JBBPOut context, JBBPBitOutputStream outStream, Object instanceToSave, Field instanceCustomField, Bin fieldAnnotation, Object value) throws IOException { outStream.write(123); } }; assertArrayEquals(new byte[] {(byte) 123}, BeginBin().Bin(new Test(), new BinAnnotationWrapper().setCustom(true), customFieldWriter) .End().toByteArray()); } @Test public void testBin_UndefinedType_Short() throws Exception { class Test { @Bin(order = 3) short c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) short b; @Bin(order = 1) short a; Test(short a, short b, short c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(new byte[] {0x01, 0x02, 0x20, (byte) 0xC0, 0x05, 0x06}, BeginBin().Bin(new Test((short) 0x0102, (short) 0x0304, (short) 0x0506)).End() .toByteArray()); } @Test public void testBin_UndefinedType_Char() throws Exception { class Test { @Bin(order = 3) char c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) char b; @Bin(order = 1) char a; Test(char a, char b, char c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(new byte[] {0x01, 0x02, 0x20, (byte) 0xC0, 0x05, 0x06}, BeginBin().Bin(new Test((char) 0x0102, (char) 0x0304, (char) 0x0506)).End().toByteArray()); } @Test public void testBin_UndefinedType_Int() throws Exception { class Test { @Bin(order = 3) int c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) int b; @Bin(order = 1) int a; Test(int a, int b, int c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals( new byte[] {(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0x22, (byte) 0xCC, (byte) 0x44, (byte) 0x88, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD, (byte) 0xEE}, BeginBin().Bin(new Test(0xAABBCCDD, 0x11223344, 0xBBCCDDEE)).End().toByteArray()); } @Test public void testBin_UndefinedType_Float() throws Exception { class Test { @Bin(order = 3) float c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) float b; @Bin(order = 1) float a; Test(float a, float b, float c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(JBBPUtils.concat( JBBPUtils.splitInteger(Float.floatToIntBits(0.456f), false, null), JBBPUtils.splitInteger((int) JBBPFieldInt.reverseBits(Float.floatToIntBits(8.1123f)), false, null), JBBPUtils.splitInteger(Float.floatToIntBits(56.123f), false, null) ), BeginBin().Bin(new Test(0.456f, 8.1123f, 56.123f)).End().toByteArray()); } @Test public void testBin_UndefinedType_Long() throws Exception { class Test { @Bin(order = 3) long c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) long b; @Bin(order = 1) long a; Test(long a, long b, long c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(JBBPUtils.concat( JBBPUtils.splitLong(0xFFAABBCCDD001122L, false, null), JBBPUtils.splitLong(JBBPFieldLong.reverseBits(0x0102030405060708L), false, null), JBBPUtils.splitLong(0x11223344556677AAL, false, null) ), BeginBin().Bin(new Test(0xFFAABBCCDD001122L, 0x0102030405060708L, 0x11223344556677AAL)).End() .toByteArray()); } @Test public void testBin_UndefinedType_Double() throws Exception { class Test { @Bin(order = 3) double c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) double b; @Bin(order = 1) double a; Test(double a, double b, double c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(JBBPUtils.concat( JBBPUtils.splitLong(Double.doubleToLongBits(34350.456d), false, null), JBBPUtils .splitLong(JBBPFieldLong.reverseBits(Double.doubleToLongBits(8829374.1123d)), false, null), JBBPUtils.splitLong(Double.doubleToLongBits(3256.123d), false, null) ), BeginBin().Bin(new Test(34350.456d, 8829374.1123d, 3256.123d)).End().toByteArray()); } @Test public void testBin_UndefinedType_Struct() throws Exception { class Inside { @Bin(order = 1) byte a; @Bin(order = 2) byte b; Inside(byte a, byte b) { this.a = a; this.b = b; } } class Test { @Bin(order = 2) Inside c; @Bin(order = 1) byte a; @Bin(order = 3) byte b; Test(byte a, byte b, Inside c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(new byte[]{1, 3, 4, 2}, BeginBin().Bin(new Test((byte) 1, (byte) 2, new Inside((byte) 3, (byte) 4))).End() .toByteArray()); } @Test public void testBin_UndefinedType_Struct_WithFilter() throws Exception { class Inside { @Bin(order = 1) byte a; @Bin(order = 2) byte b; Inside(byte a, byte b) { this.a = a; this.b = b; } } class Test { @Bin(order = 2) Inside c; @Bin(order = 1) byte a; @Bin(order = 3) byte b; Test(byte a, byte b, Inside c) { this.a = a; this.b = b; this.c = c; } } assertArrayEquals(new byte[]{1, 4, 2}, BeginBin().Bin(new Test((byte) 1, (byte) 2, new Inside((byte) 3, (byte) 4)), (b, f) -> f == null || !(f.getDeclaringClass().getSimpleName().equals("Inside") && f.getName().equals("a"))).End() .toByteArray()); } @Test public void testBin_DefinedType_Array_Bits() throws Exception { class Test { @Bin(order = 2, bitNumber = JBBPBitNumber.BITS_4, type = BinType.BIT_ARRAY) byte[] array; @Bin(order = 3, bitNumber = JBBPBitNumber.BITS_4, type = BinType.BIT_ARRAY, bitOrder = JBBPBitOrder.MSB0) byte[] lsbarray; @Bin(order = 1) byte prefix; Test(byte prefix, byte[] array, byte[] lsbarray) { this.prefix = prefix; this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals(new byte[] {(byte) 0xAA, (byte) 0x21, (byte) 0x43, (byte) 0x6A, (byte) 0x0E}, BeginBin().Bin(new Test((byte) 0xAA, new byte[] {1, 2, 3, 4}, new byte[] {5, 6, 7})).End() .toByteArray()); } @Test public void testBin_UndefinedType_Array_Bytes() throws Exception { class Test { @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) byte[] lsbarray; @Bin(order = 1) byte[] array; Test(byte[] array, byte[] lsbarray) { this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals( new byte[] {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0xA0, (byte) 0x60, (byte) 0xE0}, BeginBin().Bin(new Test(new byte[] {1, 2, 3}, new byte[] {5, 6, 7})).End().toByteArray()); } @Test public void testBin_UndefinedType_Array_Boolean() throws Exception { class Test { @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) boolean[] lsbarray; @Bin(order = 1) boolean[] array; Test(boolean[] array, boolean[] lsbarray) { this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals(new byte[] {0x01, 0x00, 0x01, (byte) 0x80, 0x00, (byte) 0x80}, BeginBin() .Bin(new Test(new boolean[] {true, false, true}, new boolean[] {true, false, true})) .End().toByteArray()); } @Test public void testBin_UndefinedType_Array_Bytes_String() throws Exception { class Test { @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0, type = BinType.BYTE_ARRAY) String lsbarray; @Bin(order = 1, type = BinType.BYTE_ARRAY) String array; Test(String array, String lsbarray) { this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals(new byte[] {(byte) 'H', (byte) 'A', (byte) 'L', (byte) 0x32, (byte) 0xF2}, BeginBin().Bin(new Test("HAL", "LO")).End().toByteArray()); } @Test public void testBin_UndefinedType_Array_String_String() throws Exception { class Test { @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) String lsbarray; @Bin(order = 1) String array; Test(String array, String lsbarray) { this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals(new byte[] {3, 'H', 'A', 'L', 6, -29, -120, -128, -17, -120, -128}, BeginBin().Bin(new Test("HAL", "LO")).End().toByteArray()); } @Test public void testBin_UndefinedType_Array_Short() throws Exception { class Test { @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) short[] lsbarray; @Bin(order = 1) short[] array; Test(short[] array, short[] lsbarray) { this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals( new byte[] {(byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x02, (byte) 0x01, (byte) 0x03, (byte) 0xA0, (byte) 0x60, (byte) 0x60, (byte) 0xE0, (byte) 0xE0, (byte) 0x00}, BeginBin().Bin( new Test(new short[] {0x0101, 0x0102, 0x0103}, new short[] {0x0605, 0x0706, 0x0007})) .End().toByteArray()); } @Test public void testBin_DefinedType_Array_Short_String() throws Exception { class Test { @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0, type = BinType.SHORT_ARRAY) String lsbarray; @Bin(order = 1, type = BinType.SHORT_ARRAY) String array; Test(String array, String lsbarray) { this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals( new byte[] {(byte) 0x04, (byte) 0x1F, (byte) 0x04, (byte) 0x20, (byte) 0x18, (byte) 0x20, (byte) 0x48, (byte) 0x20}, BeginBin().Bin(new Test("ПР", "ИВ")).End().toByteArray()); } @Test public void testBin_UndefinedType_Array_Int() throws Exception { class Test { @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) int[] lsbarray; @Bin(order = 1) int[] array; Test(int[] array, int[] lsbarray) { this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals( new byte[] {(byte) 0x11, (byte) 0x22, (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66, (byte) 0x77, (byte) 0x88, (byte) 0x48, (byte) 0xF7, (byte) 0xB3, (byte) 0xD5}, BeginBin().Bin(new Test(new int[] {0x11223344, 0x55667788}, new int[] {0xABCDEF12})).End() .toByteArray()); } @Test public void testBin_UndefinedType_Array_Float() throws Exception { class Test { @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) float[] lsbarray; @Bin(order = 1) float[] array; Test(float[] array, float[] lsbarray) { this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals(JBBPUtils.concat( JBBPUtils.splitInteger(Float.floatToIntBits(23.4546f), false, null), JBBPUtils.splitInteger(Float.floatToIntBits(123.32f), false, null), JBBPUtils .splitInteger((int) JBBPFieldInt.reverseBits(Float.floatToIntBits(11.98872f)), false, null), JBBPUtils .splitInteger((int) JBBPFieldInt.reverseBits(Float.floatToIntBits(-234.322f)), false, null) ), BeginBin() .Bin(new Test(new float[] {23.4546f, 123.32f}, new float[] {11.98872f, -234.322f})) .End().toByteArray()); } @Test public void testBin_UndefinedType_Array_Long() throws Exception { class Test { @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) long[] lsbarray; @Bin(order = 1) long[] array; Test(long[] array, long[] lsbarray) { this.array = array; this.lsbarray = lsbarray; } } assertArrayEquals(JBBPUtils.concat( JBBPUtils.splitLong(0x1122334455667788L, false, null), JBBPUtils.splitLong(0xAABBCCDDEEFF1122L, false, null), JBBPUtils.splitLong(JBBPFieldLong.reverseBits(0x0102030405060708L), false, null), JBBPUtils.splitLong(JBBPFieldLong.reverseBits(0xCAFEBABE12345334L), false, null) ), BeginBin().Bin(new Test(new long[] {0x1122334455667788L, 0xAABBCCDDEEFF1122L}, new long[] {0x0102030405060708L, 0xCAFEBABE12345334L})).End().toByteArray()); } @Test public void testBin_UndefinedType_Array_Object() throws Exception { class Inner { @Bin(order = 1) byte a; @Bin(order = 2) byte b; Inner(byte a, byte b) { this.a = a; this.b = b; } } class Test { @Bin(order = 2) Inner[] inner; @Bin(order = 1) byte prefix; Test(byte prefix, Inner[] inner) { this.inner = inner; this.prefix = prefix; } } assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}, BeginBin().Bin(new Test((byte) 0x01, new Inner[] {new Inner((byte) 0x02, (byte) 0x03), new Inner((byte) 0x04, (byte) 0x05), new Inner((byte) 0x06, (byte) 0x07)})).End().toByteArray()); } @Test public void testBin_TwoFieldWithTheSameorder() throws Exception { class Test { @Bin(order = 1) byte a; @Bin(order = 1) byte b; Test(byte a, byte b) { this.a = a; this.b = b; } } assertEquals(2, BeginBin().Bin(new Test((byte) 12, (byte) 24)).End().toByteArray().length); } @Test public void testBin_CustomField_ErrorBecauseNoCustomWriter() throws Exception { class Test { @Bin(order = 1) byte a; @Bin(order = 2, custom = true) byte b; Test(byte a, byte b) { this.a = a; this.b = b; } } assertThrows(JBBPIllegalArgumentException.class, () -> BeginBin().Bin(new Test((byte) 12, (byte) 24))); } @Test public void testBin_CustomField_NoError() throws Exception { class Test { @Bin(order = 1) byte a; @Bin(order = 2, custom = true) byte b; Test(byte a, byte b) { this.a = a; this.b = b; } } assertArrayEquals(new byte[] {1, 2, 3}, BeginBin().Bin(new Test((byte) 1, (byte) 0), (context, outStream, instanceToSave, instanceCustomField, fieldAnnotation, value) -> { assertNotNull(context); assertNotNull(outStream); assertNotNull(instanceToSave); assertNotNull(instanceCustomField); assertNotNull(fieldAnnotation); assertEquals("b", instanceCustomField.getName()); assertSame(instanceToSave.getClass(), instanceCustomField.getDeclaringClass()); context.Byte(2, 3); }).End().toByteArray()); } @Bin private static class TestWithStaticField { static int some = 111; @Bin(order = 3) byte c; @Bin(order = 2, bitOrder = JBBPBitOrder.MSB0) byte b; @Bin(order = 1) byte a; TestWithStaticField(byte a, byte b, byte c) { this.a = a; this.b = b; this.c = c; } } @Bin private static class TestWithLongUintField { @Bin(type = BinType.UINT) long a; TestWithLongUintField(long value) { this.a = value; } } @Bin private static class TestWithIntUintField { @Bin(type = BinType.UINT) int a; TestWithIntUintField(int value) { this.a = value; } } @Bin private static class TestWithLongUintArrayField { @Bin(type = BinType.UINT_ARRAY) long[] a; TestWithLongUintArrayField(long[] value) { this.a = value; } } @Bin private static class TestWithIntUintArrayField { @Bin(type = BinType.UINT_ARRAY) int[] a; TestWithIntUintArrayField(int[] value) { this.a = value; } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/000077500000000000000000000000001445000440600265235ustar00rootroot00000000000000AbstractParserIntegrationTest.java000066400000000000000000000041231445000440600352730ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import java.io.InputStream; import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; public abstract class AbstractParserIntegrationTest { public static String normalizeEol(final String text) { return text .replace("\r\n", "\n") .replace("\n\r", "\n") .replace("\r", ""); } public void assertFileContent(final String fileName, final String content) throws Exception { final String fileText; try (InputStream inStream = this.getResourceAsInputStream(fileName)) { fileText = IOUtils.toString(inStream, StandardCharsets.UTF_8); } assertEquals(normalizeEol(fileText), normalizeEol(content), "File content must be equals"); } public InputStream getResourceAsInputStream(final String resourceName) { final InputStream result = this.getClass().getResourceAsStream(resourceName); if (result == null) { throw new NullPointerException("Can't find resource '" + resourceName + '\''); } return result; } public void assertResource(final String resourceName, final byte[] content) throws Exception { try (InputStream in = this.getResourceAsInputStream(resourceName)) { assertArrayEquals(new JBBPBitInputStream(in).readByteArray(-1), content, "Content of '" + resourceName + "'"); } } } BasedOnQuestionsAndCasesTest.java000066400000000000000000000625221445000440600350060ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.JBBPExternalValueProvider; import com.igormaznitsa.jbbp.JBBPNamedNumericFieldMap; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.JBBPCompiledBlock; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.io.JBBPCustomFieldWriter; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.mapper.JBBPMapperCustomFieldProcessor; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong; import com.igormaznitsa.jbbp.model.JBBPFieldArrayString; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldString; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.model.JBBPNumericField; import com.igormaznitsa.jbbp.utils.JBBPDslBuilder; import com.igormaznitsa.jbbp.utils.JBBPTextWriter; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.junit.jupiter.api.Test; /** * Tests based on questions and cases. */ public class BasedOnQuestionsAndCasesTest extends AbstractParserIntegrationTest { /** * Case 13-aug-2015 *

* 3DF8 = 0011 1101 1111 1000 where data are stored from left to right : * 6 bits : 0011 11 for year; * 4 bits : 01 11 for month * 5 bits : 11 100 for day, * * @throws Exception for any error */ @Test public void testParseDayMonthYearFromBytePairInMSB0AndPackThemBack() throws Exception { class YearMonthDay { @Bin(type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_6, order = 1, bitOrder = JBBPBitOrder.MSB0) byte year; @Bin(type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_4, order = 2, bitOrder = JBBPBitOrder.MSB0) byte month; @Bin(type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_5, order = 3, bitOrder = JBBPBitOrder.MSB0) byte day; } final YearMonthDay parsed = JBBPParser.prepare("bit:6 year; bit:4 month; bit:5 day;", JBBPBitOrder.MSB0) .parse(new byte[] {(byte) 0x3D, (byte) 0xF8}).mapTo(new YearMonthDay()); assertEquals(0x0F, parsed.year); assertEquals(0x07, parsed.month); assertEquals(0x1C, parsed.day & 0xFF); assertArrayEquals(new byte[] {(byte) 0x3D, (byte) 0xF8}, JBBPOut.BeginBin(JBBPBitOrder.MSB0).Bin(parsed).End().toByteArray()); } /** * Case 08-feb-2016 *

|s?6GpozZ#X}_ Za ;Jb%- RwuTbmd^ZZ { -D_ ,-GT1rO`^ C1 '!I (8bxJ`ZIiO9t XD{e 5$T sV!p~B%Mv#`y,c}}ݗyrt kԵjE*f͇h}|fFD>ff99kK8od{G+ xR=:wpp>%TJ h  u 6 Udz  ` _@n]4fR F%8=@WN ) zm /  AN ?sH ZW<wi_L8 u,}>bwmI[7HntFRDNo X)RNޣ;߅!tGb4 HB?E"'w \3jmfyTMvlh > 8[ 2POy+[F4!S"8>6#O" 5!  #K6_MU#   pM sp(\|[U5@bUw? GO #m7?g|A9_.2>bi2aR0Zp~HID) gN E m1)<iZH/397Vg`}j!ZQ?8'63\GQ$  9p%  < KdOZe-]m04  3!!!&!R B!wqLFIk%i6 4 >[b 0y2 ;- ,PvGj )<R)W skkV]&2Gܫ^ۃdjT]݉ 6qC {Z]x4y{} gbG U.JT@- }a1(u 2 B  D <k XWM" qE 4 !l h  ;8   2 < +  xf  (  3  x #wG sB3 ` <j P "`Y D%l;5'$|A [6N]tp;G W F;+j3s9w0uq mdcG_WtKMBu2==@iK5]!bhKk1^+L %gT`]@t  5 ^Uc5  Q=s7}"n}hj@G%c~ 8R  , }& V6]1"V{>'j~ZR/5hEE.DwAk|"Jj8Zo|"u\A@=2kf%Vnq Ry* 1Bar[1tw gu_6!  m 7ga 3  _   lJ    7 0 ZN 3j A  3 Q t HfB7 Y{={lyye>zO9pM~]qM~"$!8VP[1:w \{~{"~yH OF:eOAtYhql i;lB0<Iaq *UNwW~kCV y(  UNQ Lm+LHi5ti,ANxyzH>R5V\ V9 } DYX GC{uO4k?l!ng3 ;L}j1NN>D=iFiXBuekzSH<+#9 2h_Cf1TPcli#retgQ1)./V8wvF-kWVAx|%)LE& f N 7 > H   g A   w_ &l 8_ IQ{d [D n6W |,? #_ Z  1 & q3~ ^B BVS o z   wdP A5bHXj#XYAPMA{DaOM5Vg\A:292}@[o@iB.3vpz!lZxeIv+R Y  J x VE1 B  .~ K , d: 3^n  = lB>I\`.@Mbu27ra0/|w83g $ >X}*#qkYxF6'5i7)}%;.b 3]20,!a Gv@"$$5p)($OR}z|Yh4v< Ie P { 5 ' A 0  O {q ~X V  &#N~g&jN}b:.?+UAm@.`!OV7&. x_~8Wm0rP<UBq8:[#% ydv   ; m 3Wme LD d2X y"  5 $ m {!6 f0_^ HGH !d   [a N4LgN==?kq/`,Qj z(\>etCR/`~#iTm`l)e69YNI}Fk3ty|wI`#2 IJm[}jrviR5b^\WUSSUUTR*K&@(1; U.`A=qf{: ; p ! 7  Q I m ; +Y ?J! P=` ]2 g+ o%1 s"( q# h*gG W7 =K g, q  iln!#1^053cREIng9 U$l@A"\I&?,*hx"2kz#2,*??Y y}NrwtbWFl6M)`&/`BO'r _ & 1 ! 8  } p j& i mH v\ % o T    ;e F& 7>Qk%Qn X(H[:34r0i/`-N+&&w3("U%r3pAO]1\xidqhuWID0WI} ""3~*{-=), 1=91/EcyQ&>0Ayx>w%L%< K y    % xz n8 jx n% y      v: I&:Lmr4Q&W=~esO!- BD:=,?DTMWY2hn@{nsMi,_K s 8+[3E6X3$-#; "j,C2W5M33/A'x')W"b2EBOSd +kvhU@-"! .*.03Ga}eIA*F'>VorqN -1 4 ` Xc v> s  RM v x FF  W   ~ y h M . ,KFkOc[3 V'PPhx@7h;D7H!qt:ZN+?H]ZMX<Z^pdu'lw]Hh I\'^<[iykD6"AP "3#u+F02271U.&wy  "xHqr6N'*E-K\Tk9G % 6B WG sO      +d u v  ] ? ?D B/9X$wn_"; 7AL_tq?]bR@FO>)983;qCO)\jx zl~`1T]H<=%2!})}!n#C&t*R-51343+1J."})w! 3Z~(i/[?pP dxGH~wnc`zSnJC2A+BFENQY!gT%xhe`sIW%9p7QOA#e@Ri{LPr:A'$wHx<W n 29G`{mV ?x(6[G:c!$y@\vqCx:hyYzMBE r@) }? cB(H^Pj\6itHyv[ @c##A5Dc) >/Oi=`|mQWovg}ccf1c C$ HwjwbqP AB 7 0 , , /_ 5 B>] IAVdE rvaqQ]1Kl8j(k Mk9  ) 4<?:1Z e%+Z3o;zCLIU]eTAlyqutsvtuvszpbKkd4[_R/HB>O3J'y REB72<IA_NswUQM!w>e U JG < @ 2] , ) )I | . $ 5O @ NR^xqRzubY::Gq/KDBXT9irwcG~W+OJHHLFRq\gvucDg?W.F(4i"6x> -.7@DFsJLLK_H3E?K:m 4(.(\"\'$ { p u { +\(V!%*C/"5R9>A9DJEFD*B>E8F/&Gt 3x +f6\@{Hy%O*UXZYX/Tz%OqHKAC8[.I#m ,%}#'3LCnQ _]lxy."\JhTJ; :zvm_WN =**1*;W FV-}<PITi6_gwmC:{r#yt#ytF;|qsmg\)_U3JM>}14#m]7 %?3G?GJDSY-^MaSaA`\W.PEI]@8w / 'n Mz ud m^{j P"Ow $'5*K+S,X,K+:*'#cIr 6M z(V1W:TD1MeU7\c{[hB)k#m$mG-kbgaTZQ&FO:"-F$7Jj\An }b{ r Jk g e f bj? py:IWqg`gNa<S)II@Rh m%L0I9X@s F.JLMLKEH|EJA <_7#2 i-X($2;t}; C[ \q o {xm au Uj Ja F] Kb Uj k~ ~H?zk 0 yK#$0*0D76= DgI'OjTHX\]_^]vZ;VPQJBm9n-!8bWx"/`;FkPX\'_dfgfdH_wYAR}I?`33(L os*'63s>~HeOTTXZZYkU!Q7KYDp=c5-^%h?viCY Y@s=pCu\v4 \o B; O*zPiythrI> ]RSf  t?pE*82dq0 ~h] jCs/$+c1]7<[?B+D4D%CAB=8(3,<$4{n *[p!),07B<x@DC-D'DCCn@2= 7 u2+%q$h* sh( z:"+D2$9?hC0GdJKLKZIFPAi<6u/'e' O. WN:M slkp}  " 5 5I GY ^mq|lzO_0D  T}-Y5 -'S_ hv]T xUh!?(}0c7y=iCLH0LpPRRRPHMmI C >n6e-$+N0rg@!@)06;D>@AA'?i<39&40r*$/oPu dre\ H,MQ~!"""![h35p]@/    : _Bc ,*  qS!  bZ 7 [ S$V'$)*+*)E'A$ L-Q q|mP o/!b%*f-L0s121l1B/],)n%X"zJm %"=K q>pWOWm4Vy 'mx`Y ?( w]!(kL5  C>lwMZ;H0>3ABOZe%"ha0 kS  !\E  b[(6 Wr VF-0^}9V %252S> b<X#!l&$\&'(('m&x3$0!s>{UfQ HD2> =X qF$NkLe CMKGun5#  Q> ~w[V%#:6KG|u) ?"r%UAa5Y-`4-J-   q :*MU#:  2EsF5pO5cz #: ls NJN> z|- iA]6uMZ3Y7}a  XIB4#TP'%^e FC XKE`3c *$ P%S #@x+otYi )"4<2COL i\t[!!B + . j 6 x 1 2 2p  *w(3% ((a~F"R%qD!   ) ?=ND`U+* Ifu!'M[n Hkj!EFi4W,dr9\-P?a8  ^p #t~uJQ\^^\"ytniC<* 'YNLBzrunLEZT1+yx ce*,<>ijKKurki)*>AY]5:=BTY+//2-/#$UUhgedGFkjOOml99::``yz~23KLVW  ~XX%%)(xx54DBHD[V un<5,%|y\Z^_8;;B\e+CO5B . 4@hr7>/3nmkfy.%$+TJ817/0(!bZ `g*5  5  $D#C -z9Vg  q vy7>KMrnOK#`V$y9-,'{w/0:;24 [W+%rfM@1$.!%(7)D7bV*#lg?A(I[   ?YtkHd)D $    FZ  `l,7ps68 ysF? wJ9 z kQ N3B&F*Q6^C jO h  ZIFT Un v3z+Q !)"@" P#H#%"! 8trO/T.G  j{ 2(P< f*onBX*]0sG^@cB|\vgiGV3?<M)gE~>' *LQh{  K-f!:#E%'())(w'Q&/x%L%#"d!"]o<8X ?Q &)% 9 ]9`yCj4X!TX!f/m7yC\?XG, w QF%'DHV\]cW]V[BF#& HL/ x @wI\!!"/#'"" m#Ym4Qg  EJI= q eI;xz2R4rU  6(nc*-#%h\D6    A3qe^f \w&HuS N"%&(-(,(((&P# H u$Pp3 @EVH w\92  %;P-nLq   !     :)dW;Acl   GdH| ^%*6.02y68C:R;J:5:8r673O0,Q);$ >ss  ?Ez1 ~X0KB "$n&X'F'=(I(`'%"O ^R) m_MT   "?TvK^ $&P)&w+D-_.v/011110d/A-i+1(&m"#\ 5U zYG N }< R #-$%&'&$%9$e"K },g;u :$ pa#\f '7Y^cCg:  r~\gOXEMHPU_bm *=axL .n!G#_%k%p&i%]%H#%e!;^: y%)yD. x{M^-KCCHMT!]+]+c2h7k:tDWq 9! eR "&LUr |dqLU6=%*  !*/8?MWky (M[<"2z&b),./(07292*10.x+D(U$  ?i  EM?& {`*& "b$E&6'2'8'P&g$" 3c,e- V@ |k"3:IS]jp   0B/YKy`"5%c(C+i.0234 32}0Q,a(#@m BKS: WR!$a'3*o,R.>/00)1-170H/]-$+R(%!VmH- CM 6<k]{ !!! ziWEv-ZE 4#  1J9iL~^fmroeXM7gI( k{ AKnX 8NX+"%'o)Z*N+I+T+g*(%##H x9o- Y> w HSv  69ki#& (1*6*,)( '$!V2c 4 p S`;E,3*1+2+28BIUev G;oWp!#$$$$t!U.a-VedJ / iXMJNMUcu6S5qX @Jr  ;'Z>uNS RNBz4i$WA*  { u { ~ >%X;sL ]"m#t$~%&%y$n$X"9r@]n s @}9 J$(+-y.y.-,*0'S$9 p@oT =Hcu  *H/hF!Y#j$s&'(('v&n%]$K":u %]J1# '9K!Y(a.h /i '`K0at /7tY I%wV 5"i$S&D(8(1)0*2)@(S&n$F!y!Y7  dx XT%*/13V6w9:;;:n8W7;41.+e'A#V/ o ^q Rb JYJYHWKZM] Uf Vh ^r f{ o s v v u p g| ]p N^ @N,6u gG<uS!E#<#A#K"X!wI"tV  'K[ s  6 ]"<%Z(v+.0 24'5/6.6'541.j*A&Y"%m DS#,5IX bw xo Pb .8 sSQ*4  2K#c@z\ }  %-?M^t  /S#-q&A(S*_,g-m.k-f-^,Q*@(-q&Z$< !zg~ Sf EU 6C,7'/#+%-+51<9FCS Pb Yn j{sVj /:  aO%"g#F&3'%))(4'J%i#N#yX  ;Il5!Y%2{(J,_/m0y23555555~3q2b/L-4~)^&8"d|J\ /;!  %.0<;ICS K^ L^ HZ EU 7E%/ bhBN"; +&?`7e ,7^v +"]';,^1~69<)>6@?AAA<A0@%><8n4L/)u+J% wXn 6D z ldffjt  y mc~\|Y~\jx ",CT c~#";% R(a*'w,1.>0E1L2M2T3W4S3M2B14/"p, R(3%!sQg &1 dtNe;\0Z,\0d:rKb  '2@R Wo iv}}scQh <L #,  y ezUpHnFoFuO~Zcu .:J^ i #$@( U*j-(|/41;2=3;272-0%x/g- T*B('%"kXqEY +7( *+87G BU F[ F[ @S 1@", orI].L>3/2:FV$l@`  1?Unv$>)`-'~174H7P8X:\;^;[;Y:U9O8H7<5/3u1 ^.@*#&!oQi,9 q\tIpEk>k>nBtIzRYgv )6CW e"#'J-p205D9P;^>b?f?g@_>Z>N;?9+5l1N-((#c=Q & pWm>`-W"PQSU^+i9wL`v !,3D E[ Up`jpqrnjaWsI` <O 2B %0& %12C E\ [xi| "$ &(("(#)&)$)((&$#!zhUqAV '4 |nb]UYbn  !-0? <Q LfYw^~fkihb\{RnH`8K -= #/ )-= <P Lf`r"%)0,B/O1_3 f5m5s7l5 g5Z3K18."+ ($ zgSo=S (5   }rhddgis #/5G E]OkYx\}``_UsLg;P /@ )'5;P Tqi!&+>0Y4q8!;+>6@;A<A<A9A0?&=:g7R380,'#~\}E^/@  ~jRvCj4\!W\!c*l7tAUgy  F_-> !LhNkJf_Uui%3(_ !-"F`-= ntb`b#!F3C3:1|<+/%.E3?2R5'xv cXy<S  NuAQKQV@N J1!R1!(m6p9V*5! >R eB lj1!1C UuTte8N Rr#- 32a0}KQE5S]]/K@pd`{c`mj;)saQFpe>IGH[bwg(7yt Vq> A (   2   ?} a u "V  qzPQA .ozvj(d&Rv2)ajlsnb@ynff$UQ O`{Qg>4rts@|A"jox"0UP  [TD G  q)N >e # vkkc "k%zWw*(Cr*(#i %7#*!S!{zC-ticB;!/V"lDx+:>^: K]Tc({E>V & k;Wwm%~[p( , -w+uQU)V0,!]/I~%4 ;+D|N[iq5 cDWR\:L; K U]+|j HTr K }!v_+.(, h=o G JrhfFRG7#o)X9@#d4mwn' {KE2X|T54%q^@l3AKP-<j c t Te HwR ;&Mouj$I;g'$ !?"B#N"2#{_?) .9XLmF%S6}A{^T7[N6bn@ctdZp@^v,AzXrQttEawHH)wgUY'3>  Q4 a)S~Cf [$e"0'Ee' *V'/:X' )n2&0#~B. `?{jK0 j\| * ^] <I xo([ ]y9<D)Wr@ 35sZ?X7<@fi\u]uY{hgUM/Gc ~iT7 8qW[Hu 3EumZ[$!?q@U5fb)>[w+![C >  fW  9y&iCe)"d^$W%(b&8& Bj%3>#zc!D&la}9 ?   y<XPDYg PQ,M)M~ka_#E ^3%>Y#"onufANjvT{/=4 )C  QAHVB<>"1 IFz _ ' rI O~ P{vt c`>! mQ  wCk .O ~ pbu)<rWUpkg0+)+,)t1!1 &SzZ()I2;82392;/@ *(J6pXbg ug ?   G{r of bzV Ox 9D /  < K K NI K. #  7j &y ' \, %fH 3&OrBhJ<tUL3)ev j8%V& 5UkrkH@~+I|-bnC]]&LNt6!KLA| EnV|Iq.\/BK)q G9 se f I  +G c    2 ^c x 0Z\ mou f=.JvP{kWy38il^3%NYTo.!\{b+B Ev;pRDD,D1Bc'w )s[BL`>E#3O\0k_|&o}PyJWSnqeIIX}]|M/g#] wEo4i]}S5)Ag"5IIR`i|>,{#dT-*&Jg&G{,M! Tj!1LtY?J.{?a#ZTd* % M s R } `!D FFH 2h ,r 6cI PEL U2 ;l _i ` %` p = m{2 -88rp\t)3p4{tqeu6U_4Hqi;Ax |COt;!LD1XYaM^9YF\ya~Y** oX7HW-IHcc6Aa$/-lcv}\A)C+-N^qgy & 7 vq LU; 3 R  < r OFR 2u 'x )) U;h {Z6 {C Sb h\M  .m ][ #pw\cv$tGz Z:BskY5 Z5hv0/WAhMSTILUI~;p 'p_[yXM%>7z41C b9W*V* =~`TBs!9n2TZi v W<gwB<ss3JM JapoFy#o7 _5aw, 10SzsG~3mPHm`aV P O _{XC+'oLTym;Q)j3_sX/}bpf\]QF-3-?9f3M"f[)]r3j(ATq0RM#skMVQa%f%FI7hW\"g==S~2E0jUZnVtWsZnZmXpSyH-2f$:'GEanJ))^ uD2yckSXXj~)_4:;AG-RfZG^G_lYSL%L'S`[/bqiVp>u*zcq\`g)a'!GWHgWs$FSmhQ@5r/x2@N7Zng l.yG_ v/(T!8oIHCL}3| |F3OT G bvJ -7 P` r  : ? + *  ( I2 =: :] p = W.[2`k-KI-oic]# I| E`Y$5??>?A=67/ J2*`U%)?PX+m?uXv!@Ur -5\qD"w:Yl;1EQzu8rE8P.>; N  ~ `| G3 4qg $ . A6 N6 N I 5\G ( B \#? v d7/*z!uO'E y=fZ*_a@Lq'EXmbP.:wsZpU#(Cy[J18IMKu)Qxw#-KdLkH6I+f4<jkzTHj{~$J3YE:TYGlFdW>{mk r tvz tlgt__Zd[p_g\f\t`ddb[D^). 7Y+};OzfJjM!#;z|Yf&(gJjLQwI0<;.p#_=Kd~d#9/V`u  z<5{9Scao8_| "Up=Ks4k)N93A2AS8l)*DUD|"XiV,4Qc_yxwQ,R/e~t77TijqmlfBL ^?%wI:"plc-t%!;Nz]_`5nC'}*n{!Fu7w\hM\bQt%INC ^@l>w=u= [A5GL|TnZdcVf=mDCv4"g Q=6Ce "$_+ei1prK93%{i'0lA.gvv  x' nM h[ fU g: k vib U#(6h&v'xWT+" ~6^W.nfO-)PCr}= [r o13}hm$wrSD$%f|~(!VkS/ ],ib6jxHatO6@O6aF.os'{2!;;8 )$ R,s4d?QK=sJU+]#[!Ga2g "kmml qmw\z^zeyixs"k<cN _]&Z#Z [!K_/g yuY^yG0 Y(;\_<|&VP|oFh0eE kXkxk|czgtqh*zV} ?#^;Yu2-:bbE_8.r_1h PM0+  t_ _ W We _ oVp rA1j]A-v}L@k+G[S)]Q1BFYcWkBJz( "C|$s>o;`U Ot%|=f+L 3Z].~?/U|kz  |}k@ZNBp8_0P(G$:f2Y-Q'E#>$A1XBv!Y,i5u;BE L2V_cz+kTyk 1aKZ)\9U2aBT5aCS5W0W0H[(5`iqub{Q6a v'%w>SkfJf!t/>-$ ! ,J| s6q5}fIXb3I|<8z&zCz#PoevT0V37j.- P9J`y|fhjHj A9h"cc3I5Y_R| FgM:|!8`?|N}^k>Y_>H~K41!0^1#TvPav1~IzdursptssptYwEz$_vm  b!E#W4ieLH?a1yd#14) C)7nrwIN8 Z/jdu%`fig7#$ BG}ij>$a4h:q} 5 ,t6j73 )B$%^XQ'|gB,|>~E~%{pZzsr_ZMI1+9 z1  r3 >1G?OERNWFS9L ?X,^3-~*,;lgnJQ7X6gYvLdVft?2\ b&R42=PE.KuOmTdX]\V_Q_O`O^R]T^R`O`N]TW_ Qj5JwZD+v@4=9<6=(nAJG}-Kt!Mq#Mq'MrHG|1>e/K !>tAt,.tq , Y7{Ac!,&D]BiXncfTP,1 P$8uC7RFm)+vqe9yj-[DZY_m4f|9d, ={G|*v#eDPj>;& H)}G&YNfqpz  yFcUJwEo@p@g<]5F)8j!3_0Z/X1\1]/X4a/Y)M(L.V7h L,g<K-\`ly||uXi"YuDM-"@j> %v*k.h/g/h/p,y*-"d$DZ5Xqr*5Nxv Cxhrmji h!f$h"lgr1zxS WO/ Gr,F:HHwU_`I~i8`n/Rp*Gr':t#5u!?s$_n.g<`HZTQdOGy@<a1&i 0[U3yH(]lr$Eoj3z`rtommqpbrIv;x(|~yk[P>b/F {@exIF*# N%z*r-k/a2M83tA;JqV[_I|j4=t y|gT?(qN.1_>b0ve< RaB<!%0h@ks ]4yc/1=MZh<| +\1/<D8=-(;#r8/=M[b2:xd(6ciA2` F(h1Y6T7X6a3n/|+ (&&)w,j0Y6D=-eD|AJp:Kn2Lk $OgQaUZVY S_ %OgCIp.gD|B=[5p. (/#S # .[P3wK@g,M_~mob7TP/GKcEsCrO@xT>|R?zKAv:^Gk&8M_ VNa8wm }`'C{|\b?4f!  h'^54nB} Qa]Gg6|k.~k.g5c=\JTZCJn>@c4'n%HY9\y|  `t.cRhDS6B+3e!.[2d!>|)J0U7Z;W9V8M3G.D,D,H/K1M3L2I0F.@*9r&6j#4g"3f"7n%=z(@*E-B,C,@*@*>|)7m$-Y'M#E$H!B;* # %J9r&M3[=lH~T_Hm39qYu|Auap!pm&nn%ao!@v}g@+QqpL6m$y+L=*Oc`?kn""{ v`NNXo|sm%ZK3dEvs0b,ZhGKo/Iq-xco$h2c=`C^F\J]I^Fb@h3[p"{ oQ8v< l|Ip"bWqMmKrN{U_-fNqm|k{En`xS`BC.'P F )o2S;:nCw";Lf TT\Ea;f/i)wm"k&g.^? SV4aEr`7)n#G]A;lAZJs \PSF{S4{&X  >j,7=$NlbC4vUE`yvSD0$ _ )c6AyBy&@KfSVXMYJYI TS)O]0THlEA{[9r1(Gy -<$J(R/a!/`!0c"*W$K!D74,//9+Y;y*K6Y@gJoOuT{Y \`b&f:m_z"&`E`v~!y?t"Wp*]o-`n.Up*8u x }W5 /Mt}[U='Q}([:2THjRTZE^=^;^=\@YG VM QW1PIhR=~.Y !F^EHt9Am ?t%i<^QVbRkTg[YdFSp,l :AqkO8v*  o/#-i5X;Q>~L@{V=`9u2 )B t  (UB2XAxY:o}  /@UOO= 1}.l _[EL9&O)V"G21! )B16#J$MD3Q=mSd\| 1 2"e&m07"f&o:q{^Q=-`#$ k(b94OIdSQ\=d,g&ununyn]r e*^7)7M[@gEl.&%e'T2j'b#lh~2+}Gr0k( Zo6Md#T|-v&`m9l |TUQzDvnU9z,  %%5#Qz15#%%&'Kp@!+:2k'I9Q@F7]IN=]HkTdOn5/jR@w **{Uhc,$ 6'kzth&{ AK P]2^P~=+(sS[x,x%|gq Z>ZfdI ~ @] /TaI f6 s  " &dSE+301#63J3:O2gL2gv1Bi1>}/-X($ !Dv  7  ri VO*i  V][u7Z3!iP$In.0ަ6c;iSE$.\;~Y!΀sdu̼͇_\f ni/~&x#E8ٛ_Q^u6+}\Vg}k'V$RL7Z <r!Glw V ? "  #'8+9. Ii1|EI5;9/j6<z>Wk?s@=x@DTy@( ?=e: >6W2zo.k)}L$  U _  Jyma&޲p>p%:/bC ZI |#_ MKgP lA7_wJ+ҰQwm1ck\޽LY:r( LO>[NS;t Q i p 4 oQ VVZ  {!I 7$ &hI'L0($=v)x )L(o()5x&^%U<$ .!< H   dB A  e ' 4 ~s_x / ( WxUHlsOaaV SV*-BDCj|?Yq;`3fl/j\gt{ߎ1ި[!ޮL:ޥ_ߋtOdAc=g% 88U\y0 pVgNsiR-O =% I HYr   WB  i # O&tS(|G)I)( '9=%# !D  ]' A T *6D*^Ve NB!/k)6rvfP24d~6>_9٩#9XK"ԀL}ӝ1?ӧҳ5DӦ1~]5:sF+x]n^tQ#C6cV; +XAlyhX`| ;  by  .q"^ %(g+-3f/kq0]111P1|[1Q0 $0/SE.-L,+}*d(%su%2"|  ` w( )kjUb>s6vDB:c޶h^;ҼVK svXB aǣ B' Y6 0 _X #ǿ "[[ RdP.zn8d V c o " ##(--2o6J9%<T<<>Fc@^zABA+@^?5\<+96o2]-*( G8#  Z n 2 e  y+Qaq"<+٪Ng d_͒0V;F #v  ȗ ǧ wǰ rDZ Ǟ z F W)P͜ {q&ӛ^Ya5ޫ_U:1?{$u60)}ipA9 Q X G [ t  S u 5c#a&x4k( ,Z* ,^-#P.Wc/r/f/E>/.-144,l'X*90(<%( # ! ~ ]  I & ]VY) *ucY+Eh&/:tRZ~|BL߀3WB!S2fٞnu*1+2/<)6y yِ5oLpWBY P;i#OKk$QLZHVmhaL <' (UZ :=  U Q n G -\   )  !  D'n G" 5 ox  @i =[t.  nZ5-/qRXuPHwf 5O /tz2UM!U}4 8.@o;pC &`fzI`2Ag tLyB1%'~|D$ C#y j B `Iv:3 t;{`8\" DI 4 _ V+" d c)' b ]*& N 01 ) AZVzh%pk&@qu W $q_nh\D{=8]tbhhyy,rBzo[O QEߍ޺G޻B}ߐX-}v{U?k'bQp!z !\# Ej .b8o, U  7 D 1 #+&R])JeQ,eR6K.$/y0 80+"0/s.mm-5+'g)& &[ #b R te  * . iTnA UcGU+RB\KG:}4M}\aAE=޸#AB"#d*~Dq" 3_=V4,ls3jgHT501.yVه"?vߑy&|1C & tZ,/ FG7Z *(;&O    x k !=$J ,'Hgp)[M+#\,O-B.6.8...Nt-B+yjA7*j*(p%V# l _"  M UiA_ 'Yz&PZS$}ߔOmoُ|JԌsҺgH^.Lӫ#Ys ?*``E^R\WXP= xr[}%\{Y~7yZUo q; .-AbvT^uL    kS ` - S  1 & B T < F     I Q 2 dk O  , 51p 9crv+x'm I6r 5rjx5*RW3KF'"|v_6 MGH!;6K q#0w=7XECN>QAOZE~J5!4Ws7.+jT0btq1423#lv`U%_ R2 3 O$(i\I5DRpizM oJa1 3Vz9@,   JJ<~t(L eG#wAlmM0K:!;"P*y6 SF[b3o)_'mhQlGUOJEHN7yT"dUP OE 1 5P.qW .tviUH A| 7 9 Cw q`WF .st3sY3rzAT   LS4!wB^Ax;Ax;jD 1T|9a@@FN{<!V B(kJ+vRZ.0YK%j#BCs/xu/BD @BU6Y,}\@( JK2O6Vahb{X% o 9d{ftK F l  N E! w f   I y, m!Gd Z" "] E#~ 0#v "E6 !1 ef$ 2 g M _ u kL ;/Z_ <4>p>9AA63=mu"M20Haa`$h<߄r޽0gG[*!,#&7cDtVEߙR@-1>_ >@,=9e(5vn>"* USI s~SnjN  e d x m _ &0 (  f * T \ } I W  "  K* uO7 ) tz ]J F ' 0 :pV (DW5HejO | E$ ~<9spTbiuDP?/U{!F r =QzT\6e,/ /PcciD[e76lf=;_OV\0Y-EBC lG;^.x3/4dQ I vs*y6 UA c   P  e # Z  4 B  < 4O X) &<Y: 7 i* D'Sqs9 =\nA!g?u MPO|dr"^E&p9*eWJQ{_[5DwE& :6?1fmzBOA!@NTlTKrjvVJE4#N sz/O(n|=VW|hqlj`6H%sF(BKEUe>Oa3x4] 1,5[b!C@^Wmv~fTZ4]:pnax%Vgo-Ga^:d$b-3O3 `b-C + [rMHY Dc8f- n u s z ~AP 9eKNIB8_ _ <NE$tTTt8-r. B g?eGx6` 7GgUitvo'`H)g)g~KPHHqp^:6oOX}sq/~&7+cNOcalW`~/ P Y 'm  YpzJh9X& ["\  r !i @d 8e  m { hMs 1. .w@&LjL !U Hru ei^p#`@:+cB7X=p4mNvx6A.+117{IROG7vv*RT. 'N?{;_"'CX?ww0 ^M#!6ls)Lu2cb igF -B y? m ;N' [,aS>f>/m6`P;( xmr T 33g ) T t B$ cJ v< o Wc -72   1<b Z>'5L(x} S T J1 Y3:yq6@2b(D]>@4!=<Z5jl>y L4qZFL8fp/#VzUbD& cH F  W 9 +BclxQy=19-G/A7(JEhX:So?Z I .  .Yg S |tgy'~` R ~ [  &`(ODua7y/\ /NT0!S}pX Q[!exlD8,H^r0 w -k7 7+\o=5+X _j R { b h{ :8 /eL^9g"  h* NIVxU;cZ-7^ &z;/w9bRLxR, K'@bsZ\)ylnII$(8S{p3w9du]F. )K%HmvcQ)Z}!go;bUUoRH+R&kvAuhV,OsPG$"Wqu2_s.h'M'"$})"W6~1f|q/5vWV5\R)[H${4c^8M(<^-H        wV-%,5wtAR\<M-?\}lIkN2P'p@J-ml9RL )awN2=r}x3oe0Jd;58+CK\XW4:ry?GgPC4i*{-I".d4%s2s rf >  X ftM|{tvuB~TmQ    'o 8 fxWd'11)fkA`02cG`G`T [26/Hd[Idj6(te*zR%|I*zP2v^BKnwUdkXLo>0:!jr &Bt]Hy#vRP)QqUY}?6#$+GgH}#hcoFR?$2(dcd   h7L Qec 9H '  3 > ; !w u /L E a1! =SA>6C!*=Ypux./iAdU~&s"*B{T;uM]lnE./$Grp o=uKxV/, [1IT[,qS 7\t<|n(og)97@MDZ::-ba'}/dCf> X$l~lE4me]/QICu@oCtCtApBsBsBs=i8b-O%x@I' m(#/f:AyFHaUP^Af4bm+Dr%0u#)v|!%wz'8s4amFcZYq+L~?2/dJ N*F|w"vQv+`WmH^3i 2D)L%J8} /aCq.^9zuQ=m ({t0K5]m(.jc <%u|TjMghu/Qh{IV$"vueS.e 4]xwSTvA.S@|3Yl2TN&aHk[k\eQV7B)9 nx 4^"p9A3SEie#yS0W. ^)w8E]CmO[ZHc7[m'%vt|]I:.%p_I5{tvvOSUOH|^@j9s4x1z0|/~(-~(.~*-1,9+>*B*J(T'c$q"  o#V'}).m6ZAFkLq,ZAk4~|+qB~8/[uM?`k (Of{Q!,w>@(+,'JF4]F+e6_Op kGEuCD&%88|,JdXRaFnhA]k?UlCcjLdY]iUy#Kj@4F(rU / r@1b@MV2]KaXdbc`aX\HW5PH@8q1b*S%J!uBj;i;l<!uB&K,Y4j@MW8ejs!0<aWjyzlWR"4qW9;yf:##4FBFhVMyf4 t`6vhl (. wSIjhhV`A)jC&F{ue8b<HFuG,Ul=E b&n&n] q:# }#%V=#"ZZXzsyA]76pRDSkz)HwQU7'"(#:TUs" z$~3@?nn\]xMu>-\EV1+Z=MZKetnvz%23CORxj H}th0^T.[SAHV<i61|{&%Kh- 6 + |d >nM. P>._Ri0,tivA N<"a[+{8'sar6O_3L #%% "'@w:_}o $zC>mh{M+^ (Xal>Lm&)YTP>rk*#^& b}Qe{%9d92K@/uP `Zq0X\gM#7y%RI*   qN*!'l.R57s=?E~LqSgY^^ZaW{cYb\_d[oU} LSB55'lre;=ar#somzgH"UV<GR:eX/!t&C}!Waaz"Sp(;_C2J>0N _ls ~w|Q8&VP2?+Hd[I>la2y." hn# 3x/G5m[`^zOo=*^t"MGh 3xu-pzr(l?iLhOiIn7uT~gk3lSB:!L $J#w)^.O/I/H.L+X&j#!}K{  :#a:#R+c1r7;<<:5{.k&YnAG* rR>40>Rw  ( |J7O8i 8wxIs0eeQY*N7FC?I< J; H< B@7F%Q9]mF]L_mEj? #uu<sTN=jX+ /ta;!,wH{ 4*J(mKlY;D-Jg#TH"l<X*x!lT&a[Y!S'N*M)M%PTYZ'aicq@z?Od Hz)rhc_]w\r\r]w`aaaa^}[oUYM9B 6%[L.C*V8(FwSaw`wL"mC9y(Y/   !D#z5{Jn=e^~M8#q P0=gOgx/+b&ME5;+_*nv>f | w \+p \,jD6;-(L L]rK9D^;f>t%h2/V?H}PvTqWqXrWySN GR?5",I!vg  7"wH*k5>EH+J5J2F%@ :1|&bsGF+  0a;%_4ET`dt,+Vn &  s1Zt-_J:4rF T&]3(X>GOwUqXpYsWzSM,Dr: .<"qU5.xH2ax>]w>w&z=uPsXsZvMy>i/5^n#Uj<!Y P'V3#S>GN{SwUxU|RNHL?6,A"o^ 5!g@.}BUnd t9(YPq[xwmwu$WLIf@A]:!X%_J@7uIB)4Rf%dgA  9$%d7\r7t=h*[x-mLJ1`#\&W)#I!ObA w G>,Bh:|Skp^d>zV eIXAeKM Wb}t5 `|Cb3v{Mz TTm ^3fooeR%%FE9,;tJ 1X kSt3s~4<@d }  "%6+  ! s~P(t~oD'tqI07{Uz$yE!~Y\y+ e5z?9a&06'HI!9$CpcNW++J+w &pFBz4kg~GwmBd~XNr8V'wx$lz`N=Q&qRz m]V&UBq)l0M"{jLhNgw~o8 sY\[nKi<j?K`R$W}5MUo!\w2xVO\J^V}dMM85Gg1c-G=P#j9 X tK R6cBs &'0 V z3 Xa LK O ^\/ vI= /[  bM/' S4u{ENrvM]9>\k^S`r'WNMB#2_.\zMbco%iD_q[RE:)sgX YYL<54`h I*7KxO>)s(j#d}{`[n{qgg]hY'6Kl)=>/NP$&K@'KD&I7(H5(H6(C,V{Dt ~>z7HQN[$1 PXpxF@8$SNIyob iyf)^VWK!z@o(W^*JSoQ %wstC^}p<`+D b:nh:$|e06k oz 6 >B, iPn wZ pd jV s?! c%    aWLq#H ,OpyU#ueH;JzdYC^kQuAG3W%(t C d4XUrwT[8MEBHQkvWNo'an bwN\3{dYB{fYC|hXDz[ZvO_ll${trlXLBk2$qNY>^AwR!z,7+9:6*.%zT L')A8In}3ZjgYksJ~M>5.z*e&N"8) ,"6%I+l7J~G_pw _Jd_4 efG/M&h@p 8YB!..EY~5glu]v(a \E?9Z1Z{>X!HCtaM}D-bhd JO##6L~;fl=XA)z >,-M1mgri,|,|x(u_zM5?f3!)!k]qPaDX>V=Y?`CxUu'4*@oJ*SLVZZkZlVZN:C7>(q H2M'g H#6(,o187@IZNT|]rdkjgle}nglpf[LE;'j#Cb;{u+/_LW;|*-7$@L*AT0<; 3')-6-A`P baxpD}Ylo0L@pfN1<7)sy]}`yr!V3FG[2uKreXR: Ar@|a?}J}gy#azC~ xy6Jvplj|lpsw{690/0;'A;+zsmkgoaFYNRB8V0$&_ $b)"9K:[jrksU{D77,a"8xlv#81uBzWypgsQN \nWkS\xL|B;> p=lA3}IdV|g&p/~_ZM8!0p:vZEy?H#wwldd^Z5Z8\'` Lgp{c+|)QgP ~6c& [% b/%5996>2s,%>9mz  ( :+ <, ;,5'1$, , 1$ E3cI_z%*,$.4.2,')"giN =- >.gLw"-+9|EWP[afq1~Lq"R4oIkFP3+z7cfO6m a%9}RK[r@i7buUuG[<$30}28CDQVbuw\fJT@L)N| ;.VOq??*|s_kf"d3d4e)kj ;ou}jL yEql+^Q1Gm<61S&hN/# }qqt|  nMz+`:!&r+Q..2568v7z 5'2X-' 2e &.GDhXnlC#v}k#cb[PUfSgSKV[bgvmxlwV`G.Ns3e'7sdFTn`Mt kircvaxaxdvjqt ji`HUMH:Z],/}d#4vAeK$RZWyZ[YTfN7G Bk<O8:6)3v0c-J).&% #!~~!'#+D1k63=WAsEG HHEAu=Y62.Y%qWX5$T,4{;CAOF0I#K$J5IOFwB<A3}`*RzkR$9KNL`.qZ#,ibke +~oM]!H'4/ A2 Dz'4n}@JQVmX_W`UsP!InB9L,/$@r5)r',-a469S=nAEH3LQOhR}SSSRQtN^I8B;e49,[ %% qoV P><.(# * ?1 gPl%%,Y 217R<m@CE&F)E(C>|7S-e# Q? L![)1t:4h@(GKNPNK%Gm@7B4- DmsY#8]Lha au@QW|+xPuktov`szB/ t{~"pajQB3B%* R@ ;f( v&G*$-0t2g3]2c1l/}0,R)~$ GTy *!x^%3)U +d,m,s$,p!+d)Q $- yx_ ZG:.)! %@3 bMo$2./7nA)LVBa9ltz37TMeGa~+Jx&pfe[ gOA'1L!mkc&1_93BIO]R@pT.rT0RANbH1_4t6665x2b.D)n%#9 qZ*"x Lp/YE :,+/ =*USu ! x`"8-J8B\L/Um]Qcghigd_fW!OFDr9,C j 8<'.W3(7q9m9z851<,jG& !Ei bO")}31u:BkI(P]UYB[X]l]l\`ZJV)RpMMG Bo;,4,R%^ |d.%o Ei"L4##1GBfp  A5 yb"E%e"(9)D)?'x1#V+ zc3*[zD{ s!~ 0HkM?)E4>dIHR)Zzag<j[m6pm9sl1ljZf;` YxPG=;O/#Y YHu ;f!%'|+P.4/#0/"/,-;+N )e*&N#v C\ zA6x3%z7,q276<e@A$A&A?<e8B4/)Y#c$( r dRD8-%#*";1UG zd G $z8)b-04667B7C6;4"1-(Z!YZKKk"e +%|(Z+;,+-$,+*> '`D# 3V fT8'X/8X@8GtL0PYRxSTRyPcM<H C^=)7V0 )w"s4$ n@5 W s/R 3   66Xf $ bR$ a&$Q)y,./0/ -*'i"CP o]90  u n y 1) eT#b*$[). 174Y7o 7w6m 4Y13-)#WRPDK g%eNA H Y{1Rz  eUE!J&|+/03^6:><T=h>q?w>s=i<X9=6$2V-!)#cV"  m]%Y r$F "%GV p2* l[ L|C"f%'(('%"h{BI eV2*&!YL \(!a&*/N3~ 6B8[9i:p:s9e7R310\+$& ^H ;2E `"[;|( l" g$ h,oFg5 Rr @7 n?xB!s%)-L0{!3B5X6h7t7t6h4U27.g+5&"|G3  ]P# 5.`S y$>N ^.h7c3[,J3  vTI&!` u9 T:&"!4, HR i2+vg A{I~#&*J,j-,.8/<.=.0,o*R',%!r}K>  xOE\ r: T:$ $" ?G ^qXM T(Y!#%)'='F'E'?%-$!^^3+ oH?~y}70k^  4Y/xJ]jqpe^yKe9D k^5.y] qG ]? V; RH ^X ly I@t  C{Nx!#&#4$:$?$<#5"'! bd;6 }SJm~8 O &  $3 JgyH@t 2b:^    zUZ4*  raVB;,' &!82VMxj 2T/gA|S`hf\~U^8@ \S0+arF Z+ B 6 , - 2* A= R[ly# NFs Q.Z!@ V!i*"u4"}="y9!n/!`"I+ tOA! vC< rOa7 K, A( =3 GA TVgxE>vj   rNT3iF{goa^<dB4" ~r@:'$n|hwA;OHZQXPS4 . ;nd jaDUJ[]l ! ~m;d U|xxa+; JIZes qg 1:!%*:*F,j z"O){-(s'!u$&Dz# HPp;!i4NIFa-!!+?O.?% 8S!4G&P_|gtuHsu]g[=bRV&Գ H.46G:>b"ߌxUAv$-\#Q@ :58V$!&)>c~  sj UI'MrfZ"%"G)u&{-M)05Y, 2.3/4 q0;7G3647206x287H3B8;43C73@8I4/ 725 t164>02'..S*@*w '&$h#!!+Q}zU S^)8Y\m ;A$Pp $&%;ߍXxkntix(`ԂPLӑAў AџAlџ@BѠ3ϫ\&`θ w_\KXF,[q!̽*͵a<ϤSҏ>bӁov׀e٘'O۳6w  M'Y7AsYufK59% hO'W BW\F wa z {]h"UR=@*6\y/"6 O?&#rC*x'-*1p-o3^ 04R{15Kg26E137=;4_8:4a8;4Q7D3}5T243g/B1w.)/ ,+-E*e*f'P'*%=%:#/ $! ;" 3 (\ _%B44=uFmNATK[0]G\a}.efOejwsfxm [V Dk16Is%Ki'Џ\ѠLӬ@Iյ8O)>$U !X73PJm n(Q21aA N Q [  gh s6V|OFSr/byS=E4B!"#!&$'%o)C'#X*!(#e*/(#w*A(!M*()j'(h& %' %& ($s%#u$"#"&#!\!#!$C"c$"$ #%#%#%#%;#$"$!E""+ 5VAN\um}uxc{P > E & +>aQ>]l|}}l^T=LB0:D߽6*4 0\0$/5h:޾=ߺA߶ GLaO#QnT~X7Y gZaWS0QWKDGq߱Bu޶;ݼ611 5;ܼ)FZ޳SbDzs=Riw[ePC:L&Ls(;<7 OObu<V}kl> [$I #9(8&+'*.-1 /31b5M3v6W4.7 57l575S74564e6R4 i5b3f4j2%371^1// --),+4*)>+(g'I%$U#@"b Qp,>~7vdTQB= ~ )]E/[)k<vM]}kWwz,3pߋhޒ2`ܚ>ZzڡVK٥SاSzاQש.P]תP\תQFתQ%ש}P֪(P֫O&֬OլAPիSP֪aRG֨T֦WפSZס]Y؝aٚVfڕAjڑ#pi܋xބ}{vk9nkf9]hT{JA?51X+A Al^*K 5e ?0HOS9VW'{WLWVU;TQ#OMILVJIcH#HG$HtJ~LFO@RSUGW-Z ]` bdf3 jg  ha! i!!jx"!k"!k #7"k7#d"j#F"h"!f"R!b![ ^=YRSjMkE$?P7d/`(! E   1>1OSY mO%,\28>Dt(JzQVݣ[]۞H` ٚ4e֕-iՑ-mӍ=p ыsψluY·w͇)w͈!v͉st^͌5rΏ"oϓ`k8ЗgќbӢ ]ըZW حR.ڲLNܸG޾AD:n4m.'   y N  s!$'+0-136]9!/!;~# #=H%$@'&B((C*)Df+*F,,F- -G?.-F..F.*.Eh.-C-,-A,a,>+I+;R*)8q((4^&%1$#-!l!*[&"RDT^43 /   :/[Ssr9DGU  ! 9o 5Y}t+t~5U)F\ v. F  !-FQbiUY-/ZZ  ll67 sx!!##@%N%&&d( w() )u* *+ (+^+ x+P+ k+*+n**))t((E'd'%%$9$M"l"I g 3P2   ! 7 j {   &/v|dZ V?vP2!Lxe+O w6!"?#a#B# #w#j##4"-!G!VfiDwHtxfg@&+^=k|  nZ L:5&A5}tuz2 G}  $ L  _!)@$^'H)+r-<./1y23? 4rb566"8m9:=0;{=>Z?Y@@A)A bB&BBI4A?>J<d:Q84}1-(! #  8 g ] the U'5/Q7p?G\OV5]ޡ/dܚjڔpَ5t׉x~օ|ՂӁU|QӃz҄y҅x҅x҅5x(ӄyӁ{~{؂vBڇTq$܍@j@ޓhd{]FT5Jd@+6{+ J  HIk m9 x *F |3;OCuKzRTEX]t c3g)kor`t/u \wF `wK 4w wvvv^Bv*utdrqipo}nWmlm jhMfse9cGa^M[rXxSVSNH|B <_% 5 . , "6bnQK eNYy#)I/75y;:AuFRKKPAUFYn]a e(iIuln0PqsOduwyS\{|}~~}}}{xgza\zUYyPjyay{~_|]|yupkeE^xVlMOCI:L0g%$VO !GN. : Hy AUb\pd~Zzn 7`UTw Iq!"?4#$5$o&-4&'&\') N(*)*)+)+)+)+(*2(*!"'((%'1$%GHrSj[;djosZovxzdj{36}~~|z\wR urzpaLo1mkjiiihhhi>jlposuxk|N}Ov[inpg`X-QhHI@8O0'N^#BFF X;[/\Y%X+-2\7<{A7 u G 5 M R X< X ] d jqwwQ}EyrMpm%id*l_[:W T%PMcJIH|F.DTCBABuCFEsHpMOSZajSu\cy j Zs G _5!7d { 54I9_/ltoZS3E1^ +6 mGa(A #M$?#g!"n}=~M^n #g+K<6M^ oxgV7G*7&'l5 ^%L4AuO \ 7 i w{ z2o^dZQ H Ae:4o.U*%;!i !h8#'i+ 0}+6q<BnIQ/Y;aHj2t}  x n e [ PkFO;"0% '09$C'L9VC_JiLqtHX~@x:jQ89M[en+nBuZ9{rftfrLp 1T" U&#89IYngdqnscs}XAO HߐC?ߑzA*ߒ~A.ߐC]ߌH߅OX}3Yqddt"T?pC0n)1 &5^T}uaBD%'5 B,5X0pLkfJ {{ h  X R I ; /}%xH JS!4"@"<!)  Qb   >1!V/$'*k-i(013 6 q:w ?$ D NJQ R B YD b l @ uY   +x _k)]:N9>).  Z&t#7]H?YWl~rv]CHI5!N" @P$,k4c;z@3E.HIGF9FFB@>:141,8"^J8   ]/./?mU,nn #W>" sa 05_UYu]zc ^( @ #  q"8mUFJr$@xJemS&H9>4_ 5 0q 1` 2q M 6( =gGU!cAt< 3_|*`m0A%6G R n kf+Kv.M2EEV%et|"Dl.^PQ)C9'91(3"Ph2 g [ t YGtCo3\*&Hd 8_!8}-K=IXljsto|wc\R[>w,' #,t+A@WElL<CXn4n ^ v N z > +   a(6IF-UKful" )9GyrLm6hjhvgvh[l'q2xu+Eevv{UXpV9 H W( G fU g?n-7[!^'lTE&DE_=iGxL1Q1)M /87h88+RBlZT/uP<o$E zsY)JTk?eKz0}E%0F\ps>oeH X LD ? 4s - * !f I   : @  (G 0| 8 ?3.F{MS0Xi#]Zbjdg g|gqg Qd'`\zW4{R5L D9 >1 5n + E [  1  * l 7 DZ Q* ^f lQ{/{dh&V8D[2wD|q *;KP]unkVA)+T||"~4~DRSrb`oNP/{CD7+kk!7%'2r(nD2|@nPf^KcPL1y_74 UP$ `0#&Up|NO+c" LwyaWf 6~ 7   T 6 Xv.n:_xRG H?9,5R<2v$h1|$o2f#V5@*:A|IEU^`-o i L> .  | 4Y  N$ gGz]>v@g%Z kZ;"n"7 J]n~}TkAXiF\6(EX+(HJ?vbUOIzCt<I[w' H4ob 5Mw ,8 -(b08C@XJ T^}jurw eW8yHG60#v(\;FTF#M@\]}}b~cH - < G c > ] |RvlM[ L g?0 4),2'-9?#R=m"\?y#I=c&"95,34){? Lv 6\hnr.y@\@'5 y Y 7!   9b Xx[}@V/8D>g`+eQ?btu0aGdt^uYps@k*:@&tu Hc4M#s{x53k'IX_Dw/z@wr$ E\fc F#*PsU2QFozx|dPQ?p 0`, #  _ c (  2L a : @ E I2 LK LH K> J- G E C  A n ? ] = K ; @ ;} 5 9p * 8b , 8e 6 :r R > m A E I L! P? S`  Us Vz  Vp Ud RI M | F 5 >w  4 u '   ' b 5 EKWj#"#3d9F5&z1=$1Lgga@ X #1BFop[Wm@~L+C7r+X! :IxU|  x1ig_G@U]@v + n;_c]_(qK(voX9\ ?Mt/WKxGYE:m(  ? bD   ydaP?*0F=S$LXTYbg i ~hVc[\!P-HA\</M`]u    n~ M? * , HTfz|ZH:"&,a@RMcfqtbcETHNE<3,L%t P2d; mOp%;oC! .IlA"&'y(u)q'$ FZm)-=l0I}dfhN@5!<Z.fy@>No`Wq?t(v \+v # Gp cd }S ] :  ^ o ^ 8NA0{4@*RMu"W_ c/gMgNf<a !X+8M[6?EM-6Ug|Z     fL @ ! A8be7LnA8C9 ^]tk2C#$}AlE[8K(te,@Xqb(D$[&rj7SS<hq"E*!D:dYkkmy{S:}=m&_lE?/DDMXj3z znod%Z|P]H?b5~- #H= # . 8F VB K T' \k . d e l s) zb    "  <  P  d 7 h = j @ e : X * F  3     ~k x5 X o  e Z6 |N )Af 2 S!V +;]Ll^=pK4gTI]+`I ]yS+CE+`{c;Bi!B)?gT%Li2I|Y.n&t]ABD U4jl0l|g4OsSw5Ds  B4~_Z,^7Gj@G9v^=%# KU q   ? y Hqk a TJ 'I 5A @;H6/NF5;PV56OQ7#M9;H@ BF 9PY -7Z g Ht4  (E   7 0 q | rc J 4/N )>SQg{Fz~zNaJB-2  uL$5`FAVed~ sk}YHmU9*aYkw73 jJl)U A/   pd[d`m  -Mp(^VSE%~=9*WlsU{=0$ k?He!y0\N?p""[F /k   c  qs ?_: !M 9=9 NJ/ a!rRH~ 8VSH bzy( j7O WoH AZE )q 7 R2 ] _ I ) X -;|]4~R`'%Eed }9EVu1 N3_&~7hGFSU;@&a0j#Yrf y8 xkck} z;tem Td+Y29MJ?\0]o!0MbDH$Bya}b?]IG1+=z1eL" dyh sxl(auY6PH'A\;O5 0+'  "4&8Q,_t38> E L@ +U Z_ h qF | * Z X     ) + E P V h _ u [ p V j I X % )   a ? ~ l Vi @$&:t CPoi)r^6}LewI3 Ssf;}Kc{M @'u@ACTVY3vou<%(bd$LER o1i,,<yt`)KKE4dc x99x`[<a2(P%cw3/EmHRA7Z 0 0PMp  gJ  ^  ) .  t (i $_ 1WE ;(Ql C^N IM IN HzPp EeUQ ?<Z) 8b .k !kvh    A x  R7  Mv Z 6>`)!?TjN]Bt ZY;?&E]r@$2A>aJcU_hKtVpix^SzI;L?5+v .H Tkp1!yx$<2z'cp-;gv4_?8Y;U=R>Q=R9V 6 [0/6dj(hn{eBOrr#{ -NsjLpSP86 U ) QByl!O&1B*B,h$ \   /  = n0 % [ >K T=h g1 x( G! 1 @ 6 # $ k+ '5 t?M ftL UZ Ak1 * | T >   -1 h  l_=2&M}&h:#LoV_[7\D]`/^LfieqnKz &`*>)i9:RSH=T,f^Pgl p~qqqunj d3]c %T,wI<=0L>0^#p$0E%i71*@H|TnPiK^~QHB`4='b& a':l[M`TqN/|6ppcJXM[Ae7-##X1f>KXW@fxsF  $ \3    &L N w   ' 5 3    Y J  ~ 0 q Yq T#!T6i9R'm/ W/"%i.5sluhpO(k1d^>W6D^67mnV>W( 0Mhdf " oo  & %p M p     *  , +    u S (y 6   LF   u ,ygCT;@_*.8yFI TbqH!fcypcFLP@uqd# D*9+EjUEdsnevwKX,H3Wv$}bv!sq!C a|7y$wrn h%c`)KZ}-3T[0%QF0"PA0%PD-:Ub&`^iw*`1*=1Z{?QZ">$3#;N}s*XX9pRD<m#7 (3  G U {*U m> d* L Za Zb Xl ]r Q g R f J q L n L o T 1 "    %NWDU5|"jR~;Y8v_q(QU{-LMo& U K\OCP!')7J 0birdY7 'c  lJo S o0 R p7 R q< V k ^` prEG & c M  {zIm8:$'y&$=M|AEN$@1U8hVGR&SL fzvgt(zm:kb4R GxDd Ft.Pe^vsrS`OD@% ^Rgw]*c=zwMIY)~O>1]Y2Tog\bU.F1H0HU ?u2rv  _ W   $F uD aa Pz D g 9X  , $ Y   . B! o [l p-L ~8 .i  !  ! u WDk {PhxrQ?G60&cFg$C9FJX;bdmp[~uG>;6&AorbW+TO r9Wq+E:/@&uD| XHj!b r H" sO ]Qpd F 7  )V   e ) &m  ( 21 =i <_ 8I * Q h# #   ~ /*@  7s L _6pG rS F)  >V "vTDlZDax$,$QlqV ` &BGcKKje^~x^uPq@tPxd &2^zl^]9O:C23Z R".?)pWRh8au/' Rt*DycNp 0<(. = ORz_ek o  n jd[W(&GE1:8/;+;*:+;+9-624529-?)E6%1LX";Pi"<Pi"9Of#6Oa#7Oc%,LS+C&369-A"Kq9Y.giry~wS:b w`n+^Kl:+]/G ~ [v/&I2>9ISn`3nAi`sQ=Uq0P&+ i {/B O%I"8W) CS k'Dx0|BvqSb,j3SvD2$TmGm8 W|{GJr bzV&MPGmBB$g=+<-:0662=,F% Q5([dFd\lkqvu z~)ASeiimhkod]=\o(^j+I:2H^2Y iEyv*XrGlQz3@cm5 I]NpapS4KFBejm<jxJm .d XTL!A'6-.2v%i7bH;M(<J#=D<K$7cI2}%q'5I"\plug~%7++yDi\VtzA.*B-F<Cn}[_D^%@*M>r8VRg d/ M sm ah N < ,W p    %- %V' $N   g .F Ai V m&v/ R W/  K % m P}&U A`+J[vob^2 {/:dV}zmU0VW ^>Elg$N^$55wEHUc0rCh }nwbCuZ qX`<my3Kvxe-T@g-+),Lcpqc%mwEYqs<[~31a~IlR8{=f6[w/ *3 TR uq c%O Ud J @B ;i ;i @A K XR g0 xm }TI := %y # Wx&+zu 4k<+S;d9bJ!Zk|EOzY73R %\@A\mm~y@r5.zZ^HG{!4')(/269 9 }8 3- #0NGb~p8 73n#P]olL<a->"o\" 79\Rp*6D(Tka&m;uJzT~!]0g@qPtUtTqPhB\0L|:u.q!lj!l6tQ~rS Y{9sYj _TTI'?9605cGu,U"fs1}\  " }v8$c2lL@/5$O\dhrZ|1S.}u[c=G})rdUgCv-3Sd*M=^P~c]_YvA&f E0f'.rvc5UJ|Bp=j:j:o<x?C LX hQyQx6u )|r#G_qewL9y$i &?8b`>{+e Q?":/dT!lF.P m;a|{dzQl<XD. {Y<}'yxwy{~).2.| u#k,h[94GBH-Xnlc~l~s5{]B ]% & \'_6!E.Tvcj]cs(B(!h0^y0@} gyS&@0#\ Y+;"39>BEyEwD|B>81( &VJ,BXE.rt"3tmo D` .Fk\[kN!36Tst0> +Bmy#b D*'hU37=GNRU&Z^^_\XS5KjA4;%ue2QSRrna ,*lSJ1YjI8%%lEs:Zey]K|es-jNdh `v #^|.^~0^}/_z*_y)_x(_x(`t !ao bm cf f\iQkGo9r.u"vxzw{ q|e|c|hz uy~vs'o5lAhPd``n ]~6XSS%vO,K1I5H8G9G9H7J4L1O+U"f[;d^m:w>G*sQ9n0 -"z6,J\dl]H9|!.w%a;K tO2w~t xpxp}rw~3Jh :Fg?uA9dUqTpAV.QV;e@d3 taaP-~?I0cM#L{z <zX&wU)2q{2 a<<QF?Q,p\m*gEr}U}^Y?$! zqibt]`XKR4O(L!O(P/VC[W`jgmptvroj`jU?G w6J" ,g)f:(L}_\qX=$:VezMeaSDE)9[ .5 *&(( )#-0 3G0;eED_Pau@$ jsV9\`ED.Up\+U<hr oVx(J=S#2 2Lz1YM; } k. [ ND Bn 4 :g 6 3# 3% 4" 9p ?yJ GY Q4 ^ l# } PF b:3S  +uYL:nB""G9iI` !oC]C ,}>tN;[ Qdko|qyo|ke\ KQ2Dc5H#!"4kFj9QHI)om=;]\K|";)*g\  'L%>V7n( LIi|!}Bwhsni  c7^I Y['(Tl/GPz6`L=yICFIDMAQAR?U?V>W>V=X<Z;\;^:_9`9`9a:^<\=Z@UARDMFJGHKAN<mQr6WVa.:[N%a8h pzVuPT)mY=EZ-*K@-_;.J},XceK|se2$1o_P.zeMO>_- E X5;+(+)36CR[)}<PeR}: vvn{O%dMMv5EA 2ZDp+{~5hUZ28CS3o %h/ Ww9mr{B _B$%e.z-7iAYJ~H|R_9F\?( eo{DVh1& xgfW.Iy?h6Z.O)H%C"x;j4^0W+N%C7 ) )  !      hO!/&{-d4K=-FKNrXaaOk=_vk+ H&d)}xfnSiJiJoTqZ. xc%iBsWaFK3O 5  *`4[5HVqY;1B[*6#lA1}HKpas[znyh1RT$;!<j ? v5uKeKec |:jeU_4 h2VKw0aMwm%(#sOAgSVB3'  w} uy v{ &2C%T^g@~j:H-|^m6Y_PPlA'n2X#&/J)uAX&mm%ARYqz>vYrqnligd #`(].Z+5V6<QGE;LVNXGgWwBya=k7u2-'>#Vt    p$L,"4}<lGfZzR@D1^.kyD])+njQ X2&G&6OF}PV_eCt'4S '?gi'Gs0fXNkDL=79+6 7"9)??GXR|*aJsk(m d@yth<~XZG{M5T"1 M-R z]lX%= Eb`}3"V.SEp|    lwSG4(15AihP;M9]3ky<\2D oh[(Kh=U2G)<#q3b/Z+Q)N(L"B:. %  yeP?%")}-p3^7P=?@1vFzRKp4PgWZ\QbEg;l2oo-\q(Ls$?u!6v.v0v0t"9q'Kn.ci7dA^LXWQeJrXz8l2}.(#8Qdt|l[ G&(*/5q<[DCqoN%^5XId3qc~a2Z_%_=(No*~=DOe$b?sMl?0yTeiVTG@:32{)+f%(^&)_)*e+,j73C;VGiSexS=Jxp"]^EMh19Q'8$ntLqY4N~sPf2YLMc5C=vp9[2s+&!"5HQVYVM C#3&!* .{1p7_=OB=ytI*mNO`'VR]Ed7l'ytH}qU9p!@ |unJ^wOaAO4=){,Y8  wfYKD!:$+&(+/s4e9V>GC6ykI&nKOb'VT[Ia>e5j+o"jrUuEw5z 'z $z !z #y +x3uArXn"mj*d7^CVSOb)F1t`<M3j(T4C-lIh49Ag{ p$`d;ZOQaH sKA6x9K4\/j+u'%#%".",$%&(~ ,r0f4W:G@6|F"xTMj+UZ]Kf9n(hw1xZ;A|k9xTW<5%l)m#.,5d?HG0oXO`+VQ[F`/!F $[,-6hAKwI3fPP"X*WI];f)i!myvHz3{(~|"z.w>pika2_7P#W*DBol=X4q$,H<$L[Fv[:k(k>qeN8+NPm:eaSgGS l*<7+J1VajR+!\_4wF+Yrhw-h!+G;g[[>bL/]@P[.tV\:VH^wb4 ySd{ k&3 Tx4J Q)j t  wu   {> (  Q U _ @a 6X  kg ? : L ] J \  p0m 8u |I5zq i T ur3Ew`A 44GWu5D"TT%d ( ig'XڿgNeR)Ԙ^XӠH~&ӴSҦ9oӺCҞS6c$֘8<܋hzwAJLnSh3S MGBgTyf MY7 N 5 J{ 5 " %,s({^*orvy,R,b.3M0\1aF2FG37"3)@3+=3Qk371/ -*oQ&! {" P ygb   p ucN"c 9DP1(kZgB@TK'>pM8 WSE,"O2ڄ٭aqY y3 ׎s:UYup?TPI$vggRC Q'D9h2z$Jz5 U +: l"Q  2 1 { kJ ' #w D$\%KP&m|&k&=H% $ "T A!   & N' 5&M K-1<2SL>@6VOtOX$i.X~c)D#hT3s@T߇cqAZ4 v1_=fL~>K:YH/)$;h t nh   A ; - o N  jq _   & iRO m j  _ 5 kk s+2_BpVb!8o ry ]5vk5*W p DB'!chw'6vJGRzߜUt-߲A޷6޾%c޲?ީ7Tފ`)p!'FlZnn OF[q\I_| Nyc"vf ;*=` C!(z _ h q ;R 9! #y0&](2*e+MW -Q.:T/T 000sY0AZ/*S-G{+g(H,$qg .} 1Z  ]E n*{-[hE#M@+H\ DX{F"@Qχ'βO-]!b#̨bf~\.u0>8i2#G|gz9'}25'8-rl1;I o d ; Q 9! $e &(*bY,"--N.//b/!,-d~Zt,n*)'H'%*R "  J y Y opF{ J_ n<&+RsL3^hKUk|dcW]dSvݽޚMm\gV5u+p&\n$xLBNo<dzz[tZ!-3sY#  NG {PLN&" c:!-4C%^iz' & n/QAkzJE# t Z MsN]}c ;U f\C g9yqE; oWtXyr>}#tS=t ` =NkjDCt|nIh=|m OvR!Q}$k$hv2]6aO|]C)q ZPW\:vs^`rRZ:N]I^4Hdw Dg b=gA(Y;O  D e  \ ] =U ) 8 o$ Q4g P$ } Px  jK ? 2 W />;`Jpug !q g\VlxBN$%Wk[=}w GaLKS!W<1~&racDC%pT}&dIVJW- 48N&;`u=p9/c=STo# MA *F i . m <! [o n k R[ # GB b ;sU q JcG:\$(\KRDKxW'jgyuODWD @y _} v#@vq O.s_A.J% yRJk!Ly8VSizW   4rUTz+ ap d  >~` n OY +>  )! G ? 6 _ t 6 a w(" L F;  W 5{c | d  i : XeL YJ8c { n- :.f@ u,nZ jMA?}Ny f1C  2 .  o` 6 T  G    K / W J B } . C 7 8 U  u $ /-8M,B` qu v} J+l}" 9;u5K}_qHN*QkmD!~rmDRe-m96n3SRn_eA9?mu8ou0: 2[s1 (yn1-Il< Ni 5} : j  O r8 o    u s6 N j% < 4 Mp j&K H{N$>U~ 9x.2}m M51D-r7lk tS!@i">fHqaH Teq5=?QBjI]@TYDH4| zbSk 1 EC zR 3 y   k w g@ ) B5 l,? '% #EKG`FfHhg 7OG,W q#fKcQW r'ORC7,j#T0/k^gy=TG!7mj8jJ``9Y+/^Q?$_imnrvsxyp\`A%$zWD2 |r(A@dXoq:HA-[p;p<}_b)}%$M*t)Rt?y Wu|/N ^L.="16- ;":$09$"ZC{  5cC({4 PSWD fPr`Xpg90 !  LSNJ`<|q+euJ .w e 5X |U Z rj w O! A(*dW] w =\ #p5(9:I<}\o 6a*&( sCuPTx( s'\QSdC99_wE~Ol$c(gm&yl<X &.@=nw6k ~WASuvKgvO: BE &L` s Cr ;\j9Hv)R]YMeh T!i,VUH*2 E~ E4 z0$^r/EuZ@BG%A`3>#J/WH#\zkOt7y'gy)ks8e^QU4NFX=FIwz "YjBtI{yO9_V38BHy=x_ ` !%! x3sIN nK2?K-`Rw"~)E;mbE[$0e y"9-7eAwJKa-CTO^9g'pjy5 xg[NnF^IdMl]s ph\?Dp?k+ &2jh$;r*HUf;*#o $ Kvh<a}Q}qs;uQT$,$*]I>m5j7<R8ab3u FKY%58tEZf|LKRQPCG:NHLFGW`p;p)=>9eg)<T;PCg>Ai }-H!wb {8?2|lP/,`FQbI~u)ho<=T,#/8_Jg,H]AnqX&,lvN 6"hQt@R4902136>BVPt b0xDY>q0Chw :^?AzD|#/EaHe>9@ SE>aV < y@  Mt vJ s? v   s vB uM N  I =| eJ %~T 9Dv6 @~Gli BmV{,,*E\aw],m vSxGu[l\/GYXj-gkYi9nQ-Hm"t:U KmxV[-kIKGidE Lv* 6A %t n          x < *h 8A G}I YZ m1i dKWi"0Kn8x.T{ 8\g/Fy{$H2tQW l |I{vw}:bwnqRX2s/QRb;2la =sJHs&QA  Mj~.fTbfX?6tUD`,2iNd)k+}h*Ms)Q R0p?CLh7UV]Ea?c:c:`A[KUXLi:C{9q9V/u#0t  ("2d1I@aLrWc)mGvc~} 52aH[n~ z 'x1v<v@uCv?w4x1| yiP9n0oRPz/H # >2fFu+Z[Mo%fu=|ceQhAO6>4:7>ANVmq=)Wo%bPb /o /Hys-Q{$XK8dm.dV*y/;3C(,kmFHs#8@zO^>~-O~>a A=CWUp$Zb#ZvAarQYFH?<<8@=ILWake )D{d;Ng(9R#**3PY}*DZsWd1UN FjAf7p)l !He$l8IUC^^ai`e[VS>C /Z// "E8oQS!"mzrgB$;r7T]Ldnv'7j66Tnm=WD gc=o;LVk9'R#%kmkU\( c72OsZGeGeaDME(|{A e T:J0 GRq} >d 8n 7o 9l ?_ ILjm W2@ h  |9 @+d3JS-GbIdb/{*U wxL}%<tz*{>:T_g;{y>,[l`P29 X&     (< ZS3pc_ BLs$B]OF{!W-o 9`lZT~g#PO43;wy&4$MIi~} v\c6 a2F`Uo\7s a{,:V+I'8KHxITa]OdBi:i:~f>aIYXNm%A-p2]"0&A{J~iVDeh |*y:xAy:{/ueOy6Isva<N:e&B| ; e :'.k3W9E>5B&gF|OJu:Lr/Mo%OlOkPjOkMn#Ks 1HxBEXA*n?2};<8H5R1_,m)z$&Hn 1\4\Pld-4jn\2JUZe8t(3 $H!bQxznfV9 1(4=lRD@2k@UL)L)yC$fZYp0N MkewIr@wJXx#v Js+:`NLr(7G", 'HAuml TvwSgZ3+OGOGHUiwAa=h:n9p9p:n=i@bEZuJQdkPDNCW87`(isg"d;E|eVN7d5b ;+o6JA%mKv,W`cJm7Yy e @={_h<Z"UXh>i hNj>lRj9?F0YeX }udZ05 A]*6$g:(Z=-MKYa_l`o\hRWE>2)tO-*:CY^')yJl Z6'JE5IJy0\Ym:Wz![ 1w?$D m:|Ss.r-l<]dJ\YShKx*B_9:1W'y G~  :#Cz5eGZ8mz~73Zr|~-vdm e]&6V3N+R;^HMDofJHywJHyxKGwtLDqiQ=cOW1L'_#2hr}7sHQy`NJ2`7da 6&y+j.]0Y0W.^+i&|?s ,=m3cI]Fn}# 2%:3BA=880-yisY9G3d9g  H'u1R96AfH:Ly Pr RmTjTjSlQoMuI}.E I?!s891R(q 4p  ,<j2cDW5hqw+B=Yfl| "+2}?|E|E}A4% y\i9*r[FB(R +d(q7:D OQp\]gKqo;n:v0V{'D 511}#s0R!,2X6oS/pSU|Krj"b=]OZ$\&Y&a/X'd3["Z!^N c<i &p yc"WZ,|h{T3@,\1V [!.'p-\1J5?76:-9-9-91845=3F1K/U,\+b(k'r%w#~!* 2DXl  2@o1hE\Rr NE9vwl"dC[a#U)yMP2oL9I?GAGBH?K;P2pU(zM\` e ?o{Z UOhK-`4 W*c;%J XheRuq=['z-8 gQGQ^| %&w2BnBd7dTzYfNzBT72*a:<g,a>O']]iw)9F2TI`]lrw~7 koSFdcO>qjVGL 7{#O0R  ltT(h'l(f.R,Z'j*`%r %s"#!)5l}    ,&A;dCr-d1p<B@E A;8~/k%R6[ 0 qdV<8GT@\  33W'Y0m?P6]iit ,L2[Lha~-#wzdYL14 /ut qnleXXSFP:?:4x#Q L5Y  D )"y'h+U/G37365.50;}? aRv}.y4! B'lLX8nHP+c[zrWohSgRPz?[> c)]bG+2LmIO*[g}.ANSuk(z4;{[<R5B=1>y i;Hto8|p8!F-#sqD=G%C^z,kvZ@ B<em N*p@ ]B<-_OF6x}1 VX{en #oyF4Q"-&!  w 't\ oT 4]_?rHk 90 * ? (u[ * mM 3K [ \Vyy(;tO}]VW% ?b  { &nt_h.gXsi*#yG :wMO7>)dQ?|=x1}}3_d{6>h/tD  F,o ]g K< .{L f .u _ J #pd5I {ZJ BU.mWs")C@N5q5v4@BQWx?/|5bakkUuWrgWRBqG)qH*x< _eu{-G<kkq!ggq!nTs]p 38oUkizgmkB5#g V Nv<>8:c3ee>%a v"q3*>Xc`^\0 O\+zx=e\RIF-A$h  '$`78NJScMF<> b|4K$k UTj/l(\=:@=F,S]dd3k'wj(j|5SzmQ-{:4#KY\'vbHg43I#,$,#,"?LsduQb r}8g[C7`gcjS{D,1-%` u"`z i$e x& TA|PlE+++Tw_yhhh n7j +s 4 'a i$_=6PIA1D5@1D5>6SL9=fj5CxA1E4Umh,F%]NkV+vE}9%> W@\7|fF7f*'>MLIT'IW; d.!KBz~YgJ?)MVx!rPa~>^o;8hekFjzZ 0# ;B ;B . E  |qj{a V~7*RA-R>X rx+ie?<_$a<ZVx~tIx,0tkA^oWBD9"8<N(afmXfWNj%toB 9md jlVM.1XHp8`{ZR(R/C=. 3 v$ b@OVXRwFdY/GH I!j, 0YW+|Y%W*B)C#_AG$\9iU||mV ~<}3Q/A0ExFdd32RB)1!%Sq!;g~Ku'u1?!D=GKA-: 0 a *k$X-18mA1E JC3$Z )%oHNd.t_|u@]Zub#j@\OsA06-++0= K^VX Q{J]FJB7B6J]Vc-lIuf(2)TAwUNJ^\aR0\%L/\@u\b|r}mO:jkLDCQ| #X5~EOUn]rF[fdxG%WN!l`u0utNbk-6K0VW~ocL_%90,C#Q%S^Nb[aZc^hq uAcx|fJz! u w wn`^&KA55DkTBflyT:zQ`2Jh9)`<['ABZwHdmZ@Y)NBu`UrgyC Zo "2/Y}B^l]U]8]};!7gPXo5Z2C}*D : n!Le  n$LCj@"\0x rw5Ii"pu$I3>/L\v7Rn[={6!sPwGiRzZi ,v+9=t.tQdk RcD:O0)3 ^` @_'??V eSkhjefYcO`EaGdSjgx^!oujjea$^3[FThN BL TM JRs$]:mX=Vv[ihY2K}7 n(*)E[x-Jo[Dx<2:-)19;ZDx>zK`wOwvQuQsUm_ej[y,JPeE:F0 %AC  ';Jnh!q q!rkZKoNsVT|IlKoHj1H Q%A2~?IOgOgJB8Q/ &<i"[ Cc$;7V0rSPgz w"y~ysvw`~BR4<&' ~oG^PNAU7",!vU}9S# p~A W &8,2|7P>CLlW1[bqkdtWz{LpACP;1558%=8FVxQXrZo^o^m`jefk`tW-VLh?2{"Gz%.[Oj'TGB2,TN&=JU2`i g5kElFkFkEi@ g9 b%{[ WO';: 4Ooi1 kFJ()W ,5Z>EM\W&PbqmcxRqLA13+w)g)i0;IJwUwVnaem^yV*YOOInD>7N/%6v9  0Fe+:AF P2YVe,odz#QVqqcfVKK?05$hX/-,2,10,7"aLA*JT_]gUtdFDtTG74t +>x>#:.96DR5m_wkguXnQ}NEI4I4}M@ySY3q^sgl]zT'[Fx6J&*a 3Ip)7:FTMa#lcx 6(bg|1{3yZ[F>@51 voxhUb.ZhRHL2FAu:J4-1/ . /2507<:M:N:M9I8C7;1)yMn"0 p{C]!A%)(+-+&" Dl.X  '8>XVyt&0:TAF"F#EB>n=e;Z:R8K8I7B42- !Z~ .A  eUQ\phhq b+? uPQ_4kzu>'kf2} E|M} E L8zl^0NOC7J*q .AhG*5B>DgIIK:M3|N*oP]RLS AS BRKP aL5~IDHOGUDgBo@z>:5D-& <_yCm #Iew&2.= uJEVzbXny6aO 8wEoyh e.c=c?c?d1gknsdz:z 7U;vftWF:6K! '6 U,<K1Ycvmi[Wt_.yX|T|U|U|U|UzWxZu_*nhPJfs[P MD[6+*KoD]p$-7WD3Qs`ZmvF"`FtawuiP}?rh_[Y1USS~ R~ STTV W)X.V"R} NjHLB1: p2?+!wUu 3F ^X#*1W5.:?oBXCODLBSBW@c=|9 431Q,~%BxF  =S|&1:<FMNtW5_kgn s#v3w: w8t,qoiazXBOyCB6b*g 8L yf`Y\\eq  )8Xw} ###!~eQn @W 8L 7K ?U AXE]F^ :N#/   !- <QSpes{wzzndntzzsbQm D[ 4F*8!  * 3ENhi")0G5l:)>=CUFiJ}NONKHpCZ@H<49#4 h.;%F^ g4 "(0D6;e?LB6D(FvGpGpFrFrE{D*@?<^70<+p$5j8{  CYy +)2 a9.>FAVC`FrKQ8Wd]b g"i-i+fb^ZXnUZSQTSSNSLSPTVU^WoYvY{Y{XtXpUaSSRGM)H?V5|))_~%0 |RL %+l/;5 9e=F@/C|F gHUHOHNF^C{?59b1')y i8  ;Mw),2g;ECtK#RUX_e1kUqr9tSvdxoxqwiu]r~Gma g@`YR^K&Cw;J4"}.T,A*4*5)2(*&# zoigecbYuQk CX >Q 5E 3B+9!  &1 ?R]y{!(,.`3*:P=eA{DGJ1LCNRPaPcPbOXK@H#E A|>k:X7G430m*@# d =O ryQk>c5j=xPi  *Rk% +P/q2-3/303/1)0"w/q/k.j.d, Z+Q*G(<'0$!|d Lb 5E%0"-(43B EYYtv !!"$ '2)I,].l0%y263?5F5G4C3;0-.n*T(@%(#!}tmg^y[u`{i| $ ';*Q-n/,0.0/.'y-m* S%,q FZ!* hoGU&G7.%/? V'qI v  <Lk"*[1A8p>B#E;HSJdKqMNQSS"S$S&T'S'S"QMH_B-;5c/:*]#)n Pf5D"+ y | { { tguQ\1D(!"$y%m%j%l%k%o#~" 'GoI   CU~#0*!l0N6u<A6FgK P*TGWZYi Yl Yk Yk W\UNR=O'KE]> 6}/H'Z w H[' qwTc<S'FEEP#_6{Y {        $-+69G AQ BR<K0;(2"+% ('04A Naf!1% N'f*5,C-L.P.Q-N-I-H*6('o%T"< &m Pc4@!  | ~ %. IZ f~!4# I&c'*p)5+D,N,P,S-V,M):%`!9xl _u Qc CS9G+5#+! !(+55B K] ax|- 5!A!?!? 91# v d{ Re:G&. -6 FV ]ruo `u K[8D$,5A Zn4$)j(O-u147/9B:T<b=i<i;a;^:Q9D7352/+n'L"!_- oDR% |uW\9E4 &".9L%]:lM ~c }  !'/5@ES Te l, C!$a$C'a+/3(6G8a: u;|;: w:r8b7R5:2/,(g$D X& ]o2= l mPP-9# (:Q/ lN p<G Zl x !'+/.$ x k k fz k r {$1 >DEE =0% u ]oHV;E.7$*#  %+09BN Ue j~  6G#X+b-d.e(^M@, asER,4 &-BM dv ,>L S"U%X"V)]%Y&ZNL=1 oRa7A!&  "*29CJWTc `p n u   " :$T8kJYb i m!p j jfaVL=q)Z; ev@J# ~j hP S7<2   4 P4 r\)0Vc },B!N(V)W,[+Z0_1`1a4d2a1`.\*X"OFB>; 4-('#$$(/ 49;AFFED@< 2$ hxP[4;} m wc nX nX hQ mW iS nX xd q 5<LWgv *=)T8eBqJzGv@n5`(R;  wm|jydranan[gYf^kYfes xt z 8(* {]j  JS5=NY/5(-#(/5KU>FP[|m|  =ft!"0#A   #B&!h$ N%Y -T o~8? lY M6 & .  N7|K M6 ^J6<:F<t36b1ImG{\OQah;9 3f(4K4'QG|uyiq3CAa0SOv@sR n+ ` X   3$i+pLH~"#]"j&R\%)aY&*v'e,)-!+.0e+x0Z*M/).q)E.(e-a&L+@$ )"&R@"J& 84M Fv3E/CQ)w{eiMa l1"K3]{Q(ݳ#Rks= i| 5 hT?(ۤiv|ݪٿڹz۞5݂T!kmK er+PyzU|KoUpv I x/&XXvii7"1#b'4:'l+_J*.,1.30516t27v2717025-2p,0M)."&*;#='j?#{6"rY2 y =Y9?2jWly\3|gtANA{&ܿ u٤b z]&'h'e8֬\xյ\؃Wi=L0AFsuhRA+}$(ILOp{R wf X T;o 8FJUeb fv"imkdjZKM;FE;m,V>a #5b L{g2 sw u g(R{A/ F *GW+K;[JY ep#9qxfaL|$>C10#X_d&((+Ki'u=qWop[G4P=G{>>c^ `B(N[!,8 @kDLHCG&@2y8^^ ^/1V!FXilG1!2!^0.KDatm؁_v׋TM֍R@։V[~cڀnt[Ab)G )OPuFA`%] d,P?,ݖQ,qaZ~qt׌}gӄ_:҇\ԭц]Զу`|iqt`J2ۇ0݉P=P(SQF{|R *Coe#Kq t9Qn/ '!"Hk#%h&)$*-}-90g/2W15JK36@4%8>4|8A4P8H37Uy25hY03-0|H*'-Yw&)2F"$ 1['}Q, !>j8b9.12,ݢ*8; فHeQ;քXJՄ]Ժ` 4a_-ZsԨU=LdAs2?!Il)& 6(KB^[ yeK=3oJ>2!]3HDsTw  e P ura=QXBt4[)sD !  "!# {"$"$#"%"$ o"r$ !# r"( ,4;kK@\.p=fihm|PV - @ )xa`WB 0+EZEouaN+=/o$c[.c޷ cߡ T*7jG$Z3pM3qW;I z2]J g _ p|V~}wuok]jjmIvqw~xkVnbU F U 6T$ @DQ):4K>"FUKvN3PPuOM^J|E%AS<>6P/')BV7-/br!&*T.U15@ 8 ; ;T = $ > U > v = ` < 2 <  :w :d 9] :x ; $ < _ =, =U =T =- : G 7H 4 0^ ,/&?!2rF   '1<PFPZmclls !x}~|{{~}{wxqjDaVmK =}.WJ (3S, ? ;Rct  t' C!i"$`>%&X*'(S()O)*N*}+N)*+P)H+S:)*Y((|)_&'h$&r}"#~ xj3[YJr:t) ] F 1C5zN$2M>IySI\"djpZwt&w%yoyz$y x5Av߱t;Pplid _"aZpU9OyI Bf;L4D-b&8 c=> [H$ - 6Q>fF\M@SuY^];aBdc e M!g8!"h!"jS"##j"x#j"#i"#h"#f^"(#d!x"a !\p& VIO_GA>3z(4 2  gT!S,\6v ?\G!MVS{W[*^_"_^]ZW.RL Fs@9H2"+@#v/ \C  GZaB n   4t$;')O+!+R++*^)`('M%U#!xy        &,"#%)+,-*A<WPsiXG eS m  O8 T; +wpR o | RHo~   m Qg    /D g z s E T  x(,01%$ $SK5*0% !ZVGDRPpo      PQ\\651/daQNWQe_83 o i   $!1/GFqqCH(.8@|Xc%2x&5 - l / | Rd "YgBONZXWQLtj   { a  Ad!m+#%'W():))7(j'^%L#]!lo2   v GmP$`"j'=+ /2"5U7 |89Q:s9d8;6n41].w)%A _, 'n| Y +  R  !h$'*,.}"/0@1]1Z1-10,0/:.,k+p) (x(&w%  #O ! V     a cV e/\.#)/5H:1?5ChFfI>K4LL2JHHy EBp=t8-2,<%qq /uA%! @ ) 1Z :"@zF&KPS;TTStQ_MKIhDf>87e0 ) !  ZA G\ ;P !]#e#J#U#>" 7p5R- kKZ&-g[ J6 i z j XD  tg da+)0.yu]P  t/e<f4 6E 2d|P   %)-:1/45t5y4%26.C*$.4q zW^C5@=' 1; 8 DM M B U:\vb}gCzjlLm2]l 7jg6b]\BUL* B} 8 3 ,D LlEQB(2$,)?3o=FG@OFV#[=_Lc dfcfdebX"_ZyUIOCIXB9}0i'S Zb ~#u/' *}+-|/,1"3c57Q9/= @ D H : N{ SC X Q ] bX f4jn$p@fqW|qBgo+lh-cW [ Z S 1 J @ 4\(F *G(b7kErTvbp{~nbpY-sQ'|KoGmFXH?MJT:]?|iwsc ShAr/x G #q/@[Q} `q n0Z z{ tok+hJgigXiNlq w6!ti h  Z6 M| ?A1#@h Ql3`B~#'A*-F.~.r.w.".).0.5.'0b1-4r7;?C#GKDNFQSbTGU7TJSzP"M0IDC>m821 n*L!XU )*g9FF>SA a m ; yF ] \` =1yuX7sds|Xv+ z  t g W Y IJ9(<e9 :m&2H=GwPX]Dadedc_m2Y RJ[AN7U,u!a g` *F5e?_G4OLSgWX7Y4W T\O;H+@Z7r-w #c!T' D+>9DSf9!u& X()*)'A$@ /gQs _l*'uk ~s>9Zg  Uw$K&"7\ -waDL$B/C;IFLQI\;e Pn uq {  X d w  } # w o dWIe8'e Kp%7AH)YIi}xozcoXP/IUEeCHD_HIM$VbovfdU*Br/ _n=!%;wP cBu   z/  p h cI `5 `3 bO f kf . r z 2 , \ i u} j`WhNXF$?82,<'&#r8#]# ]Ty qP&-w4v<GEM^U_]fGm~uw|^coiMmc_\k\h^cjt~NXsN@eyMUDP0[`=+?C\9sB > r a3 Rr E~ ;[3 W-)'(,|1K9\BN \n  l } | 1fsN!6A;u) =P1a)pv| lMfea`}bf!nw{/3qg\zPPD.8-s"E; I\?8 ( S/pI^mu|""O$~:(s, 2f8j?LGQOkX[aj3kqy1M~|XnulG{bUHO9l* |#l0j=vI2S\"djj&nqrqmiI'bYu2NEB+4/%Jz"15m GXguQ^b N{ y x { z; +}p#Pa)rP{=s+`j@%!!0?KKV^Rdhiqhd_GWNy D?7+V( mA$0.8s @rFMKM+O>PHOBN1LJ{FGC >T:6@1,\(r&$5!~jl|"&L+G05-:u?@BpEF(F&EBh=(79.g%.T 1'b*48JF`S{-_wkyv'$m}d[vUYQ&O OR@XVal{tYSeX1Tx-AH,x";XWSsoo 8[ S J P ; - . %9b    !e )  42 z AQ QM c% vp9iW;@(6#0Q= HmQYSafaj?$noomjC(g`b]$W@QJREH>9k5&1^.)+W)s(((((('&% "5+k Yp n6 $3[CSkcCtDwjAl b SZ$ S  N ] K I" J M e R  Y c 4o}oX?I& }'E&=XSin}}YaFh5({v/!F!6+&:OK^txdOR: %).'G.M@5Ps^ jUt|8R{s{+Ftmlc>Z{O`Em:u/W%f2 LAUH ~)u>$}(,./Q1h 2l1^.>+%J$C9 NhO&-.c6l<BFGJsLVMML\JxF@e9I0K&24S K1QARIaoBu|ci~;}E'&%u}f U\B.4#\5E6Tgal tvm|1@hhk~0qxjq{pfqOX.I8q&A +@tS4etGrdb}z>xV,yN%{2~gb zn0aaS&D6m ($ r u-W#"o&P'@'@&O#l L+=\ q a{$H L'Q,2b7;g@;BDt DnDt C?=<i58.%c Nk )x?BVn}kmY)Ih < g1 * + &: Y %< [ ( < . 8@ E UIit~|d>Fw'e eT!F(R>fRdttc]}UIJ@K965z7;(BcKmW#ett**fWaGq 6D&n  rI.<HsPSW\'`DaRaR`E^2ZVRcN9LHsFaEQEPEPF\HmJ N,RRV|Z^(bIf'ljJmnqsuutrmgg)m_'UnJs<j- ^M ,@=N4^k.85|:>>?@=9J4-B&;D  ~g#S,\38::<J<G:573.g'x20wE8 f*R+2>c B5 *E6AoLNXAai&oUsCsuZv\sCsnOg^yR uD 5$O~-U!z0;?(K>V_f`D}j4 {l|k)gS:`|X L9?-0G tN? *7NIdxZyo=hc `._6a!ek^sR}OEo6[FEx/Z#?)q'.N2f4u55~5z3m1a/L+2'`#: !)(h1Z: FmSJ`Vn|Hj,qxr!nLmYoGr"xqU~NmiR:7 M*,@ESu0fkvXLxH<U2n+@& & )7.Y6)BQQc|wm^T[$G3H.:Iqa?w,Q5wlDd6_b\y\}^gaIheo!tOy 0hsiyT5A(1G#-~c h2^'UVzbb y_ !!z gQpC"*?3V;D,KOlVo3ZLZJ[B[; Vx;MF< .e)U =ZBp#>|g;pyU{vFr|g;yPuulzxGUcai,f9- , .%6E?oO}]<d|gZ])SaNAb6O.#'lE w]#B]Euai4 smg"\!T]RlSgVLY3g#qZ/9` @tP4 &[t4}-j =]4(TR $6[]/7R0{&'3'/.kM]pl|; W+) g  }4 f5 7ISe T4- g$i#M9 ?D =Fox 9HO^ AB 6K6J !\ y^    2 e YE )BxmpF =)|eHt$bK2 Nf-"HDVx]y]qoQi2z\GS&BFr49x]e<*b:Stpjq 5C Ro L   u5 *WU @G Q:r[ J?<1 @G )Y;l rI 4    ^ I   @ o  Y o  2 > ^9 7 ;j]{/[ 'ZLT.`pe98[w#v)sy.>,ei6G^xE:NslQ9`! tEiLz dsP I<9}!%?C9P H` s ;  P W Y ^     ]oj R E;*DS^y Z)(!B"`kJcwL(GC3?.x];C(Y9/>cv\nDz141 @0!6#Q>_f$nNs.e{nmUw fFX,J<5m/!'* =30bN7uD J K" J <* 0'/D)]}wYs-?/K]vJBs~!w^:oDM;T9U9U9U5X1[f,_Ew&cZ g=i1&c!^6WPDE9q*2+DxoXEL$^]}+pkOUM;)W(#h =  os FN h4] d  n _ Zn J` / l2X BQs } S 5  f @WEw|LtAL*$ LJes5D9U>#c\88~|in!jpo31pU,[NAx#_G%H/btLO& >3|AixJk2n  ( C s Y KNMO |*>    ^NQ3F;z=}3; K  VGvh x $   m<*M@Q.^ic']>RD 9O>cpb?h|}\B>'WH#' 1T(}hM?U(*_vI|(Q{YElJM$*ioN v3tk 0 w t c 7  @  | 9 }U 7  w  *  ( )T xs VE60);BR^`~n~vB-RaoHR18s +d& 3?H5__u>*zDu<HaDa tD\phrkFD$$F@^Tq~Ao~ySiZLj ?3%,/5*(NL))IH".&8DXT1f.{D@`X=}o &?wVlkhgQ:-&,X lvF`6b9O8)AFhnSYRB7(U )OasxFX ;:qb"S  ;    x  = * g+P5"`N8LKc9y~;w Us5DgjF+7(E[RG z`eoT^O4'}  u7}zgtMsHtOzg Y-g=)o31Xx.Z 9$VwUoajqZIG5$;)dTd l p_ M 5l5 l [R ~9 "( H  * #6   % 'o rB^ K^ ~ l "- >~PBxPHDyf!u))hQa#E3I8^&yj^K =x9g=yGW]oMpb^ fl8CmuXMA.P}'SO,'Xl+b D G4&\ 2 G C t   kI *y 4q >k 0 Hd: V O_c s S\{ VZ VZ R]o y Id: T :n  '|n   J  T] (0Vm{B  >+!BI\coy1[LEDK k^&h==R:nd t)b[ 5}7|j|U,g?;j\!-Lbo}Hg UfGQ?+wSTt$3|Nf.r/0T   , H 1N 5A +  ~  L{dzD$"5buPjO&e8@h |V(!8:FxPy=W@}[!}[$~[)WEQz>IiA8=2r-,-/2l7H=CIlMUQz?Qt;Qy>PGJeC;+/!FqmL(<JL"XSauiFpjtwzzxswocg9`nYTS;KBj;A2(d A- V.>KeXErbiibn\qWuhVucXti[r~bmmfz\* OR>*f%-R4xXN|cRL$7.AB"]_vpd/ Qu    T i 2woU./0^_NtnwhWoX@VP7Z3vMakfK}2d,>cx"K&(%O]vAR#i9/SwfncQU=)'0I23ZG}TEz aG H10MLRo)_ c  2 Y r  ( (| #y h <   @ Ji eB%{Ce.pvl<9),O_Pwh<Bv G+I!Naex[:y1lU/;Yn3SCq58nc @)nT9I|ab2$:OP  w I ]  | ^ L 5 +   z c PCm PT<+TC$; K_YYgx nOijBQ}T "Q;>SZggKzd/6UW+]5raaa(iE|e sHb5PWqzY]<@'> vOAHm$npmQXOEz.7C)W<Twbl "r ?q <j]O!`?+@+06#>JXVQbq5y<(s?X%>6#s 2! E%G0:LEMScUX~WyRV~VQlJA8\-"XT bA$x3>1FZKrM{LyIjFYAA>3;#877 66 540,(#sbwO[< <( qP?0w/v'q&ph c ck,tBYk E-`2GVZ!mZm g<ps h\^U;M'cDd*=0845737456-:(>DfJ9m RE\iWyP,o\I`$sm9_Su]k;S/v(g(g[\p5|04%08I[l}Uk>8'E!({FLf9dU+|oo#a S0GBl19D-T#Gb?pi{ )J`hkcO!y jTZ XF,.<MI`tb2x1lX 9 ~RC/"EuZv[Wm:dUm y'7n({ksToFmBpLwagHNp(xNssZt[A/&Q lFA-i:R$qY&CMkF.cg | lI (8!; + G mfo#K+0=uP'd_xqJ~Cte)L[3T ^#^/':bDOuYhgbD?VlIu<~0J%i\[` t+67nFxUmdd9:r[zcRJC%;Z5.1'R%tg  5"R.C,Vi0~8j8CurlfF`k]\^~`ldOj&pya:7 U|sGaQi?.|O 1 _%u&U-72#57 52 -7'P!lO  7#pG)r8JKWco:xR6fJsUzJs}+_uGk+aV|LRA7-|!]jC 6" xQ+y# f'R*E-;.5.4-:-;*E&W!n:\   7#c>![)p17<B"G7MPRhY`hr9|UOsw-Tz.|?xQv[tbsesgt_vXyJ{A|.]76bo-ZD'-y^;6,C:pGyUcbZYNo ;{)a gJ}0bNNWt+K p77}Rl0qYE"/4QQCfMngpj_TR-E  @9WPM1tY*b$h!j lk h!a%Z*P/xE6k[:>9<.E NWb:moz;@z~Lf MG4vI Q'Z5 B?MrWbaddTj(?Hr [80;)X %z^ 8"oC&_1y9B G%N>ST[tcjqx+B1TUku~gu;Z >t g[tN<A6*hwHN/ & nQ>5/1>Sb~!H,zI*f7EQE\tdmsx$z'{*x#uo i_UVI'=/r!NJ,  a>"x'c+T.H0?1;1:1<-J*X% n6e O/#S0s?L-X`er{&A:U`l}|}|mr[hJ]4Q"F 9+xq h_xVSL+C9-h{IK-  t>&k.K6+=nD>JyQnWf\`_[byVeguTf\oQhTjRgUkTf\oWdly_`jYvRI?b 3;&lg5,eK%h8y|hSXV5II=Z4Ac.e0k)Cm(Gk)Ad.f0Z4?L< >E-NXR c cm9x~6GX'kR>=$R 3A(g1B:BMI#zOrTkWfZd\`^_^_^`^a^b]e[iYoUtR{NH&CF=q61.O'm={ N.'V;P4d~x7[a?yorl2gHbZ_g[2tZ<yXFY?{Y>z[2s^"i cVhCo)w ?LW iL!0kH*q& p4;AY ~KmVd[Z`VcTdwWbZ`a\iXrS}LG5@^ 9+2F*b"%X  .k>+^:JUAamlv}' /21 /)#}zusonlljkiigezdubn_a]YYMV@S3N#JFA<6t2k+[%OtC[5B& ) 7T0!xD*X4o@KT6^Yfvmqsutrmh}afXEMC4n'R\4 1 lQ;!*$}#~"& 3Ga 3h;*W<}KZIiw.=Fc\o}|k^X:D0}rfq[JP"E;z1e(Qm>P- -  qR;#& u*h-^0U2N4E5A7;7;7:7<4C3J0T-`(m%{ 4Qn  5f9*U9sK \Jl)OLr{Krke)`:"] F'ZN&[L%[K_=d, lty3|ZR#{ddL5jd8 @(p2M;/CV{K)oQfV_ZZ]Y^X^\\aYjTsO ~I4B_:32P*k"*Z H(!uA.[8oEP]Egiqz!+5BB]Qt]lu~~ytlgw_fUPI/6&se`U(D2`i: .m 4)p2P:7AiFH yJ-vL"vMvL$|I4DS>x'8?/\&~TL)&I5eDQ]Cgbpw ysltf^_FY1RLIB~@y<p8i4c4a3a4a4b4b4c5c6e6f7g8h8j8j8i8h8i9j8i7f6e4b3_1\/X-U+P)L&F%~E$zC${C#xB"r> m;i9i:f8 n<"u@%|D'G)K+P,R/V4`7e:l>sByEJKMMLKGC{<m6c.U&Fh8R,9 %    6K(!p<,P7eD{Q]7h_u ,-H<eKwU_imqsmd}W`H?6 r}aDQ@s/Sf74 z_NA!6!5!3!5>EWct  *A#X/!n;+L0V7a?oF|KQ W\/bChWqsx~ 0+M;iJYfsz|o]gH;1{iXUAp+KK( c!8' ~.j3Z7L:A>5@w/Bi'Bh'Bj(Aq,>6:A5Q/g(} ;m9$t=2WApO[(gOplx  ~wqll\eG`5XRLFwAn:c4X,L(C!i7Y/C#, s[G ?!5!5!6 @Tc  9_2+H9_DrS ^,iPtr} yqhgI^*T K|Bm8]0P+G%w>!j7^1Z/U,W-[/ d4#o:&x?-J2R5X<cBlGuKzOPSST SRONJyJyCmBl9]9\/M0O%s<(}AV-!f55_1/l"1`SmL1nL5{|"!s,GAc+ #4_<C`S 'V1h}Cg-TY ,GgC  ;^ k%2U4}3QRzIm0 8o>\(twS%o`"@w@WYO1')ghB[@e :-t}Ch/&z!I^otF=ddP ;H*rIJ ;K9RBt5>@VWWVlK:rH`&q%p]QY9eJ]bP]S hN,jM35\2NnssCuE3u!D5p~>V^N1"#l}!mx)iB\uF4d1y&YL $UD&d2 sL%7uG#4a="k  V   GX T;l:k]P$Z' }@HxnPG[pTL y-og3e^Fk+~9d#obtGX*o5eN-t_>` z$)}?Uq\o0J{6_W.57 a  * E rE'tf * D] TV lII ') ' xDl 83f qxk2K ?UUDh9:jeM1w<"j7;Pnyt=c h8g8.OGuFub4_*e;xv1fb{!%l|.g?_.gOW8bJYxBxB{AF8a 'FW~7lk rWTU,~.w H#`qNU'Hz VW3_ <D|YL7Se~.o. 'Bp1XWePL\Aa6g!rS }yo_S@z9pIQu@]=X7P<W!b01(6a5erF`3OWbN1nHT.^P&\P![Q`O*fK>#mHS.~@F~@E4g2o% j 9O'"d16MJi]i.p4jfQmM'TZUZ,nik~8-E! 6?Jp5iS[TZK^1ktauTA[V*rX.{5e8^vCo=kIP._N0?^4cB\RU gJE(=P5g.z/x%)"7[YtV4j|@G=P2q&  -?Gbe#qEi-h,Nl%j3tD!)u9%k30CA !}$,3n9\<SyA~F~?L8_5i&!m N&Qoj/n+e/o^Af1me?gT]fUBj1Md CmW0|bBzPD{J~F{SAy"je?O!M3;+Z79uN _X6lnP~eF_M% d6fVQ3b vEW\D&zmu&Q?`n{)r^r[qcm~6`7_NT oEi?/|L S(Ywtf#5Gji| rzvgZW cS cyz/qMGeLc\\[]BhCg2oVzvU.$i%9K0\L"9(0{6imEh@HW"ir@<}jSYKC;2/${%:_}jQjFWrBxH2w"4/7Ha ~Q [;lfiWB` 1;"b*KQj:N?LA4lOX- 7n9"iX1rF Uz'=  Zf;K- "8:`UO-'(ewE|| ~{)vH qhk)c5^FVNS ZM;.cIT:oCtI7j) n ::HesIm(|&3D~a#zk2s>4rD>n\HizScXaTcHiw?m_>n]@lcHixPeUcUcMg<oU1t8{$zjV5R"b{LxDr4m'|Ms6m%c zSgEV<JBQOaXmVjUiJ[@O,u5#]+&g//:DSXlkT.A>>=/ #4RE"~tqlt Ci&/ AU0v(n@oSKjpVe`aa`^bWeRgPh}Em^7s:v)ze#|]TOPH7!C~Mh{WhJX;G,u5>!=* 05u|;diDpLWLB7PO//LQ&+IR(ET"@VDU!ET#AVCU!GS%FT$?W6[2] 8YCU!RN52dFfHy<a7o1~,$+f 30~8R`s.v3`  :"~S;s7pMkg\eh_q[zWUUUT|VvYm]abYfNkh@rBt*{ Z 7NMR_!U&S.p@ZPN64/]lz5yo2Pv9` N C~B{IN Zb$l0n2r8Gfhrfd?U #jB}U.u S%HSjzf!/I})3>&~O0zY3x]5x_;u,fCr>nGpIrFpGr?s6j:v)d3y[-|T&MD9 .Qru-^i?G'e,& I.w<f[HYGER"/4Ydn}y< wGnf,]AS+UKKAgCzV6u)Z  ""V&;AS\hs{:]y%5$ hR{9p{i sgrcm[cRYKRGNHOGNHOIQMTRZW_V^U]Yb\e`henj tnyr!}t%x.{6z5z3x.~<Ay2pzbjJP9?.u2+k.$[(M!A/*' !    .28G P"'a*+l/6:?DEILQNSU[X_]d`g`g_eU[NSCH?C=A7;7;=AEIRW`fpxCii=msSW58BJ+6zl?d[FhSOKJFHN8?IN:?KMBCTIVK\FkTaCwZdB]j@cp=jw:p4-'Px '*g,?B]a~63j&18}DFw!RMt2YWpJc^l]kdjjpakdnakdnjhxvkg|xnf{sdvbxbxbvbvcqe|nfylg{vejko_l\jUqD`Ku*UCxM<|E4<)1" H Sl oRT8:,k-"S#G7   _$.* /221/+(#2Y C::VWvxgU%'9~<Nu*Q_nSbqgta\WRPMI)G7F9I-NW_jjkkNu)O01YsFssa`PP>>+g+A! xrlux}   4!P!.o-@@XVnkB6e)#93LxETu,M[r=T]qAUZs9RXt3PWs2OVt0NXs5PZr:RWt3OQv"HD|<6.)!pBQ~+zo leaZWSPGD><76.n-(_'$V#"R"$U#)a(3x1=;CAGEGDECDBA?@=><><@>@=?=A>><@>@=A??=@>A?A>;9.l,G)  +55BI%V#-k+0r.74B>LHXSa\gamguowqys|#u|$v}%v.y0z/z/z~'wtnicd^ZUQLJFEAEAA=A=<83w0-j*(^&(]%%V" J8/        zaJ"9$+') *+* * '$, I`v =-h)A<VOld2zc 0M h$ (+# \/n>~wme\RKA<0n,E"  }eP!>$-(,0|4u7q8~o9}l:zh<we=td>si;xm:|v6{40-) '#6!>NTY`n  /,e(D=XOl`#rOnveF%rl`XM=6'X"% X%*,~3l:}`>sSCxhKFeaEIW[AJPX?KIV;LAS:M@R7N9P9M=Q=LETsi:|v5/) #4]   A,d&:2G>TI\PeXj\m_o`qbpao`rctew g{kn"o)r-t7x>zD}H~LRTTG~:x.t~myhw ftdn_l]eW]PVINCE;>55v-*_$"L:' '!K+a%7{/B8K?TG^PeVl\p_tc|i'p6vB{P^jxiU;w2t&o!nkkl m)p9wJ}TXamymbXN~G{?x9v6t0r l|fs_mZeSZKUFL>@55u,*\#!G8(    +="K*[".d&3p*8z-:/A5G9K=O@O@SDRCSDTDXG]LcPiUt^i5sWlqU~6s|diTSC?3,`#A!    + F0g&?2P@`Lt[h?ub~ ,19 DLW_]VLD. cCvhz_jS]IQ?G8?17u+2j'*Y #K<)   " )1A&P0g&:|-F6TA^IjRw\f0nFvV|gq{2BO[ f#k$n$n"h`QID 6+uiQy<q#havZrWkQfM`IYCT@Q=O;N:J8G5E4C2E4D3C3>/9y+7s)5p(3k&1h%3l'9x+=.A1F5L9O;S>T?Q=Q=R>T?VAWAV@T?S>P<L9I6F5D3B1C3G5E3I6G5G5H6F4H5I6L8L9K8L9H6F4C2@/<~,:y+8u)6q(7t):z+@/G4Q<]D_FhMjOpRvWxY{[|[{ZzZ|[zZwXvWsUpRmPkOjNfK`F]DU>P:H5A/:z+2h%,[ !D=/       # -2:!D!E"F<5.$     +?)U5n&B0M7ZAdHnPvU~Z`b&f3k7l@oJsTvb{owxn^ySu;m*ga~ZsRgI[AO8C/:w)/a!$K3     (?'P0c"7q&C/J3Q9[@aDgHjKrPuR{V \`/gAnTtj||zo}dyRsBn1h$c]}W{UrOkJaC^AT:N6F1@,6m%,Y#G9'     ' =-[;w(J2X<fEvP~V^`)d-f4h6i3g,e-e'c(d%c%c a a^] ZW~UyQuOoKkHeD`AY<V:S8R8Q7N5P6O5M3L3H0C-A,?~*>|):t'9r&6m$0` -Z&L!B7/' )% $ % ),/005< @$H)R,W0a 3e"7n%;t&B,E.I1P5S7U8Z;`?eCgDkGqKrLwOxO|Q|RyPwNuMqJmHfCb@[<Y;T7S7R6T7W9_>fCnHtL{QSUVV W XVVVWU XYZZ]^^]]]XU|PtKnGeA\<Y9Q4O3J0H/C,B+>{(;t&5h"0^,V(O#E=3$      08$F.Z5h"=x'B*I/O3S5X8[:a>c?iClEpGsIvK}OQSU WZ]!])`*`,a2c0b2c5d:e>gFiBhEiAg<f1b,a%_[XTR~OyLsHlDd?]:U5L/B)7k"-X!A+     1;(M2a<u%I-T4a<oE|MTZ.`;dHiQkXm]o`pbq_pZnWmPkMjIiGhDgIhIhOjFhEgEg;d=e5b2a(^"\X TP{KuGnCg?c<_:^9Y6U4R2N/I-C(?z&9n"5f -W*Q(M&J$E%H%G'J+S.Y6g 9m"@z&D)J-P0S2Z6_9e=k@oCuF|JMRW'\7b?dUkaopsstvux{}~~|yzvstfpZlHfBd3`,]%[XURNMK~JyHvFrDoAh>c:a:\6Z5U2U2Q/R0Q/R0S1T1V2X4[5`8b9d:f<e;e;e;d:`8]6Z5W3S1P/Q/O.P/Q/U2W3W3]6\6`8`8a8a8_7b9b9e;h<h=qBuD|HIN QTVX"Y"Y!Y!YUTQMJ{GuCtCsBqAo@o@o@o@m?p@rAtBvDwDzF{GH|G~HIIIJIH}G}GwDxDrAp?k=e9d9]5\4V0S/O-H)C~&?v$=q"6f2]/W/W.V.V-T3_4a8i >s#Ax$D'H(N,Q-U/]4a7g:p?r@uB{EHL PTV-Z8^?`FbGbLdLcMdLdMdQeReSeOdPeLcNdKcGbCaB`?_6]3\%XTRQMJH|DxBs?o=j:d7`5^4Y1W0V/T.U/P,Q,M*J)J)F'C|%Ax$@w#>s">r">s"Aw#Bz$H'L)P+U.Z1[2_3c6g8n<t?wAFI OT+X:\F`Veahjksnyoyozoyownsmlkhj`hXeOcA^6[%VTOJF{Bp<k:`3[1T,N*I'Cz$=o!;k2[1Z+O)K#@#@"?!<$A$A$A&E)J)L,P,P+N/U,Q/U/W0W0W/W2[3\3^6b4_<m <m ?r!Cz#E~$M)P*T-Y/^1d5d5j8k8o:t=t=u=u=t=u=t=t=u=t=t=s<q;q;n9k8d4]0V-M(G%?r!7d-Q(I&E#@!<#?#?%C'F(H*L*L+M)K'G$@$A$A&E*K*L,O0V3\6b6b;j;k;j>p Av"E}$K&M'S*S+X-[/\/_1b2d3b2d3c3d3e3f4h5f4g4f4f4h5h5h5j6j6l7k6i6i5g4j6n7s:y=|?AAABCDDDEEEDCBA|>}?q9k6m6g3d2^/Y,[-Y,^/[-]/Z-].Z-].[-_/X,[-V+Y,K%X,H$M&J%P(H$N'Cv!O'At F?{=>Yw; Ez" 9@o7) #=5]p7)I4L%f2v:`/N&Ez".Q>ny;+Sq vN\FZM*Rv:E[,$AcWgFEI/mo:<qZ:6Oux%qP0cST0BXX.<,DJ;7b] ( o2( E 8O k ( tQ 5 7` qk{:( 8 U7N { \QQ9[{V)h1yho6>*Qs"~ pS[@AQ`N=w6fTZWj*ݐ  On.Id,H'Ehx3Z4[S'@ A ON m1 =R pt  T`U,/]r# W&P ' ' 1?( j' M&J 9M% I/#s O|!C MY%l^W,H{0"!7w\ l!1? 3 !$ 9),kLHc:IP eg\TJ=рK/G_ʶO ~XvƯr!IWŎDqDq# ?GnNv N;]`$bI3  X?p hM B_$ xp*= / '3 S5A\6a666h% <7^5(M2: qi- ( x# f  l P u]6  Ega[ /Ch FI  ^W+}aukOle߉rU45 L8u&UJ5Ob|X__M|2 2u  U+  q] =y ?W=}^mrxto L}4s) | mB  IX`c a( S uR)&' %P>iABnm%V]J ]!tsߪmX_XG@jӄ.)1Q? )uד:CTIX852p8z)[ TqKK' R!ubE% ) 6L:,P }a. |0e ,J2 I4{ Q4}  3G S2 0h v. u;+ . ' "=ZU es l W# XHCQ&#;n)Q } 7#h!<>BE@l#Mk(/Dޝkv2G0ޗ[=9S@#oK5KUkbbl {F*T.~"Su0MR"be k ?T] U89ia3!UE#X%q l' %)P * [>, 75-R 5. . . A. S+-d P,% * Q( Y%l "!y2C:t $o Z_GPT ! BU[&ڋ uDX!_'5!\6ݱŐ_pܴōnݕ4cxʚI,w-{58&vU`&bd8)o[A  )6 8~lbN|Lz /#s@%/ ,' 2 ( {{( ( ( m`( B( %U' "P8&f u>%' ($r"v1S $[\^zD >N y+qNf _, `akbL,8j8Y'@JkQq?>}[[E6y !"6qG#]\YY5c w K #dqx0 '$3S' n) T+g + + M+a ) p' zx% F"aTXE:c  5*G |[w'8*6vC#ra-1ps6b)"ho(/uV/}wb'>^7L?XA2-r%dF ` M} 6  e; !. htsBP3J]Hi R% i rS 26 > V9Q\ o Jz Ah? ^~P q2j!"C3&~{mDn6F!0nO/s2!a_=) 8 G:du}| 5aZDmW95e:!BWdb[Q07; Ca U?pbYKk;w Zs [V K } PR "[#$\~@&!$0_& 9"(m ) 8* )  ) ='? %J#N4)!GC?d Db$G npJ*2E8.7 9fPuq:sh8%J=۝f^={<*6LwDcc'CzۗdDAr6FbuCcwoF $}QI-k& pL  ZJh  UA J i@~k0M4t $% A9&J:96!)0z?4 Tf%m}`(T - & \r2  [} I ` [~}1Ig-6Q(yAX43q 0r!ApNRa%qzLerD۫ip|TYKrdqNڝpTwb9lhh =Bwu uvXtGt[m} bS M [-/_aUXz("#$%bRS&&&,&%G#2! ^r~S 4fj bD) + )(tFk XF%7h Z$\]JK|pHfp \UV`ks4ybX]SiT =JWvGKJ(g$]M=myM8&\T~l T 7;-nZ ,?jN z<3 : d&& v  v; T* <O 7X @I: U ' t0  |` ta T j c`*eCwH/{3l#%;C},; ?x^5lzAiW 8 ,G- C~uMyHvM49h.xHK DS5  A + b ' ,[a &8K7 3 D (eV [= ^ xP*>  Q[F85+A{SCpU`[5| 0'p[^0=V(5lLmv,6Q:o'g ?_*6rX`+pTI;*!2 *ZICinE{q^eH62=JcmmSb { 5f uq9 K ?/ H  w G+ h<[U ~ -S U~  Vku vMi  \Y6x?W Ts5[ X\fdvyq'88 +m~ DNYlj";eH}7!^Tl]m ~06a0<G 3Ow  _- f <^4xQSUV9(V$y+GU:T/x\anh^ z3 K  *QS #\$ } U/ =vQi:StP .W4/;6?Jq Q>gX9b jt4[t|=hLTh='i# j6 *CEU)3~-hUqYYUR09V*:Z34~}G F +  9 K S^YM(!(-)-SnW[ S  . :4 ( ~M C6/$GjM: @@!RV .;X`}yhM*J 6!R -9Un{iuE(`Bx) r 12b9 iE & 2 Y ( ] ( 1 0 ?B sV zs P9  swA*V>NJ oHGc]fSrIjGK#TK`y0nw{gx_eK- / e 0qNl`<4g0D0!bT:/>P_k*rsnatJM.A!zzm_ /Qx]us;,2*0q  } <J  m  m) I x\ c b \Vz @ Q!   Bi ?k( TT WX >{;"}>Z ,YO}94$ouk.*WvbmF6<H@QJkSsJN"!pm8AG ~QAA=XwO[Qo r y >_ c L< U >  6 } 6 e ; / Er R ud \-v @h %e :  B' a_yAp& i kcJ]#VK'=?53(He^z&"a`&y>jee$"$ZM#vOW6p8BIg!LN+(2$2>-~!De& R_4+N;o_,'!5iC,6v d U% "q   =Z ^]?~(t381}*pA`_  b   C ; , UhRhp8C7DUC.w]4nD$R'0g,De`.jC.,h_sqe6"1EldWD}F6S%(j(r :St-]?@SDhak}XfOq4a$-Iij /    r~ )n A a SJ XQ `_ Uo eS W] b0 ]* Xk Ey~$ )\ :Z h- ucj7RKj  T<tF=} M^Pw=:/b"]<1+T0$}eD1 e,%fn1u(NS]2+/:q  L s 6S _F R R d K n K n S c> c& Kzk * Oe }: c2%_!;c"]<buGg.OX=>GNuYaj5+}OjjW1fG9/,:"u;~  wQl.]Y_yA/B :6AM`h%:s\PHE 0&@gC Y\l)zOd \Ct@fT7X?1#KF ^Cz]nlmif<2F `>axG*^~e U*I^`HL4cj0{pI^QjPtX4P IZqJa7/Z@KJs  h  c Cg El /EH   .v * & 8}0 P<\ mK 44   F^1!\gr}?Ifk+rBT:bwgn`h/v[~PJFCuAp@l@[=97v1L(![ b2R +JS f0GHq9c\bC) b@lfs/S2L' ziC .T G x y  c <m RnS Ee =(q ;7s >n Fcd T_O i 3O   w  J> n`H+B7"bkcl%& "3p E@Mc|$5C$hI:&kQ,NV,00cav1ci'5VHE? 95 5679<? B~F_J<nNMT)Y`yg<nxuH}EzoUS.+vmsaT?D?1 g &:OGcyv_UG12R \2qG,&7"]NW!14iLre\@E+z^!<huQW:4xX t|,       8   ttU1= /^IuZ1|fJp{L6Xx^'6thkUS=W&s2G)m(lP134k7(62.Q(p3o}tn+jLjQl@pvm~;\ mF|A skcv^\ZPWBU<U>U8T8T3S1S.RPMtJgGYEGA4=r6J0 'Ni `)%27I@O*^loz\ZI :Q, W7'+?c$v2;D|Z)i3sTb<"!MtHyaJb1A ,: T3 xE  $ 6 h   \   "   o l E.SPx,Pv19Q mnJL9-(g.@  %5On mX 7/?SOxa kro]N!:@2.$b/ _}(i3J={G=P{YbHlNvM_}}wrI"myChcd`]Y4VSThR}QQSvVYZ3_Vfpe{dzf*pgU18 "I<XsiFC% iQ6T 35Y H~-VJ];wdV4W}zDo3oUa/oVt?O-(6 7 ? Q B  ? @ 9.`$]!0@R#O`&qqat^H-5"b qD3"(~+Y.;///-/6-C+W(u%");E F G=/!$'}*e+S-E->.;-=,F+W)o% 3b2 y_{N,Q9+HXehy&*ujome_E]_]`_Df dnyI4)jq Wg< d6*Y?xSe}mv_hVdGq>M8C7P9o>YGR@{&ams$]NLD9$o;M 0'B5]vRJ+yangaQ_j^m`Ze1kcr{NnlvpS`RCE55'DX  c6#"(`-.r2W5C7891:1:79E7S6h3~0-/)X%!:W&nD{VafdZuNsKj@e:h=lBvO_ /=oe,v:`JH[ym(~hvj!(_yUMVF"A5?2@CJ}Tbs6m`? ;QSiy~_UIp6u'-]FT|O"-:P J[=s8ndESC5[2!|X )y- @#RCcrK-~$@6VHnHmAd0N2z^D1 ysmif3`^Z5S NfH6B<r7K1)x,I&!"hBS % |f>2%h,?4;uDjLU[]d`koz7ouejw{bX4_M@DW1 ~\&m4 K^bCwD\xmZ`d^ Y:WTWWZ;^ee"loum}_~krX]DU0BR Q% **43;eCyHSL:O0P|(Qr6OGNeJE@-:q23t,$m T) <KY&L.x38;5>D@F@=?/=;85j1S.@+&n(U%="%   2! N$%l'G,e0494=Q@kCE FFqCiB/<7d/$j'"*c> %WV-6=;DaG(7zP.$V4 =LC*87JCX'io=rdlX\djgSmbRPXvCrc-,G{04 e} oNH.28`5mDJHsC\FxBA>B.452"h([6 x S*<:l?_KPnMRVdLNI^K0ES<f>L;45<:66 53520A'f,f,h,=&^+66C:B@n>2(718r-1B'=bx mMn'27Z==oAmAMD5F(G8FJmIwH{WB6[0 03;,$f*P9 LW?a^ey6htnK5{x@'xku"{s)]Y8_RQ y<|n{ FQ2M|b%A-6 DyhY>tM_yn}m}VX:HgP&JThJiE>A,LX`z   B < M xX# x tA { of a pmZ |  ( u ;  u a Y ys _ .dC =L '+ \mx^~H. Esg3Bf! w#S"_3No\__\_[]^ W.e`[dV~|;9zO;dsVoOpC(~@P/-^wr2J,dmhfZ=C|u#(f{ H>qex)e)S) ^C B /   3 \ -{ Oz P~ Om 9T S@ 6I p ,    ,V < )J *akZ>  +?=Z&vu\fB'3l *i"ID2_:i#4)Pzxtr VQi8v , B{YVsqgf\ QN FE  ? 1t . 2\ C : Dn mL XJ jN)}G~}}vsAp|Kp,h`fUGE77'U7J$8&#'().a=jKz^qtuwdM\> [i?_~di{o2x,sqzi =bfT5ADl5'&QX :}3~Tio$na9X ? C 5 | +L "  & 5 1 ;d M C I P3 T \{ ~it!;K_JdmlYuF * 2PZm:b+I:'H2`[< &P1A T7P^V h77)Mb"s)z%_W+Kx2%'HojyoX DI 0} !o M O  "~ .L:A?kBlBoB}CP?7<e9 25| + U PY (+ ? = $ R k ~|h|UC50J~!&T2P>lTcvG) p&,"n(|[dXPq5S4}'p`@/!uf>o^zCi y T >F *   k-0=GhL?QOR5P xLE?;.    , @Y S! h E[zJ^3A!W4LtbRvUyH$dh#RKD6J(A^LpX~EW,:K[rZj5v9zjdaX&cOEe; 2(_O$.60G3Yo5iR8{{qq j# I c _ [% Z< YX YO YN ZL \0 ^ c c  iG q }33\>yi)fZR>=)u)#K;CU =V#Q,4hf;C[J1OSDVWVS4"OxoJE* AP<83m6-'C e.^ IiwzH$-a6p>YE?L-R}W [#\1][XTTBL7B&8,Y C|&?@+.1x2X4?5+6v6t7c8x753?2W0m/&,f("=h U  Jt,q%+?166{;G@3G3N U8] eNms6yoY|}E(xrb)i`SLGm8)  ,,8pBK4ASZ4behSnjt~|zzw rnlkkmo~sxzqhT^SdG[:o+; $cn+}?3QP b]5rXM 9 G  u- l \ c [^ S M{c H D@ Be CY E7u H# M S \n K g f s \ g - {hfV-F6'  .%D2_>IUamx{<,qh*b\k+W.TRQQRTOWx]3 enzzloLcz,Xs L?)1>#K_Nr~&`7DJ6\m | x r p i> k d ` _5 ^@ _3 ` c K g l o r z ' a T p ti]QEG:o.#  #..Q9BLTQI]dk.pCtOv~v~6tBwqlef_/X8PHCrA:4!$-&th(sBH L$Z*$0#4p8&;f>1AhCD+FDGJG4FE~D]B:@>s=J;$98Q4 j1-g)<$&Ts !~ hDO5!#x$a$\$b#}!$f,zE~ SD "bk  1 2L GcYvm s2K KQRC U2iB "]j$ <&''''?%~"6/j B3y- DqQ"'.+/72y4K6s8 9":>;I;Y<a<f<Y;W;D:7:97T53=0-L*J(#2n =oG9 ia 'Y-@39'=ASD"EFE:D^CA?P)<84k0,p'"^:  mcAF)  ['#|.C4;1@EEAIE M Pz  S u V( Yo [ T ] f ] g ] J \_ Z W K S' O IYC<A5Y.i%} 3u}9\:A;4;4;I:_:87O 4Y2/U'+(L$ c=zb J+ wGwJ$*(.376: <h?1 As C# DM Ef Ej E\ E: D BC @t=v:x6;1%{,e'!Ji gm<2 / 2 < P!#MP%&&'''%V$ "R ]!\-fE { h fw  +E 5Ir9(Z<oGz=p+\ 9s>d 1i1J    Q`!.}`dEH&'B:qQ ?7 #- 0 Q ?x~yFI ER         y]jDP%1 |DG =6ykC4  %8(K7hbc_WUMMST jkad$'68EHBD<>)+cd>?VV&'``CC** &&44::;;99==AA<<??;;AAGGXXjj||+*FEnla_:8 xx:9}~ABilPS<@-1+.048;IMZ^x{ >@yz32zxVR0*wpC:| F9zl}nG9_S+#QM}`crx,4ku3>jyRbFVCSCTHYScn~(P] \d orRQf_' : $ 3sAb?hDa>K(%xY- h,  t _ WI6/`^~:Eo$ h 1 mis#= j ' j} !.EJyuOEj X  [ A#<G&P.T2N,H&D#@8- $lL1   d P +)MTDS v `w Yp Xp \s m ~ ' CSp SZVS 7,gY     > 'Y An U g o q t n in U\ DD -#    zkK>miJG*(   0.B?VSje}snQM., ^e5>  8Bow&$lf)j X  Q2d>c5Smw]j;EqN/ O =\W iq$ C] &sMu5_#MJ$N0ZDmd2M e} 0=gpOKS > |_:v(h2g  !7#[$w%/&B&L&O&Fy%0]%3#" {B(sKZ C 965B If *sO|-]J ?:7; =L6fU~ "Bf JWD@8 "kR)a$F \m /s 4n /a $M9{W`5<lqVV == &$   c\62]i- xMn)M:,*6)MKls  ;JtGAwn * K 1qTrs{]fIO4= $0    xlcWPD>61" !/,E@YQyn  / G,Z=jKuU|\zZuUoO]@O2:'   yo`WD>3/%!       ?:e\ O1~[!? Z"n4F W!c"n"v######{"o!]~ A`'6 v`?! D=ix&;  }S|(V2 4!PDop+ Sd_V "Y6_".2=D Q b!$m!.">$R%f&x'(((((('z%d$Jd!%3aJ(  r72{:O  i>mP9$'8N7hY, Ma 0+pd $Z4f  N" t%/'L(b)t*+++++*{)i'T~&7Y#7! ^]60 xk^SPGB;5/5/%!"  {{{  .(RH}n - J#_6{NZiuzzyoh`TsFe:U,F4% ~nh[YNKBF=>7:3>6@8F=H?OE\QcWoap z !4FS)\1^2]1Y.K!9& whUJ-(xl j} j} r3,MDi[ t   $9T(n>Ym!"#$ % %% % %$$#" oWo>M ,  o^R;3]s ("l!^!Y bS*$"v $,%}  5!Cd| +L p p=4 oa**B`O1. g# !Dj !8 #SJ3'!F(%kC_aBuk}|tp5_4_qb W Lb P qC5 x{NXH.:(B$|oD^M2juyX fKo#;4-Rx0]l,W0*g9)-&fe{ j?/F E)VCAt Emf^3F+1YdfrXr@c13p$u ".MY +1 KNc2 kQ<bړۘ>b jC?}E/w Fv E~Jxt:Dg"1Z(!0A(BE8<.:;$1m(:k?0a8A.E3* x3*1k)"r-?%z,. % ,$(!;$e/ *t#L ( $0Sn:Qj|'j| tzۋ4#C2ќ١s ;|gO73Ukc4) zؼҞڟk8RO9'B"Z*^&~?b -7 E  BaY4kkQObI)!hU\J 7N ^zzj ?jN\Gj!C W]_#+ m,ysIBeH C-6QQmzq+MmLm^ >9#,&Rzn\#{w?K=q:}>8/|bST:CbT^,/H! qre\  ds ekXM~-t`Z]*'pmw JQ!n4J"z*"s"V !o4 OM9~X?r'r .| ^)Am]:Bjߝ4]:35>|? 7O\*H5(]fQބuߢv1k?i<6~yn},:s\m&(lJ -) 5V  Zffo>Pqz">v}N sYGc]:h~Q$rE_oc ^o  n $ . uG{9sjFk'%!i9t"NS9gjyLI7,q [86L9*Cjw ,y;I)b2s'Rq`v i{FHEES< C fwkPE!uU<]]GIU Q  >UYr !+pH^~3 Yx! !BZ"/"g!.AV>Snh&s /@t,  qk $b^|6Ru?>W{FjMV&1 -u<0 s;5fx_w{ M0JE,ueHTLC7o?w6mE?!A49gz]\]? %  Yv  z` z  |  u3 n ; d Q1 9 # " ! # 8w @ Cd N ]u . f 4 f 2 f \g B K B 8mH /( ' .,QEaa,X`DZvQmV:w-HC];JpS38.<19Dy*f\ 6t_3~/ow6 /~_I;COB @d3 k a  ^/C"7rW%w2*Jh:X?^Pc%h!oTq*,  dKA d2 T X .TO ISC23q9G ,bL^z:.L| C3w RND0%-L1?k T1yjX3%:E1IpC )z$ [h  R o  "  Y: iG d & I  wA r  iG _(i [@ `  fG pZ |i f - k 4   r" s J': $2!W8NX|Gx-Y.0.:,c/Z[vS\G{%R:u"` lN7 A"6,w.z1 $<p KFPD5+^ xb04flk<;2lS,fv[ G  U2p M.<3|}LL~e T  7 B32 v1mY3f0M 8 s  ! L@=s"mrl\9|+d@~Z 8k3vw*x{{Ek4McX\@D3$ wibD8]*_H|d=G[*_y]s0Twum]v-a\L<;/'t =C M SM Z c nY[ H l [ ? ] A u; cs, V8 L8 M1 R=&r ZF `% d lt {&  g  S0kjDR9js$Yw9,tSiHN+fX u:w_90p\JV1bg8Q %6<~!c0/dLe9Xsy;s G3 sO _   I c |     o 8   v$ MBn&h B?aLs@gpUTB=qL_V\v|o=y% ^t&J>\vI &V/[8AIGYmrlk\NI='Y/$?,aUPv{adQZA4*1r B - Hf Pt R"f QH M8 !&3[@L0Xw_=h+t~d Nw}h-VU>K%[ E $>@s]mZ}'[A~2fsS52[Y B9i =m|I!*L{ t?    O w/ bIGR P_ >yI +B  *r   u  Y 'gNd K C w?  i {FEDw#xR{9g=$k&n33 gg..CH$_VvXpCK*t:_v&]KBu#^)%C XKxlee?Bu{Qvc{<Z =0!Vj }&   A M  ( q*U j7/n f+Z i  w v    $SKx'y(>Kl!|K#U,4ka/4G]-h4oT~AD/%9 q[KY xa -';e|LX{c!sWnTk}`;Qf@a/?C 1|[ 20C9I\w(=skaqM <# 2./u$;E I8M;M$JA~0r#:+WbwtVn: %j< ,N*o@L P~^UjWlZxR~OMKHcCE>O@rET}>fHp^FG{-nwV>dbF^p YQD3>+;(9xIn]ohS  4 ] ! k < h   qm < % OB HYP1D%OC1d|&Rl% |-E R[+v-^oZwro# `*X1U3T3W1&a)zo+}tVc`66DbRq_O*K7nfE }BD44$:]/  9  :   "- z &? w 'A v $6 x  | ?  xx 1   "X q*P. ); rB[\z1wzYzW9rM:.V2\JtC4V gNt|mYH9 V{pJNC UI}4k(CP,V/l +VafqZG'f!ZgoX-+7fj z lh &  , v ;B kK Ej d\ Iu bJ Fh d8 C\ f =A k 9/ n 2 rw #} ]# d Y |Sq+,<Fb$,>(_19"|CtcF[ 6jCP#0=>O!1ct#!v_DC97+(TLy}\A)% F 1*J*De{)8}HyImc3\UXRMG:B'P>Rn9)02):$<#9$6'2),.c y6B@ IQ1\XiCy ]q)5X>#'>m3RscYZst@'$2 ?J -`a:TN*D%elxjzs;{G}<{ughDO 5) 3>?Xpr<gc@|V,Fv`BShx1Gc@(64yU'> XVxvneh4TBg4 )J!N gGv.]@N(XL_g1gYnw}A>yVs5iTc]a^a[b`aj_o^q]h_Pc+jXs~|#yZP5J3s,wIlf DII A/RLy^ sKsJuPxa |l 1~- VO CKkfkLo3&k$Q(rl|L9r*zh O . @  A a s t j V .@  V ^Pb`5Y.QP(vOeEF4;s`?L"[(8q~w v w y  |-b-:f}B1&50@XRL{Zqmdg=VGq4Q" "yWB`QI9~g4R?D5=.K _sW2y- 3M II E IA*dg>R)CE]y4*YuuI\ 0US{PvzRNu bksT> :I'C+C+>I'{UEf?{Py"kLyZ{8YI:6y%6dSk5rcPYhT pS%sR%sR0zP= NB N7OkSMY2^eak3r w{s^lJM5. )nq[1cG2_% U [!/*3;<E~M_SczBY@s*^*oa!mblclb!mb/o `Sw8\\TK&@3O$yY;$x7rK]3-prK0`~xt1pOlohd c cceohSk&qw|tZa=0k]N.B, g3F~\V(lb[mNvzHzuGzKxPuVq_khd`wE[~OB4Y" ; '+jJhN _scLSy B645G)qT>!^^drfw`dtU? )E *46gD>TlfyX3eiOWOE6,!gtLW9& J `!8*2a81?HtR`tH\eeVoHyN;0t,f,f-k'2{H9{C{Sq%g"d|eVF 4c! 2 +Q=xv z`4eI93kY0z Gj{@;okRV!<2MsE\u2nZ4R5 $q 4HktS[UoP7Nf~uyD V>2Gx]jrtX[L?>3D(%rq U5rOkBoX)uy;i]5P['vA>2+T$h7{p 2vt9}qKD[  E/g+{?&RIgWKzZ 4O3 b"u7KipV]W mA{B0 #t]K=:?GSVY_"s2+l8ExRp%`,fmpZ\P,B2 c"%lKHk::wudbQ>'ZC79C2J-M+J-C1:8K+bA+M[$kX|-=~ng(mP=)wqH)  iPFITf  fSA,yo g"`$\%_$a$ d#b# e#j"3~bmE2LJbwQig(L|fvpk5g:f.hlyr^x>B*}^a'C$d d#~ 7IrvY8_?fNp@zu2J),"7$f/?yW)kszXE1/QP =[xKKJzuqiEcVaUaHc0fksqPym'$}%~Xk%[OLA81+t#aoE>'  dOC6/~p!_%M):.'27\=-CK}RsXlq\igb`UbUcN`QdK`PdWbWbrih^oy[zTM!EZ=4G+q!a R3*p,Ee ,Z"2}ko9cXN,EG;H<Im3T-[)]'Z)Q/kD^7%41A!Mj\yl-}w:iU1. \&k;Nugm_L$p0 C%hIwIwY8 R!5~.UBixTZ>`&A(3hUu2cy62{[qwjhgzikmVr;xo7|Zr?b%R~F }B{=z:y7|>M@bn~$EPBv{<];_: v!~9|QAWQh5|8lp5- /~>wG7{9TNivUW?T* *iDOt$m{| |ljeZ(K<?WKA6}UX0Ze-Xa.pRP2=F1:5DR,b>t.Je+I-qI,fd&22]>$FMzRwSxS|PN(F\>#6H-r#[ S2*g?GPz]gl npm ibdX2K>/twH5   7!oC(c7F>OeX`kt{,8?@=9|.z)w sm gr\FQA1wL@' i(~ Z)53l <+G|OnXa`bYndWVgfjZscc^pVM@C9Q,8 $ (`1Lp \jyrc)OF=r^B.pn#}}mb&NV,4;D]X?onsn>;#U+MBoWRSgg:,v(sUS`!1{"FJo|]avRNAB03 zI:HSgw1B2N4O.L@3{qdXX"J</l$TpBd;e<wG#Q,g7FVXk&EXK'sVf4}]g!U-N6H:F:F6I,OPX ac,qKGXpGf< r%k=pUCKck)A|W?3#"3,aHsj0'zWOohsR{;v$ [6DoGWS~7m(q^{+Q=FL=$W7;]36[4U8I?9Ii&Tf`4md{9M{zc K5wwF?%    6d:#N*^1l7y:;9~4t.e&UpAI+r7k'P.;4'9v=i?a@j?}<-8D2`*%!j b94r'NhMVdz%nRbI$|W4MBEM?R<R<M?BFp/Qa^&mO}ISv My F3 47Ja\XJwi:Ur.k>Dl 9}v 9g'd[_-S6N8Lv3OM)yUU_%lN}VXJY4o?%ly%N0-9r>j?q>%;86U.v%S5#J4oF<Vyfu#.4/ }rqdGYK<~+ZY3 V#l(X.R0P0Q0Z-g* #W=#+YGic3AzWzn:d)%^ZY2zR}<LAIAJy;MX2xR##ZZ3es"6pE`7 -!87 {K[^I@vm%>||^Ud{!&B|@=rnTasPi@[.I ! ,ZIsg)8k@}J|Sz=~>~x]U 2 sS_O<y8p-Zo?X1Q.J*Y2a7d8$H(O&L(Q0`:sBO6WZakfthvijffdZa^bG\PA/^A$jz&78SCmRT`gDi'2hs3&h] 9/cu66qqbXDFc,%"" \3M^c&3r)c=4^TWA[?0y.@~+['|"Z's1RDZ=mP$.OXqgV@[4f' y$Q21|IpPaXS`NcyGgbAjObaXnQy'>]/=t!@;rBRrjw ! -W  sN;% d-tNhTKWG5wLp|!V{E6PaD[Cj+dV31urPa2&#Zz!U:m Qpg]Q ZL CHW?)=*v2'5HV mYJ""a_$< ;:   3p^4 B B u CU " | \ dY%x/XDi,7?o6$ z#;~ucCi:v>MUu(PEd }"4- z k Hm~q)Ml$As]wq }U E5- 8)<-nos>8+B=8peo70PyT[%pEjCh,B0.bO4oeCz3*gb =q@ X0 s!c "] >k   C  : K #IE T'  f  L _. Q6 =@e S BnQ|~L=S4jy>tWLf`,A:`.gGtEH  $*e'z5*s6*tGr95VeXg%  &nF+*)=Eb(:95PE4#"- P}/yc F?Y &x!cj   5lY  u  m xM ~ H !2( 9O[c8Y[ 2`,  c  A? xV!+`TUJppe%C!g``gfb6sCw0Eu0@_`pq@kQ"( !.+mW~EWtpx8g 1JX^ 'LCcy}M)j3W G#$6!*#n-gQ;&sRpszYX'A#Ph|P2{jpp~mNgXCr& v%S5W| EgP^UUZT[_%Dg07!v+#{)@< dRx6j4tbP&bEASd1eoqbUFXw C~J y#Np L F  V/ [2  6l  U5$u < u1z  M u,I_TC\@7QFbNjxn} $FEq3 N5sGC[m5 =e'cX-fX@cQ| ^(   u PlH"  +'G":]oCntOIwFhf>k@Z(S&Q9X/NFXuoYer I_}X*Q]UqiOb} F@MdRi$/SnNlzPlKk1JI\q]Qw0{$CCfZ' ze p &kx "mI u{CFr_7e o})?p0Ht>y,OZ8vup%z6f!bPa5{6Nt ^j#o6gL&Nti7ehy4D!% 72HKl`<kk{UuJh/}c3;kKF^/jUrIsGtUq-j<cTW@nJs6BW7r. (.#&b3AoJ3hS.CEMYL0` Lf/JxWg jiFF%|,P"if KsR!jC}g0a +XA0SR +;g!LA 4dQ {zw )@  O@ 3 MFg1JT+52^5 c 1q zZ S! ;2nCj   tN RZ F[ R7v6opUByf;%{*Ck'r:y'e EV;V3NfeHG}mTCmUBP]>J"M'R8 \jMv~esVkS_}0{'debJG:#=1ai[KX]HfKUZiig0 zN  o? +G|sYm=2+E$B%#/HC_e Z | F F(cXJUZDMkb"/ )cq8E%j\zn0/6[u|uV"E9\PwriZ?8^\uD"K[Nt:!_WD~HvB0eM$q"~pjSe= n&NG:iT iOm/AI#55]!-4GKgda| ?5`C%sMa*wL3:Y,!u#(y! k&N0t"?eUe2m[:Ro5}o30\"N+f7x~ooCZqsBC}.\ &G"L$E!E!E!@S']!,.=D[c}o(l~CfwN+;X++u 7{k$K/fBY\l~ Yv[;^r%. r6 );Xxq)~p,@Wr5 1&.poe8(j`Ah,9 f)c ^oi M 7 &' =: L@ O, E )& 6 L~ iye0/'f|aV)#}M_7n=Cu:L!&o4sqECRR5_un~z_Ld$>yiBO3G3HQVr 5>G`SeUdTPM;F0A8DJKjVk!_"}1`uMAC  Wp>Fh\ CDu'=H- vnc5 E  *c &N VS ~  Z 7 M T JH - S  <c 1 }oaD3fhY4|-:k6zX5JBVy:kxR 6eQZ we**Io H^d5/ %(FV/ 6DDw, i{OexRSD811<-6/-3!7v>GFL P|QMGF;A*Pq#5Lu4=Szoy^b ;s  ?by"5nX#hz?sEUjz2mjGEK!S3^ go p6zwOP{#S&Cwe0C@l,KY%Ny1HR9m<^-3ZVg+t,553vtj@x ~kgBN]":@1-y#I<am?a`UE!mDRK"5#|2rUA5~'oTX!A2%*(()%*.'6$%B? NLSJR> M> &)7,Ija$Hp~w6m=Z K,=tc)"SD0 ;,Hs zGv\*=s)=|Lal&}3Zy5 DK7X,AG+p]Kl||}|mcN#vn:8t2k ")AEpa5Sz]$tbkW_NUCI5:^%)*c'~3na?ZYJH.G7P>-T :(V<*U)E5QIRBKtcUDym:/-$l  /$x6KRDK aTuVl;a?ciu~gGPBK+ >ByV#AYvT58>Dog9}K<~oS~t/V *"9RANeqX^"&TTII i$tZZ?K0L1S8kRbuFw&]mc?NKPi  :Lzudyh[P+I>FEE;F$JOU\ndkatj<N`r9I{$2@tzDG-*%xq:-C:P g,|Jn\IE17"6"B.[IQqVu*![nh>G CEOu ,8>Q^rn|f]ZZ]rdSZgn =Iz'f r2}dgOQ>@?AACTWqtT\2:za.H/?%4Qa]erz>K,&Jt|e*1>e)*/gXUF%![grfUN,$0NA"9Ip-D?d" \zx8LL(*[-EI#G*.HMIMLPNQ<?"%O$SS !w<Y;WWr#xxEE R+5yz8st:5+E s0/_^({z[wl2~mlDC  *oY[Ea=y9mr;Ah4+](/HO)el+|h$9=Ror<5# N! yvzumg7G'ZM O+dPWjR~g^H7!]UE 4[7.S97-$mr<%8>Q`l^t2{-|Tu|o c ,4VhTZF2B!)GD1{**2&8+5(-  LupEBAH +3y6ls<kr<ry9x70(%l #96kep@$O=ytxdhYKj>:58h?-IWwzehG4}v ]UP0)gq=.9EP']d ijf#_0=TtVaD2M 7KE#}uK6!J4|9bJs[pWmz~dgjerasakdgf`sYl2`FtE.~*a 2zRL1)~ZfB)\Vumv Lm*N8`)I R$p0$JEhgr|n(4EQ2y40Q6W:xU4SMSJ~6uH5A8WW9xUbYF ] *}4q~8p}9{4-2#t  v3/^U(tu*/":{dZ>80 S2gM_F,\opD_?m)YKII R5e%P~pq@ye:9NNs7U@9A} 7iBs}Y_A)(k`J>ob~4!A9V&c9vdVL"&TCYIivq?tU Ez? I#] J}h13MOr7O BI@obA|luY,tn`UJ~81B qRB!B!G[q  U&!B9aT0uB.]-z{Vh|WKD55'AWmky! w pm7S1):N\eT$~[ *r>5az3[D_H4\} n(|tY^(H8{1u4x=7Nph!}i&,LQr6u)l\pd.xf`2I7,-;'9'.,7LDwW\FmA-hXA )TpA!DUenqBx1z>xcrg9YQ>]Im81# 5TFF{F<$k0wSgnZP)J0I0IOuWXd5t6tA R I|UG+2#q6qKjD(*NPp* 57/   BsB.OEY_i}J{K; r4DX#|hRu5Crb`hbt ;ExhR5_W}=\ `J{9 n{l=lTabZgXcZW_HfR3pq{Mj"^~{^P=U( ulu 3<.cL0mM?c2%ud8mDgJdJdCgg8m5&uhFa 5obJY* {'b9W7hHIV0cymFv~({_r)f FXQ4gIj6>! W)rVA|yFejTJ F+;@4L/M/B3,;CHbXn7lg h&CrT>  ([;;(`MA[)f{mhppo#i4a LVW5jHe7*$% S=4m5D u|g3mNabWoP-xL9}J)wLmQ\YDeE&scz0 +iV?E!+$s2T=h!  @-uRJq)](<EIG?q2M `y`fGD/K%   & R(F1bD^Uu$i0Qq)x_%p4i>cF`H_C`:eo*m*xgn3gziH/p1< gK3f|WD(g8nTrH[w>{B' ].X4\=u=c[TQ$tG?>P9X6V7O9~;@GpIWU;dBuf_.lz{S?+O{,[7@A`1uFU+qH\/sFw<}AO;k2'd  n7%hF0f.v;Yp { { |mTa1TquN|>)+$_6^/uFVU>a,jYqDuKse#o3gK[2fMP;&)`?[s{?s$mH[,bOg7uDL=T;N=w?zBE&iJOW1f1x\/Wk6# _KX QU  STNWF[6cv"m. yqu?  $_Y91 Qt.E=>mKWVG^;d6g4h8f@bL\^RS&uGN:y,S  u<&mF1c}A&s@UdjkeYGR/TnRX8V, v2"{+d2V7L:N:U7e2 )N B)S~ Kdq5aNTI3aKuJoDRtAQsBgCjF4)ZNA[#j~V xxsHF$ #$I;dPBaK"rt ~TB75=Nk6u:f[Un0Bt-yK.$]0!`}k<])'SQaDeG|RmCYqAToBbEeG0+UP=\$i5yoV6jsOG+% {C'p.a2Y5W6`2p.&M-C)sF4a4&{Kh#|0z}oM/_J-$ {*k-BVYCcyC<H06dzzT*pUY<rJG:Cx+LanwJznDe ~a?G>VM6*^%}EHR[2{]SrLku _kvj _<c~^d_ezSh z6D1F?TM%r c !i& u. 0 (uc*]Q f ,) 5 `&5 *9 R2< -7g yb Mn QUB ~ $a }   \ UD F }dRZIm39LQaDI|4{Vq[# `YYMngrY$ I8>'5809:@=%C>Kw E*6{3c*oI@d~Y XZD:zFiHmbcE1BUP#  fidA(a7ia?qo}5._'}yv3]N,j8 I)@# j<{sNeSN, f. hQ2'u[EddTG8LM= D9AyHYYZYR]KaIbUE%#M 0\l#cm<2 %&N2B.X5"*'$Ol ufVHhC8yL*xH|Gi9k&!_ZPH`N>KJ-8V8I ((;)w(uu>i#rMh--ZD:jRWINr7E(`e8 U s !{M  i~E~suqk~1tl'BRv5l`wom< k9;hSl=jO Ni4cE>{`$G.J9M9#(D *,B~SlXxW^nQ}_sT~_eLzagY Fh7%64HW[ *3N$W'yCh},>T]3{oljl9C  XO im %F {p  /F J- = "  ^ #  } ti \ ?L0 'D rkn(I UD SK)O3Qasen` 2aV1$ugZvIZwbrC-{emRB'v'oe)Sd)vsNo*L*Drjv &qZvoH.YMHZAg9 r3#y/E*L)'z/d;_=YAa<r2F* !3+!!a&0{-=+T(_&O(n4bp@Nmh5 `.]CmROcG{:nQ%z6CqAMC&-VG;V~ $4p6qcXH FI4i n8m.x";}J{<}cWZaS*$#4:;H sOeWY^iFha1tF$|M'zN(zJ&{F${F$|T+x$d3scEieWi@$O(C[}Xy G!]V?"Fz8d+s2s2[&i/l k 2Cw,e6OCCh/U  iCHl@M, DS0&=<GwMqQpQkUfW^\^\aZiV uNN?X<d98z3{(J9aZ[ h~QfAE#"r3iGTsb_9JT:Gd0a#k,g&l,Le0V8DC/O(m]Bhu=Q[:~rg_\AT.OJBA>@$Ma\p*Za/W}ozwxozA Q\#dIe-0 P]+$97FwQhYZbNjMi*UCo:8u&/{)&&/3x9^Hla]}Mc ?N.'!+ f<V&o1>DGGE DA>9n0[(M!N. E_*%81GsS^`TlPhTlPhnzWdc]yPMBA1. =$;g6Pv6];EY,^1r|G $EPv\_"knx C@k b*9RJT_8@1K^`3;uziDsg]i5$TZ0|F`PinW&Gu<83U+q$8Ofv R1b)>WV p.NW4sheG['O>BO/T4cj'z[  !=5TI`NcLb=YB~qF[/0IrdTuC6!]:i[ 2| P%j@op ! 408 :711GW',C u bdb6,0BsvS+5o3 =iWj4u@ ^,K1$G:~SZ/yi%q!to"pe(_AR3>?*LtW9 _ufbkUnNpBr8u*x{|Y?     |Ke<~j[U=^%0 NG-KAwSQ]Yf=wr$mI`.HAI^,B^O6|)DQklZH'55b'SR @~P_nfZ+I+*>=aKK5`S0kW.iV.uXP3L?E9 9@)JyXgGt\&R h2Io+2 uZWk V5p,BbTe0u.Wdyu,W"owYAd'7" bd%+2_=&EKOONK'EP?740W(u!P~ % H,rG^$y.7A4HXQmVZ\[{YnV^RAKB8u,\#|MyK~NS["l)0:2GtWg=vBh&~-bs"gm\T L )F-.D!20B//C'HOz ZhRw; Ex_Qn)\.1JMPad8}q&9b=*(7.[|I ^ a+OosY;AF+^pF6_PgU|v:i$<}MyZvbudtct[vVxOyLzC|>}:|2r+h%c"b!g$h&o*{1:B}C}@}:x0d$K"JrAs^CI0`<K4./BxV,Yj<|#"I P s|~#,x9~,[i}S^; d# yM8F<oJ|r}]*Kcl4=E2N,S)T(Q*J.?5M_.@,KY=fzsD3 ~&^z'gU=F:/m&Rb>I.:% 3 & ' + 1 5" 6# 4! *  fJ*wg"W%C*..3q8T<6@DGIIHE2AO=y7$0J(*w~7$ a!8eN(d.fy#\)Ic{rx|vyvoyZ~}?K^EmW >u'H.<S%/-4Y;J=J=_: 5).Q&;Q4n%8OHV-dMn.ixRsfBv|Vq)byQ=x'K1xd!$/F=I|RuYuZC9ka%&eebfbg!cf93ibaLq]r~UL#Bu76+mv%tL~):WI W;g"csV~|kAxz_rAh%^ WtNQG2@; 866 8;3ASG|PZ7dTm6sw`M{SlY@C,eBh 00?OYCs]`iNu[>r1L%3"$5V)67Ix^jbHu[Jo6[" <( -lKLhyS1qtClve_ \,Y4X-Y\ `zeRj#qyj`:z=zuMg)\QVF->5.{&esL`@B,3!         ^0vW";(/w5J; @FJMtPkRlRqPzNJE/>k6',d zhE/vKSf eu|g7[ jQYI~EAACiG4x OJXc9pS8Xx:^[E-gE5xF%.y4S9><;<H:a72(+N#9z} <( tNm (/3668874/+%fzRX;5#   rdTOJZw3" wQ%5_D%U[dHr~4)C3C3=0 Mr bf/WuH*:,iT9 qJ)na Z [ b4va P6d+%8lF%SM^nhGpdutxzwyivAngeB[NI?/mH1 b gC$%* /y3_6M8G9H9M8\6{2.0'W L=* a-C=MM\6ky)(TFs[lprpgkVH>!rv3iQ]#PgC(7,z"Z_AE/ 6% +#%0! @, L4`BtO\o#&())&#mxRL4 U Y'(0B9 @FgLXNPPUObLwIC.;w2.'1q [0F`]}u]S{g q;i[cn_y]t^_bCg|o6xu+1^n\[J58({U&IU/&,/n2a4Y5b4r2.*/%L &iOz $3# :( :( 6% /    8' mLw*67 D@Rx`fmz-8e_y{^[$3 wLh_YG36$nM f"d4$)/j2V5N5P5`3{0,(;"\IE0 [ *%3N:uAF+J3L9N:N8M1K"HCb>E9&3 .)$x_bEA.  4% dFn&0V;FIPlX2aZguloponijJd"^cV@MEX;/$cK5   uO2n"b[`,jG}g- dGr%/E7i=C1H?KDMHN?L2HDi=;5+ ~Z:)  h.jH#% )+++ )&6"WCy{ 1#uS+Q8)EZQ-]ths}9<PQ`blblSa?S:}tj9^gS.FZ9-![8(  tQ:r&cXTU]-h@vXs :* X?lN_gjkfayWfIT<>- *;+ cGi *J5 @ULW^bl u-|0NRglz~r`q?Z@!xpye5ZeN1CW7*]C w0iA$),j/[1[0\0g/w-+(%5 UFy} 5&tT&(/X6<0BBEJGQIKGFF9C)@x;W661+'#vpkhhnq"&+70f8%>DDiLS?Y`^~cddck`FZSaJ3@Z5+rT$ SZ3$'*+z,*( &'! J;pz<, {['0.b6"<8ALD[HcIdIcI]HUFDC3? ;j 7D1+%tqTM9 )  2%O: xYt&&,O2t8,<FBYFnIvK MOOO NvKfHOC6>y8K1)"yZD2   !@0 dJd|!$ '**,3-7.:.4-),(%!hqUS> =. # $8*S> kPf~" '7-Y2!7==WBtGM5QJTcXsZx[~[tZeXOT2PJcC;<i3++!{^F  _)^; #&'((&$!6#YU 5(nSx%&)P/f 2 6*82949*7$6u4a 1G.+* &"oe{]y[z]du"&=,^ 0'6C;\@vD I%L6NHQKQKQAO6NJFa@@:t3=,$bF5  Z,_B* "### " )B*]W  XC o#1)e006K;`>rADF GGEDwBg?R;<7'4c0C+$'# vywtYt!"&%%E+T-">7\ .%'*'!&k0H7 nn>kv(r Bn Ew AY7O9D:B-HrKWD%>dCk)d621"% )ZW}'Y G7 ~R? zs0G*wQb.B6@)1viXMg.n0'43q/ #|%1X ,A|&1a:s/CJb:([f;)rgq3Ou[sB9$C>]Uj dHfSM|?+p!$"n_x5,c<&:?]0%PJ $ bn M ] # t c V (Rp R "NY'2-#.^$~z1&!9S5)7a+B9,z&9,}:.IQ9<.p38B,?6U*6*o3&(W'0%_ /${+I"gQ)E '-+*$@q!2k!5!86-#qOo;yx 4G j ^&Fg@FC>yhZJ}so Ry@+e=g[Q"]l _w2|ugvhogkC@!#$$!&2' b|*!L ,S# .$ /v&A1(w2(N3/)~34)k4H*ak5*P5+Ni5*R4*\3)u;2](x0H'R.%?,#"q)R!b& 1# N&b'D 3  6Yg'Nic~6%]%Z"2eR wFjj&޲` ެ`9޿cCe_zj%Hp&ިfmS93ې"  2TپQرlw߽rެQ߲{1ް}-ް}@ߺuGY07fvHr|7B sRL[U3"q'}^/zBxnk s< a |[m3U&j gwGW j:-!p5-!o6!V{,"W# # #%0&&s( !V)!A4*U"WxL+9#sa+#}Y,D$G/-$:^-$7,\$G+#y\*"Zt)!D(!&7'H%`T#e!8g:(f,j'b{" ?+^`-  OD a ')h"cz3z}quI,G=W cQ+ng ,P_w<`]pڤL8U\rܬ{FKΤ#)]IEӪ_XӘnjBӗo#Ӫ`)>ͽτ=>tk@ܲoPF H iy?e$BTl#:6Qi({mW7:07Q:d$ V0f 2 6] w QtILI)C#hiNYazRv &h!BP"W?#Ek/$4%%FY&&& s&W& &e%V8%]$x##Y'\n*l3])\da8  d.iQVBrl"g_:-<1 {)qIA-Yqn~FݦG'܇=rE $[w۞NO޹,q{_p&`Pt2VEFsdN?h/e,EyFW5,^]G'q.B6{oI4o_#c j cv_@y"gt%LZ3'.!7*J#,$W.&#0(Tb2M*;)4+'5w, -5~, 4%,3b+&2O*|?,1*)\Z/%(?qQ.&|,A%*#q)"'U!0&^-U$r3Q@"yM \t6{OOk{$42^ja J    VqA7if\P} j-FwJThi14.v<%~+v#[b5OD AP)*cos }ݝDPzy(fIJJh0_{u'g=}L^VhhqQ9"0~.T\1Pkpsw?QF% p4$i8~v V - $ ; D9e {=K~\TK~q{+tn]hGa7WJb<&#04!G';]@{BEFHG HGBK (IE>Q:5.&EW2@mS)jn<h_ . , /cr*]W'\(!HOr &F3GYLF e0kukv+i6xZ^@2YDVG XEO3]Afb=m3߅~%TB6_PO. 'k%4mTnsTK='&!g }G 2tXlvFbE@xeo Z I ? $( (tHvh&(+#Aw=l3exa` _hZXY)V=NI.Cf<84;N/!+#)#)x-6>yBgPWaww@vMeK -R[  4 g O0 k?eu5\k@% &C%M;Qezty?n]UF,EdQ*BV>qYVqf<Z OO;ߙ\}"R{/ۛm;.bE]J^HbE_k=Yz0J!M޿ a`h4[nK (^t'pU lEB`21Ky bk@ C "/fWr{<xU]CC+1=!Gi"[#m~$~% ]&w!' "'"("M(-#c(B#j(I#-(#'"4'>"&!% k$t"[n!8? &B%bKWn@* #< J eqwaJ3L 1`T#{u'_Zl<`! 7h;-L?<N'q^^nGP{GBc4(p z o q =.$-W92mH8Wv#!if3zW\F 4$0>r,PCY=Gr}dSKv2Eyz8_OAdx w ~ i S \  Pq CT 7/j ,<Xc] x&;_Q-g*^|~wAuui?^DV6QZPNQ(T[Wu^Zh rV|`q?uQ`J 3vS 0  H ] b |vWH85 {5Oj %jPKt, U$0eBe6S%qd _s2Q8|F=_51/ 1]7@=xJ0"U\6OIh  wd  c e P @<3Fx&-G ?=  tT#&''7(['*s>*O+^+l,u,i+i+r*\)l='M"%("r_  "3,W")#3 p=# H/ S# ` o M _}j]UjBi.r R,z<JLM\lxzng$fWLGW9V+Yo./c JT%;-)4%9a=#@RAKA#>p:L5N.x&Nc$ W! 7TP%AkK3o>UV:7y3#L8uf. = ~ y d fA U>DO3#+;r#1=zHCR}Z&bjMosuurcm;gb`0XiNB4&kW[ (H*^:HK, ] [ o ! > w\bN;y,)KE L/?=KtWjcnn}xt%lAncqZQLI!@M7/x' n*Q$ mdHY>P5Y<yY  W"u?%;+,2J9 BLWdq~wQ/jd]OqAL3I%  GBr(,m:#G9S*^ & i + t % '  Y8 uMjb_TI#??5[*g!i]f-' k$ S${%&%v#d: h(- h|xT(s4A Pn `uMr[ \ % Q n0Wg?']iGB.;BUUetl|s d{W-J@7'.]'W!{=qD6   Hm{4"b(#0:DN X*dNdp|{pem&ZmN'Br72+F#2vH7(U3g=qF P; ZJ c[ mM l u]j ~EC.} vo^jac3]X+SO'KH4GdFE EtF.IXL]PpT*Z|1`rfnv} z r j 0 b F Z q R! J-B:;=3/*%! ^S*&/o8>A JzSU\Q!dmvz~rciQa?YAQnLF |B?<O:&99>; =yAF1M(~R z7M !O(#/r5k;sAFKEP8SWGZk^&axc2egZiFjkkkji5 hh;g|ecZ ar_#\ZY+V\SGOL*H7Dr@"<m894}/U*8%  *748,1 - ""-!P&z#, G 2 t 8' ?h EKOQ)~W^]chmqEbtwz{9F|iu}q}|_m|(9zxFauqmhb`\ bUN#G  @ 8h 0m( f6 :8 O $d)_-o14C7:j=P?NAlBCQDEEXE5FFEEEE4DWCB5A|@N>/<;'8*6g3p00-*'m#c ^h  ->' ;. ' n "v / $0 & | (i  * , + -  /*0 2(35-6 79:f:?;<+<L<p<~<x<Y;%:s9!86342U02-`+ y ( % "    wE>:M  n#oM "%p(G*#},1.<0q2'3C3M4g5;5554_3A20P.),i*)w'.x%!QLv6T \m C?aN      o f 9 * j8::A LlQV#&tEsHK$  u T   Y > Q <  $SHv%$;<hk%-Q[ny,[i, '8 2C u6F!" &4Yfgs6A ]c=@:=BDZ\no==tt  ?@st   / 2 I N glntryfnS\7@8E0@    Ym  t  6K Ym ez Zn ':  V e  W d FOOT>?A<~\P- " cM w\ zR3!7aP.tD"$gB$%"   m  z 6*KFcd!%o{@P   v4S  N w    # T  0 f  0l K g!"2#Z$&m$6~$g%:$$$V$F##T" 3"m ! ( U   8 G {   ' %   phC4 bt=b&Q!E$S'h*/-R/2C4179:+<n=H>3>9?d>)>=%;d:E7I4i1.F)V%$!r2W> ~{z 8]s #F & N ( + p - / @ 0 y 1; 2a 3 4 4 4 4 5o 4Q 49 4% 4 w 4 l 4 i 4 P 4 6 4  3o 34 2 \ 1  1o /$  .  -m , U + )) v '%>$"D<r>l8T ( NQUF N,t:I!7$I'*h -/Z2t4g792;K=~?@@A}B|BB B(BKA@?@ >b==;697/5!2a/X,)G&C# ro>:  &9gf%D-/ 4~ < C C K*RGXX_Id"jotsw~ky{|}|sc{ ymvrn+i&c] V Y Ow  G' ?5 7l/f&d4 s rRpC~ER!|& +]/M4*7::G=p?%AtCND2DEFFFFG&G1FLF[EpEE ED<j:U8*5$30b.*Y '#j) ~LP7 +-bv "9hm"P&+>0G5}:>+ CA H g Lj  Q V 8 Z ^ B b fU "i l o2 q[:rftzutujutWuV8t0sr q ao= m j| C g c = _ ZE V / Pe IC&<A5L,J$9n C x_ 7!A,P6 @3KUs^kgoo|w~yr.lof"b_l]Q\]\]_e2bgxlP>r$~zwo1fX];TgJ @W6,#!GpT "+l3>;~B;IJO Ue  Z ^> b \ f i k<  m_ 4 n{ R o h p j q e qu M pa 8 p=  n m k j I hH f d v a{ 1 _@ ] [} XC We U/SQUN MWKHVF C3@H<y951K-$(d#U?X  # `?L} 7%O+"0+6":%?4D+HQM5QlV9[_'Xdglk5nqtOVwy||z}y}y}y|y|z-.z|dix~u qxmic*i\,UNdE1=4+!  4d$71J>ZJoWy8 c Z o q {|  c { z8 cp?h`XYYRrLcG7Cz @->==(>n?.BYE^J<P"X\` i s } vZ io\KN-@c2# $-V5*= |DxKNRV#d[_cWe4^fCh4i+i -i:h+QhJsggfdcb`I^4q]V[wZXW9UcS?QgOM$K^I/FlDAe>';i8@51S-O)%] WM; ?F Npc!9(1z8@&GFO:W|_PfngG u }j f K _ } w G r{ m  i f eb a ^ \ 6Z DY 8Z (Z \s ^6 a 5 e i2 f n sN j y ' $ ~i |rgL\ Q1E=8?+>4" '4ApOX]'Hi:Av}Q>oxLb^UJ'>4X,r$d9B%4,Q6@SL5Z$h|w 9n.f_>PTAf2v+#Y i&62q>,I=Sw[FcjqrSv{-# WT xy              fi MM 0-  ~l|Z?y0w troPlVi.gdaP^jZ0WSBOnJ!ERA;+6/\(&!qn|P: /I%.n6S?JHP6yYa/PipGKwz}s{cl&faj\b%X3URONyLtLvLMNO-R]UZY]"b~ahoQNywr_i'`URJ>v2j$l| w{-@2Sdgy u e| WL J = 1i :' rg * DW\t[rDY$ g`  i A$ -# 8a D Q ` n~q _M!<2*M|l{ l+]6B?*GMO-V\=ma)ejmpoWeqAKs}9Bs|6>t|9As|@Is|JTs}OZr}anqkyqxpponmmkj i i!h'g ip.Uela_'Y]=XMR~OKK=IDj?y6)2i.$X/r] etkj+7QDsPx$\e/kgs}jb8P~ sV jE _d V T N? F " A v < 8 6B 3u 6 /t 5 /Q 2 c , U -_  0E  2: 2 5 e ;p @ F f J Q q Ym dsx{O$r%YVHR2"<ns4!.4=Oczs`,ZBBj/ @!5.3@=2>8-|?5N*S!,L ;}OR();n4WtzLN3#gx l6V arpo ]F L ` B > 4 )   ! 51OIeHs;y`ioW8Fv  m FVpw\Nz9{ *kN eh{p^TpSZ9]M{w7I yA;Pgr,3_8NNd$hYo/ [ ' ? k )b '[7\oSH)) Lcq5ug 8Bu^3U_ ^ 3? -.plNY3sysyVM> _CnR`!geeu${5g#FRq uhg,gNW>{ۊgۋޒؾPtc_oۃ =]z$G~,(KHOHyqGO"6bwv K{ c ; N>3+ogN6GMz !n"L#~X#}#yw#z #$"D!:@ 4mX{>jAdf]z *5U  5<i|RtK2X1bIWk.>K(Qp<qJ'p"c 2*4uDUkd/1<`[)m,Qo|AI&,Ya\rE]# I0 2|  ;| P a 4mz>2C8~ b =  e A,d Sz.#VTY=fq14}MqbXuB*3. $] O1D#mKNp u-i,rUewR|Q4t|Q 1s &A Y(c$>tC[m]RBaW hRT~csad^H$8%pX p t  9 .cM0|X8 7C|k )[[v"Tf-C1T 1ZcQ42ifyP߇ߕޚ&ߔ3ߋI~.lT$Y) G>(Y-] )#*=Sf|\]=DRxUVx} G\ [ 5 O ZkfuDO,88 ``r#\nkHqIAPi% ~ ^  /f8 -ZGde+g <i/M,!w9"H(6}<~c@z}=}7!-#xiK6D8V#A ea\H9BBnq6~Y 5   I < } [ {  }AEuarrpzo6v 6- &8 T ih LT q *0 +lMNvbr+8 Aup~/yDqp.8YY*^vH 7 B fPJW)Qq)\CNTH\H\MU&XHRi4Y})nE!QLP{  P j ->Moe5s3 E< lzpC]P9|O`:(_Lj>Wq5k$v0Kz+F{*o'v/o9 fCdV]OS[6ZFj8}W(#L!/lzmHY%!iCes@tei%_yX*S6R<Q>Q+SWN[bfh*qnwh nKQl2jc<]/fJofv~bvFx*?pJ nEt[5P!WSej|OMX0nI,>ma~PXV5 ,  @/ r y +gH0E-\%TR x%TI#y;&b}};:A_  >~ w oT| 4y;kjuZEC#,k/cs?(5x7xi9$T/fJd+D@*OlY 6_6__\SnGG 73#Mm j:!3J`Ts+ K^d BL]-?.ys,F "_ WI  }   Ed eI,H`2J+c_u]xn<fy+e@]0MqF2 d_ Az   L Y /2%Tgy+{sK]0;_]|7 lYJ-)N CS 3W<vvX1GN>5uk@\RAl' u:T], nPZxIC<a3*%#$)c2+>HM3\m4b|) Ri S~<")39'YZ|a5E R (&Z:mIZD7VI`@Z)xb: #@&=Pe\30 ^0-g=IEh<E(&$#I LW qF 6 3   g  zyh h X1( LBf qCN =W 8] 7_ 8] fdXA! g:Tmpf4Rv6T/AY *7Ql b \E. >b,Owakqz]'OE@;1!\G_++7ZC L=SkY_aba]TVLMAZ2"Sp H '-3b<2D\I+OTX\o`\tc{PdeyGYfwCRgv@OguIZfwia}\UUKX>J00 7J (aFe5QqS\IS,7&&/T;OR a1 po |G    B% V? _L cR T> 5    r z jR6dI2jJdb~khI ( +~P*RAW(&nlTL-]KE]} l%s?|,i'\0dD2`E2`P1b/e/eQ*k}$u( i C6AE!]?y[mdx0Qo=)o;Pm'1@6W mtW9csbeFXCK?'5\M-'(#.3763%+\P-!%5?JJPVqbpJ~M%}noaTXI(?W5-%  $*@2z;FF{QN\fr@{gLzwbQ.&wlb`S;DF3!B[_,!X1j@ PO]NJnj~S~+<%\ +pE[8+;Sw+eC9g^U0J<-rc^~7IQ2Tt o;G^F%o#U<_p"3J V# z+  9f   h z q n- d< ]E2 $YK4 (XL \G a? j3~ . u%' 2 Y  Y4 f (e@7 Tdrp4Fw 'bD`z|Tk/1 HmY  ^{0pG&f#*`,^,^3(bX$h8pg{ B{j2 F#j=$yVgp5UkCj0DOo",2}??JlU3^mhpx!: P>dZurvtha^SP?G3:!.& |xspl^f4_s XSQ6JCg=N9>5<5C7Q9k= EHNs YHco{D2@}?ryi.`jXRN+J3H6H.ILPUYZ"ani9s1~PEwu[X;lO58%`RlgCo!A}v&4y 9|tponquG} 4mnNTr %DzncBV=*% @["3STuTP-r`NQ&?0)#;0eLm [hLu:O ` p0 x< |B v9 ` AazjRW"B.+@@RhfCy knBR87u( w<2%}0&;}EEOBW^xfokgoakr]{cs[ir\o`iicq\|-bSkI=x12%t78PX~u%)--,*&!^<W% LM ,B8DKO /Z}enp_^4zP'A4\(/  1 f*8FI{r\cq:pSA/a )< &fAa]y52nq~\I L:6$$> MW[l5)cC    & C U #\ !Z X  =    dI !qsw R['<?&XMtYkwIf.]Nmb6z \<{a y4 \1CL:@.M|X 8a| gnjmj|g8a|Y NXG?..Fec  ,fDXFcoF *7Q4<Wuv$#QrNaMQC&8&X^.4&? IPTWXVQK#Eo(<PT/3)6)>HAR^\(fMq|H[ {FoheH\-UNzH\C:<$83.(%y"s u!t!v!$(/50:UA}IQ2WI^\cq"i8mKq]ucvauZtGqz/lb eD]!SpG:=/v"8S s0)*L9I7[Y|jcX{J,/K.Ud)/ ~edr*VsAn&W|HclJ/g#5 4>]Lk/sH[B:M+>^v% G] t       \ )4 }i+ lU+;-<<%CM /^Co|,hkP7` m#$,I;HTUcloX[|F[ 5%$s8 vt}&_F-{7=wNk_0F]ssPB3N$g.Ev$8qK'ZVhw5rVyl}m~c|Nwp+pPg)[|M3>.]'; Rd 9(0l6F<&@BEEEB1>]913*f j/G(QE=bo]"q_k.M<) #+BRYmK)Qn>]oobl=L ",{uf.[#P38eE6VWi{|:X mxN.-D _ !u6K5E_ouNq0 l 8O}o,^P#Er);\b-3SU/1UW/1ai-5v(;!GU` gG}=FLy!0pT]Xz;>V$ YSG^q][gMN74Q"U{sV FkRr^P*%;ss/n],F=-LM^oYf'u]MG3_-F  &: 8WGmU]c!d!c!a VKs?a1L"5  uX?u)j \#N&A)3,&/3z6`:?? CHKPzT}hXwZp\rJW_l>Ebh23de)#fah^j[lXnUpRrOsMuJtLtMqP mV%g_DMajxV{JW<9,B9Y5Xm9}3n o[P+G"5>-"ZAtG>u $   UX8t'Twl<2Sl2pXwpQ-0 a# }7HnYt)i\vFY1| V0oM<62<Md(= [y5(i3{?tImT5-f_Yg^l~VxN GC?t8'0E*f"Ao 1 Big$3@SLYe?qY"zvOi|o}[nD])}K w6o!g]yUUM2E?96435:!BLK~Wc>sg:"cOzzp4fr]SK 7D+VO=6kq8>}4D2H2I3Fpy7@TM=61E)Pn]lA|CvkS1* X'^<QHBe_xAI%f +y&imJ_2X'Y)\-kE|`T;d V'~6uE3kU`e`fUwI_<5/r!0Mp* C\4suW"1}qoMaSF+W_9A,V!gQy 6Tiv/z6v0o$Y? ]}n'^l2K5,@7M!n[ ~j?y0pR&pqW=a%2 6V(01{9P?0DGJLLK JF8CU> u:540E,X(j $x$ 6DJF@0s!d%P*=/#5 t:IA!FLR{VtZmz]isz_fns`ds{^f|\iZnUuR{LGK@v:11S)v!Z' Frs- A]Tf0wZ6z@d){8wJsTpcmqie,bF_`[yWTQN$K)H-F0 D4C6 A8 B8B7 C5E3H-L'Q |VN] ~eZn3wQbF#tmY=R!g%8K]f:oJ/ET,X& !%LHkF)w;7{3w2s0o.l-k-l-j,f+`(]'W$K~ An5Y&@h5g'E/"8JB LUr^cfQhSEpD%x7*_E1 ,2 C f +,v9R-mIzqcZXm L[?73j&` :co/ D[Xm&~Q8vvIyz%t:oIkZfhc q` }3]A[KZRX\W`VcVdU oT#sS$}R'P)P+N-N.L/M/M0O,|R'aV =[gb Ii'rf|yD&vl^ Ea*$=nj',6-GUo{keUHr?}+D  /c '{/Nq@yffS[dQw1Gs=94X,v$<e   0 %@.N8`FwW'g.|7B-O[[j x.TFw/d{t0mJf] an*\z>YWV$\U&hS)]T&YU%~FX n+\UcKe  qujz,s}QD4{ng^]:S)OHD~:{8t5e.[*X(U'EwFx 4Z1T.  )!m'=466a-A&: {%q&r   W/ h $ _k U"  % * &7:!i"[ '` O}   gY @$  -O_}6  Zqr~ (um+:D'2 YD:]1[ js (wVPcTHܜeL[t"+7Qvh;Jh$m;~ we"  DH  eg id$|Ge9g  @t  !D o!_ !j !q h!_ ! h" #b #_ FT" y9_i ;  Ms  CoWP9u=7OU{F9pk[e#K'Um]W=Vt0ܪf .fXeT eS j["mܡ"\fL+9CVo #% U'  s` G ,  oh xpq,Z 8 5" #3$BR % & 4'0 'N 'Q Q[& p$) "  4_{ G%  7  kcv( *Ssa*%+'(NOId2nW)K TUy:oc:IAOK6Sp9m k`v\Dbx( CW Rt -O }m G *$ )& G?* Yre=iC9p/G[r7i\2i0 Ef .O^ q:d9j"TG6Zy"=)"AW8h8"Azi OR/ {!vP%vJ(}-_8AJe\}=q1{iiKK 7 G~ )_l^Q  ]` 2() 1! #B % O'~ R( Y(l )QB )OF )LL n)Z4 j( .&v $ -" *1 $ 5G )VwUc +,qJneQJA] F݋By-wהו)ג!| Oo%SX$0_AErݐ]w=V,MK,[cD("S_%;;15kSZ UV  Ei MQos2  "mQ w $/ ,^%* & S' ( G( $( 0H' &i $ r"qQ kP 6 EGh -Z &q8DY z xlO,cH/mT::u /-adwaF/+>Z mfݙ56MJ "x2tf8h{2s/XU-ܰdp;N HE~@Hoy& #{~m  ";FE4P1  "[ \:$ $6 #%G %E $! %#/ "S !F Y  w 0 &n9L E C W  Q d>hDaT0\r*7@Fr a?wP_[G|#8CZt|ND:lLH'z 7{S2Vkz(.e}kk5iz@; J;i 6 3a@3- [,ZR !oK #l5 l)` ,3M 7F +3M% P-Y@ %ic ~}    $ i  F.XnR^~n]  >TNGb-Q 3KqE(_d2vSeA}{`bQWIXJRF]Kev,04`z`z@#BRu8  C' p>sgOW/ U.  \|7 ?  9 P G t)U z x? g+S P " GO ,Z ^a_kb6 eT O=lvnbAnBh'K#NjZj%F,z^v(#AjvZMgLr#\\S 8UW F5On^R`NNpS1i_"aU^ jze1</ a@PsKGQ\zJ? v0U HmP ' u Dm t   { z Q2[ o <r ByT ryN G>rm&:t`@3"~,x-v.x-x-k2O;2dEvYNJtI{Q1&g4V:V:b6t0 )D  8i sH8 ZJ^6 R7aXR$$9|.- w3y @ Z O ;)xf?aJg=.w0 U )a 0( xX{\Zq"WtC Jtdq]=/Bu5}9JK\>xEZvE~*s!,.LxUvsoo;{#f+^B$"UFy(j.(pR E% " }8t.v>x _Ba    A X C  Z B<Q2I[|  !t ?KylYaE "L67"UQzQ6M 1&!RM^s g|,.#\,q! 4pDY#$q[nS1[7S/bRy10Qu>qCv(f`J @)8{D  K j; vHP H= 1 {N F? 4@! yR  i c ]B $ M G H Rz 6 V }kOmQV @ -+ t>@Iza8w|m fEX';<M\ \ ^y.5knL+x[%v gn+7gBd! U^Nz|l;fEp(+Z{ZSn `LdCt$ %LGC aa_ @F % % E r8 V7 bQ [B L# ! \ Z /lQ  6 Xt7&R^NtUnY;Ws a6&(\(6sH5}tiIjJvJ56qnoDX%&i# )I7gBW+"d EC|0 Vs <5 &w   8 A /w S8  0 S*!*bj$dI'nSY\>xpf .jkHJdbu`ultm-^mpHK2w{;$$}b41e*AkEo& 9y+gW^ |iATnv?P+zC  wb{FTl  +21) RR) <Srhl;J8q*Z qIxCrl`bjzeJO4Mzj1q>Nz.U AW!"6OYbmxoI@c jvm;YHfxty#>/}]y ^  !B y;c S: r~DYmJ+A;>"CaN5^vwF8;k e 6:  Rq4wO`Y#L^<aN# rRN/=9[1CG8X _ `#[_QA ..IR{U0*~=hpe1T7^Y?$f[n7yWC{1 K<)pjE]BvYUW`VSJU itV >2M /5  \ !a"m5Tj%A~1&5#>%9.%t@<6S q|%A h bV >>5`/Np;kb)2 xw1e3j{t"\ ]Am$PHhB> PW(  o/)f~nFkmXcKcu%/V@t<\mB$33u/9~Pz\ {XP@m*2oG: t N&fJ6+ }<LZmfT~Kp>n'v0dz(Z~ Y~UQH> 3"X1sYK!JJq64wzF(#!73^bj.%}]/Hv+C#md~nd=sLEmGpJP 3OOO\*HW1sUsv p; lB jC j/ m rv}}jtXBD2iQ8o%f[\t)!(PCap;! .xGROgF]voA\  .DwKeH_{gBL$`*L.vhoAT52S%FZ/4#63`3tG:{Ou9 u4 Ryq 6- #Z$ u< . z d 0=S E  ^}p 97TWx25XJ|6Gjd*c(i *v7^AhPcGZHOJRJTITQfF[^Agp:x/;"?<9VRjdtnzswqnic^UQD@1s."O )  '039;78@D&Y$.l,;9DBPM\X^Zd`b^^[ZVOL@=/o-8L2WoE\$Y,nwZYfAO5C3A:HQ^qK}>y^ f?jR,ro>~6U,gXO[G p ^ R% M% N  S} _ r~H7<9R/]@mhjf zGF *NUIQ e_e3: PbFAEUx7'+QUa\t^R^HS3E +h+MMn nS7a##77IxIZDqZovho_VLQC8.%'4)6Z8E}HP SX[[_W[P TCyF0L4"/c?Rrfu8~;ao p#U$|7{%["{Nv'! If"?9nLZbd ^LRB52+(rf{Hia%\!1TV%_84aA/)Pdy$ oG iG i/ mvaFz/FC u/Z@4P]Wng&%quBD/ i(5~h@bQSJK?%Q8/X'_fn\t5{dzv5fd SUCK9A-;(7#7#9%?+F3S?cPv4dr~[te<S2eB^/_'a)glG2~BwaOC%GgI`#$QCqYNelciXMAr"P_$B? b)*I\alq nu!T$,ZbGO#^ir :z j4W&fB[+y,n/X_>'%{~;elIO/u25 oF %&+05||9qr=fiA]aqDT]gFPZ_GLXXII]fFO`mERiA\u<i6y.&%d>6;RYoyVlGu)lveo`i_hcmlvy2eE (5zUbfRq=* JM=z_vApypxH]5 s <:W7yYHt?F +N:N:m3WK(vA}k d fm@|r -VW$3V-|cUIK@2u ?Gz9T<GGpqq^OOC:4 1127i?@F O~XfbRek<.uf'~N6 W$mW}Bw4x7w3y:GMNN|Aw2kz^kHS187'}:jS?M9'`^sR_Es8r6y >;T{pDx%aYOJ@1z  ITf+<6snp];G4#@be/H[#fAmRjJa5S;v'L26Hh_.y`wt.4=L7pR:M5$b[ssfdPZQZV\jesDwl'aB U \RI?{<c/,$t + S&-u4;ECOKYQ`VeXh]n^o`qcuf yi~lu4zB]\0Q77ssTfn[NRC91%*6&B #H $H'@,15f@Mi]ASoKb\O^C KavR~}Mf|O`8D!X(  f;" )/3y5r6o5q4v/+,$W G!7DXlv>9* L(xm?\nSec]nXtUySxStUjZ]_Lg:Ro$zk N.M [n)ayVkPdM`ObVk_vm&xGu&7<4|o.Nb&g/+y>VLS"g9yhOK#6)#$ *%7QKdpo3_Y7N,:a!&  9Ig|(X5OpY_y PUD8$.A'S"b1k.j$!e %Y,F5/q?)KeYIf',v{Ps$feH\*s69 wqnu  0J#"`-'m3*s6'l3"^,C   D *3t:_u~BJi\G:bJJ2`DK/cLJ3kbF=z?R6i+Q *t7PiuEQ> a%0v:jlMc_[iVqRuPtPnSdXU_>yi'7u ^1!vIOi$e0#%6infF>OT4ak Pt0z~~7y^r#i:^TRpmDA6h#%K$=R^}}_<y-CSa g h g dUJ4`"3 |w|2 Ln*}1><A5}1O1 zrDRp:P>8"6h{@JSSHX$j}mZl Lu+z_rgpNV #jeFK,:[,$/z(k89OtKwSrF) ]$r.f[['x qGv0`&+ {_uQd <T'q7*z;#f1+ Q'$h3>VPpZ~V"o.PSbd }{)[o&Tq"y\[%$ ) W{1FP' m/#A 4"udfS*$($DMq  em]jq@ 4"(.{3l9Z|~ACiGJ(^'O_(OlNI+|}AB<Q )C  I$Mo^.=~},x$YqK]aR:F8L`7\mLI(h>K"_#PSU.hA^'~k+iE\s!0q(*:U? *<N %h ^Wr2l)k'[R w>9cpUG, i $zb(OdB#*qYy@B*$m77[1jj;LqOH&g-j/r )/A ` g1  } @>+@E^(3hwd1g;W7w f,}g*:L^ S7!3em\W,](+$Q ) D (WYL.L>z*T & f Fm tDK  he t @\u 4  ^ *E  I/y\Pn9"%2h"`p7-<i!5u+MAgL14eY~X:%S3=~/J:bl% wr2E &h+W:*YN>3rD_Dj6c`An{ CP 0p BS# hrY-H0`Vsk * D.B wE Wa[ ICv >ehs EOh qg qD w sLp^!jod39Q- QF[Y X W)@s0Uo`R6R3t H`=Kb %y7VQ*A'opDF P)#*kOA  m 39 %  ( 9 E x3 | &U[*E4JY^y %; H( a {51)|=a4b O[8Qo"k={TqY;E2"-4WzMn sE?_ll@< F5JLq?J |Ou*8 kh = <* ` 5[X 9K  e k ) EN `  #  " J `Z C}?)$hO3gG?!0i8hI.Da]`y{3w+V2_Yh8D]%0%0%.j:AAOPH-[f? xRWY$g :a ; OA 6Y}O8H t A x3 p/ E  S  r me' 7Dcbp>y7 ^ $ jz,6CL,Q1)#&z@9J}or^Bd )*Bm[{ww51,Bi2k4I|5Ysx_Wk7VRxaLMw_8'Dru=I'A"uz{OldDbel' 3a O}j 2}R ( 5 D 9 CA S< KOk`<On> Q+ 0  /5t L r`qIt j@s%ea`8/en"N5jkm=x*+.m_2,=a&,,)'.-9hh8$UQ5}n_.GVHbNBseG(bs:8 >cUnH} x w  `^/ Ys) [p eM, I m<$d F{W;g g[E}'G'G+O4^%{Bn:  lA3a#m/J 7Zm5Pnt@(3_DzA?QPG'J; sE P\j +9? & , j/vIcY b_ofhS * 1* 5 ? v@qR G]Mc"r?&]Y1v,&Kco"0<}h>JG 6C, a/JvIe7m6$|~' 1R`dB% (4cko<;My84TPz0  TK\w }7 fG \c Xr V P+ @ T *Yy    5 6 " B i F[ 1F' B   R) b wVSv   Xd;{r+'K/]/*snY>$ YO;XNVt%X@E@9-, CmF~0)h]:+830Gxg1S bXx$^9T(f -<^ix  ~M p~ i f h q{ }S@ (rU8+ALf9;VPkgB|n[=@k{O#zD.]9HY^Ccj3,sk(yU0 I Ze?%#%#"-KyN&zMTaxL7:%#  4iZ@{<B)pe,)&YUDJU@OC=nN2GSi)(YX*+XY.9Va-8Va"]If'r+ra!rP!#}GQ0RYBgWE`i&{Ey^b 4 3&8/qh_[XC &u ?$=}nRQg*;tMf5!)aY}.=h35bQ~s1`\+ZW4BtPAImtxk m?Kt=U:O;QNx_ e!_ TB];N@WLr\tW->mm&8#r]X`%J.U{4mYIuJ}`OP@!4Yf>(E]Yu 3R Y w y1 kh b \ W# V* V( _ m }y( CIv{ME\ 8 g9x&PNuP cXtB-7 U0L/aqpwzhWneLhb>__7[hTkzumdRXDF(>p5zP-7"![_z<N &@x~a{`Z;J_#5S; B4 v*Ef 8}\ m  z z } yP gd 9L3H_" oi+$?G dO)\_f*] (k`@lVN_hx7uD"o ptZ]MKB#*uYm1y5Rif}tQYA /KJ%RS?`|0\sO<`md ! # wfV,4H ~ \R:'mSm6: 4XS,e c#y0G{;2GvQgZUmd}D,nV9u<0z'$ '8u:ZbJ53&jT =$%U;N1`um|71KbhzTa 6r\hG7&YK-  , +W50d> )GyPkY^`T[gnL=k\F%oNBqD?sBL :Q77S=6 N(9B@1J"}SG[egmHt+b~$GhE L/*W6'8DuTZjct?t4&\~qrssijsBx1|C q9USgdnX7D-Klk@=^z5AkWvUA ?b+Z4GbnyGc3Dv[}/P=ASidp}9Xd>]8 "v8L`waw;x-W+x|Ej__V;OJ J NW;`ao S\|C:Cs*uT5't=%E{2{ 2a*KG?90f'|(!4DF@865*u ~'l"^+nJR7>2EyWipz)Fct&Zq;~M ^%h15n; 7DLvTe}_wP#l?;y %q$yecht|| %O)7{Fr$^]cb}Qg<%e 34Ysw2RmEv?h~jZ#M4 CAC;<Jg5SLq4YIf6REV8I>5=41EP \]llu!YrkF&"W!s.=e<JpX_Vd[S&k>L o,Nn.T+jA\LeTdm`il[zSL*ES?o; 3&-@ $gUJ.h@Q#\&e(i*n/}4<E%MIVl]d kr.z D!R:bSr`y[vIl&Vw<m!^QYC6*n}MJ.  l9"l'W,A042+4!4677663%1+0/0102.;,D,C,C,C(S"nV . W7OVex GVv+\|<v[qmpsqnsewU};^*\^@$c! *z1)9A~RcRbQGt/m xlc a am #52uBxRmbJcKuX~LN>/0$vgA3NXh 2b &Hk(}Ew_tnrvpoooorxvfzQ9]!9 ~W{9iz"YrBh'[NZ?0V :%t #d3!DzUa7ehp S[EA.uL _!g*>0n8A>%BD FGHF2@j81(E ot ,^8Nqd2w1l!AZ.h7p<m:_1?pt&fd3UGQ:-!fqJI0 * }vz   6#U8sKX"i'y*+(}!j_T|Q}R\"l)2<!Ib[lTzK 46@"I)P.S/B$$yMhJT A=/U! O&E4-@IPyUbztXRxhZJxiZKynYN}~WXRmMH&@e7. $PD 8%a/>.KpWcBq)n~j%lDmzt9oVkihrgrgei]jRk<nszfH-t<|`:Lx9'gD  n5y#S*0/2 x4c7@<ABEGGFEB/>D;Q9X9b7l6o5o5v420-)1#P2wz"W0Hj`CyaH2zvij`$XL"Ro KLoHEDDxF OLSj[BdUmyr9)uQ^AHk1X h,Y8CLzxUMvA]'ob jehfhfjenb s0_zRZ2{UNOkHAD:m5/'6f`  -aAY!r&+.233359(<5?ECYHsM ORV+V,W1Y6[>\B^J`OaSaSaT`P]GW2NC^9*/%ZJ3  c4v W&9,1e6R8=;0<,<-=6<M9g51*!#G+ok  4#|T&/8&?LGtMS)V5W;Y?Y?W:S*P!NKGvCb>K9.3,$^W; . oO0r_!N%;&6&3&5&6%<$B"L T Xc2sLc  <)|U!z,6 ?RFxMU;\Saedqe vf$ye va g^\XHU<R0Q-O&KH Ac>O:88-6%6#7'8-98:><F=I=M=J:=6'1,&qwR I2  xO!f#B'*,..-+(&$; V?{u Z>!~2CsS?e*ru;=m`yrj^JH&/{ulV`rTDF:E/"jJ  n5s W#:(", //00.-+(&)$5"? L[,lPo ' B.Z?tQ_l|%+/ 4&9B@fFM1VT`}jXu}(6QSpix||tmgNQ%4 xnta+UTH:I*l :) PX%*,n1P509; =@BC~EvDvC|A? =#:48P5o1,&"!B,jc   ;)jKi!*3&:RA| I+PIU]Z s^"a5b9b<b9^#Z rU`NEG'>n6:. & nb{WsQpPsR|X\bp!"##!pbqPU< B/-   )Y@c'/6B=nD#J>PTSbUmX vX vVqTgPWMKJ=F,@:Y31+$xsR @.  j\RF{Cy?uF{Nav +]Cg )1(8V?F4KIP]QeTpUtUvUuTpRiO^LMH<D-A=u :b5H24.+(&$# vkc`yWwVrRrRyX}Z~[yWoPaFZA L75& iUAv5m-g:qNd}  , N8oQp &-4E:lA(IKPlV[=_S`^aa`[^P[?X*UPpKZE>@%9h3@,%kfJ E2! q];q'bSE 4"(#$#$"' 5CW9o\  <,mPp&-#4M9m >#C<HRLfNnOvOtNrLiKbI[GRFJC?B8@/?->)=&>(>'?+?-?.?/?,?/?.?0@1@4?/>,=&;7j 3N-'&tU& d1hE!*#% '())(&$!(=U7lZ 'aGp )1H8x>1DMHdKsO QQR&R"QNKtHdDP>5:4`1F--)'#!r^iN T> B16(1$4& <, :+ F4 Q<bIvWjw !wbsV V@ <-  -! P<pTj!#%&&%$$""#"|jyZ`H J75(   1% J7_H`s#$')*+,81W5v:8@REpGzIHGzA^>K7%2c +2("!bjO7*$  ) `I<-c ]FjPqU" Q=+5,14KIuI!2&>U #ce)x[0$!$2p@d>YR\HN;]iIp4pyv{|{}./l]PHt[Vz:G(& N; ?%T,#16m==6m8d)04|<>@6k.06n:N|( 3=.u(! k`v#"rp#icbqW' -#  Q>-T@uFRjb<J4<`2{#~}sn`2[QiRnK7[fUcFoOY O]^)oRUB;:$g bBXU-I 8$ fCFwtagMGdt\ ?:^P2r6\GD0V],LpfY  |Q/.!pH2 U! \$f{%J'&:'$*/&:&2g%N'%U#|y#(W ;g. l$gGa XwGQ";{~}9z [ IVAqr&+7KAn yP1g5+dVd94Y۝Er$/پ\ڜ.YC>\,!ww1{@g)n~\2x/us:br}6A&&% `,R:]&/w$sR$: 4fY   2 +O n J0'!kB&U.=*!$-H#.$0f%r0]%..$U+"( $ze!!!&5cB {' ^LQ 5L1V%~FZ&Z7/J ]se+~^[H2k6     AmCtJ[Lv7Cx^"t7k738o^g:E u p U*kD3wF,ee>WI  = I 0rOog{l  r9Vt v,,pR,8{w^NbZk Jw78jZuxI%\8{%8=89\=fIxjMMb %t? nz   G$ { L {K }> zX wx wu 3q zg 1  1   n  E0 ~ %JIOo~Wu7"sq L.F`IyM,L;+A@ZgmLN'@k!=~pI\ ^Lx=br = 4<\l 7 mBTVhFmBdISW"Bd.s#+_Y9mtZeYq5P $H,B10?&R`r> V J n -j`j5lwZw<+yc_4[i:T;UZ7pg@x3|XK$ VF Yc7@oyIO#^2 # KZEw 8Uo vhZ:L#V-" %AI( zf*aM",<#:-$$-9%-.a%-.% -*y$+Gn#k*l"3(g Z%CU"A]+yDy*V] / #B-`N!3u4uv*0HuL2m߽#L@\߾y߳rs*x _.KI6d]ygh&0Iz`!R *:iOA {m?; -e`bcnT' >w *; 2  E C _  ~  $K~dL06Lb]x<.iS'eqoK'FPwg+= SH I )l:imJ%v2(P_$Q+ gE2cRBWn&8TbY !TYu0|~OcwTC1\nOP2m^=+C ^MG[^jbZ 47, Y p zC Qo     3 w, ^0 C #  T .P&wt?  iW&%yQh}5*OXZaoP' 4=[i}ZKi4@a<x ]2},G8&shT`+])^$^ciOdm!>p%s/rKp k;e)_WOoJ I D A  @ >+ @ D4 KTI?^ibvG8ayhhQw:|&V8/$}V4PHG\w)r#],A.$Kt- |ED c+Y!M7kUBwd/[&<-ZU:Z_dgE*F&Dh0BeBHp S$ 5i   5P Vh uTg|d6MK98-)=I]&O fQ Q KV@/O&F::\S0$rzB: \ #y F Z~5! &WM|wwFM={ V+f  h &}?k8$Op<|FJ"1u[6.f _?8T^ASx4 _i Kw 8 K *z      !2  "C *     _ i  $ 3# CP Wdhzy%kS`WRPhK5J(J+LBN]PoRQ{NXI$C[9x.T##j[0_&1;DL#T\Tckyso|g/Kb__djH_sybnQ5a#QA10qN g xg i5WG/YjyPnkc^XxGhiY G63PR3,Y+Cn[\!ztbN=/g%V#/,1:aMcdpS42<9ew ej F S +  ( 9 EPKKGo?i3%  60 Qi ` - i < g L W ]- ozdNS8 te$&(<Pc{tiYK3@<6).>(t$@",")$1&H*v/-5<WDRLT9{]e0Rnv(x*p=g]QYnE&:0F%:t :2 2G^xy &| `O C % u<q!Z9v#VkyjS* |^6h`#f&@b[ 7 cZ K|nAxA#i]Iz7q iN'<5d?EY_I>KOIEF@7 P+Dr qP.;hYwyx^@YC/( ))?C!Zo"( ~) p b  V KG t B :] 3 . +O 6*m 6*m +X /( 3 ': B LY  W D c mG x& -yukp%Rlhdb]JUoI/<g+ue93(lX@XGqdIo0m~65~st4 OEk 9jO5G7GPXg{<T| q sx el YD n O G @ =1 , B H ^ Q \3 i[ vg _ [v<dPy<#)bIA% =R:$r.6CP=AB_CTBaA~)>t:5NT/()^".J ] M*#@*.B0h 0c -3)$L1Q. mb& =Gt'WD ?!Q@my (zqf `!n/&*+=-T,D)%s>~ IB "QT )27:A GKOM"mNL*J_&E?3t5R+V|!B4G[ _m &  R q% Z f ;[ T~ _O L M&kO T Z\ eA q[ ^ ~M  lY$yF 2P P0&I@P]rjstu~leg`&\ZuZo\^b 8ho'x|ssiPq^6S-F69G+a=|| ",c5y='D?J?NSiVTYZZYXVfUPTDURVsY\OacfkH |p u5 U z }  < F W ` P Y ~ ) y r < iF }]@Py@,.d1 +3y@EV9gWwOpcYPbJ~FzDDD;F\IIOWvaUla|y|nM`Q{nA0 uk; *;K {Yog r } L @ { (w juH ta td fvF y } . ' } y o d XK`? 1t$B<  *3$})8,/]/L.gC,)%:Q"::o`aP%[ $Kh)/$5o;AWIEIwK,hLjL JA%*FL7?#>HOYLW-7 dnyIP>@\\xw'l`- \J !   D1 H: pqRV"('%>Ljut|qv&'nl\Z\Zkh CDJO FK |12SP I@ {~r|oyJAfcPRU\YfH [  < Tm  {R k  # / B KSZO v_ vI* /cD # I6|yJL! P g U|  Sz):ZQ k g y~ 34vq|p9+ )_Q F@ytqk>7 ugF6  %cS ,7 j /[3,i"$]f&(( ((i'%Z# CQ  ,e?a6!"w"b"e"z! 3FT N$mO ,"AD;I  To$(f5P     v r u }     7Gdw- 40&Ut- i { j ]m. 0" ""!+i)&t2  nB8 @a \"&QV(*+++z*7(%.{"?Sv " 3tl z 9sgn F+ w5(59 0\ r !n : N |7;{rA5  TI*,) =} =Yz:a( ==AG: bEwZ*  > WT5 aU8 Os B#(Uj+/1M2q33y2_0,.;*'#k"&jU o7;& 2d0!N#j"$O%@$A$V "w0!`*~K< {pIO B\)>EC 4fD_  8    ";Vn2bz  #LM&)*,&-:-<,(+~)1%y!bKh  bT H"'g,1!5379S:;:?9Z75<1,-(n"L dU8US#D(-2dI5x8: ;::g7052N.A)$&} C<[ 27saQ K9 >+ 9& B/RAm_ #JQt     KRr 6" vU)tJt2 i& q.>o-ZZIIQ TwD'(,}167 9z7;K=S=J=:8W4G/)(n#k [ebQf'_!z4$I$G$G#[$R!x1c R?x]g 6pP="}i$&((('&P#! ^4  4W !jj;PV%o@e6:_ 4q`#'Kq;#~S%h')(j'=$!O<a BJ @)RvFn=a/c1m ,`k  ~!F;%b(.0G/""@#\ %#\#` . !%?c069$M/+8?c@A9M::37,,! k7C-&tl<Q>dmtC4*9 .> =O =O  0 {t[#neb[TGGu\</"' y!5B=&Ogbs~I3)% PF &TX k.H=LV6M:ha<}\rQJ~/Y6ai8 | 0 e " ` KQjctwtvez wyza{M*Bfg!G*0. ' ?u  L ' ]  qs AeJ^$JxwbE3 T@6[Dgc^jfHylC*}KI67HvlTIgKdHZs+Q~6-T "P9Im\2)'<nbxt _ x IS 2 Z  e&<7[:3FQ:XEyzaJ=xrk7_ Lw7L #B : U~ v}*W5 CWovM'l!'t4-64=1l/'$)!U <F;Xm]=gtF:'<X3UUt-A>r] I A 7 g *_ - & o     o j  '~ T ,3 1 3 1 5 ` E ` ^| | fON8v! r[#17&Lp`1vwcMJ</0H%Y&/>L;FmHGHIK KeS#^}nkVA&+xw.[H_ v 'sFc6 T K Eg = u 52 3N , e ' E -   l # 0*O 8O 8 1* (  '  ) z @ ! ]p bzSfJ6<G"LFmOG;'RptqO4xLL #N S+2@:vIjmVYZ;iICU%PR8[kQi'3r"f  U0 +/  *? Y{@TwX.B= 4,?(;&}.#2#3$2%v0&k0u'V.]/$AWyiWhJ 5#c F Lq x_Wn">9$* U R~k X>j.hD*V&k5|=Jr IfXY.f;Kx:& j' AKvtF2@/Ur&h1/t = 9-FxYqkCC$r6 T[iyz0)}sc^KM+/C`%'rz, F+  I zcg+ s7`\3-b0Hx\`nK}D9)ZQ \Tb5(by=RaYGy+nG J3+_a;n1=a2r$]Y9F 7a / (K ' %a B %c E %^ ? %d F )4  +!  +  *  )*  )/  - / 2 8= >? C IM R_[gfpWz?Q,wd6NE3'!T9 Qgzmc8Be60<!G~m$x/H}AT"(0GClTWc~E`m#8r1t/s0p4jJ<`HTVFh5*}r#=2kBZm]n8aq];r'` x<hj6M74 e0 C+ 5 N X S P R  Kn ? P.p  /4 AFToi4~tl5Q~7Q f{X3  "#""! 9W*lFagrLV); $s(J+.--v,*:&b!Z. n 9.> Mh[ fdloomhr`5FWMB3p7!+oy R$!,#8(B8NoYDepQrRzdXwMJ?5W, b&Q$`&+3J<MIZt5l,cPe;#GPu-R[V:k-k$s OG  f y e @  = T|   } G d  | b  H q S / 3lTyU%}O /OtorruMI+7 CQrjK\ +oT<# $Dv cyX{e+KKMsgmaWH96($,cSyj P7H# E! d Q     C  Ow6 pi l #l pd v4  ^ * d    T  e ? )K/pl,OkC Bdluq@9  -zOOo&}m@_^"#ZUBdp#ON+}CMRKd~;2h;LMz`ACb*r]3< J ] ol xM            t {X u! Zl ` S mD  4, !"01@lR,ey9emy[c=73 J'a$Ew{eiIR1@\"[R" C0,a v7Nth_:^J03#n 3zRb}pC'"}icVvEj6 *0 w%|067 2(,#;vM(av2LUwmW67F ,U>:K+W`?afz(ivivgy#c)}C]WUK.@-4 &UE ["-7H X~-edo(wb~~ymts=Dj `WwO:G^@<:$62f.K+1% MfoM#-Q7x@5ILQX]j`VwbHdc@}Zd7{Me0zEf+y>f)x;f.yBd7{NaLj^gZWSPL2GaB.;^4-Y&9T/ >5rLfz!+zf:Qf;c.'!x;SC [g xH  2 L O <  m vn Yc @&#?:[w{S=Z;hcP{hAG?Rh|H C&wb8^G+I,R;X3[0Y2S:JF?oU0iK WP e{:" ,@w`_wE&+wd= Q/?R.Otwa>Lt8R '&%9;]O`T 0m bw # ? H A . }} ey Is %l dc X qI9?&&6U0GZ^n*CfgQD"L 4-MIfWwh~U4sY\^ q}-)id;VFGPD:.V1Y -Y-U"2M\=M?O.)fb'h'5(j{YT -j--coO21W@?8b D }  NH nu v kp JE   t" v \?),l!aC[6r "z_gnL2Ix s9,y9 CINKXa{iVp]p)g!v_zYSOKiEFt@(f=a<c=k>~A4GYO{XsbkMmNdw[R,J$tBZ90Y(7h0v  AZ~)6X<DzQ^Ym9z ~k~9lvmpkg5f=g5jpx]Es^ rY6%@&6 K X+9EKQDZbygyrilpshqpydu^~U#I6:)p9]1/QJdq,{&Zv^olloub}-eB k~U?*b  j0$$.v8AmJ5S7]fsoVgOu,]{UOKIHIM|Sv)\oVfMes\QOD^7+( a# "t.AfQ`Kny=cR  ;~L{&dy6zvFsZonljfd#c2aA`J_O_R_Ma@d"jq]{!_{w{aG#C$  p64OP!ak}j|D: f 8ku$.gq* ^?,\/* ^?$1ftou D4?T0-y1V[> C%CLrxx}@gR>{M-*EAj ViV0yf 8 ^ q f A h -ziMT-=L1$DP Xolcpqy_+D)@[zB&%3W@M?Y$dsn~dtxCV!J?6e.8'l!\OF<81&  ~ )?X|#/C;"Lqr_`avO;}:$#4 Rt9J`'lSS^BtEFFD{Bo@g>X:G886,4 2134 6.9=<M@c E~I4PMVf]|b"h@kQm[m^m\jMf5r`TY1O F;E/!Moq1"B*1~5^7F7J5Y1y+$:m,}8Q$:?+Osbty(MTh{rscuDH mSPYCo-TzW - :5FQE~[|1enmauWi{dM5GE ,>:78$;8A}\J*tWmjf]x#OW@ /e . C)Dp[^v} Wiv8k bQZyTDOfL{LNsQVV+]fe;olzKYz{h*>WFw8,+rNr0 G! `s2"P)&3v=&I}V:gcop[}lI7(6%|)W9t)\ O'Z:u] X*5xPcdnNj7W`gC_pgeu+a@NZ= -M6W JZ eKnw#t%tq kab1T$|E0=4A= L h\Tmk8Gbe Ef~$ g),=-PbboqXoD302$\5  &At)E7~yFAsVhf\%w@Q`F;;1(5bS  (+ @BaS|n )1=6It&VVdsL.>k{pCf ]=Ud O ?J^HpHqK[O9V^_/jpvr+ejN.2J `3|FWKsfhsSBEV56,('%%&2*I1h:}EUtRj``p,UUJ>.2&D? 8Ty$6In,ZajwTztBUg&YKt<(,a0H   rs}  +@ah +6 A>JnS#Y5_=b@b;a-\WODP:.o"Gk + cAq' e!^ !ak~;c 3Na,:FVPX+]-^)]WODL8*V" 3G"\*328b=@AFHKMOONLJHD>96s-(#Z^ *h":RSl~2[}uqfe] V-QG MXL^&L]$OPR= Ybl6xG|fs%W;f! q$-+:WFPX^y~^BpSb.i5e#f%e"e!c)g-a3k=]Ir_Y\x{SyNHD?8;P9f6v555}6t7o9c;X;X;X:[9f5}2,2%Qu*m  Fl,B5 XOnj +Hwl+coZS' NAIT%G_6Fc<Fb;IW*N?T  ]hLrU5}N.eHN+)? u)1C Tz~e-d,sM7\l#;tG,"0Iy=n"4TuJf)a$Vu{E4"b Ci/C4V1gXuxIom}Rm|9^w"Nr<l4i)e!cbb"c)f3i?mJqWvg{1tFXgpzm[qA^y$=ncTD74c"+C;(M3=LG OWyy\_oraKhRd=cO4*D nb # 6Q.?Od_<od}0guj;EsdTxD/2`"5T _}2#d 'S)J)H(L&W"k:i  )Ems):G<V|a(j>rFuLwHv >r1ne\QbD*6p)Kx$ 9uO9v$!p"k r{-@Xw   37XItX a#f%i&e%\"SDl3Q$ 9 xqtx  0<`b$1@P[`0q^1{ E*yxKs_oonxnvpmtY{<Y tU5sY~> ]# 0.;>ULYqeQ^Kn(O vB{:44~6z_.>5 X#@ XmqU- 1]k|"}nf<sbC4zo]SVA 9f(X#3T/N .  % , )C ';a(AM~3TY$5WZ$%=R! Do7Z!z$J9{_zc~\l@w<y0gX)Wp(\ G S"G#G dzy%gn\OqDgl<C!.U[0LXt8,K?!p0JXq$}l8)J;R!a&b~!~ e5;G*pDIhfL2"v4-/CD7_bdlZbmJL)`~acq\iz6slON1k)d$iXP?{4{4_b$6nN0[f [g;% E"eX6[{3h]RG^ )+S6)BICC #:jYVkT |:J$Q5q"ssZN6#Q>.B^r/@^ou*M6 } S,*5%TEdj ]<cZN%7Q2|UDG>F8^5/BJ%m 8# 2JE#|RE^\O[-(hN|w,7(dz"3(6 ;2#{6WlI*.7cls}L0R'p2fWG9jJ!dx*q }  ~ .@29Q L19Q,9gt#Mkf k&j z WT 1D?Jsa< t) d= Vs t' ]Y PS 3+K3IY[%>[E:hlOR"2ZAFY'wZe,,cWwJmiO2i-'*igPi}p$fxRclcH?gvP_F360 / #/Pn1HTBVKZY`mN)UCs}5km{t~WLF.hi>"o^gKX(+JCsO$^+>s4;z7e.;e_+Fh%yreI fG j7vls7B+@'xrUAHJ>FDg/S&s4H!}E*a>nCOA^58#|m_pJ.S74SqcT~krGOqY<$x?0z8,tN7juISbVYeQsH0;#'oKwVE xD_blzxwnrkqr)5G9$@r f; [c )\^ Xl%9P3nWo&>l!&lz8!wnV<=T(>m .!:g#~5?G6WieOvoh>6{<(r vAmZ(xJ.j|FX]]QqNxI-J(C SG:=!x<%DK<#}1L%yU ;io5[KuwC8;q _IVh+<Xd)5EI9`+wO ,E q>Lar{ {&F/r7EKYY''wk0hwAD!x  #$:/Rok~BT7ge0kSZQUSZfAnax4 WFe|C"iEdYaRoI~/K|(G=G==(.]D 2Fk9.hWY-/@W6h(Y% o !&d'](W.v0-y81r2q0t$2q,|@/v,5k<^IGYM*hunHL|Z<n77%E LaS8%bN|Im./'%2w9K=2@DHEGA7U4i,$==8]D,Rx\( Tj6BM _1W/ JS|AeOk@\6lc #q;1I9W@`EbGzS[2[.}UoOD9!evC(0yDT[>+pRdY4`2 $##7COqiAm@9E mL 1 dsB33%$%- <(RP q5G\[vF }n lz[!`RA!i&~H&\;`uieVH7)GyQ8m)?) Ke3e{kwVuSoGM /&N6sHtQ}Y71k~*b1/qSg76d/e#&0_<7G}CRjWaX^VbUcQl G|CH {>I z=F~JB"g=2<54Q5N5L0\)vO%Ea5W&h^t ;Fmv]h\&/Z-3_)d !d  go zDfKdBU8C>MATKfQrF]8A,,!     #4=9ECXIePpKgKg>P%"leV}FY25c "d$!1]@-vJ v7UbaLj9or,DyqP 6r8&D)Xt'4j:u]ROlC%e7K,o#*e,R\4Ook '*Eag z5r if![7NHOIDhb;r1)+(2+06|>{nIVYfU.B_ .hr k|+T_5f;7 #(;AW\r(B:Rq Gfq"T+5-/+5)<&F%J$O!Xg0` 7;%TWqY^|o^M=:IoAwgz4e&S/J&Afj4)%!Uvb'-1=tuIJ\\XAf%x@xAdrmWj?F*6^0gD'~bXXi7{w@Lp}~2Zi?Y%I>g 9T4F2B 8TCv1ORbrvdcRw.e>T^B.l2`$%~"A[7\"3NZ}t Rm|g'b0_5]9^9a3e+i#oqv G}|O&8jNYwIb<L.5 f"B  pZ ?%+x0h0i,u( $$ <SdkkQD!9% ) ,x0j1e2c3b0i/n.q+y'#, ?c -%I5!g>'zO1a=mDzLS\d6iFlPoZsdwryxzy{}z{{|yxujralMe8_&ZW R|NwL{NzMrIsIsIzNSY`'d4e6b-]SwLiCZ9D,-Y/  t =)4a??I qHS\ \Ke8m)lrNuvq>P9]5h4k4m3n4m5f8^w <yof4TcAA+7l"1,|5_@?J!mCTY \Hg2p!av9z %z #x2tIp!ak+d8]FVTOc'G+sX>H2h&s7S7Z ~{*S\|m&p_BUXMg3H'qPC5zl>B:Q4b.s) %"#*$'&+|3eg1j)n"mp`rVrWp_n#pj)i-e4b;]DYKXOVRS W P^"K"h?G/qZAB~~:T1p(U !BC.aCYpJ)YIi{q"Vi3b@^I[PXUWXWXWWYTYS[O_Ge<l.usK|eH'Zw[boNV<9(t; S$'*0u3k6c8^9[:W:X8[7a5g1r-~("3Ns    % ' '   4&N.!_7(rA.I4O9Z@^CeHhJkLmNpPmNnOqQqQuTwUzW{XzWvUsSkM]CO9C1=,6'o+Z"F/    gF$*(+,,++++,/|1t3n4k6h7b9]<T>P?N~?M}?L}>N~>P;W9]7d3o0v+&Eo, [P:tVm7y H)l6>ELSY[[VRMB4Y*rD|\[D8*v' C+5j?P}H7jYO"Z-V J[@`6f+j"nvr\uHx:y1z-z/z1x8u JsXqfnwj e,^9X FSPN'\6G:k_@My8b1w*#4f '+![G6`I|^p8i .EV#h(r)v(t_H*wKacLB3"G  E+6jBJt|L._@VJ_6h#qhwA~ }y6t Um|f%_6WGN(Y2F>jc>U|6l/'It   C6)rE6XDhQsZ{`ek'r9xJ}Xk{u~Yt>j#{a gQXEG85*q( X=  m^O!>#2%)%%&$%)"7IXjw 5)!Z8,yC5K<VD^KeQnXv^fn)u={LZanwyufVzJv@s8l$h}e|df~e~efgjm'q1u;yH}P`o|{h\|NxCv=r1l#~gxap[gS^MTEN?D8?35+t-%c&S%QE8%    %8E' U,%a5,v@6H<OBWIaQkYubm!x>[tq~Mo'vcbSREA7/'h9 ]!?&"*/3z6q9k:g~;d|;e}<ay<c{;e}9j6q4x1~.* $+Jh 2("Y<3QFbTsbs+Lr/E Rbly  whO:! d|B|khZRGA80*mD " a D%((+-..-+)' ";Zy  "&!V:3ND`Upbo|A^z!*04640*%q`Nz8q#vi k`_UOF=6-(f?  {zvwy~  -B($[2-r94A;F?LEPHSLVNVNSKLEGAA;=79461|3/v1-q/+m-(g*'b)%^'#Y($\%"V$"T KEB@<?F K($\/,n62~>9C>JDLEKELFHBD?@;9451{,)g&#WI;%  |yysusx~   '/?!M%"V*(c-*j30w85>:?<D@IELGOKSOVQZU^Xa[d^gajdmgpjqk pjqkojkfga`[XTPLDA85+)dA |a F$-(-/246|6{6{7z6{5}6|420+&$ Caz  &>)'`54B@NKWTa]fckhpm vryv |x&~3}3@GGLLMOKE9~z,yv urnlhfca][XUPOKJEC?>9710t$#V7hL#3'* /246|~7z}7z}6|~6}431/-* &!#5Le~ #H..p@?MLWWcblksrzy%}}-=CFHD;~~1yy$ttoo jjdd``\\VVOOIICC<<7711w++g$$WL9-   zlc]ZXZY\bmy  "+6<F#$U)*e12w78<>CEGIKMPRSUWYX[\^]`[]Z\WZQSNPFHAC:<57./p*+f#$UID>92/01159D !N$&Y()a,-k02v57:=>AACCFEHFHCE@C<?5702v&']J9)   ->!L%'[*,h14y25|69:>=A@DAEDHFJJOKONSRWRWRWSXQVOTOUNSNSNSMRNSMRNSMRMSLQMRKPIOFKCH@E9>59.2t),g"N=*  sY D#2&%'(('&#%+!=MZi|  2!$S),g48>DGNRYX`bkhrpzv)x0};BGNRUWVSOG@z4v+q|kvep_iXbQZGN=D38,1p#'Y>%    5#P*/k4:AIJST_]hfso}w1~DQbnzvfNx4o}dqZfQ[GP>G9@/6z*/l"'XF2'            !*5>"M"'X&,c)/k-4v08~3;6?8A;E?IALALCMBLDOEQFQGSHTHTFQEP@K=G<F8A5>4=08~.6y+2p'-e$*_"(Y%R#MBCBDBBAA@B!J$P %S#)[#)[#)[#)[#)\")Z"(Y %T%R"K GC<;0&%     *1< F%S")[(0j,5v3=6B=IBPIXO^TfYk]pbvey h}i~kklih~f{bw_r]pZlXjTfPaKZGV?L;H8C0:+4s'0i"*[&S"J@4/'' $()%&    &<!)Y+4r3>;HETK]SgXm_uc{ hm&p.r4t8t9s7t8s5q0m$gbz ]sVkPcJ\CS=L7D/;'0i'TC)  wpkicfinow~    -5B&S%/e*5q/;3@7F:J=M@RBTATCUCU@R?P>O;K9H4C/=,8x(3n#-a'T!G3 %      %,48@"H&Q%P )X!+[!+\!+[!+[ *Z%O%O$K%N"H"H$K%O&P *X!,\",_"-_"-_#/b%1h&3k*7t*8v.<.=/>1A4D4D5E5F3D2C0@.<*7u'4m$0f!,]'R!E8/ %           $ $'  $ $ "    -(T%N!F%1g+Y&4m.?/@2D4G6J;Q<Q;P<Q>T>TD\=S;P;P%3j0y$0e2pOli,uM)8u4H="F   +;zRqo>e i-[}E`'5n c*#2,R(P[%RVg*RV6g]L\AG d7 WQhe6mC$(uCwG0h^Hu?zsGj0}`E\N]LQdRaWXc@d@WXFz S>GV1Of '/hQ;LBdq+VzIg3R  V' ;Xy5EkJp*8q?t[J`=SK]9KIa@Xf'xij  r\ FJh*H|{-x8ZU 7GB>o1VA yl*U2@,3eR|fMw!ow2m1h%;?DOe% uDJg/L8u|BlX  : KVlKRS[)# 4  X s   %  A * uu^F. . KiWL |iC<_=Du|Qkw"w"T0G7Yv#1VzCsZ3%gE#\@  *Sk1z^%4{[z' }; yx f N A8  c m m f!=0 ""d S#i ^#/J -H#K "{ C}!E ! D/   q`\ > 6gW8n q  r[*D]Lko L#n.)aWG0wO+d1Q~fY+ 6*oa:ti'wQ #=fanj3 *oHz)V,JM679K-Je;zojalR;S< 2 Z, l/   Y 5?6  X o iO ;' )26 DH50 ;6E f| Y "hN ! S O'jdN s Ubf&RMl 'vnKiP+c!WQP [P|gpQ|d5Lvf:H_kepR]O@>|,<*CeJO6S'B~2F[9oy/{K L=|n  z | 9 B9 _jZb>4  ( R0jH Px@*,I[O`g#xB oV@^ }hRI}1T+R  )1#A D6QJt8]Pk5myu ZPTzqn0ZeAZVLr 0=83Y$" :eoi&*jPLA=4h E&^Uou|s|c^E$(1Cclf s4[C$cv"{'=71qLD~v$:N2vH}VNa-Xf7f5c%LWIF/3LwRB:Mc QVLPRV?G  4s e='% l  }  3: N6 VG R@ 3  W A} 9<%. p] )lb t& Nd$ Ub.Tn0 vI!@RWn0v_NIC af1'pi ^,QJDBIXrfCB:\q|G\w$TzE :a7gAOwS#_7 st4 |Z  oR  ^ qNu ;7 ] vj)u^S<^P"R_|P@z DA M1b h Idoq {'x l=pLt'ZLqZny;I} !co)_G'sRZ4]*NX%8Dtu!9Mj q x|'vT@%o`Qg "hlG}+o R ,VBm B  g  &74g7s>IU']7dFgLeI^<S)E4u!4 F=  v ;7 d 86rB'z$!'LY7g+hR9eLAW4n,{9*K,{:1s9eBSN>rb]$@l |?XghpB9eI$ &z%{%}#!AQK"(,a;.Lx`Ux+X lib0qucAT E7m*Su:I%-2l3c1n,"&(;pm^L2,m?{M<*Y@Y)W(/ bp ;$[D { ~  Zj`n D h0 %C -#L ;*3 : Q n? ]Ku+rL R \t q,vExafr~cRL<!|b G^Iip,nxFK"`  (pWg[u/' sG+ ;T|K\N5E"#4d&5r7`%F>U$Ky]S/p?_ >qMgNN m O/ 4& S& P v X"D~e/ og xc ^ ^ g usT RP '<G{ ds @n= v Ig4e zZiaWxE[HGLJ Zgu<hS*& ~UiAHFO^{:ZjB U]%D.dqUCT=?y*!#<"~&B, j.(x$t Gx~ QB +. b@ 1      , l\ JD 0$ Gm" ` | o> :Q\s3c0P0rn#^^yUA9zBr +\&q 5;F=YgnCd9b} V[h;#W(5u,>RC?E6E8AE:_0M!-ROY" dUyCK${3piSFo$W"JBF0eH!~Mx .Wg] ;!R1cFBEV#Ui>~O-UDEv *`F7_^u7@~)q>NAV&o FwO0{g^k D@+j1p=9[jd{U`--h/jL3o.x8&nE=bY{|.$BE]lRE:/}'|!}&~)1E` "Sx9u:9hQmWZgM}BQ45'g` 7-hL*oYGu[BAC(mM?z0A<gj } j8{ Zv N4 Fh Cz Cz IS T cV w' l 7. "uiO*}a@4{uJ+ 8Twe T4p!?%f=>&&M$UVPC5 0VW}~.-RtW'nZg5]Xl%k0PeoAH3"ssQBci { mH| bs \ Y [ _ fg oBp {" t ]s Ft ,7N~|7 ,aR@?UtkWL4RJ"S'cG%hS^JcDue+=\y$d|?N$ u"?-GZew|d`Hl(Q>O'|/,R&Sw!}'!Pw%1U~(en :Pg z ln [ O\ I H Ls U7 d wEX b 5w 2sU k0WS{Sx-l 3~"w>^YnoJ:,7lbhx @'z8 oI7eY^R_ezXoTwQ{P}O~Q{RyUtWqZl^f|`brtc]fac]e_c^gbb_jh_ex~ZkUtP}H@Q7,H!y$}$;&a;PMcu)S*yh'&'nT}@ h RX=)i6X x5(X20; oB?HKKIC6; n05$j o;a8Xqz=%vEc/RB4 5IXm+Y%d#g&b-V|9BIRJ(^VvE}GG,&=J=a]iu|8 Mo<1zlLc'^^c$lHz(]!$c t*pqJ\myF!01JAh<^R )z?k]`RK# D-$ ?59/=9D@<<JJ<<KK;<LM;=NP9?V[7C`l5Efv3Ir1Lz/O.Q.Q1Lz5Ejz:>W\?5>3F*!MV`RkwRXwt:Tj1 !5*HE!ad[^6VRwUJZ.Gr^q~QS>5B<\fzi/R>9 @8_xTuZ%t}9Q%] -NWx~7Y 6As~uc2_ 'o 7 A8 GI GK C? 8# ) - <Q:+&[?2SFZ thwS]5)A !o2#A5O[pme_@:oPyA4w&<y W4d/ziTY;N(I!J#R0eOz6*b)?z@jY'XuvE0)EXE6l5Wm T<4ng&Wu'rb Tnr&'V;6yuQi)0nk5?7V,:E]nZjsK,BK Z6$`1RuJce>@Q?=-2}38X"c+279962.)x"dU?c+C zkffn}  2@d _+7A-KYSZ `c$d)c&b#^Y R|KWB2: 1)z!bP|;\ '=  %;\"h2C8Sd-s]crt`]C9%p e-/F^oFWuK*t W]5)# ';4_j]F7KqS]qJ^H2z3Ks;[{zE)Qzxl7^S 8If7@'w9157394965;.A%rIh7Q AZejpz}J,vp4d8Q=#)Bd K$S/ 8fA'HOU}e\sE\ak-7fdl[qSwJ}Bl9?.i $G" m9 A(Q~@_m[XzTuB*6;Y?1jS]jOSI=(*E+^d sf )9=1x w%at(H"B;+O `fu~} zWT,m+.@$TegewL5l".h)! R(=6k|EcrThc^s0DTbJA8]0(9!\ {8b .ELrg'1:#EXR^7kbxZ&Dgj}wp4lZhxed fiooFvHkliS8b ![ 1APk_r"5l^wMz>r/2H*-&%(#-57V~DGsThfY{<]J;A,#s-  Nt(7E^O X-]?aNcSdWcR`G\;W*RK~BN8-!sJn #4 sSw5l$gj!o)B`  #4Fgh'.6<3APFjJ~M PR!U(U*U(S$QMI{Ee?F9)2+"y_Jm ,A ! -B=ZMrYae`Sz;W' I#G. :AEO_X~/T`rhhmao]p[p\m`ifdn\y8TDuJy?2{%>U9T)Cb]OulSht?h%_YW&RMOcNhPZT?Y{`Tj"tDm&"mHXCBb,V~j&)%5a@KoT;l]z.coifmap\rZsY}rZp]maigem_uZ};U7gPOKjFA=:77N4c2w0.+) '.$@ Ur2b  %6g-@\S9f-yUa|s[k1dT_q\Y*Y+Z ^zc^i;qk|HLmWUB:Bw O1y@OL\|'glp^yRe>IB A"; 8547; B%I@ xTiEp_gm\{PA}D8@+U n-a(6+BqM-WR^m c*g@jRkWkYjPiJf<d._t]jXXTHO5K&HECwBs?f@i@g?f@g?c@g@f@h>b=\<T8@4(0 *"qKk %5 xq4"@*1p9.@EuKUNCQ0kR-fS&\Q0kO<|KTEs?88/}&)aLd,/EG!N<UZYj`(Xf\s`&YjOBED<]0%lNo dJa!A%*%) G"9b x?}Fo +>Vz'-. Z!A. C\EVfH+F$G'H)@ x8KC2&-2&b=V/wBwb V/%$-) #4M9If4L`h75+i Da!oLdt]M6q4C@V(`Nox:qYcW$t^M8) }QN,q/Y}8OhsgkvM4i4Eh)V>e [Eud~rnucz (/ < f| T;[x2 |ZtRqcKN'gu]83rduqbN?SAQRxh8!3T9%iB$jJK4znL35|P!<;FH&:&8H^~5A=ZQ L]8#E4q}-rJJ .@Uv ;m)Y{LjdVLDW\kmhjnhSM[U"J4@s`{zXklceogH?^~:<]p15JQIO0-,5MD6NdE<8b4IPk|9,^of>cQalo'y~CX-V#MdIQ.#}C\pwD>b ?W# D] xF^",|;5/73B3A0[,{0^@dWXbzhq_gpf-#mkDCu`LuB~4FOP}V8O8!`=a~TevtH0# j/3Qpw]{_T]8=d O .$    [   9 I.i50Sjsc3QIcv/^S ml54Dv1&fNprula Z,BbLVz[gtR_ZH&}<t5(7E5MIUb|uYXVK^=h8&N v,'5_6l0A,*/<$hNi$1y*;&On$ qa2a5a4feido.urhmkMumyd7^CR D;ma~jTk+SVuBK))>Eg})-]y]U.Y $CZ_~Uwe*Mt0;W}LN @r$'7 k@ROmLXzcGn! e2/O?KH `KKH b?K3,,h*{'('')*t1;:hB1N,UQ L:H\C+8z+o$7kUq';8Q>fwyI|jr_'eXpMA[,7]|n>" /@7x>F@<;^0;" V.MH\lHrg/nPh1^ZViT_QCBf/UF 4PmpK&'_ V3wTaPpZ]Ie{4?9g6FlVpg`RCe5 )'-> L9 Vv Tl MB ?* /"$df-9F[ UzglzYf{[8:1.:@)O^Homq~[V3J9=,L"r! -P@gXFiwV}C r0'"b2o z j]*"$,M3<:DeJ+NSVvXfY\ZYXiSLFP9R(uqF $H"nDemoKT;%z1 @T M S S Q G~ ;=k *,S:@vUkz_/8MZuqF0$Ngz~\2?1&nz_&,Vg#T?*}^8f{Q;1&V$Sk)>2.:]@H1QvZ_@gq(qsvxvoghv-aLYSM[C:a.k# :Jpg:C )/S!vM s#6ML^^Enj|>,ga~~ywpkSFsew+UF59d*S)5 *+D;&JWAzajxsmb]xg9'{b|ayd+slYRjyY0fD;/N%)B\GrF1 ,bSt`dHQ/-N'a:(JW0idornqwi-}`ZVTrW[{ctmC;hz]/OoBH4S(XwR Od1BGP\_qm\yjl)`:X~S6QORAX`Al.zgL/gaKx.0x2YD*QT[0bh{kxtlwmykysh|d^ WXMmB75?&g8  Zqv&0M"1VC-S=aouGMyg[rS`)N=OATf2`tnfU]C 1Vk-4Jetk9Qr_{PEg>Y:;|@GF]NX?binO|yS-aSGu.FO"%i6(ES^bnv4=zgXY+J>@3L* $V!;#K'q/*:zHZ`6undS`@0.b Qe1_='G}L#N2L'Fz=$2i'^w%. uRL ,#%-DuR m,D:G Sb_hku_2~ +/:B39y_k[LG2rY0&I:`M7]my yj]3 RKiGFHJLqUaO/voh2IYJG:X+l  n!'(n5?UH'PbW3\fadgi"h fdb\oW:OeE9#+O Ob: "r/@;GDpL(~PIS,R7MnF^;C-Q q=L&2DS)alRrVuqv}v{tipwBkPe#`X^PF2;>,`=K $H1 ;>CkJ)LdM cJ$FQ>3(y" f! F0=aKbW_^bd2c)_WhMx@ ~1!N {]&}'09G?BdEHECDU@x<"5m.4%74 k$ i<)U3 ?KzXdMq|{r%A{U{ynyl{W}(DwzkT[ Ju8A$4v?"Q1?oM8Ycck~pxjrsuJLsuJLqxemk~cYWNCD6V)U^< N_7#2r'X+v+~+w)f%DK7B j3 o#9&l'd%s!"H^=)1E,: tFvRv]CfKlrw0 zT5|^@zM,u&ohq`hV I";.UU2}*/7+B$L VN],divm}M]oz,6rxsw svtusvpy )lRef^UMJ*<b, o"={6cJC[Vhr,w`;xoKv^7r2j_/R E8z+#[HV^? - lP \m3#Y-4[;;@iDF-G>G9D@o9116'~C P)3:;NBnG+JLM|LIDD?7n/d':p(  B~(086:L>vA C D1E>E<E5C)A >{:V6+1M*"_.!)x$F,3,7d9C:6:78O5u0H*#Ot z (#t.:7D>ERJ N5PNPPOCL)HCC<}2u)Ax9CJ(2#9/?[CFGzG~EC%>n7?02( g"9 }JT#&&%"yF| 7 o fLU c#:&'('& %&#> ]N mU?I?!y)1'7o;?BAbC{D EDD CBr?M:4[,V$&V bH$1n=MGFPXW^;cddb_)Y=}RJ m@7,P H nW 0 y)31L8>TABB?j;860F)8!$QXe wb E()&9 X> l"'l}3bt#(8,$w0S3x56640^,!s%r- -4 fOt99#&(`*>,|.a0C2#4 56w6q6u42 /S*>$T L2 ~E!,@7*?;G)MQ*TKUVTNQ*MG-@K7G/m&7r r |i yV! $'W(?)<'Q%r"2N @& t)Rp #$&P( k(%q'!k& P"V ', =#h1$|'C)w)k)p(/%Z# IZ XA]i ,To"(&e)J+k,--+v)V&)q",T )/ XBpE #v%H'-'w%&3$V" S} m[JS 8_#Y*t/3f668V9a8U652d.*|$%iUx  z5!&i+.v1D2-2(0F-)"%v(?  #n{ 7Y( 41(vGm4 [f!$}m VB >' 5 7 D/ YEue"&T] 38\]z Lq EL`OmA!?&"+t.&1X252,251K.z+X(#S v"m !$ ?a!D',r1r57K9e9k8Y513.J)?$P+J ?FiZ ~VP$71: _4d YIDK .)G2Q2Q'D3  xFMl^ 5! eh@C) >sN `P!$ f5k!%8)?,t-. . -+y(@$: 0cB` JQSD c*bf*G55G j.f1g O@(+ D` 9E< a 6 7;UF  _c@U0O*V3fDa ?/qeLR~ # 2KYuyd <V 25?/ vWF$"@wY h\-0 Rl+VY|g@l6 ,Bdl) fHyIE 'T#\( m VJ25  Tl(OY|d5\ q %9DIypI=  !C6i_DIkr   fm/2_V t[( pHP&;7 Ig?m qY 2&jp .@ 5VB/^7g6e%S1@a -?C^V V?   B* |g<1y58afkpQU9<}wd\LC8/'    )@7c[=@+ gx   )CWs}l ?Z    z'4ou B:z 9% hiL\>aDtX ~ ]L yORds   & .E 7N 7N 6  JWcg|B:tvgo_o`|n~ :2mh[^(2KVgs~u[f:E IK HBviXJB4=.>0TFoca]CEDNmxvPZ'cfwt,&dX/" /#dY61*+}W_&5BHUSaP^ES1>~CJ FG gd<8 "KGtq23acY[))}{FCMKUV/3v|%,NUnvmuNU"(\`FG(&=8%!`\42~ MM+-0224/1)+bb<=poZYJI@?6554=<FEUUjj--bb##MMooyyUU&&ww11dd))nnPQ882322?@PQqr PP//zz  FEzx~TR"!qp76kkCD!"   79WX{|! JJvu96PMc`tq{w{{xvrkgXT=9YXZ\#&v{qv{7:vx0/jh$ >9NITOQK=8%  \[''WZ&*HKca2,lexZS+$wuBAbf&+mvclZdS]akq{~ 8=jm<;qn )!4,2*|WUY]tx 4HWc21KPY^YN@6cX~wvH=@>MS(/gulz  z 07qw0.niuqGEMK[W0. OVqbi+)niMJGE20PLB4     *}rm^Z.,NJje^Z31 ;8xvqJKIHhMm<  zt jX  m r`kf,WR {uo0m")k!AE*Q1$7P4)3^6/e(<d?+ C C B  F/ NP dw {%YUy@=D% ' )&RuwF=ދ ݙ[Z(m p'Km$ d&C8 f^ f # w.GV( #!q1/ 416 3x74+4714)&L%,#%#7E)&<)x'E*l(?*')'$E!X4NjIw XXuX:HBG.0a vT*ZݠK/dˮICȗW6nͅdҤGԽ +`N+y~:@YC;?($*gj 2? \FZ,I,a &M,P2>}KeX hb [ 3 N; *&??K3Tp`vREj]@`qeAph}{}pC*[E3' {8/]=mHN7X : e\ ^~ Kn9e"T+ ?o0 ),/t0b1O&!3"V. sa 55 wne]\T2SRPPF<)@*8H-j{ z h  O.& ,mJrdy}/3gS{Gw!@c5di ^$8qD"LaL_SY^fts[4GP,N ph$P<fXw } k].Lp=q.Y'Kf# ?+0r+(j.6H>|LY*i{A o \PK}?j/]"+# 9 (H5">5IBWE`Ul9v-$u4@k\zb`n`q]6n`p[DSUKDm>z@'FKTd9uwypuy|rhxllo4|y_[q]SDy24!3eN:^)umj ~ |r ^ o  `! J92L(>z`Q]  C7  !1-zG$^ x t x 2 ^7FV&. N^0EOYdtvo !jf]f^jmx|l[=ZPDq8x+$"5n& %"5!\,68yGN-O9LGEsB:?EjJJJ?GDNA"=G8]6/:w 9o9i7<684.8.-))%'&#l!GE%1, =P?k$A4 ?EGK[PNVWtVSV\g(]F ZLVTTN>G"G*Cz>q8u/1!L4rQTn 9 r V ` Q  HZ ? = > C7 Lv \1 p W q<V#9 4NO^it}g$FVAHY7I, 8> ) ~"55aOq bf6F+'J=9`9 ;j C M @ 09O8/f=:CF*IQD;U0Y& &~:J Q5 kC  p fvg[hO>@<3b'Vwjiz8k J3g7j1e!',}0r<J'UrczwHEeP5Ww "L4)><6+Lx F&A6XOwzBE]W@k s6n^Dd% B $  3DN~fk}KQ 8<5Wq&YZA %] H ^ f | izEC'MGduz[.E4*%N!} lWW&W"E)43<:F3Uior_YG05_'X,f/ 5 9 D[ {]2H9[br}tU *o ti cr " Z L { Er ? / < X ? 1 C K x R  V9 \ u ^ O ^ S ap  e0 j ps >v z~u _mvdK?2zF8s0AC:TAcaos{ c)OUhDs7,-0$M%X*0\9WFVfjuyHKhR3Sc8fI&7 GgW!erEp~vx {lMyd}@&(pFX%?l$ Yp!98J\XBaf{isUhbixZicgybY{;M4A1j J# M)23[?SIUUy_Whq%yb56}uEk^8Q7G.;2<(T 6 K#)*'% kb0E3, 5R5v+m=aP;dvvc"N?<{,<.*j!_)*6QAjLBYfMjwu'%eU C 5Y' m]'GfLtw_Y G - 3 #   % 'i * . 1 3  2 / +t $L/   0 ( B D U YiY|diuk3mM++ p_8KXsx27U=+M 2IV>|Zi{ z_ NOM. BZW;pz-7dgAO?="- ; ~ H5}Nk9(5|H(k8Z' I 9 F + !L d     - ! ! c N & x * 1 7Y :}: 6O - ! K 7 t -w BQ X opNM(a9bRBiN4h/O?9i&X nGN'30;hkhB3" 8wSErw[)?+#%q3;9T%ft(R~zgt(Rq3kfaI`8dfku&OC=YOULE>&${dx5Zs/mhcK]dZFUJ=>:.R m[ *28G]RyZ_%_ dju{oOBvrtuut yp;2kqbYr5K%9&e=}&&V/9k?4BTEmGH HHIIK*K$J M8L,IEe>"4')i12*.R!W,,7=C;M+POGm :,- lDL? n6%=*m1:bETQS`<qJ]dFsiv'br]^`xc]g0lJp v}oL| o|dR[SRK}FM=w701(TA zd '$?1 >K_VbF}lquVZf~^[zYeYj[{\`i| rukmznie) aU-\gTG48M&3<_UF%t1AZupX>; " 1 M e F uo  C A ( | sO h R ZR I U 3  5 OWixZq:2 n-EPDxnW7Ze tJa. tR%J1.:EBQg]h{L_sr}jbtOZT!MqG[@=0:O7v1/+ i$^6\ VE,R>OQri$~gQ{<A ' ' > { Pe  ^ j f2 hC e [ K OH > o * # 9 LQkh}W31p t]1Y.TIZckf|i~HR-Tjj  W:ku!&*=aUnvaLG5 2qOEhyOuL@r_x~wxb]L<2|cr =h]LRQG}<61`-D)s))v++6-E0V1`0Z/P,;*z.)k"(e)l")r'+5.C-A,7*x,&Q  pY>2 _&Y3;@;JsQ~5URVCVH TZQ}5NUH>N46)8 qZ)gA8UNf{rz,Pd}#hJ~l{{~fH1jr_4Hk-5p z%m>]U`"xkoc^K2@:\7D9S?L@_un[T[.F-Dk qZ1KU;vt^dN|D9? i:T8a:L> iE1MX d$t8ScCB0""+X*C=rP__ wlyiv`\]a}gxpqMN{h_Wk/NtD,<1i'd*L4`-[C%y/z80?bFKNOxN|MKGC@?i9.2j(Z 9e eO 5ULpgN9MnYSF67k*! &Z M- v0 . v&L A&3KxCRb`wWy]G?7DSfqoOm.hhuZ,gq7 u- `L l4W{hK2q lT%*>Vp{:GuLg^rX/UDTLTKY'_gfpk|; wt/=xqsh.u^:ULNH7H/JAPsX^4d [k?oaszrulCdR\)SF!7H%!i$V*Z> SzgbyBYM9b%m9&X~'AJ`{8S_K&A{  );NTsl{nST<&it61 J W( Y: R F b6& #!/ )A\Th{} *0n;yNL/cmSy%9SJ+VyaH(xhslrmuj}d0[XO@$,VTt*7;PJ%XfDrQ|{sUhO[M7=Z,JvZq)\,  #A?m  Q=h'3t?_N7]k PtPyt{{xisCymXg8a[TcM*E{<L1d $y/$}-a(}1,7v9d:]8l4-X&!<6g dL$=KXwuGy ob`vT]IBI=3m:H}<=u?]F,P|]mhn JN+,;<-Aq.SEu[{koT?m@1I$uPDP$u0H?SBphr_eJM2,Db-;CbWaj+zT~ ^Fh}~~}^I5 {WsRj%aYkS<NIzFjD]D^EdFiJ~Q2\}j!yAm>*fHrQpPX=#|Szp9cS5<0!Q~*ACXnmRa:$);pqb<M'AD Pdt\C=) l?<h{dQ3@ 3Fd,&&-!. ,"'%!*0Wo8:CP4]?l@z3}BimZ[G^4X"{[ g?r&_I:30 .!(!%"###$$&''()*+,u,y+(&%%%% &'& #7TCu| oQ!3MFOY@mLkBxvk&a~[.WIUVS\TWWE\#cplxj{o U";}" :+ O,|;Jx&Xwe|in9>^v YzXz\xcs#mkUS{aUF?46!+ H5)E@^WsHd(U'}xA{N{Mg4[qt`]KW9g)|W@  _9pYLI P[:p` 5'_".:iC0LXRuVWU~OcF<={13$i =, `Y"-)/m3L594A1]-( ".'cdH"5FF8Y'l 2v'J~Y!}[#Hw~~,iTp9]|Y&7*GNU-yailil\u2Q~JFDFIP~Zw+eoT^seZMu-?0nIk, =oJCW x`Cjrx|~()#|xrjcS\'VpR^MII7F)C @};^46+"jcF D0 qK(fUH C!>"8"9"6$,(/|4K;@CqFdG^IRKGNu2Q^!U= Y ~\|^}]}][V8Nu2Fc< 0r"7e_1!MAie#&nT^8O B$Y6+-1(2'-+$1;Z~F:TeyZdBE( G2 [-:"FiQj-Y(y_tctcwa~\TSKQB88-$3)iy 3$jKp"(,,/ ..+(&" wohgn ~$*29CBxL.X]c2ouz 6@sk |.wUsqp,p/q)tf{4:AldWYAl*cEY$43j@LWVGy^ qdnfnfrcx_X=NMB5V&-TmL*>\P7ayom|&?BZUg^d\HG)ud*TBE~ 70*uwSQ8+  % 3$ I2\@tP^gp y%*/483<I>R@]>R;C5 -'"pbXmKV; 8& - H2rOm(3<DGP)YJbmj?rewvtpnSe"w[RL=H+xR S0>LjXR*pcclZrTvgSvbXs}`mle|Z>Nb@2{#Gyd7'Q)k:*)qu9l{f3bI_X_VbEh%rR}t.,ukdgR(?O.c 2"J`"J$@&7'3%;%?#H X'lFf  . D.P6S8V;U:Y<U: E/*  |L'm X!R#K!Sa:zj  <)e0Fl]Hu`dK~n^`<Uv LdE@?>@E L`Us_@kuyg }aOD[( 7% !i/=,KVpFt_0kdfgbjakdiies_.~WkCNsC:J.$I=}%iG"t/;-E]MTY.Z4\9]=_B_D_C_C^?^>^>]<\6Y.UNIqEZ@D;,98:"<,?;BLE^JuO SV!Z.^:bGfUialjm!lkfi ^fTbF^8W"OCR5!n A+,r'?/8Z?%GMQlT_UZUXRgLC 7g*23x  Y4NfN|e*~Q{{{|~b`<|fhRR ;"#p 2!a! ],$6n=7CFEB>88c1'?'py kF(9IfWeEp g|]~ct3skT^-RDL6(pJ 0 ymmy  0dB!g,8 C@MsT[ c7iJm VnYnZgDa1[NwA70] 0 r f,06x=ACFGD@.=>7o0&K/x Y6Qf=cW-X~iwutuw~jHAww0m_#Or:$oI0 d%r$S1/ ;R@/GKS{OSzLIF?59e3,2#]Jk !f,wgCY9 IV=WD=<D;EB#jZw'd)zR~S;&QjaE=4 2J&L;W't6C6(xJWQyb%e,e-u[qY <WVk3A*69K+bjj8at1BzSo)/Vvx~AKN9b"gFD$H"[z-9JLXB,W[7 z: 8 :KGW NMYQ "cM%(Oz( ,p)8 ) <(3yJ'+%bH!kSWai( s  =  , I<g? 0@{U[fq$vzQ;f)u+}3h NQ7ܪ(J/BTCSI;M(I1Z@/NlQ}k{\~n6iA-_eNG$`t& 7 )  B[B[  W  xpgh;&PRPuKpPH < aG 2fhms kKp TQxu > C gRK3ku6D/[fb o#trB-XC@Y 24MIdlFl2qyFۨ8݀=߮zP _%to(OYUj/CK<7@!*bE Mz #ss P ^ _IcXz 9 1u"9{#$Y%z%6 k&q n&e r%P%$#+A"G1 Eq | ' ]RA7 Qh 8! sa2D7GMa0.\/.޽RݿeF'&m"isّ2<&8ٝ!@[' ݞޟx9^~dXUd-S(;Nbu!('53}Cc(g   \H iy# %'- r+K .j T1x fj3X +4K  5  5 5 ?i4J 2 |0& 6-k * )p'#LT nN0 B` "t${$:xkB,"Z'_-&bqU%Wx(ۣ{b %]))&Ҁ4:~h Vem.pDUQ+FItw޾Y"EP Hde01;C%UR#\4Ho=gta F~H> W Qc?ABo!0"#r!$d$ y$ `$$#v"u#"1!>4vbOF)!O  Yb  -4 XK8 /UJqbfX5=(m&^=C0J?Au } 9r{ 1A8, pZl`,T2K$itG o= f> ;k (E mj bcc.c.o-TU@I|!]*Rs T  _ g_pi x< bg MF Q, y/<D.l9lE,8]wH6y",S0^ Zj}/xYx6raWO2{^ !Ks Jx9PL pve,x>piaR R H jl ^i E &$   /= Z` z`    u UL  . ef ! 6  * 2 ~ s%Szs Q9-Qdk_TCTt;rTP>qHsJ{# 'ZhzE3~.u6O"69g Xm Tm~[E0z*Iq} M x= ( y GP#-na??) =|!!"!P!w u )8GUtnMc78\   WT fm G\4 lB k^iZX=ojR'wFCre!t>HHg 5VzNaa39ޅ)(fe2#{M޸}$Vcp(+0]S+c9t^hl;P-*ay ?Godt z" ,) 1> u }B (+ $FC !A "> x# Z$G I% I% V$P u#= "&!/@7 sU K cux v  m\]/N:=L%""X;S;Viw/X2!%^\$<'sETB_0{ަ:EYVll Wiouq@Pb |u.O Zx*>n/d_7H1q o7 y /c %k [j  % ; Fd=[ E  +0lJ3x%@C >k=  | %  M` : 2+ z vFmwpS+VkB;o#%5d8|m3`m&~&ux)j {:H&=u`e<2, Uc%}7+fEc6 vS cY  c _ Fo 7" 30 ; P/ ofS :_~ 6@/ [ w1BDH[IpRX * Y:@{zn6G*EZ=5 9/Ofn/)4Yo`; ;+'!hBbdnRS2f.;mn1$-kl D ;T  !8 u C `dJ Z  ._ )9M ][r  Y^u J hND*qAa#|E}a: Jg^{ rLnf,3.\QW!I1F0mnd27zmq"%.uao5SFd< *O& mD 3_D S[ D[ >C " rF $nf e  Iu /"  1 `c 7Ym :\y..h?kZ2%[xR Lt%hp-/<ZErJtAa3ISJ+ j%|_V@'hyScL. iZ`Y TJKH~O } =x 8z qr .' &W sp][ N2 6# $7_ l| p} "Ef /9 Cu ` R |  F< U8 ]OH RJ0] C_ *:p5yO,eU()>)K%?L%9{&#VAis &_&<GDz 5A8   d4w]x L< B[ ?0f >4g @+b CV HE M3 U `aqaeN  ~ g% NYK >'7ci;v^0YNjRAk%'Ex`oWMx-q|mmv@:\o)\kE.GZ/^}W7m\DXk*M KMZ\  bV a tB= } V[  : 'w H  U NJ . S u" A.H u +[1[&`H8e%CfcPeGPs+/b*) A7rKSq4JF="BsgDe4RP 0-vUYGzhQ t "$Qh [3  L9     M  k 7b0/ ?C X; {n 0 jN u8(b }1W?|1?g*i4# KC;gctq.wbzl\ H}>l<kF|Zt_j] .j`~<gjR~e~EW jYeBk$C^|O P> A , :`B5%   ?5 ^ m X )(  }v 1@n\x #  '_ JD`qs<}w:d9G+EcCC3aE3]s_^`/.!)* -/X]c& WuK2^I5yB<q~|o;B~jAe CG}j \<  1 8 sHxyfXY8RcQiUK` jn=Mgc0h   &`< f ahZ) 1`uIVpK3S[6%HkWz4-("L^k@N.1xs To*EM%0@^w6A`$VX?Z v p -  S {Xg6\W[Y cBs XqN    p8 d g{["GSP-*}W ;Ax5O\w-U]msZU2X+B bbpk%00RPGcfx7RWt>O[As4.qR*EojM<&g~.eWP-&nDtJ9m-P 60., }' 9i^M=,5a w=I[ )Y{De-2i>y?9'pFPcVCg/qL7le8kjHd]YGJ.] ;6ED`/nkGG9a)*$LuuR 7] eFo P5 1 - 2 u> fYLF :6^  s[ `Z6!P7i:aqDi1iPYp h '\$*p=6gQM^:h-nb)pV%rJ8hK_eQ{FT8I+pP,o0ai0x0QY<KJCgr[K~ 6clHQ-) M-Gz&r&j aM: &1 x  u D> sW b> a; R 7 fF + * iJI *q\ Q-\|Az? { B!5auA:Ov<Nn+{rHP&A`+k~(mr-jG\rGS)-x=\ y(|Bw<nK^fM2[k5?a/q[ d1i cP 9 N* {H     { b #+ 1 c? :aR <e y$ G ~jAU Z;IM[ |N%FgX:TRVp_g-T q? B8e`L=(<^oo v@ z-oo2cVQ"{>S*  5$DzT0{$LY1n Mp ~ mL4 =%O|N;w( dU 5 EJ      c - ZHE ?g BCB{FW!y'B_<P-hbX(#U hQ!DOn X;8ubHa;&j\Q!8_!>!a/Qtyc*]z/loH`XXhPpM,uJ<uJ:rK1 lObTWYM^Db5i&qSy!jNt2.svTk6g)cb^Z~SuKj=U(t8=#25giJK,<_sW}rG ks[FT2X?io}R}7awBwBI,d y!pO$u lX0w e^a P EE @e BU K$ mZ ?pF w+va#$uhSiAY:\0dzXHgq/h?vPJ\V?R,?;T9=S&Qj>aGfpy~0 fMGun%=&q J/pWSd3s?X1?]gIZ)o3 !"8.:cmCtJ^KJ7RP)(MR#KS QP''VN4-_JM8kDnGy>X7k1}* "5`   E 5AWj{I.'yhUdO< )>Ql 7IT9U;K <!Kh.1G<YdMv; V'uv?X"e 4;LIf.G<5D,H|.G;BO8l+'L26r"tuSIS%/ :ExQ .xizU6Qf>v&aTxt\Q'|M %C=wSPiu"~Y'/x=`rJW4>$]*6   e$..9ibGbGCU!!duRu)HeCs')>fj&lg(`v>_oP$y:+1xcO (49OB @xlzm8 dl 2` <bw 6hS &}s [1Mx54A ECRXh|*Wd ";jB{S%a~vt9J):$0>s_""Jwqi,^ULE<}:j(y04;Q\p a-;KOT\ VWUZ et%1>%L1zX=u-fGpIqPm]{Xir_fbeddcdaeYhuOmZxAs7i-|R7w~BMVA #5{:kGQ&1f|*WfoAW%D;{8u? Mg8<[~fVJOC-(g-t(Bt4eYBU->H/ jGX]YM4 oPO($;~P_hz%BQr!}6;u-k@]CR 0 a mvL}'y;qlg1ZRKIBx;l((e,SZ@H0(B2|M6z Q6z P0}K(A5&S!X}:t$~r{r{v)?Z.h !#98z OGs4`RoNk[kdt\kfuUnTmIs9b5|K.j }bi$[' 'dB{Z%^wG|7bN1)j  $*m O4z4ayA$^j@a"9CGh!1ciqwO 6 !)8Yb<gwRq$M@, DK\CwSKIM  )l?e?R#6c ri}'}'t[j_4V ,JLAC`DvYv:p3+$,Kfqx}{ywyz *+i,JLorvS"+Tq@^]I*6 $3aBTcm}"HQP6y _m=xJ! 5Nng|u+10}OQQn=l*Z&uWxFqHA$y+0' OJ\|ov6T4y8xa JKU  @{CtfvTE@;|3-+-18BTN [ijhiDyETJkkSS;;-m-$X$!P!#U#'_'1w1==JJ[[kk}-}Y|}Qut^]AA M ]+ x8yXEmZ>O0A(Y+a f ijhc \$;Q'>[Du^3!> /JH64KxEthxlYMDF=m98;{A\G5R^enR[<3 ur><%&w8zKJQP%Y,g rgz9{6rhf $Y,IJOPr:w'0q.b^[2 5)Uv'Hll]]dq_|\[\`zzg|jgnQXPxA4'=BfbC@"O qP";#5"9 F`{   H0p-;8FBPKVRSNNJD@3w0 L  N* {5b@kNG_Y9P.E+V 7 Z-\* Z.%X25Q%AJIUUc?l2"7?;wo5-joMRXDH0/! Rk+oE[iomeSc 8?<-A\WhpGO' gPJi{5;N7Jfy<zw[-zBd4X+O/S:^NphNuWc6P,Fi<u' 4x0_X<} 6M`^VD&qS>~)u|q|q't8{Ux B}-D&Uy6fs9EtmXQxlaU|kiXylbUsnTOfs6CUz3=E|pIB/ ";w6GIXZZ1ixGvzmgldqp}{3k \/DJSXu6"< 1MD1w*"N~*unSLbiVLF=BW?j@cBWH1P[whzZ`v"8;f-uSJ#PN) }3o8^?q]@p\@od<wr72* !Ax A92XMu g7xe~[6xte]QE<,d&/mZLJw  /i(MCx gO4$7 Jgv'6pr<>m^Knt7<tqCAku/9_y.F$NMdV*_$%(q6QCwj@JR\&TE_+gfffd`*?JP[Z?rp7"67C9.s,~S vqC>abZrNJ+F>K"MZrc^lcJbz+qo^\Myv x3m8/O0P&SHRB|mGFfe YZ "Z|Q <C5R0Lf#V{|m(u*wy% 2a#)  $ v ~e #p \# i2{s^8b :cde\fBGbgd5hbEGagK`oR)lz"3qpYYT?B1ct ~r >#Tv;0hS5 1k!-tHv9zL]0s? 9>qNB~r)DwLO~.3Z#ALImA e nJ=AW ?<cP<&o)VQxuV&S 3Y M CZ7;w Jx` y1 GI  _\ xjuL 6+ uT 3;(7Fvk y ?V  .=eLblf>sInD~5:\P@5$t5/`4=G`j+<iQ d 3-l$-+ci=u()llOe0Z9:w)>m0Cqy$rQ$h8XXWZ[Rg9|6zoN l3ZI{$W =ga:d5lL{~YUQ5c>BV\h ?O rG3 v# z"& U/ K/ yw) 9;i.}  `&y, *[Uj!q+ "C M ^m{%*KY'IIk~~C; ,pZ *B8  D J\ /B>Z Z U O jR/m+.De{=^B_<3Ny3g*bxpxqyP(Y h,VoL< Z}WSt.mNNls *Q aE< s- $' "6 & b4 !IG oi /HM5C?h/T&bV9G0_?/ePh/H=n3eA(aQoGk[^<7i!TT:]EzY:fTY?lL][N.hfP7aD`ELe>I7oa( o/(0M[ (/k;} :u ( UE ,   $@X Fsa t]$ i .uO u R R;[b1,x3 J?mV[{v(Ollp3o~TWk u6!dCOmP4o"QG=r.lTj| u@qX"p]%rP w0yX0qxcBA?! ^,^T(cod'@> -aa3JNe xz8 {: x6% X PQN~-bUOHBsFTuEU_LYG4[+qz iZf 6  =c;+Fky03u|!'nZ8(W ;-BUMMCH2/pJ>\"RnMw~ _48l#tPNCxnd\,0,mMg ,& n34LD\g)~PkL/%0Bn`Gy 8 U0x7{uNbj~4 M+ke1>w&quCnoqRDxkrCI/URaeW-E&=;3VH{(%Wt,YT]vvwA"! W8YfRWX%,U|xJxtW )R$M5h IK9tWpxoZ2?6%9[<PPe^M-(MS&c[9?ATD*30/!-{icQG)yG_J^NSZ;!V)v MZ "6 g* {Q  6  14 ?9 A 1  * A Pr W<"s$WArvt =#( L/pqH)*^k}[th|p1u5$[S&Cel-J|uBu+D:M'obG{x!3XvT#4rqJiiuh>*Lb ? / q4R: 3{ ^,n= Y2[XE /t,/"C+m\~7l*`tRuP0n]g]{F &,D=dIq*2\IS6?):L'`. m2X+ ya8 !k7q^NXH e$Mc{l;!5'@r^TZ|LrPJ66>VrHGg}CI6zy( }Ca!:ip q-1=3>~17/~]OPBq|zVdi85 Ss)G6 AhJ0yOpPmL"uFH4;b.<F%ItlBIs*#n*pu=!-ERhtDMiDw'PR:+3<. a r7";" Is x6<TP(Fm [Fap0\F/D=Ts}bx\\j@NFpE@qBKn$GbiKQ`_UrG)9b(b3U6PU qP2>{sx wz~SgX&{k[83UHI. >cLXm}a{_fgTU^iGR_hHRrciZ\gRxD:/4z" !6u0PTteQ }k,vDpPmNmAq*v_iX(z|^@Q!  *C/>bIUt\hy`y`pbk]wau`^eXnO}C<&6d&sS"*Is~w?s]IH076NNs'fw/KI)vm)b+99HN%vf `#uD0OB.Iv`wbu>q" sSEHToH-/qFrasaO~;rZ(uDp8XTmL zl ~sR)$sbIOB4n,`%_%h)2=2JpYj9{bC8a%|4y:w9w5x*|~t]E)wcF g=Lk) o70KYeP\~4 8zXn3f%r:h7z 0Kh:WL40~/MD4vhhS$A4d3M(_ kpq k(]h2M/@'8N `J[rE[2 f,Gk($:;d%O*H+I+V(l#7u *DS|/;'EHM[QfTcSRO6I >4a%:^z},G+6ACJR|Zpg_pgZbZcYbYbc`ifw[nUxMB56O)H ' d%!C_JzZUvi__VP L K!MQW aOmCz0Y(\:8Z;00A?N}Yr]`cgGeA_Ag7]HeB_Za_fyZqQE9tJ*H $ \";hRfLxtP~_`/?sdoS@v+<_  dp!H*,046638-X&3y ?cr);JJYe8oOx^)}h9h:i:c1~\&{RxDs2m!g _WYM3E:1m&Mz3P"  "Bhw*@~U2lqENCcxys}szsjwK~#[ rE(gVL/1LA4-?EN^\qo'i\sL{?u8d4i5}l [YK=1x)b!TKuGoLvS_ j$w(-13689;;; :741,w(` Ku6T( Qe"7+6~@5HPlWyQ\epC`Ol=aFj@`KkM]^ogXxPG:Z.-u)g"!>Za!u4fi|rk5e`a|``bwf[l1sp{B{ &eHlU=x'9X ds'R%7*'.010'.9*K&e!<d * 0IDhUg!t%)-..-,~(s%g!]PzAd8V-E'< / '# !$%0 (> 5PFj]z'2,>nLYBfk)seQ{Vl![gJ8q$/G}9M%.6t<I?0BA$>7:T 35*e l&R|-4?P2_\l}Bwi~jNyl'qOh*\ RUF:+Z(<  p{7]! D&9(1*0*;(L$iE6Ri .#;`G R)Z=`LeRgQgOfCb4^&YUOnJ\F@@,<83/+(r#eVHm;Y.E&8 + , / '; 4OCd[u#-8CAmIQ"W0[;^;^5\(XSwJNB9/v#Nu'; g{9]! D%3)#,./.-+*>&U"q*Y-C _+1<L:\o#lnz 3FD`OpQsLk=T)7z|=pZe3Y MXC$9/&cKo3L.  - -C 5O?]EfHkGiHkDe@^6P,A !1    -C T|$ 4[BPT`Bo| 4>Mcblmh\x@O'*Jo`bPO@1o?\v{=P!8&, /2/6a /+<%T!u4f -B p:Uz"+, 6T@@<;;.9+4:%,0<X4ZXi|@ f|aYebI<)X 4LDKx!O16]6i1H 7: qd{FI"zFapDEmJr~[+5*= :UZNFthWat08%lc,,VZiN 0_:Zfl`YP]O3)mOG{voo8$o H H #P&E+UMxRQ[5iCNNbhvC 8 $Hsc(-[@9!"<[f']+ S/#3S %686 (9a ); 9*g< *S_= S+6R>5 ;*i< ').; (: '8: =%Q5 $3P !08g, .)aXH%f$#g XW 4A 5ii3 _.6&[sNvv~gZK5X1z\یc;N5hyp{rN ۍˠ[ګ'vE!b={Ŧihԥ-Ք+Փ-vWeo)a=ub2uf osRčJę+0ž'#80)BٳrSrGF _ !WdHsMJsgO(t^8+WEmg.<LDZTt 3W Bf_ z$m~ {( ,!!0V# 33%4+ &g5] &K6 '8 (9 m):. )|;L );L );: A): ({9 '}8 &U6t %A52 i$3"}1!$ 0MA!D/ f .~I- +*TGq)!)/'F&`&*%[-$5"! 5^#<RBI.t$ & FAp Dic@6WI~Hh2pYhQ$l+v Lf3ԫީm }UqفHؽȃ2B r %*9:@MkoqqCضȏؤȩPوB^ 55HG[͎yzޒ,EԘ ڝAwn$T0Y ~i1}_{bm=WK/  |  -P Pd St "l10[8#~ h!h%"""#,1#h##Y$i$%Bxs`&[^'yF'.,(P'()* ({+.U,RE -n [-{ ,be+7x|*)gS'P%$"E3JE @&\c s1 O ju 5vc =%K2*YEo 0Y@[Cd%v V9`2o;,ޞot"DJ^ _euafi]gQ\{ا1؉HU%;AezS> ӽV:Ҝdщ6t6sdlwXҚdAҺc@BGث7+~|`Uv-|5Q)B)eCG01~qbW > 4|"#C2toBV *nw$^(ewg+!.u3$<1&k4Y'6v)h&9 *9:W l+; ,< ,|= ,= ,= ,= 7,< + < *B:I i)r8 F(k7('5% 3/$K1"/ ,?i)%"aaB1J .4D )0\=8 <@ikdUt6M=VnKe@YM419lݩ w 03C3U(~[mװYօm:ZhIDB i<5AMG{Rkm8և-?ױ(w S0H G{vݪl pTv%1Uo'A1p`|W/dcyapi,_!F l : V k( NO 9l D ipc2hQhjX-5 dmDO "Mv$ &_'}[( p(1[)N(GK*i4*? + ,2!-1!-:!-!, i,Z +,_+;JS*bdj)>q('$""=b 4v*}9q@X5:G )~ u h b1H\:/-i~U;4lHhE(Z6XlSA m7IHB"ܻ1Wyhԍ+Ypyڒd}\ټEAi [6W4qX9 ,0ٯ6XkڍcͅR|cfAe\iոqt)@1HRۙq_!`J8|I 2 -x;hCtNg;'O1^zL:  R  wa 2l)Mlv]gE'2M/o Dn"4 q$JG%M>'()C)"~)gu*xs*r**x)('zk'l& |%g:$8&<"8_!_~ ~Q\ ,!F` jt1JQ+ c 5e [U1 {1  a g%$BeVy$*rxa*E *s-e%Np@g]83 uHy%2} eQ5A\n0W7(j6ڬI ٠= ٟZ ٥kکyڹ)nK?x-f6VuDM-tJK7f;g-XDv?pQBR$?/Ld9"e} )Y  }h /Pgv[9~' t6 !OG# %2&R2')*c * u+,!o+N!k+ !v+ + K*sv)('qa&O%'r>($!lk" r,,{4uZJ IB   5 ACI}bSCx/R+VH {KNeBw="N3Mt(uMVQD >;ME3USi|_GEVsir*dXROe[eo&uwqn+ruuxviUoV C )-|Ofxe+u܉RGC 4ZՒ\^z9<܌"ۘKۢtۣۡۙ1܎}A5YbOXn?ԆXzEt܍4B6!`Y18nY($]o2ujb36 B' yX L }  n- K1.W_vFw u ;"H{<eXQq)An2 %sq Ee '9 /C -   h^0d!6N#s lL"/'v8G7 d T 2 # [ $lW'V"OTV~}Fat&uyG#kGIjywNO :/."+f(8BoJxL_NiMrKICF7m+:-O=u`f6b2Clo?4tl(<o] ,W@\" 4$okF.  i X % tF5WFb|{10>}y; I" $GFuK&{|R' 3')Z!+*! *c"w+"+"+H"Q+ !* !&)0 B( e3'}%Z4#!!MMhhA('/Ua  % v; .CGy||f`(}[$7] ~bH; #| >QpKVFQh;z%d}LEP ZY| jqgbi\=tV$@Q(L*I,F,G)J$%P$ [W[_g oj1"~|'ALQ] ~\C :g ds} !UQ )<hCOlMk- Ih#0Nr'9,>^o~juߊve<ZoUtVC] jxu.߇ ߛy\[;?.l78>#]Qu6 }X|2Jnx;J'5E*'T{aWE!( 5 W -A O5 n   ]tle YsN;B%]6Z3)CaS`)kstG~/g?p'N 8vck?\y L.;h'"Kgr,?5w o i $Jd !(eb.!+Pe!(6h $p j| K6O~ \N,X6Ln0[ 2 d 8B !/ Dt jeu,d u;a@#-JNe}dh BO"Qkrn zR.<,HXtShG]F\gPwpLEy:X/Z' /^ &)q;p 62j 5Z.sCbWQlV=.* 3Tvs\7Y= WW%RL%rc,`@  d 8 K]-v|iVQ8" :=# sY~p~H0Ng=MF%H~:mG Sf ;M:0Tr>qk d ;: - @G dVbgd-9 @le[0nJ8%dWuh"sH*B 98 L[avA0X,qKV4kd:= R^6:d]m@Q5 T47>ROl8*$~@i^XX GV q 8%  *  I Q   'F z1 >C I "S]re\lsCy~A~D'jS 5?Vfl~|{ul|EMZ7QrTdTN Af +_ Y W ,# ; C [t u~r,d+@*](%C%4a`kGIS(=9b6K|~`dqP>%. dr]2k` =I!q.r,=vLf]VqE1`*>\IpW~S 86^n J-5R;w{n\ > v "V  $UB*_z*~TnN`3QTK,Dp?: >P ?J QCIQH-Zd"q`Mjo4X@| 'f ^B , 2 @  V k0 {Td#N8$M -+;Z;FQ[Izfpjzdsh`1^SpJCP/tD\3x/ugcM6~: 5n1QL fC/~n}i U- A  -   "+;IPY hjtU$9o}zww-z~=m:Cr_K#4@ ! 7 f P j ELklL?+{ 1pLhH4mQj7fg93&] NvZejZF1'#9(L%^/~"q'n;`ER5D164{(%O Do?$/'9@C_NY:b%nxLA x h l  ao W5 o M  C 9lW 0 &k  !y &J*S.v01~0h.<o*4'"e{ {r _%0 /| :F E { R ^ l y wfT^B.uX CE.*: JmY?i xyhXH9&+C6J ~$  kEf(m0 P8JJ xP$F3ND`VzYgxzfU]TC134 }W/7BxVvi{ u g& [* PC G ?pg71K+ 'e%#"##%(+FH.27[O< Cqg I QS^ X ` ' hg q y G P  v0l?aX7N CR:G1'5` /4T&u&-35y;BrxIOZT)Ym_Ych(lcoxt w|zyK}DuspmnldEiB h$gffddef%hJ-jzcmps&|"wqxt{t.oFicB\+UwN#cF=4a+7g #F5 )-#@$[#s*8DQ]i t ~ V ] z$ s R k e!b _ Z62VRO7N_M|M|NoPKRrUYy ^G c i? pm w  ] [ | rgF]n RF;!0$ + K"p2'I0c9MAHNpU[={`e4i]mzo.qst|uxuvuttsqpm2LkmhebLm^Z 0U~QLh{GAjx<60,*%Q+O) j] &o 2x%(*,x/e2 5O7":c=?HAyC0EkH4JsMOTQ8T{VY[ \O ^ a cD  er 8 g _ h j j k k j i Y g]  d as ]Z YTXNVH*A:93!u+a$$ KQy 9}!7(w.u5:_@@E$/JNORU:XR[]O_``aa`_]![SJYVT28PMHq{D@p{;61(%,'hH!n> y%]d (OH$c):-c2F6M:)>AjD@GI:KjM N8 NC OC N8 N# Mx LYK,IGAEuB'@I<9&6*3f/Q,(+r$!a3NtXo  KPh^7* f L7jT)f]$!^a#"%'(Ld* ',-.i/K0+t0i1h1i0w0//.M-u,8*x(&&r$k!'1l Zs \}v xY+f!,#>&(#*e+O-.0G0q1.2A3P3X3S2K2921^02/.w,;*G(&c$P" X,)QGd ev x~!G 2 }]"|Q4W`(?# !;UvAjO$\A"mQ |  C /o^ 1(WPzuADpu /<ap  2I j )IRsv!<%N7aFpPzVVR{Kt?g2Z G,wMl: u :N  ~;Ffj"#KEr>/ tU >% jfHN0:) "0D']Ay ^  F 2z g #\PSN@B}$LTv-6EIXWfbrk{ptvusl|iyds]lWeM\CQ9F-: -s{^fHO18 kmQS67nkYVEA2. ||uwqtmslqkrktmwq|v} # :7SQlj..JKdf#&14<?FJNQTXY\\`]`[_Y]UYNRGJ>A35%(  stbcRRBB22## java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/hexengine.clazz000066400000000000000000000515641445000440600326460ustar00rootroot000000000000001>aG t8 t9:; <= t> t? t@ tAB tC DE tF tG H I J K LMN OP Q R S < tT UVW X UY tZ t[ t\ U] U^ _` _ab *c -Od tef /O tgh 2i tj O tk tlmn 8< Uo Upq r st tu tv tw x ty tz{ *|} ~ ~ *   * t t Q t t  Q Q t p p p p p p  ~ t     t t k m< p t DEFAULT_COEFFF ConstantValuecoeffORIENTATION_HORIZONTALIORIENTATION_VERTICAL cellWidthscaledCellWidth cellHeightscaledCellHeight orientationoffsetForEdgeSidescaledOffsetForEdgeSide halfOfSidescaledHalfOfSidepoints1[Lcom/igormaznitsa/jhexed/engine/misc/HexPoint2D; scaledPoints rendererLockLjava/lang/Object; modelLockrenderer8Lcom/igormaznitsa/jhexed/engine/renders/HexEngineRender; Signature=Lcom/igormaznitsa/jhexed/engine/renders/HexEngineRender;model/Lcom/igormaznitsa/jhexed/engine/HexEngineModel;2Lcom/igormaznitsa/jhexed/engine/HexEngineModel<*>; listenersLjava/util/List;DLjava/util/List;scaleXscaleY(FFI)VCodeLineNumberTableLocalVariableTablethis*Lcom/igormaznitsa/jhexed/engine/HexEngine;LocalVariableTypeTable/Lcom/igormaznitsa/jhexed/engine/HexEngine;_initMainParameters(FFFI)Vl2Lcom/igormaznitsa/jhexed/engine/HexEngineListener;i$Ljava/util/Iterator;tempchangeEnginebaseParameterschangeEngineBaseParameters getCellWidth()F getCellHeightaddHexLayerListener5(Lcom/igormaznitsa/jhexed/engine/HexEngineListener;)VlistenerremoveHexLayerListenergetHexCoefficientsetScale(FF)Vi getScaleX getScaleY setRenderer;(Lcom/igormaznitsa/jhexed/engine/renders/HexEngineRender;)Vold@(Lcom/igormaznitsa/jhexed/engine/renders/HexEngineRender;)V getRenderer:()Lcom/igormaznitsa/jhexed/engine/renders/HexEngineRender;?()Lcom/igormaznitsa/jhexed/engine/renders/HexEngineRender;setModel2(Lcom/igormaznitsa/jhexed/engine/HexEngineModel;)V5(Lcom/igormaznitsa/jhexed/engine/HexEngineModel<*>;)VgetModel1()Lcom/igormaznitsa/jhexed/engine/HexEngineModel;4()Lcom/igormaznitsa/jhexed/engine/HexEngineModel<*>; calculateX(II)Fcolumnrow pointToHex5(FF)Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;pointXpointYcalculateColumn(FF)IxoffsetyoffsetnormalXnormalY emptyHexCaseZxy calculateRow calculateYisPositionVisibled(Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;Lcom/igormaznitsa/jhexed/engine/misc/HexRect2D;Z)Zpos1Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;rect/Lcom/igormaznitsa/jhexed/engine/misc/HexRect2D; accurately5(IILcom/igormaznitsa/jhexed/engine/misc/HexRect2D;Z)ZcolcellXcellYcx0cy0cx1cy1rwrhrx0rx1ry0ry1_drawHex(Ljava/lang/Object;II)VgfxcalcxcalcyTG;(TG;II)VdrawHexgetCoveredHexesB(Lcom/igormaznitsa/jhexed/engine/misc/HexRect2D;Z)Ljava/util/List;rightxrightytlColumntlRowbrColumnbrRow elementsNumresultCLjava/util/List;u(Lcom/igormaznitsa/jhexed/engine/misc/HexRect2D;Z)Ljava/util/List;drawAreaE(Ljava/lang/Object;Lcom/igormaznitsa/jhexed/engine/misc/HexRect2D;Z)V6(TG;Lcom/igormaznitsa/jhexed/engine/misc/HexRect2D;Z)Vdraw(Ljava/lang/Object;)Vcr(TG;)VgetOrientation()I getHexPoints3()[Lcom/igormaznitsa/jhexed/engine/misc/HexPoint2D;getHexScaledPointsgetNearestNeighbourPositione(Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;I)Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;neighborPositionIndex6(III)Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;neighbourIndexgetNeighbourPositionsf(Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;I)[Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;distance([Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;I)[Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;array2[Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;7(III)[Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;calculateHexesForRectangle9(FF)Lcom/igormaznitsa/jhexed/engine/misc/WidthHeightPair;columnsrows areaWidth areaHeight5Lcom/igormaznitsa/jhexed/engine/misc/WidthHeightPair;getVisibleSize1()Lcom/igormaznitsa/jhexed/engine/misc/HexRect2D; cellsAtHorz cellsAtVertwh packColumnRow(II)I extractColumn(I)I packedValue extractRowgetPackedNeighbourPositions ([IIII)[I[IvarColvarRow resultLeni([Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;III)[Lcom/igormaznitsa/jhexed/engine/misc/HexPosition;(Ljava/lang/Object; SourceFileHexEngine.java "java/lang/IllegalArgumentException!Cell width must be greater than 2 "Cell height must be greater than 2 xv z ~v v.com/igormaznitsa/jhexed/engine/misc/HexPoint2D   v v    java/lang/Errorjava/lang/StringBuilder Unsupported orientation     java/util/ArrayList   v v    0com/igormaznitsa/jhexed/engine/HexEngineListener java/lang/Object 4com/igormaznitsa/jhexed/engine/renders/NullHexRender 5com/igormaznitsa/jhexed/engine/DefaultIntegerHexModel  java/lang/NullPointerExceptionListener is null  Too small value as scale X [  Too small value as scale Y [ v v  v vUnsupported orientation Renderer must not be null   Model must not be null /com/igormaznitsa/jhexed/engine/misc/HexPosition                 Neighbour index must be 0..5 [ 43com/igormaznitsa/jhexed/engine/misc/WidthHeightPairjava/lang/IllegalStateExceptionUnsupported orientation [-com/igormaznitsa/jhexed/engine/misc/HexRect2D  ()(com/igormaznitsa/jhexed/engine/HexEngine(Ljava/lang/String;)Vjava/lang/Mathround(F)IgetY4(Lcom/igormaznitsa/jhexed/engine/misc/HexPoint2D;F)VgetX4(FLcom/igormaznitsa/jhexed/engine/misc/HexPoint2D;)V()Vappend-(Ljava/lang/String;)Ljava/lang/StringBuilder;(I)Ljava/lang/StringBuilder;toString()Ljava/lang/String;java/util/ListisEmpty()Z(Ljava/util/Collection;)VclearaddAll(Ljava/util/Collection;)Ziterator()Ljava/util/Iterator;java/util/IteratorhasNextnext()Ljava/lang/Object;onEngineReconfigured-(Lcom/igormaznitsa/jhexed/engine/HexEngine;)V(III)Vadd(Ljava/lang/Object;)Zremove(F)Ljava/lang/StringBuilder;(C)Ljava/lang/StringBuilder;5(Lcom/igormaznitsa/jhexed/engine/misc/HexPoint2D;FF)VonScaleFactorChanged/(Lcom/igormaznitsa/jhexed/engine/HexEngine;FF)V6com/igormaznitsa/jhexed/engine/renders/HexEngineRenderdetachedFromEngineattachedToEngineonRenderChanged(Lcom/igormaznitsa/jhexed/engine/HexEngine;Lcom/igormaznitsa/jhexed/engine/renders/HexEngineRender;Lcom/igormaznitsa/jhexed/engine/renders/HexEngineRender;)V-com/igormaznitsa/jhexed/engine/HexEngineModelonModelChanged(Lcom/igormaznitsa/jhexed/engine/HexEngine;Lcom/igormaznitsa/jhexed/engine/HexEngineModel;Lcom/igormaznitsa/jhexed/engine/HexEngineModel;)V(II)VisPointInTrianglee(Lcom/igormaznitsa/jhexed/engine/misc/HexPoint2D;Lcom/igormaznitsa/jhexed/engine/misc/HexPoint2D;FF)Z getColumngetRowgetWidth getHeightgetLeftgetRightgetTop getBottomintersectsRectangled(Lcom/igormaznitsa/jhexed/engine/misc/HexPoint2D;Lcom/igormaznitsa/jhexed/engine/misc/HexRect2D;FF)Z renderHexCellC(Lcom/igormaznitsa/jhexed/engine/HexEngine;Ljava/lang/Object;FFII)V(I)VisPositionValid(II)Z getRowNumbergetColumnNumber(FFFF)V1t-uvwxvyzw{|zw}~vvvvzvvvvvv,h *#$  * ~v v z   T*Y:# Y$ Y*%* *# *$ * l** %j** n*  Y* S*  Y* *f* 2S*  Y* *S*  Y* 2* S*  Y* 2* 2S*  Y * 2S** %j** n*  Y* S*  Y* *S*  Y* 2* *fS*  Y* 2* S*  Y * 2S*  Y * 2SYY*` Y*!:*"**#*$%*&W*':()*:*+**#*$%ç :  HKKPK*(-38=Fdr!/AVr +369ESR+!MTT~vTvTxvTzMTX*,**-Y,*-Y,.*/Y01*2Y34* Y5* #* $*#$%6. + ko t+x9}DINW4XX~vXvXxvXz Xr *#$%6  4 ~v v xv z  j *#$*7  * ~v v z  A*   A*   l+ 8Y9:*+;W l+ 8Y9:*+<W&')* A*1  &@# #YY=#>]?$ #YY@$>]?*YN*##*$$** *#jA** *$jB* C6 *C Y* 2#$DS* G0**$jE**#jF$**#jE**$jF YG-ç :-*.*'N-(-)*:*#$HSn<=&?,@LCSDXE]GjHwJKLKOQRTVWY[]_`2a<b?d>$z2 !@@v@v @A*#l  A*$u  :~+ 8YI:*YN*1M*1 *1*J*+1*1*K-ç :-*.*'N-(-)*:*,+L<??C?:!+0:FRpz}4p \!~~d ~~dA*1  :~+ 8YM:*.YN*4M*4 *4*N*+4*4*O-ç :-*.*'N-(-)*:*,+P<??C?:!+0:FRpz}4p \!~~d ~~dA*4  N* @)*A*Ffj*Aj~ *EbYG -D NNzNz NdQY*#$R*#$ST vv  y# * c#*A*Ffn>$~ *Ef*Bn6*U8*V8#f8$f8*F%*C2*C2*C2W71*A*Ff"*C2*C2*C2W$*B*Ffn>#~ *Ef*An6#*E ~6*U8*V8#f8$f8  *F=*C2*C2*C2 W)*E ~ ~t`6 6YGz(5MV_eku     *Fdilo5zMzzVqv_hvebvk\vzzm dv[vUv Ov yyvyv y  $ * U*#$R>~$*Bn6 $*E 6$*Ef*Bn6$*B*Ffn>#~ *Ef*An6*U8*V8#f8$f8*F%*C2*C2*C2W71*B*Ff"*C2*C2*C2WYGb+,/(1/556A9J:P=^AaDnEGHJKMNORSTX[ >zMz/2z^znzzzqvhvbv\v  v v  N* @3*Bj~ *Eb*B*FfjYGg i7lDo NNzNz Ni*+X+Y,Z~* Z*U8*V888*Ab8 *Bb8 -[8 -\8   -]8 -^8-_8-`8    V* E*Fb8 *Ff8     *C2*C2-a*C2*C2-a*C2*C2-a*C2*C2-a*Fb8 *Ff8     m*C2*C2-a*C2*C2-a*C2*C2-a*C2*C2-aYG*!*06DFLRX^~!8:<ENnzzvvvv!v *v 0v 6v Lv RvXv^|v "*U8*V8*1*+b!>"""z"zvv""4*Y:*.Y:*+cç :ç :"(++0+%3*444z4z44+[*An b+\*Bn bj> Yd:*.Y:+^8+`8*+]+_Rd6*+]+_Sd6 *R`6 *S`6  6   J6   9*4  e#*  +ZQY  T;W Ƅ ç :-F%-39IYeq|  =z uNz 3v9vIzzYjz e^z qRz z%%*Y:*.Y:,^8,`8*,],_Rd6*,],_Sd6 *R`6 *S`6  6   @6   /*4  e*  ,Z *+  c Є ç :ç :F,< H!T#_$j%&$#*+,z c3z XDz vv,pz<`z HTz THz ^*YM*.YN6*4f)6*4g*+c-ç :-,ç :,GJJNJSVVZV* 45670897?6E;Q<]=*"z4z^^^^   A* E    A* N   A*CX  ^*+X+Yhc z * '7MaqQYdT:MQY`~dT:7QY`~`T:#QY`T:QYd~`T:QYd~dT:YYi]?';K_uQY~`dT:QY`T:}QY~``T:iQY~d`T:SQYdT:CQY~ddT:-YYi]?YG~p rHtUvXxkzn|~%(9<OR_bux4zzzUP _*+X+Yj z i*+,X,Yj*z c *j* z z z  * o#*f* *fn6$ * f* n6$* 66kYlN`$*f* *fn6# * f* n6$* 66kYlN YG-F 3K^adpsR3=zK%z=z%zv vpb! "#H*4g<*4f=* tH*A*Ffj*FbF*Bj *E b8R*Aj *E bF*B*Ffj*Fb8&mYYn* ]?opY %q.   4F]`v4 $z%zFv&v]_'v  ()mr=r>x( )**zz zz *+I|<r 34,z z -+Ir~<r >?,z z./ c YsOh6+ +  :+:* #6d66 6  $ sO~`6 6   sO 6  $ sO~`6 6  $ sO~d6 6   sO 6  $ sO~d6 ۧz`~~`6d66 6  $ sO~`6 6  $ sO~d6 6   sO 6  $ sO~d6 6  $ sO~`6 6   sO  YG&IKLOR#S,V/YL[O\U^X`baqbzc}`fghfklmnkqrstqwxyw|'}6~9B|HKZ`cm|(+1;JMSV`)0[(z z (z (z z  (z O1zU2zXz f(z (z z (z  (z 4z Z1z`2zcz cc0czczczK3z/40 c4 QYQYTSh6+ + Q:+:* U26d66 6  ( QYTS~`6 6   QYTS 6  ( QYTS~`6 6  ( QYTS~d6 6   QYTS 6  ( QYTS~d6 ק0z`~~`6d66 6  ( QYTS~`6 6  ( QYTS~d6 6   QYTS 6  ( QYTS~d6 6  ( QYTS~`6 6   QYTS  YG&I(25TW]`j} 039CVYbhkz    $'06@S\_eo!$/c,z #z ,z ,z #z <,z W1z] 2z`z ,z ,z #z  ,z 9,z h#z z1z 2zz zzz~3z5f 567java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/indexedcolor.tga000066400000000000000000004610351445000440600330010ustar00rootroot00000000000000 hڗ`ÞiЕ̡iԡh얆ǗɞkڣwwĦ֭{쩅q𧻓ۯ쯏ѷ粍ӵī½|ญμȾ˴䬿Ѧ峩}ČÃ˿ﴨ㲵׶ຨ뼟’̿ŕȋ̈́ȿǑ§φÝƙΜ˟Ѝú̩ŢǯƫΘΕ˚̠Ռέۜڂ{ɳϥ¸טǯƼȱǔοɵəЧקͮѮٖ˗աӧ϶ۘүȻϾۢ;Ŝֶ͹ҹѵ֭ܣ䒤פݨЦͩި晭ִ߳ாܼ墯߲̩ܺ磯ȻູտŹʻͼ¼ՁˁсЁނ΃ǁˁǂˁЃ΁ˁ ՁΆdžˁ Ђу΅ˁDžށ΁˂т΁ы΁ǃ΁΄Ձ΁ǁчՆށ΃΄˃ ށсˁ΁т ˂ǂс΁ރс т΁ ΄ˁՁˁˁކ΁сކ˿ˁуނˁЁцՁЂуοՂՁރсށǁރ˂ՁՂ΄˂΃с΁ уЁށч΁сՂށЁՁ˂΁տ΃΄ˁˁЁ΁Ё΁˂΁Ղξ΁Ձс΁ށ˿сЁЃނˁ΁ Ѓ΁фсс˾΁ށсނ ބ΁сނ˃΁ ށсс˃с ՆсЁт΁сǁ˅сނ΁˂Ձރс΁˂сц΁тՁށˁ тǁކс˂ނՂсނ οЁޅ΁ՁǁсՄ΁сށсˁՅсˁс΁ˁс΁Ёރ˂΁ՂՁށссށՁˁށфՁсՁˁՆ΁ЁՁǁс΁ނ΁΁ǁނˁ΁ބхށсނ΁ ˁ фсǁށуށˁˁс Ձтށ ށ фс Ղсށс΁ ΁ у΁΁ՁՁтރ΁΃ށ ށ΁т΁ڃ́ÁÂ˾ ǁǂՁтڄځځ́ÂǤՁ ΂ށǂՁЄՂфځφƃςƃϊρ͚îîǤށ΁ށˁсус΁ˁ΁сځσƅ ֏ËËނ΂ނц˃΅ˁƺ ށǁ΁Ύ Ƴqe  ށށ΃ˆ΃˂ƺ ieˁՁ˂сށ΁Ղ΁ф΁ЁƁqeށվ΁΁ˁ΁ށ؅ځqeусξށςƃςƃϊφƂqeǂсՂ ڂƂρڂڂځڅڂςƁ[qeeс˂΁ބςځςƂƁڈϳqeF Ձށуށρυςq[eтރځƁځqeTՁ Ձށނƺځe˂΁сރƺ̓eLՁ΁ށǁ тƭρ ͎qeeÁÂՁރ΁сރϳσ Ǽށ̓eTeîՁ΁ˁсс΁ǁ Ղƺρڅсǁ͎eǃ΁усǁƺρվ΂΁eqeǤсттށƺƁ΂eq[ο΁ссǁ޾с οƺځ΂ǂq[qËт΂ˁсՃ ǁˁϺՁ˂ ܁qqqՁ΃΁΂сǁсށނδǁܷւՁ΃˂Ձǁ΁ƺ˃Ǽ ͷ΅ˁ ΂с ށՂֳ˃ǃρρΆ тˁֳփοˁϳт΄ˁ؁ނՃтсރց́ށсˁǼǁˁρށ΄ˁсՁ ނ ÁЁǁǴѾƁ ΅ˁсށ΂ ՁˁՂqqՁЊǂǼƺ܁ށΆˁ΂ ΂ˁށˁ΁т ց͂qqeՁсՁނсՁށǁˁ΁ƺ܁ށфˁ΁сނ΁ǁˁށ ނւ́ee[eՂˁтˁстт΁ށƺ΄ՏՁ˾΁΁тсރ́ee˂ˁ΂ǂdžǁˁǁсƁρցރ΄ˉсх΁ ށށтт qe[eÁՄуǁ΂ˁǯՂƁ т΄ˇΆˁ΁Ё ˁՂ ͚eeîЃށ޾΅ˁՁ˴сƺށыΉˁރ ͂qee[eÁЃЃՁՁǁށƺ тч΁у΁΁Ղ΁ށ˃т΁сeqe܁܃ǁǂ΁ƺƁЌˁǼǂށǂˁс ނ Ǽ͚e[eq́܁ǁƺ؂ ǼсՁǁޅ у Ǽ qe܁ ΂ƺǼՁށ ΁ށ޾ˁ ˼Ձȃqee ΂ˁƁځ  ǁˁсރރǁˁ ˾޿ qeqe ΁ǁƺځǁǼуՁ΁Ǽ˦Ǽ Ǽ οîeqé΃ƺځ˼΂˃т΁ˁˁe[ށсˁƁǴ тˁ˂сށՁ޿ǼοlFTTeeeˁƺǴނ΁˄ՁՃ ˁ ˴˾ބՁނՁށ˼{\SlJTe܁ ¿΁Ɓ ǁǼˁՂˁˁ΂ށށ΁Ձμr΁с΂тο{{SE\EFJT܁ػ¾՜ ƺځδǁށށЂՂ΁ǂсѾՂށ  ΁ށ˯r˄ˁ΂ \VV{\V\E\EJÁāٰ؁Ɓځ΁޾ˁ˂DŽ΁тޅ μVLj΁ǂǼ \\{V\l\E\El TāvxŁϳ ˂΁сށт˂΁ՁтՁՁބЁ с΄ށξՂ ޾с˨ст˄ ˿\VV\V\l\lE\EJlTĂby|vxnƺЁ΃Ձч΂ǁˁ΁ˁ΁˄уՁՅξтǁ˼ǼǁՁ΁ ޿ނՁՃނ¿l{\lElTJElāb_? MXuMvƭބՁс΁˅˃΁΁тΆ΁΁DŽˁˁ ˼ЁՁ{lElTTl ęb?|?Mv?|y_ĂԂƺځށ΁Ղ΁сǁ΁ǃ΁΁сǁǂ˂Ղ΂ǂՁȁÁTlTe[eTT́y_?AM?_hjԁځՁǁстфǂǁ΃˄ǴтфՁ́eTJTeq ǮӰ|vMvA?_jfƭځЁ Ճބу˃ǃՁ˂޾ ρqeTeÁ ǿ߶vMA|_bhjsԂϺف˂΁Ղ΁΂  ށ΂ˁ ځρeeÁՁ ŁnxvybsUsUspԂϺځՁǁ΁Ձс΂DŽ Ձނˁ ρqeÁˁ ػyspԁƺځɹՁށՁǃǁǼ˃ˁǁǁށсDžˁ ځЁˁނ‚́sUsԁƺ ^I^IcтՂՁт˼ǼǁˁсށՂށՁՁվˁˁǃ΂ ƺƺ ËՁտξлفԁs ƺځꁹ^I7*I^ނǁ΁ссǁˁǃ Ճށ޾ǁρϷցîՂށǼՁ ξׁԁsƺځɹ^*7Iށˁށރт΁˼΁˂΁ ށˁϳiiځځƁցîտ΁Ճ ˿ ԁsUs ƺI*7^ǁсՁނсус΂΂ ˼᳟iRiRi ο΁Ղǂށ΁ԁsspށфվƁɹ7*I сՁނՃˁ΂сՃށ Ǽ ƭi9iRƁڂ΁˂ՁǁсsԁՃху΁ˁƁเI* *7^ˁ Ձс΁΁ˁǁсμ ϺR9R9Riށǁ˂ǼǼ сˁԂsԁՁ΁ˉ΁˂ށρƽ ɀ^* *I ǂǼсށށтˁ˃ށ΁ƭR9R9iՁ΁˃ξԂfsԂсǁǁˁDžƁ๗I**Icс΁Ղс˂ǂՁ΁Ǽǁǁ ϺiR9R9RiځՁсǁ΁˂sUsp Ձ΁ǁ΁΁΁ǂ ƺځɗ^7*7cɂ݁ μǁǿǂс޾Ձނނ ǼƭiR9R9RiځՁ Ǽ΁ сԂfsp΁ǁ΂ށ΁ǁƺ̥cI*7cmށՁށՂކˁ˂с޾ނс˿ǁǂǴϺiRiiڂˁс Ձс ppffjʁс˃уЁނǁ ƺc^77*7Cgmk؂ށсфсттՁށՁՁނ΁сЁނˁξѾǂϭiڂ Ǽ сԁpjh˱ǁˁށˁǁρ ɥ^76CKDkuށǁшˁˁ΁˄΂Ձ΁сՂ΂ˁǁЁނˁ ބ΁ϺՁѼ jh_y_yοǂˁρ ̥m^76CC8kutǁˁ΁˂DŽˁ΁ǂˁǁ˃ˁ΁˼ǂނ΁сՁ͕ƁςڃμށǂՁԁjhb_vŻα˄ƺ̲bC6C8 8D"kPtDŽ΃ц˂΁ˁˁǁсˁ΁˂ށˁՂρچǁՅ_?AMxzށс΄Ձƺ ֽⵂgCC8D@8D@Pu‚сՁՁՃуށ΁΂ф΁с˂ށ΁ރǃ˃с с ڂǼǼˁvM PnxQYVс΂сށƺ}ͩvDKK/@D"/@PtރބЃށށՁރǼǴˁށ΁΁οˮ μ۩|vMnM';'Q'QNowSVǿ΄ނՁށ zvMD:K8/@P@"8/8D8ku؁сˁށށфЁރeq˼߶łnQOQ'ONONGSǼǁDŽH#MA/@PutPu/D8C8Kk΁ށǁˁ΁ՁeqсȠON'ONFGS ǼՂqHqL' /8/DPuttukCmҁ߁˂΁ ˄Ãe ǼoNON'NON+OYGN¿΁͚qL'#/ MukkmCcmɁҁ˃с˅ e[eǁ ؾGNGNOQOQOQNOQOQnÁqeq[L-#:u݁mgc΂ǁǁ ͚qee[e ˼ ޾GNO'Q';Mv ЂÁeLeH- :v߁mg^cՁǁ΁ ˼ַqeǼձSwGNOn;MvA|?y?y_y_|vnow\Âe-# $:?߂ ɹcc^ɁǁсǁƁ ǼށǯSwQnxA?_bhb_?vxnQYw ށՁH-# $_Ӂ^c^c΁ˁށ˄ځƁÂՁǼǁށδQnx|_bhb_y|M QnNwՃՃLHd-# $b_ʹ^ ΁ΦˁՁǃ ƺîտǁށѼ ˁ Ł|y__AM Q;QOGՁ΂οՂHd` $bɹ^Z ЁсǼǁ˿ށ΁ˁƺ ΁˴˼ǁс||vMXx˼Ǽށ΁сǁˁу܁d hbٹ^c^gɁ ՂՁށށՂÂނށՄǂˁށфˁƺρЁށǁՁ Ξǁс˂Š nxMxMAvA?|?vվ΁ǁЁՁуοсda hbɹ^mҁ݁Ձ΂ˁǁǂ΂˂˿˂˿΂ނ΁ˁƺځсށǁǁ˂΁˼ǁ տػznXMvM?_$b_ˁсǁǁՂ䁪24bɹgcgҸ΁ǁՁރނ΁ƺƁځсс˼ނˁǼˁ˿¾u?ybh ΁ˁނނт΁˂΁сނa2 24hɹ^cCmuǾށсށނՁރЁރ΁ƺځށс ǴǁՁс˂ǂˁՂ|yhjjā˂Ձނс΃ǂՁװa2  4gbɲmCKkuVrrVr΁ށ΁у΃΂тՁނ؁х΁ƺǂ΁ ǴǼ΁ށՂ Ձ΂ǁ޾ށʙjfǁށ΁ˁDŽ΁ށ齪`24ҁmKmC8C/@,;Oowrο˃΂˃с΂Ձޅ΁ƺǃՁǼˁ΁у˱΁ǂǂ΁٘jp ށ΁ށށށՁˆѾށa2 24bgҸkK8",)=BSVrWrWr˅΂ǁ˃DŽ΃ ށ ˿ƺځǁсՃстǁ΁с΃ǿށށՁ΁ǁwooV fǿǁсуށсˁтՁށځ`2  4b݁@"@,)=BGwrrсс΂сс˂тρ Ձ˂Ё΁сՁˁǿށ˂ǁVwoow VVˁ΁؁̙jjā؁ނǂ˼ѾՂނ޾с`2$b绡tP)=oSǁ˼rǁǂˁ΁ՁՁ ˁ΁΁ ΁ˁ΁ƺρՂǁс˂ՁтсǼrwo=O5f>5f5U.f5>f>5>5.534 :vu¡ŁفɁ^I^I^C8/ 'Q ʹ^c¡tP"   mރˁс΁сˁނށ΂ϺՂ΁ с ˼΁ٹ^c؁ fsf.%3b_|vœ؁݁ق ɹc^7*7ICm8K88: #'ɥ^ҁػ’tD 6Cm΁ǁށ΁ǁсǁށށсǴށ οƺǁǁՁт с ɥc^cՁفԁs Ufp3jhvŻ„ɹ^I7cCmKk@/8$ #Xz ๥^~^cء’t@8  7m݁ށуˁտ˼ށˁ΁ˁǃтǼǁ΁ˁƭ˄ՁсˁǁЂՁт΁๗^c οقԁsUsfт΁Ձɗ^I*7mKkuP@D #|ɥc^c^’u@  ^mɸՁ˃΁т΁ǃށ˄΃ˁ΁ǁ ξ сƺˈсѾтǂˆՁނՁǁʁ^~^ ǁԁsfjٵǂٹ^7*6KkututukDKC46|ɥ^c¾tPC ^cЁтˁDŽ΁сǁdžсˁϺˁǁǼՄ΁΂ˁссɹc^ށ ُUsނǁเ^*C8kutukmg6 y̹^cmktukC 7mɲ߁΁ǼރсށՂЁ΁ Ɓ Ǽ˂сՁˁނށ ʥ^тs ǁс ๗7* 8kx¡bC64_Ӂʹ^cCgKku ¡tkC^ I^ނ΂ށށт Ɓˁ ˁсǁՁށс^Ղށsށ˂ɥ^7 ut݁Ɂm4  ^cCK@tmC7 Icˁǁށ΁ځƁ Ǽ ǁՁ΁ɗ^΁ sˁी7 ut߁g_  bɗmcCKDPtkm7* *I^ՁǴ˿сƁρ ˴ тՁ ށ΁ˁ̥c^ށs ނǁ ɹC7 @tҲ|D $ɹmC88"@Ptg7 *7 ΁ ނˁ΁ƺiμˁ΁ˁ΂уށ΁ʗ^~^ԧsUށѾЁ̹c6 @uk:4hҁk8/ D@uҹ^**^˼ ˂΁ƺρǴˁՁ˂ނǁɥ^ԫfs сm Dt߁u: 4kD8//@Dk7I*7ŹǴށ΁ˁ΁ƺՁ ށՁ˂΁ށсˁ c^ǂԁUsՁɲm DuxP"4b݁ k@"@88Dm^I**I^Ɂռ΁˂ЁƺՁˁˁ ˂сс ɗ^ āsfsfؾξ˂ށ ҸmC8k ؠtM b绁tP/8CKmɁcI*I6^ μނ΁ˁ Ƴρ΁ǼтǁށބՂށˁ ʥ^^΁Ԣ]f5>f ‚ ҲmCCk šn; 3g¡tkD8CmɁ^**I(^ѼՂ΁сƺςՁ΁ˁ˄ǁǁЁ΁ˁ΁ށՁ΁Ӳc^ނ΁Ɂf>5f]¡݁߁҄ɲC88»nQ (b޿ukCmC^(*7I^6^ǯނ΁ˁЁϺށՁ΁ǁ ΁ށՁ΁ǁՁтҲc^ށɢ~]5!>5]ف҂túɁmCC 㝛' 4 ˾tukmC^cgɁ^7*7*7^c^˼Ǽ΁ނ΁ˁ΁ƭƁځǁξЂˁǿ΁ކ΂΁ Ҳ^cނZI0!!5]ɁɂkD@P@k@kKmmmgCC վ'? 4΂ ˾umg^Z^I*7I^Z^c^ʁ ݻ˼΁Ёރƺނ΁ЁՁ΁ށށǁс ǿˁ ο΁Ÿg^c^~ ๗^Z0!!0~^mcgCCC^c^^C^ 7Cmz#4ӁǼmg^gZ7I^^^^±ˁ΁сځǂсЁ΂ǁ΄оտЁ޿տο®οŸgcZ~gǁʥ^ZI0!Z~^c^777*7* ՁLXM $4сҲc^^6I^c^ˁǼˁ΂˂ЁƁˁˁ؁տˆտήÁѮǁοծÝ|?gcтʥ^IZIZ!!0Z^cc ^7I**        6cށàz# 4h˞ʁ^cc^c^^cmɲˁсՁ΁ ƺށ΁΁оǴǁցÇÃYzv_gC6c~ˁ ɁI^I^ZI!0!0IZ^I^I*        7^ށzy 4˴ ɹ^^c^c^^^cm¾ǁ˂ οˁ΁ƺˁ ծ{{ځڂڄڃ́ÁXM?:4chށ΁ˁɹ^Z0!!0I^Z^^I*    ^CοտÁd 4_μ ɹ^c^Z^^cmkuˁǁށނ΁ ƺՁˁс ׁî{Vl{Îַ̓օϊƇƁϷFL1$4ރɹ^]5!!]Z^^^^(*    cC߁ ȶ? 4_̰˼ ɹc^cc^Z^ɁʁɁ^c^cgmuށǁ΂ށ΁ˁϺǂ́Ll{l\l\lqeeeeLeLH#$$4bՁ΁ʥ^~Zf!5!]Z~^I*     mҁ΁Áy  $gށǁɁ^c^cI6^cʹc^cm u±Ёǁށ΂΁ƭځсѾTlE\E\l Teee[eL-$_с΁๗^ԁ]5>5]~ʃ^I7        CKk݁ǼՁȩy  $˼Vʲc^c^c^فc^CmkuЁ΁ރ΁ںƁځՁтՁƺeTE\E\ElJeqqeeTLTL'$$gс๗^Ԣf5>f]ɹɂc^gCmK8"  8Kkˁǁ˿ȶy u¾Ǽҹmg^c^c^c^҂ ɹgc^Ccmktˁˁт΁Ёƺށˁ ϺeTElElElTe[qe[eL'$_ ɹ^fUfقɃku;<)=)=))=),<,@ D@ށǼ à|$ ' Q˼߁g^cc^76^CcmɁgcC8uށˁނ΁΁ƺƁځ сqeTEJETeeeeL'$$_ʁ^sfԂقɂt=BSB=B=B=o<=t,@Du¾Ձǁv )OǼVrrҲmC6^^^6cm߁mC8k;ˁ؁сƁځˁںqeTJTlJlTeeLY'K:Ӂށ ๗^^Ɂs ’wSVSVSVSVSGSBotP@Pt˾ǁ M?$ +'o \ݬҸmKCCm kmC8D@toˁǁՅϺс ƁqeeϷփψƉωƁρeY';/:8?|Ձ๗^s оVSrWrrVtPkD8 @tǁ˿zX  =OǼVrWށ؁kDCCCkuػkD8K8@sށǁ˿ссǂ¡uK8^7 CKkt ǼǾցʥI7 *7^тậˁ{eqѼՁ΁сɗgcC@to ξ’ukDC60!5Usނށǃ DZ˾¬mcCmkuށρcI* I^ǁϺ΁lL[q ˴ ށˁɗm^C^mkut΁tuDC*05>5fՁށ΁Ǽˁ˼ǁmk ܁Ϻ c^*  *Iтځˁlq˼ сс ɹc^Cmu˾’tD8*0!5.f‚؁ ˂ǁˁǁǿ»ku ƺځ ɹ^ *΁ρ΁{qρځǼՁ ɹcmu’tuD 05!f݁ށ˴˃˾» ϭเ^**6 ށϳށVVlqԂ Ǟ ՃՁ^cm¾ Ёtu:053]ƒ ˼ǁނǼǁǼсϺɥcI* 7^ ƺ΁rlqƁԇԋ ޱՁ΁΁ʗcmŻǯ ¡t@/ 0!3] Н‚vyԁ ΁ǁ˿ށ‚сށ΁ށƺٹZ7**^ނƺځ΁ VrԄԁ˴ Ǿރǁ^ cgѼޡP/53]Ё x|yǁ˾сށՁсƺเ^I*7^ف ƺځ˂ r{{leqځԁ ssffsps μ Ձ΁ɗ^c ˼ ŬxP (~ л¡x_bhjǃǁ΂сՁǁƭ๗^7I^ށϭځ΁V{leqԁsUspfsff˯ށ΁c^ Ǽ뻸P:  (~»v|bjsсǁˁϺɥ^Z^ނςځǁ{{lTeqځԁsUsUspfpp.pδ ˁ΁ ɹ^c ǁvP: (6bվ|bsǁсƺځɥ^ށ΁ϳǁ{\TeԁsUssffffjjjp%j¿˿ˁɥm^ cmѼⵂ:$ 6gفՁ vsUsԁˁǁ\ ƺɁ g^c^ ƭ Ǽс\SlTeshhjjhӁǯDZǼǿҘmcCc m˴ Ӳ_:K  6gξ ŵsсr˂ѾˁϺ^^ѿѾƭځ˼˼{lJEFTeԁUspy_bhjh ˼ ǃ˼ξmcC mǞr Ӂ4$ Cmނ؁īUsǁrтՁˁ ƺɁ^ƭǼˁǁlJTsӰv|y_$_b_ؿrށՁǁ ǼǾݸkmCK Ǽr r ęb$  CKg΁s˼rтǁ˃ǁϺʹ ^^cˁ ƺځ΁ ށծlJEJlTԁsԉ۵ݶŻŁv|y?y_|v¦r΁Ձ΁»kK8 Kku˼rWrĊj4  CmсށہsˁrWrށс Ǽƺɥ^ѿϺځс΁îFTlTsUԂŁЁnvA?|vxDŽr˂ށǁ’t@D8DPμrWrWǿсބĊj~4 8KmՃsǁVrՁˁρρɹZcтƺс ÁTlJlǁށԁsԁރ¿nxM?MxQoVrWrVˁсǁt@/8/@Pt¾˼ǴrW΁сՂӰh4$:8ḿ ΁ˁ sUs ˞rWrǁƭɁ^΁ƺǁ܁eTlTlˁ΁ށs ˿¾¡nxMAAMXnQrWrWrǁǼǁt@/"/Pu¾˴rWrDŽ ŵhjhb_kD@"8CKm Ձԫs ޼rrˁϺɥ^ɁтƭƺǼ͋eT{ǼՂށsUsуˁ»xM?:A ?AMtYw\VrWrWrǁǂo.s%jh΁tuD8Cm Ѽށ|y|y?_Avuw=)"8ku t’˦sˁǴՁ ǯVƺ}Ӱʘ^Z^ف Ђqq ϺǼ˂тŵhp%s΁pUsUs.Upbv΁ ΒtukKCgm Ξ܁ߵӁhbhb_|v ޿ǴSB)"/@ut˾sμǁށǯϭƽ~^cނ΁с޿îդe΁ ƺÁ˃΁Ձށсξ ŵ_hjppԁށspsfph_yǼށu@8KCgmǼξށԁhjhbyȁށ˱wGwB)"/@P5f]ʁҁuxtutuutǂǁrƃ}2h46^Zc ށсǁ΁΁®e [TTl{ށϺՁ΂λnvM_bhāсԂjjhb?vxQYo߁Ӂف ߶xv@/Cg œQxv|_hj˦wo=)))!5]mgmgC87C8@Ptˁ ǁǞrWrׂ` 2244~bՁ΂ރ؁eϺρՁсՁ؁nxMA_?сh b?|MxnQnOQnxvĂ?D: 4$gćāxnQOQnQn;My?_Ăˁo=<),),@Pkukmg^(!0Z^68"@tDŽс rrہa224hb Ձ˃тނqeǁՁϳρˁ΁ŻxMyM|ށ jhb_?vM;Xxnxnxxv|yhb_$  Cbjjb_vXnQxQ;Mv|فゾo=),)"/@D k8KCg6(0!0ZI^7    @P΅˴rrׁb2bǁށтǂՂ e[eǿсϺρс ŶzMA|vՁԁhbyAM;MxMMvMMA|?_bhjh4$bh hb_?vMxMn'M;MxMxŁ߁վOo)=),""/:8C( 30I*I7*7    8PtՁсǁ΁˴ b2abՃхނܮÁeϺρс ݶŁvM M _?MAM?AMAMvA|_hjf3j34  4jfpspjh_y?M;M;MxMxnxn ށOo),"8/!*     "Puˁǂ΁ނǼ ̥bh42bׂс ΁ށÁeЁϺρݵvAM Ă?vAMAM|?|?M?_bhjpj%]3( $Kbhjpfsfsjhb_?M xMvxxαo=),"/888  0     8DPtтсѼʁb 2`ۂ ՁˁѾށ eϺρЁ݂v|Mxā|M|A|?vM|y_bj34   :_jhbvMxMxX޾oB=)<,@D8@* ** 7CD@tˁށՁǁ ʥgc4342`ށ΁΁ÛLe[qϺǁ΁ |y|?vMxnՁق|vxMvM|yv|vxv|yj~(  :AM|y_y|vxn ˱wBo!!53j̵ՍЁ˼GSB=),,)5>!f]jށœoB),)=oδV+V+G+OQ¿΃o=),)Ooǁ΁ƺ Ǿƺ΁ϳx@D8CKms΁tuk@D",<ut޾»ȁԁf5!5 ]jhVˁ΁˦wB=),)Oo˦VGNYվ΁ جt<),)owՁтρρ ƺсẳt@8CKmUs ޾˾tuk@"),;Pu݁ξ¿ջŁԁ]f5!!>f]|˱˒=),)=o΁S+S+NGoǼǂ݂t,,)=BSсƺځށậρ΁ˁƺÛtP@8C8mԫsށՃ ukP@P",,PuˁԁfZ5 !>5sfj|Oo),fsǯ tuD8  7^I^cˁǁ๥I***7Z^Ėby|?MAMxxvAMAMxXnˁނށˁǁǁɁc^Ɂǯ Ǽƭтƺꁹ^Z 0f5>fU Ǽu8 7C^^ށɥ^I**7I^Īb_y?|AMxMxXM|MxQαˁǁށՁށ˼ǿՁɹ^cμтϺǁՁс ƭفZI0Z5f˼u8^g^g^^c ǁɗc* *Iٰİ_y?yM?MA?AM'n˿΂ǁ΁ˁ΁ށ˼ǂтμˁɁg^cɁ ѼՁ ƭ΁сƭɁ^I05>fǯk8CCmg^^ ɗZ* *^Ɂ_?A?A??:MQnO΁˄ǃ΂ށǼށಹ^cفǯ μƭ́сںƁɹ^ZI05!5sѼ tuD8CC^Ɂс ́^* 7^晁_ $_$? ??'xQǮǁDŽс˂сǁǾˁǼ΁ҵc^δ˼ϺՁсϺρɹZ*5!>fс uk88mg^^˂ٹgI* 7^ٖh4$ $$AntYDŽ΁ˁǁǂށǁŬmCg^ѼǴ Ϻ́тẃٹ^I0!5Uf μtuk88mm^ǁɹ^*I^Ɂjh$$#QOރՂуՁރ΁ށǁǁˁ»kmC^g^mǯ  ˼ Ɣվˁ΁Ɵเ~Z0 !5!>fǴukDC8m^ɹg^I*I^ʁājp3% $?A;tYՁކށނǃˁсtu@KC g ў ΁܁ρծ ΁ ƔI0!>5f΁ tukD8kg^Ձށҁ^7***7^cف%.%$?MQNoՁՁˁ˂ˁtu@D/8CKmKǯѴ Ϻс΁Ɵi٢~I*!Uf ǯ t@DDkҁg^^Ɂ΁ɥc^I7*7I^pU.%3$?M;Yст’tu@"8DKk݁ǯ ǯ ƭî΁Ɵ๗^Z0!>5fǼtkD DkuҲɹ^^ށm^^^I^I7^sU.>.%3 :AMQN΂сށtu@"8"DuŁ˞ ǴϭîٹZ0!>fǂP@/ 8@uҸ^c ¾¡ukmgcc^sU.U.p%3_?MnՁ΂ǁ催ukD"8@Dut ˯ˁϭÁՂ΁ϳ ๢ZI0!5fԁсށμt@ @uҲ^»t@kKmCc^^sUpf%3j$yMQYNށ˾‚kD88@t¾΂μ ށϺÂǁͷʹZ0!>5f΁ށu@/Duݸm灬tuP@D8C^ ʹc~Us jhb?vxnw΂جmCK8@utсށѼƭ ξ́ʗ^I5!>5sނ΁ Ǽt@/  utݸ^ҸP@D/8Cmɹ^cshb?MnOсkmCKku¾΂μƺ îǿՂܷɗZ0!f˂ށ΁tP@Pu߁m^cҁkDk"/8mفʹcԫsԁh_vMQo ށukmCmC^cmku΁ˁރǯ Ϻο΁ꁹZ 0!>5f]΁΁ǁP @݁ɹg^~ɲmKD88Dkmɥ^sԁājb_AxQNwtPkKC^CmŁށǁDŽсށ˼ ƺîՁ΁ Z 0!!!fs]΁ǂtP @5f΁ǁǼDZtP t廬߸^c̃mC8C8@kPu߁c^sh ?xQYN绁uP@D8gm߁Ղ΁ˁǁǂՃǁǼϺqտÁZ!5f]т˾t@  ,t嬁mc^cɁcC^C8@Ptٹ^Usԙhb_MnNGSݬuP@D8K΁с˂˃˂΁ށƭށZ0>]ށˁ΁ ΂ˁǁ˾tu@ ,toˁ؁mg^I^cg^gCmkutɹZ^ɁԫUsԙhb_MnONGwk@"/8Kҁށ΁ˁфǂ΁ƳeЁɹ~Z05] сˁ»tP" ")C^7*7I^c~^^gmktʗ^csԁā_M QONGwmD88/kuށՁ΁΁сǁڳeÁʗZ!5f]тμǁՁu@8 )oo˒kmgC7*I7I^cg ¾ɹ^ sԁh_MPQNGɹm8D"@uЁՂǁځ eq ʹ]!5]с΁ с˃ »uD )oukC7*I7^߬Łˁٹ^sh_AM'ONGwmC8D@ux΁ ᷳqÁַ ๢Z0!5~ʁՃށтǁu8 )otkDC *7^Ɂ ɹ^s _|A;QO+GFomgCKD@Ptсϕqe܁فZ5!5] ށk8 )=)<,  *7^ރ ๥^^s_AM'NGoɁc^CKktᷚqeq٢Z0! !0~΁Ձށ΁ˁށݬkC     *7cʁ ǁ ɹ^^ԫs٘|MxQN+NYwɥ^Cmku¾ַeLϺ]5 !5Z˂сށǁс ݸKC    *7^ށ ɹ^^ԫUs ĘyvM'ONɹc~cgmk¾ qeqځϳāZ!0]Z с΁ ǁ΁ŁmC      7^Ɂށ๥^sӁ|v;ONQnɹ^g¾HéςƁʢ]5!!0Z~ʁ΁т ݸmC7    7^gʹ^svMnXQʹ^ҁՁ΂сՂށe qeqځρƃiĢZ5!!ZʂǁссجC  ),^cс๥^ʂssvMXMuv^ сˁсeqϺāf>!0Zʁ ǁст »C  ),"8CCm΁ށɹ^~sfsUy_A:Mvyʹ^ у ށeqƁʢ~5!0Z~˂˂¬m7  ,=B=)<@8C mgmтɹ^Z^Z~] fUfsӰy$_ف๥^cǁсǁËe[qƺĉf5!I~Ճ޾ˁ tukKC  ,=BSB=<,/8KCm ɹ~^IZ5f5fbh^cʁ eqƳԢf!!!ZށсՁ ¾xPk8C ,<=oSwB=<,/8KCꁹ^Z05fsęjԂ̹c^cˁтÁeqƁƁ]f5!!0ZՁˁ偻tP@D8C   8Pt=owVSBo),"8m߁ ǁ๢^Z0! >Ufjf ʹ΁ˁށËeqڂτƁρځf5> !ZIZǁ΁绬uP@87K8@toSB=,""k݁т ʹ~Z0>5fsɁ^Ղсǁîeqԫf!!0Zބс߶ku@/88C77CkutowSB=),":Dku݁ނ ʹ~Z0!5fԁsɁ^ ΁ށՁeqf>!!*Zǁс ߲kKD/8KCI7I*7 6CmKktow B=),""/Dku Ձ ~Z!!5]sUsɥށ΁ծqԫf!>!0Zށ ̲mK88"@P@mC^7*(*7*7^CmkuSwB=),"@ukՁǁԁ]!!]ԧs ʥ^فˁՁт΁Á q ԫ]f>!!0I^ˁ ҹmC:@u u^I7*7*7^Cmuwo=),"/Pu؁Ձˁ٢~5 !!5]Ԃsԁف^ ˂тÁԁsf!>!0Z~ށ๗gcC8@uttmc^I7*I7I7^mk¾o=<)"Pu ǁ]f550~s ɥ^^стդ ԫs5f!0Z^Ձ΂ɥmC^gmDku»Ҳ^I*I*7*7^6cmo=)<,"P,PtсǼ΁ԧ5!!0Zԫs Usʗ^у΁Ձނ qsU>Usԁs>! 0I^~Ҹmc^cCmutظm^7I*I7I^c’o<,""Ptx¾˿˃]f!0IZs^c^Ɂ ޾ޮ ԧU>&>Uf50Z^˾؁mc^cmk±Ÿg^I6I7*7I7^cgo=),"@P;tˁDŽ f>5!0~Ɂ fUsf]c^ cтǤ ԧsU&&U ԫfU>5!0ZI^Ձ˾¡umcm^cmݬ¾ѾظɁc^I7*7*I7^cҸ݁oO&&Up f5>5I0Z^¡ukmC^c؁ɥ^I*I*I^cgm݁¾O<,@"/@t Ԋfsf]Z ^Z^ ԧfU5>5]Z^~^ ޾сǁձԧ&&UsfU5050Z^’tukC cgт΁߁^7I7***7^c؂ƒ<@/@PutQo ŵss]Z^Z^cfU.5 !0IZI^فǁ΁ˁ U>&&Uf5!0ZIZށtPD8C mт؁߁^7(**I^gɸށt<,@/"/@uto hpsf]~~^c~ ٫f5!5!0I~^ՃށނՂÎԧU>&&UfU>f50ZP@88Cm ˁɹ^I*** ^cmt@88D@u&&Uԧsf5f50I^ق ػuPD/8Kʹ^I** *I^t@88:@utOoBSGwNQ;|_h^^cԢj]!!(Z~Ǽ οԧsU&&Usfԁ sfU>5!50IZف笡uP"/8Dk߁ ҁI7**7^mҁ݁؁tP@D8C8D@PU sf5 0ZI ݶk@D8 kuˁɥ^7**^cɲ؁tP@D8KC8C8D@P<)O=B+)+'Q;M|ҁ^ʢ]5!!(^˂˿΁΁ ǿտϕρԧU>&&&>.fԁԫf> !50ZI~ ukD8/"@Pʹc^I**7^cҁ޾tP@D8CKC/@";)')+)O'MxuҲc^g](0*(c߁ ǼށՂ΁с ԧU>&&>U>sf5!0I ݲkD88@uށɹ^7*I^݂¡’tuPDKCC/")')QnX݁mcc7* 0(6h߶Ǽ ǾˁÎU>&&& >&Ufsԁsf>!0ZkK8"@PtՂ ɹcI**Icgҁ¾’tuDKC6C ")O)&&&>&sԁf5!!0ZɹmC8@Pt˂ɹ^7* I^ctuDKC67 ;Q&&>sԁԫf> !50Z~ҁmcC8@utށсɹ^* *^mҁށ ukK40(  ";)Qجmgc^gɹgmC4  $:v¿ބ΁ǁсс΁΄ˁށՁ˾ͷU&>&&&sf 5>!0ZʹmC 8ku¾Ձс ɥcI*  *Icށ’tukKC(* ,;<t¡k KCcCgmҲmCK88:Mxnсǂ΂ǂǂˁˌ΂˼ՂނށՁַ s>&&>s ԫf5>!5Z~mC^CKkP ɹ^  *^cɵ’xuAK6(0!  ",&&sԉf5!5Z๗cCmk˂ɹ^Z I^mҸށMk$405  ",PtPtukDC CCmɁgmC8@P:AMvu¾с΁΁΁сՃՂށՄǼǿ΂ǂт܁ ԫsU>&&Uԁ]>!>5Z~๗cmk΁ ɹ^*  *Icxu?:43(0(/""/@PtP;uD@8CCm߁ʲgmC8@,P;PuMA?MvŻЋՁЂށޅѼǂсށ с܁ցs&>&Uԁ櫧f>5f]ɗ^cCgсށ΂ɹ^7* *7^߶Ł nxM:$K46(6Z6cC8"/DP@K߂́c^C8K@P;tx|?_y|Іށ ǁ᳭ρԁsU&>sf>5]ʗc^cˁՂՅ ɥ^I I^g݁nxMA:_$_bgmKD/@"/8D/Cm߁ɹ^g^C K8@PtQx_bhǁ ф΁ όsU&>sԫf5fɹc^cтށс๗^I* I^vMv?ku@"@"8C8 C8݁Ɂ^cmKkPtyhjhǂǃ΂ ǁϳsU&&>sԁԫfUf~^mǁ΁˼^I*I^gv|AMv߃u@P@"CCKk߁҂^^mkut»ߘjjՁǂˁՁˁǁẁsU&&>UԁsUԁ๗^ǃǁ ɥ^I* *IZcgb g?vAvunŬŬt@8 8Dkmc^mkضۘs΂ˁ΂˂сށށǁށރs>&&>sfsɹc^т΄ ʥ^* *IZ~Zhb$_AMxn‚؁݂utPD/8C Dkmgcmc^m ۘsԁ ˼Ձނтǂ΁ށށс܁ ƭ恧s>&&>UsԂUsɥ^ǂˁ΁^* *Z03]4h_|vxݬ݁uP@/8C :8CmCc^c݁ĉsށǼˁ΂ށтˁ ށǁၺsU&&>UԂsɗ^΁ꁹ^I*  053b_|x؂utP@/8C7  8C8Ccmɹҁ ¿sЁˁссށсƁsU&U>ssɹ^ˁف^*  %3_’»kukD/* 8CKmӁՃԫsԁтǁՂс΂с΁ ϺԫsU>&>Ussɗ^˂  ɗI*   53b_’‚ukD8"/C7*  8D/DkKkҁނ櫁sуށс ǁ ƭԧsU>&>Uԁsԁɹc μɁI* 3Z|uttukDmK8C8CC^7 8@"@PkPuс sусˁ΂ںsU>&>sUsԁsɥ^ Ձǁɗ^  (4muuPu@D8KC8C//8mCgCc^76C8kP@PutPutuł Usуށ΁Ձт˂ƺsU>Ussɥ^  ɗ~I*  0(cmkuP@D@8C8C8D8D@"@kcCmkutނs ށˁށ΁΁ၺԫsUsԧs^๗^*7^mK8"8C8K8Dk@;ukt¡΁ՂDZǁsՃсˁ΁уϺsUsUpss ɹc^ɹ^I*  7C88Dk@tutt݁ҁق́߁Ŭށ΁ށсǼ sǿՁՁсˁށ΁ށ΁ϺsUԁsɹ^ZٹI0  7CCD@Pk݁ ǂ΂ǁǁ s ˁссՁˁՁẟpsUssٹc^^I ^C8D@Puu¡؁˃ǁ sՂ΁ՁނƭsU>&UssɥcZ^ فZ0  *CKmkuPtt޾ˁЁсǂ Ċfpfԁ ށǁބށ΁сƭԁs>&>Ussɹ^I^c ߉^I  7^mkutނǼيpfpՄ΁Ձф сс ƭsU&UԧsԁgcI 6^Ɂ̂ففʂ^Z*  7mkނՁ Әjp ссނ ƭρsU&&&UԧsUɹ^7IZ^Ʉʁ́̂Ɂɂ^I0 *^mɸݻοՁՁǁ ˁǾݩjǁՁނǁ ǁ ƺU>&&&Usԁs๗^I^cɂ^^ZI0 ^mǁ΂с΂Ǽ ˿޻bhjԁބстсǁƺs>U& &>Us๗^76Z^c^c^c^gc^~^Z *Icɸ؁сՁο Ǟ ο»v|_hʰށˁс Ձˁ΁ځƁ sU&&>Uss ๥^I^I^c^~c^c~^c^^^^^Z^c^^Z^Z0  *7ǁށǁ μǼvy_hb΁ˁЁˁ΁сƺƁs>&>sscI^^c^^^^cZc^~]Z5!  I^ҁу΁Ѿˁ ǼxvM_yӶсссс΁ˁсށƺU&>Uԁsʹ^c^^c^c^^]5>!  *^g؁DŽ˂δ ξxMA|?vރсބс΁сϺρsU&>Usԁsɹcccفʁf5!5   *I^с΁ǼނсxMAMvˁсˁՁ ƺsUsԁs ɹɹɹɁɁقԫfU50  *^ɲсDZǁ ˼сػxXM vx΁тށтՁϺρsUsԁsɂ مɅɁsf5]0*  *7c˂ǁ΁؁vAMnzЁ ˂ ށ ѿ ƺsUsԁUӁsf]ZI0 I^m؁ނ΁ǯ݂vA?Mn¾с㾁΁ՁтՂ΁ǁǁϺρsԁsنs]~ZI* I^сǯӵ|y_?vxˁ΁Ձтށǂށ΂ˁт ƺsUs sUsUI *IcҸ؁Ǵсb_y|¾ނ΁ ށ΂ˁǂށϺρsUsUԁUssʢI* *IcmҁˁՁǁah _vˁǁՁсǁށ΁ǁ Ϻԁssԫs ʗ^**^ҁ޾Ǽށ Ċjhyо΁˂тՁтˁDŽуˁ Ϻsԁs sUsԁɁ^( *7^ҁǁǁށсˁӁjjусށт ΂΁ՁՁϺԁsUs sUs ʗ^I I^cҁǴсˁĊpjİށтǁǁтс˅΂ ƺԁsssفI**^cmҁǁނ߰Ċf΁уǂށǁˁށ΁ǁ˂ǁƺs Ussɥ^I* *I^ҁˁށضĊfp΂΁сՁс΂ބˁǃǂssssUʥI*7^gmҁށՁ޻ŵpfԁЂ ށˁ ˃΁ˁƺsss๗^I* *I^ѾŶhp ΃˂ށՁ˂ށǂǼށƭsUsԂsԧs๗c7* *I^mށ ˿ _hjpԁт˂уށ˂сǼ˃ˁ ƺԁsԁsUsเ^II^Ɂ˾Ł|_j޾Ճ΁΃сՂށՁՁˁсǁ ǼƺsUssUsแ^I*I^|_bhсރǁтՂނЂއсˁсˁDŽՁ ƺsUsssɥ^**I^ҁ㾿v_bā΁сˁ΄Ձ΁ކނт΁ˁDZǁˁƺԧsssԁɥ^7**I7Ɂށn?y$bվ΂ǁсDŽу΁˂΁ˁ˄΂ǃтƺsԁssUs ٹcI***I^nxvM_فށՂ΁˃΁т΁ст˃΁ՁǼDŽƺsԁssԁɹ^**^IɁnxM?|y|ށ˂сǁ΁ˁс΁΁Ձނނсˁ˂ށ΁ϺsUsԁsUԧsٹ^I**7IcŁxMAv΂фՂނՂсՁтDžՁށϺρԁsԂss ɹ^I**I7ŁvMAMvއނ˂ށϺρsUsUsԂsԧsʗ^I**7I^|v|Mxvǁс᳃ρsԁss ๥^I**I^فӁy?AvMnx؁΁ˁсނˁсϺρsUsssɹI7****I^́b_yAMX οǞˁ΂тϺρρsUsssԁɹ^ZI**I7^ ęhb$_?xށ˾ƺρƂƁԁsԁsUsف^I**I^jh_|x¾μՁƺρƺρsUsUsԃ ssԁ^I**I7^jfjh_ v¿ǼՁƺρƭԁs ssʹ^I**I7^fp jh¾΁˼VƺρƭρԁsԁԂssɹ^I*7I^ԁspfjؾ μrWrǿځƺρUsUsԍss๢^I**ZIԂsUs Ӷ΂Ǽrρϭځԧsԁԇԫssʹ^Z*0*I^~ԁsЃс˯rW rWrẁƺsUsUssԁsʁZ*0IZ^ʁԁsUs sу˼r ƭƺԁsUsUssUssԃĢ~Z0 00]~ԈԁsUsтсr ϺƺځssUssԢ~Z]Z0  !0Z~ԂsUԁށсՁǼrƺƺԁsUsUsUsUsspUsUss]0! 5]~spށтށށ΁ˁǂƺƺԃsU sUssf]5!!5fsUs ǁՁՃт΁ǂǁρƺفԁsfsUsUsfsfsf5!!>5fsUsUsԁ΁тނޅǁˁ΁΁с˴ƺƭԁpsssfU>5>!5>fsUsԂсǂсЁނфˁ΁ˁ΁ރ˼ƺƭԁԁsUsf5>!>5>UfUsԂՃރсф΂ˁ΃ނՂμƭϺԁԃfsfsssf5!!ffނǂ΂уՁނՁށނՁՁՁރ΁΂΂΂ǂ΂ǼƺƺԂԂpp]f]5!5]f Ձ΄ˁLj΅΃Ջ΂суՁфǂǁށނǴǿ܁ƺځƭقԉ]Z5!  !5Z]Ձǁ˂ˁǃ΁΁ǁˈ΁Džˁǂ Ձ˞ ϺƺρԁZ5 0]Ԅ ނс˃΁ˁ΁NJ˃ǁˆ΁сރށϳƁƺځ ّԂI0  0Zʁԅقށվт΁Ղф΁х΃΁΁ф΁ށ΁ͷځϺځʹ~^0  I~ނЄсՂՄсՁބ˼ {ƺځZ7I Τ{ځƺځʥ^I*I˼{\S\Teƭځɗc^**Iޮ{SV\lTJeqϺځٹI* *I î{lE\E+Teƺځɹ^(**^cîl\EJlTցƺځเ^7*I^ÁEJ\EJT́ƺi ɹ^I^eTElƭρɹc^᳷qeTlE\lE{lƺi̹ځƁqeTJE\{ƺʁفƳel\ {ƭƁel{ ƺځƁä{\{ƳځVƭρځρցՁځƺρځÿ ϺځƺρڃѼ ƺڂƺςځμƺƁځ ƺƂڂǯϺځǯρƺρځѼϺiρڈƺƁځ˼ƳڃځυځڄڂρڅځڄρƺځǯƺiƁϊƃςƃχƃϊƃ ƃڃ ƺѴƁiƁƁځ˼ƺρƺځǯƭځځѼƺςڂځځڂ ڂځځڂځڃρƂ˼ ƁƁρځځځڄڂڂچωƁρϊƃςƃωƁρϊƃψǼςƁϊƃςƃχƃτڂςσƇƇƁѼωωƃςƂωƃψځځƁځ˴ځڅځڂڂځڇځڂ ςƁƁρڂځڃځڂځځڂυƂځǞρƊϊƃψƇƁځǯρƇƁρځίςƁƁςڃ ǴځƭƆψƈƃςƃψƈƃƄχڄǨڈωƃςƂƁσځڂځڅځڂڂځڅځڂ ˼ځڂڂρƁρμσƂƁτǞځƊƃƃځѴځځڇځڂ ˼Ǵμ˼ ǯ ռ˼ǯǯμǼ޼ռ˼ǼˁՂ΁ށTRUEVISION-XFILE.java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/logo.tga000066400000000000000000003443421445000440600312630ustar00rootroot00000000000000 ? ('aC P\L;XW9Ea%6VY>J\Q$ Ya F[S,&Q\Iu%sarFh fPd\cL`]X;VWTWR:UFEDaB%:7X5a5 ) +C)['T$0"P\K > YV8N\N 8 V Y==a+"""Ta ((($$$@'''Z(((U)))4---///O000\111L000...888;999W:::W;;;;333BBBCCCCaEEE%@@@JJJ4KKKULLLZMMM@KKKSSSRRRITTT\UUUQWWW%\\\-]]]S^^^[___Eaaa fff-gggahhh=oooHpppaqqq!mmmwww7wwwVxxxYxxx>K\Q# [a G[R*(R\H(aCCa(^aC a`Ra] AaXYa<0aa%7a[WaH\aS_aKaaTaT^a& a_Sa~Yur]rapFjgae`bRaa`^a Y@WaUYRYQaO>F.EaCa@%I9F6a4a5 +O)a(U&]$a#-a_Ra\ F a WZa9`aRa` : a ZXaD'aa+333Aa a %%%I&&&a'''V(((\)))a***3...///a000`111R222a333^555 666@888a999Y:::Y;;;a<<_a]5X X`V]Y O P^N_L JHHFaDaBa@%<9U7a5a3a5 0,+a)T@%#a!W+Ea+Ba/ a Z _^<a58a;  ^ _ \a Eaaa+Saa a &&&%%%a'''X+++***a+++[+++...@///a////222<333a5555888 888`999]777 @@@ <<<]===`@@@ CCC@@@HBBBaCCCaDDDaEEE%NNNIII[KKKaJJJFFFNNNYOOOaNNNRRR)SSSaTTTHVVV&WWWaXXXK\\\S]]]a^^^___Paaaaaaa fffeee#^!a!^a'aH) a GQaUaa T  a S Ha'%`8Ha+CZ) \!!!a $$$-&&&a&&&D***W+++a---///X000a222222!444a555N777#888a999M ===K>>>a???#BBB+BBB_CCC6DDDKEEEaEEE%IIIJJJaKKKYOOOBOOOaQQQ-SSSCTTTaUUU/UUU XXXaYYYaIIIZZZ \\\a]]]a]]]___6bbbabbb9dddfffagggBggg>>a???-BBB@@@DDDFEEEaEEE%HHH"KKKaLLLQNNN;PPPaQQQ7RRRLTTTaUUU'UUUXXXaZZZa]]] ^^^]]]a^^^^ aaa/bbbacccCbbb eeehhh1iiiakkk=ooolll qqqLrrrattt!www&wwwaxxxK|||@|||a3Qa! aa# aaFa-_aaa)aI +aCFa((aC  aa a^7a?Aa1Ia%.aH 6a=YaaaaaLa)Waa`| {ayUq(namF d eacaa`\a[_V7UaS?M?LaI4AF@a=%3a1a+ )F(a'0 Qa%aaaY : a9Da.aa a 1 aE;a:Ca+!!!["""a &&&@'''a(((6+++K---a...+333 000a111a... 666555a666_8884999a;;;?>>>>>>>a???4EEEFFFFaHHH%JJJ+KKKaLLLKNNN5QQQaRRR@TTTVUUUaWWW YYY`ZZZa[[[[[[]]]a^^^Wccc)bbbacccLjjj1jjjakkk=qqqLsssattt!vvv.xxxayyyE {{{9}}}a:Yaaa aaPa&Zaa]#aR+aCFa((aC aa aX1aCFa,Ia%(aL :a8Sa!aa aaGa- \aaa|%zaxOn(makF cdacab ^[aYYV1TaRCLCKaH.@F>a=%1a0a+ )A'a&3 Va aa aS 6a>Ia)aa  a _ + a I@a4Ca+!!!["""a (((;(((a)))9,,,P---a---%333000a111a444333666a666Y888.:::a;;;D>>>C???a@@@/EEEFFFFaHHH%KKK%LLLaNNNOPPP:QQQaRRR:TTTPUUUaVVV$UUUZZZaYYY]]]^^^a___[ccc.cccadddFjjj1kkkakkk=qqqLsssattt!vvv(xxxayyyI |||=~~~a5Ta aa  aaJa* _aa`(aL+aCFa((aC _a$aN(aINa#Ia%aS Ba/Ja(aaaa>a4aaaaz {-yaxEn(makF c_aaa\![aYPT(SaQILKJaG#@F>a=%1a/a+ (8&a%:^aaa'a J .aFQa Ya a V"aPFa)Ca+"""[###a&&&'''1(((a)))@---W---a000222\222a111555!666a888P999%:::a;;;L>>>K@@@a@@@&GGGFGGGaHHH%LLLLLLaMMMVQQQBRRRaTTT0UUUGVVVaXXX+UUU ZZZa[[[abbb[[[^^^a___aUUUccc6dddaeee<jjj1kkkalll=sssLsssattt!yyyyyyazzzP }}}E~~~a~~~,La%a_ aaAa1aa aa0aB+aCFa((aCNa$4a<aX[aIa%a^Ra8a:aRaa-aFa[Wa~z=xav3l(kaiFcNaa^'[1YaX?URaPY@UKYIaG>F=a:%0a.a+ )&&%a#L a^"Qa!7a 9aV\aHa+ + a E a\TaCa+"""[###a&&&''' )))a***Q...---_..._111 111K222a444'5551666a777?:::;;;a<<F<a:%/a-a+ '$\#a!>Oa9*aV:^ a UaDIaFa[9 [ a JaICaQCa+###[$$$a&&&+++(((Y***a,,,A---L---a///@333$333a555Y4449666\777a888;;;O<<>>G>>>F@@@aAAALGGGFHHHaHHH%MMMDOOOaOOOLQQQARRRaSSSTUUUWWWaXXX_YYY;ZZZT[[[a\\\*___9```abbbQaaa=dddaeee\mmmlll1lllalll=sssLuuuawww!yyyI{{{a|||I}}}C~~~aQa^:Va& aa `a|azax^v#l(jahFU_6]a[aY_W-QNRMaKaJRK=F;a8%.a,a+ "!Y aaI9aa_ (TaaO 1` a ` 1 OaaUCa+###[%%%a&&&(((+++V,,,a---a...L/// UUU3335444`666a666`777.999===Q>>>a???a@@@R>>>GGGFIIIaHHH%NNN OOOKPPPaQQQaRRRWSSSWWW#XXX]ZZZa\\\>```ZZZ```Dbbbacccaddd[eeelll1mmmannn=tttLuuuawww!xxx {{{N|||a~~~a~~~V(^aa; aa \aaC@aa]!+aCFa(  yxp][\UNJF@+0     $$$""" +++000 777888@@@@@@CCCFFFPPPUUUOOO SSSUUUUUUXXXUUU``` bbbUUU```pppfffpppfff ||| &0; >cY200sC yÑL6:G6 &BB%0L@D*4G<>F0}/}F{>xm>kLg`+^D\@ZNO9NGL7G @%@B>B;&..-L,"!@D,3F<>F0/F > ;G4)DA $$$5%%%L%%%+++%...B---C///'...5556555G777:999>>>>>>@@@@D@@@,UUUHHH$IIILKKK(QQQQQQ=RRRFSSS1UUUZZZ[[[L[[[2```bbb;dddGfff4```jjj)lllCmmmAooo!ppp ttt9wwwGwww8vvv  JC6G;GLÅJ:G6###YYYf%.LUaF \aaaV3aaaa1aa%'aaaa:Raaa^`aaaFH~a|azax`ul0kajaja<_a]a[aYaX(U P[NaMaKaIWKA0@a>a=a;a:11.a,a,(#'!a aaa=Paaa ^ `aaIEa a a ` ^aaaR+++<aaaa(###$%%%a&&&+++0,,,a---a...a///a1114...444V666a888a999\777<<<$>>>a???a@@@aAAAaAAA=GGGIII^IIIaKKK4PPPPPP_QQQaRRRaSSSaTTTI[[[ ZZZY\\\a]]]@bbbbbb^cccadddaeeeafffSmmmlll9kkkammmannnaoooaqqq*uuu tttZuuuavvvawwwaxxxYyyy~~~KaUVaaa\@aas \aaaV`ppp{aaa0aa%!TaaF8a=Ia,\`a\4^aa%Ua^_ !aO6aANa* Xa~a}Wz y-xavMo o?makaiae` ``^^Y[!YaWWUP5PaN>PJHHaE-@A[@a=>:`8\0;13/^-a+a*(#U!a! ^` aQ  6 a BKa- Xaa Y * aN>a6Pa$ `^aW+++&&&"""8$$$_%%%a&&&a&&&++++++[---a,,,111000`111^333444)555a666J<<<999>999a:::9>>>S>>>aAAA$@@@BBB]CCC`EEEUUUHHH+HHH[IIIaJJJaKKK4 OOOIQQQaSSS.QQQTTTVUUUaXXXXXX%ZZZW[[[a\\\a]]]@bbb>bbbaeee8iiifffMgggaggg&lll kkk_lll_lllnnnoooaoooXttt2uuuavvvCpppxxxFyyyayyy0|||~~~NaaU )aK;b@@@e*HaaaDDDA杝9a=999??? 222a\3^aa% aWCaF Xa&aL!aO Pa!.aJQa%a[#Da+@a.a` aa8a76~a|:tvauas o@mak>j]haeb+_a]FXZVaUPUNaNH#HaFLC@a?R :N7a4!2+0a.K-Q+a*(%!a [(Da-@a1 a` aa8a74a = a a ^a,aC*aI Wa """4###a&&&F%%%V'''a)))))),,,a---R000M111a333$444I666a666(777:::a;;;X:::>>>a???^ CCCACCCaDDD.FFFGGGaIIIQIIIIKKKaMMM4NNN PPPaQQQaUUU TTT5UUUaVVV:ZZZZZZaZZZV\\\F]]]a^^^@ bbb^cccaeeefff)gggaiiiFlll(kkkammmI pppVqqqattttttRuuuavvvyyy yyya{{{O~~~^~~~\?aU Iarrr.JR_WRUaT!P_NaKGFaEW@)@a=J 7F6a5,2!0#+F)a'($!aS;a7Ia) aa a_1aA> a 5 [a aa %aN3aA O a !###'$$$&&&O'''a)))+++)---a,,,J222D222a333.666S666a777 999:::a;;;a333===>>>a@@@V CCC:DDDaEEE8GGGGGG*@@@JJJ7KKKaMMM4QQQQQQaRRRa@@@UUU.VVVaVVVDYYYZZZ1UUU[[[.]]]a^^^@ ^^^bbbadddahhh hhh!hhhaiiiOkkk2lllammmA qqqNqqqarrr#ttt\vvvayyyxxxzzza{{{Z~~~; aU0006ccc&&&(aa 4)))a=== Ia)!#Fa%(aF aa a^6a@@a3Fa%*aL6a?Ta`aaU*aK}I|az*uUsaqh[fae_?]a[6VKTaS*QNaMaJ GFaD`A3>a=C7?5a26+F)a'( ' aL3a@Ra!`aaX*aKH a - Ta!aa$$$aW<a9H a *'''O(((a))),,,3---a///C222<222a4446666^666a999:::;;;a<<a<D7B4a33*F(a%( %aM6a=Pa#aaaY-aHF a /Waaa aT:a; K!!!a"""('''O(((a)))...1---a///D111?333a5554666[777a999>>> ;;;a<<|?zay3s_qapf[dae^4[aZ?TVRaRM^KaHEDaBS=(=a<K5I3a2)*F'a%( aT=a4Ha ) aaa`4a> = a 6_aaa (aJ1aB S!!!a###(((O)))a))),,,(...a///K222F333a444+666R888a999 999<<>>R>>>AAAaBBB_@@@DDDHFFFaHHH'LLL7MMMaOOO4UUUSSS_TTTaSSSWWWDDD+EEEaFFF\IIILLL7NNNaOOO4SSS?TTTaUUUDVVV&WWW^XXX`WWW ```.```aaaa@fff4fffagggLhhh%iiiYkkkallltttooo[pppappp0sss9sssatttNvvv(xxxayyySyyy${{{T|||a~~~#aaaՁ aY%Mzzzk/ aa-ƅaS1Fa%(aFJaaa@]aa\Fa%Yaa_$8aaaP Taaa}.x.v`sarapVoc[aa`Z%W_UaSaQYPDHIFaEaCa@B98\6a4a3]2%F$a"(Xaa_& 8 aaaQ Taaa. , `aaV Naaa:$^ a!!!a"""[###(((O***a)))222111\222a333a444]666UUU999?:::a;;;a<<>>LDDDAAACCCWDDDaEEEaFFF_GGG&LLL7OOOaOOO4SSSUUUSVVVaWWWaXXXaYYY1```.```accc@hhhgggLhhhaiiiajjjakkk<ppp"ppp^qqqasssasss[(((;JyyyFzzza{{{a|||a}}}FI5>aaaL a(ʅ###hayyyS&&&xFa% ( #$  !&('st&p ic`bS T%S"N UEC(B; 5#4$1 ###  & (' & $$$( %"""" ,,,+++...111 222#333$111 ===<<<(;;;333CCCEEE EEE&FFFMMMNNNQQQUUUUUUVVV(WWW]]] ```bbbUUUhhhjjj(jjj ```%%%V zzz{{{(|||( k(ǁhhh   M;$2Փ HMXa%P^tkEEх=GWEēP 6̅?2>x8Mc0}*jW.}ȅ-ݙ7 *01(-70* &1 6+mt#s2p#`ed,c.bU T1R&IUE1B78613(5 .'(&1"0* %2!6+#2# ,.1!!!& ***0+++7@@@1112221333(111 :::;;;';;;1<<<CCCDDD/DDD+DDD UUULLL%NNN1OOO!UUUZZZVVV6WWW-fff```"___2aaa$ffffffggg+iii.Gsss&xxx1(  /+XXX97-"2$337$ [a+\aaa/<aaaV(aa 1aaa[ SaaaCGa~LvMsarapanLqge^caaa``^&WEUaSaQaPRJ F1CaAa896a4a3a1W0'(W&a$a#a!;0aaa[  SaaaFDaL LaaaN^aa`)C a!!!a"""a###S )))0***a+++a 1119222a333a444a555X777::: :::W;;;a<<>>a>>>=DDD.DDDaEEEaFFFaGGG[HHHGGGKKKQMMMaOOOaPPPFWWWCXXXaYYYOfff___J___a```abbbabbbNjjjeeehhh^iiiajjjaPPPxuuuTvvv ԅ444faaaXR-aańEEEa#&&& J!!! JaaaO7a[Oa@Saa+Ja7!`^ a[Aa=.^aa  __!9aH3aGVa Baa~Lw)uarNooPmak(fSdac0`(_a][]XVaTVPQHNaL4IG3D`Ba@a&u8a5[21@0a.=*<'a$@"![ a __!7aI 2aHVa @aaL & aPNa)Pa2&a[a V """F###a%%%4+++)))2+++`+++a,,,a ...111a222\222444>666a666@:::;;;;a<<>>[???a???@@@CCC^DDD`FFF#GGG7GGGaIIII MMM/MMMaNNNJNNNPPPTQQQaRRR Y+++cWWWaXXXaYYYO^^^$___a```QaaabbbLcccaccc+gggPhhhahhh2jjj%kkkauuuavvv7tttzzz1###]>a@j^`#5BBB|zzziaaWZZZfffX#aRLa.6aa[Ha@#a[Va+a_Ea,9a7a_DaOaa -aC`a Wa-aB^aP~a}LvNsas"n$makNfeacX^O\a[#WBUaR.QNaLZFNEaBP&ց'5b4:1 /a-_33(^&a& "6a8,aC^a Wa-aD^aOa L Ka%!aNa[La#? a"""1%%%$$$a%%%Z(((K)))a***O+++a---a '1116222a444:777 666a777`333333:::^;;;a>>>>>>6@@@aAAA9DDD)DDDaEEEFIIIHHH^IIIaLLLLLLTMMMaPPPPPP*BBBoXXXOYYYaYYYO^^^K___aaaa(aaadddaeeeQggghhhaiii[lllI666vvvawww\yyyH{{{aIIIn郃= a`)aFrrrpaU[aaS TaHa(aTTaOa[`_Pa@&:Ca+aX:a8Ea-aa A'aa 9a9Zaaa#aNL!~a|L uYsaqlkaj[f"dacO^D[aZ.UNTaS$NMaKaMz20,c.a,a(+ 'a%a.!,aD8a9Wa aa!aPNa L Waa[!aRAa0K a '$$$ %%%a333)))E***#+++ ,,,a---a 000B222a2220666_888a777<<< ;;;a<<ggge___owwwaqqqzzzC{{{$xxxAAAV_aׅA4acccQkٟ1Ra(aI Ia(Taa_Ka[F+a@Ca+"aP2aAOa&\aaa Ca2 Sa! aaaX}!}azL saraqkkaiaqe+caaG [>[aY8UXSaRUBj $j+a()&a$_%aMAa2 P a " aaaY a L `aaa+aJ;a: U!!!a""" %%%a&&&a&&& +++ ---a 222L333a444)666Y888a:::;;;;;;a<<<_@@@#AAAaBBBODDD@EEEaFFF5IIIPJJJaJJJ%JJJ MMMaOOOaLLL ׅOZZZa[[[O ____```acccbbb eeeafffafffggg(iiiajjjJlll8nnna ---xxxayyyUUUYa}  Ra1aAAa1^a aaa[+a@Ca+#aP1aBQa$[aaa Ea0 Ra#aaaZ}!|azL saqaoljahajc-cabF [=YaW9TZRaO .ʅ+a)&%a#^%aNBa1 O a # aaaZ  a Laaa-aI :a; W!!!a$$$ %%%a'''a)))+++ ---a...a 222N333a444'888X999a;;;;;;<<Ra3a@ @a3_a aaa[+a@Ca+aW 9a9Ha+aaaa <a7 Zaaa #aP}!{ayL rZpapmiag\d%aa`L \EYaX0SQQaP"G7g[)a%)$a#a-aE9a9 W aaa aP  a L Zaa\#aP Ba 1"""N"""a"""%### &&&a'''a'''+++ ---a///a@@@333E444a555.888_999a999>>><<>>a999BBB)BBBaDDDFDDD9===j}*IIIWKKKaMMMUUUOOOaQQQM[[[a\\\O ```Wbbbabbbeeefffaggg]jjj"jjjakkkQnnn?oooaUUU?mmmgyyy{{{c_abuuuk>>>(((ddd}Ra+aH Ha)Waa^a[+a@Ca+a_Aa/=a4 aaaa 1a@aa \a,aD}!zaxL qPoall hafPbaa^U YMXaU&QFPaM+I<@ =a;a%g'+#`!a 5a;.aC _ a\a)aD  a L Pa! aPaX Ja&!!!D"""a""".###'''a(((\+++ ...a///a@@@444:444a5557666999a:::a666@@@===`>>>a@@@ @@@2BBBa,,,PiKKK_LLLaMMMOOOYPPPaPPPSSS)>>>z]]]OaaaMbbbabbb%ffffffafffSjjjkkkakkk[nnnGpppappp)666aŅlaa^i***\aRa"aR PaLa%aSa[+a@Ca+Qa.]`aW7aBaa a^/aL;a@Qa"z!yaxLp0namHihHgae.mTa)"=Y`W^QQ%PaNQH"j ̂>@ <a:a#E!a8Waa^ , a P;aANa#  a L.aKFa.Xa'`^!!!####a$$$R&&&%%%>'''a(((:+++ ///a000a@@@222555a666Y9998884999a:::F>>>D>>>a@@@:9 KKK+KKKa>>>\#OOO8QQQaRRRCUUUTTTM]]]Obbb-bbbadddKbbbfffEgggaiii1fffkkkWlllammm(mmmooo_ppp_uuu ja9994aF ^a[>>>`_)aRRa\a aZ+aMDa1a[+a@Ca+ _aaa;Haaa\aa <aaa_YaaaNz!xawL o lTkajahafVeXaV1`NN$@ ;a9ak# \aaaG<aa a _XaaQ  a L SaaV'`aaa2 ###N%%%a'''a(((Z)))+++ ///a000a@@@ 777E666a888a999a:::^<<< N  MMM QQQWRRRaSSSaTTTa''' ^^^O^^^cccSdddaeeeafffagggWhhhlll%mmm`nnnaoooapppaqqq4attth[[[zaa^^a9aaa`!Ra-aaaa)RaaaWa[+a@!15<#(<31#;6 0>,zx1u&ij.i>f0 ?XUT@:181"2=)" ; 6  0>-  1 &.>07:  %%%*&&&='''2''' +++00012221@@@UUU888&888<:::4###Q RRR TTT0SSS>VVV-*** 888<[[[ddd-fff>fff0ddd lllnnn6ooo;ooo ʅ=1ppp7ȁӀ&<4/1 ;7)189->0 1.1 *̅>kOez[#dx|VlV+lrZWCDž,1Յ  ہO !og ܅,%̅;Sgr# >IQe_{t|X g}-  hU!qDą,\`lT߅ +s)xȂDž@#rT! !  I/r}!yfcc`ȁMF @!i! ######?݁@))))))+++555333333:::===!>>>k:مoXWWWZZZZZZ xiii iiibbb6ԃwww||| }}}!|||!! ! !! [a#aa)_aaHKaa_&& a^~a{ c;caaa_a]:FiNYKFBR@a w'&&`a  \aaQ Caaa 2 YaaV9aaa; T!!!a +++a,,,a---C999333 222P333a444a666^777 UUU===I>>>a@@@7V݁WWWaXXXaZZZa[[[2(߁\\\<333qqq\xxx}}}Q~~~a}}}. Naa^"$^aK_a\aaR  [aXaWVa%`a 3aOPaa\aa __2Na44aN3aYօm*2S~a}a{ƅ AdcaaW`0^Y=z փ~NaLTUE CDAa@a$A#r-[+) &_ e %Waa [a6Ha@(aS1 \ a Ra=BaKaX0Wa  G!!!a"""D_,,,1---R---a000+222>333a444I5555666a777\777333<<<<>>>a@@@7 BBBeZZZ1ZZZ[[[[a[[[مw HHHQvvvawwwVfff|||C}}}a~~~a.;aK5a]^a4La7TaaYa8FaCNaaPa??aO Haa%Waa )aaO'`_^aAa\aa3a=a[[eV΁&^~_|a{ dH]eO0]+# MTKaIED]Ba@\>a=+Ʌw,a+''0%a#@ \aa '&aI_` Qa 4 a ?aT[aEa+ G V""" a!!!]&......a///T666222_333a333 666F888a888)999<<>>\???a@@@7b@@@bOOOaPPP\ą|ZZZ1\\\a]]]?@@@ g___lrrr]tttvvvOwwwaxxx yyy{{{\|||a~~~\~~~a.^a Fa*-aCa\2a]aa#aL_a '`_^aaWYa^a\a% 5a\aa Ra\aO7FRaP6aa Ca/aa  aن | }X{a{8KZ v\Jm GJJaH-E(CNC@@>a;+z+a)7&@$a#2d aa 6a;Xa_a# a N)aHOa!VՁ !!!a"""Tv000 ///a000a+++000333a444] 7777888a8889<<<<<>>4@@@aBBB7h CCCIPPP NNNYOOO&RRR(%%%ZZZ!\\\a]]]O```555333eee`qqqsssasssT vvv@xxxaxxx0zzz!|||Q~~~~~~=a.a_7a:=a5`aC? ^a3a> Wa7FRa&aK Ka&+LFa% E= aa \,aO Ra  aaMa'^aa+-yl3|Xzavt5ZYa م rIaH6C >@=a;++ +a(A%J#a *e!aa ?a3Qa aa  a W 3a@Ha,2333)!!!a###Lo777///a000a555 222333a333U 9990999a999B<<<@@@@@@4@@@aBBB7IIIPUUUUUUQQQ(IIIh [[[]]]a^^^YRRR Okkkoooappparrr&sssauuuL www9xxxazzz9yyy~~~=a.aX0aDGa-[a ^a@<a9+ۅ (F$O#a!&| ga a Ca0Na% aa  a [7a<Ea1 """."""a###H###<000a111a333444!444a444Q999-999a:::F@@@4AAAaBBB7΅%%%~QQQ(SSSaȄ<\\\]]]a^^^^ |-jjjakkkajjjrrr oooapppapppsss+sssauuuHwww6yyya{{{==a.!aT-aILa)Wa^a@a3Ka+Ra4a??a4Fa%aa aORa aaE+a*`ah{-ƒF>a6QzXxavr1oam@ ʅU[Xa<tzHaG1=@;a9+ &<$G!a -`aa a ;a7Uaaa  a R0aBLa'҅P"""%"""a###N'''@Džj000a111a999222444a666Y8884:::a:::>@@@4AAAaBBB7QQQ(SSSa )))444\\\^^^a___UG###IIIb kkkYkkkammmppppppaqqq_rrr$tttauuuPxxx=yyyazzz4=a.a[3a@Da0^a^a8a:Ra!Ra-aFFa+Fa%aa aORaYOa1aaOل>4a= haxavq(namH#YWaUTӁHKFaE)=@:a9+= '2#>!a 4aa a a 1a?]a^ a  &| (aITa"""###a$$$U'''H 111a222_777555a666`999<:::a;;;5BBB4BBBaCCC71SSS(TTTa$$$tZZZa[[[]]]&^^^a___KgggfffagggO kkkPlllannn!ooopppaqqqVsssuuuavvvWyyyEzzzazzz,=a.a`9a5;a7aa ^a.aBZaRa%aNNa#Fa%aa aORaՅm}O%aLą&[aXЅ0vavrmak^f fQnR>:z4G H]F`E;@9a9+ok'a'!# aQ%aM a a aYaW D cd Ra^ `\M ###]%%%a&&&+++ (((\$$$iuL111a222DUUU666V666a999999:::V;;;a===BBB4BBBaCCC71: ZZZa]]]5^^^B___a___/gggfffagggOkkk5mmmannn@ppp8qqqarrr;mmmttt\vvvavvvyyyyyy[zzza}}}=a.Ua TaaT"aP^aa[aYRa `_ ^^ Fa%aa aORa-a[aauLa\`1varmCkai]g_eaӅ#H]FaEA:@8a7+i y&k+`)\(a&J$Va[aa  a a La\`a, PB o^a5ޅ>###7%%%a&&&_'''](((a(((A///a000[111a222`222777+777a888`999\:::a;;;MBBB4CCCaCCC7NNNh\\\a]]][^^^a___[cccggggggahhhOjjjmmm]nnnaooo[pppaqqq_pppuuu4wwwaxxx_yyy]zzza{{{D=a.(a`\aOQa[aa#^aIa\_a/Ra>a^^a8Fa%aa aO;FBΧ=RI %8PKP;lJs i3gOfMd/y] HPG4I9.7F6-'+J)Q':$#<RI! FF 7P[{N/%%%,(((L!!!\  U000S111H111777%999I:::Q;;;;<<<BBB%CCCFCCC'=n^^^]]]E^^^S___C```dddgggFhhh9nnnoooBpppSqqqFrrrvvv+xxxKyyyPyyy6qqq,F!$IR;;QJ#DF 6PL+;F1N12FFF9օ1ƅF &uʅ-ml]kwR;҇Cލ8LN8Ђ Q `3ih(O; 5a5O lVӚ z*Zc>j= ib#`JpԌ}--|ˁ̅A6 s6$@ބ#5}g D Iv tfQP fNU63 Sʅ2  Lu  ---((( $$$999CCC @@@ DDD@@@QQQXXX fffbbbUUUfffhhh qqqffftttxxx      Ta H``ErA_a1Àa}.+sgeQcaa^`9UR SZQaSJFLEaC_A@F7*6[4a3Y0 ) 'H&`$` 'UaMфC a"""1&&&'''W(((a)))b555777V888a999\;;;0CCCBBB^CCCa@@@JJJIIIQJJJaKKK^LLL8@@@NNN TTTYUUUaSSSWWW[[[L\\\a]]]_^^^@]]]bbbfffSgggagggppp mmmHnnn`ooo`pppFmmmxxxKyyyazzz%A_aL Ua\1]aQa^: Xa La_A)[aY!!aR=a7SaF``GCaaIaPQaB#=aSMxlug}.w1me^^dadIaX_a]/QTKRaPaMGQFaDLAT@a=988a5]3F1_0a-)F'a$P#Q /]`F0a a"""1$$$&&&G(((a)))a)))X555 666_888`999F:::[;;;a<<<$EEE AAARBBBaCCCa@@@IIIIIIYIIIaJJJILLLXMMMaNNN-KKKSSSITTTaUUUaSSSZZZR\\\a^^^L^^^T___a___9]]]eeeBfffagggagggmmmFnnnaoooPpppQqqqaqqqEfffwww8xxxayyyazzz%:aTLaP]`F[a$ QaaYaIWa0IaaOaMTa<a]F_a[aR+aa7@aaDaPaEWaaa aY\`ޅ8 __ Wׅ:}.mvYsa 'h%eabK_^a\W@W$U[SaQaPaMGFaESUD @`>^68G6a3)19/a-:*(a&[$(!\ daa%S.aAW]Oa a!!!a"""1%%% &&&Y'''a(((a)))a))) 7771777a888?;;;#;;;a<<`NHkak8g;dac7@]a[aZ UCTRSP^NaMG.FaD@>W=a:8[2e V0".a,P(%'a%Lm݁^)Ga% 9Da.. N @!!!a"""1%%%='''T))))))X***a)))҅K555G888a999+<<< <<P<a(0[ C-a+Z(.&a#Dm݄HFa %}Na&ąa!!!="""a$$$1&&& )))X***a))){z*777Q888a:::#;;;<<>>a@@@ BBB@@@DDDaEEEa@@@IIIEJJJaJJJ/ OOO\OOOaOOOUUUfffVVV^WWWaYYY[[[:\\\a]]]8 ```Pbbbaccc#ggg]]]hhhRiiiajjjlll.nnnaoooD sssGsssauuu.xxxqqqzzzF{{{a}}}%~~~#aQ;a9Na&aaaaCa/ Ya[a7a; Pa%aaaZaR 7a7Ra.aDDa.Ua7a<Aa4bGKaH6aۄCHu"sapTl)jahIdLcaa)[WYaXN^MaME@DaA3=K,a+a'4%a$>{Fa %~Va a!!!="""a$$$1***X+++a...s'''q333[888Y999a<<<===`>>>a===@@@DDDaEEEa@@@JJJLKKKaLLL)OOOWPPPaRRRVVV^WWWaYYY\\\A]]]a^^^3bbbKbbbaccc+hhhRiiiajjjooo4oooappp>tttAtttattt4zzzF{{{a}}}%+aK5a@Va `aaaJa)Ta"[a>a5 Ka+ aa aaaR7a7Ra4a?>a4Ua0aBHa-[!9LaN;a}A_{ay.srapYk/iagBcFaa`.[]YaXN^LaME9CaA9<Q+a)Z&.$a"D+Fa %8Oa% gl."V###a$$$1***X+++a...000333a444[888R999a:::#@@@>>>a@@@ @@@EEEaFFFa@@@JJJFKKKaKKK.OOO]QQQaTTTWWW^XXXaYYY[[[9]]]a^^^9aaaQbbbaccc#iiiRjjjajjjooo.oooapppDsssHuuuawww.{{{F|||a}}}%$aP ;a9Oa%aaaCa0Za[a8a; Qa#aaaZaR7a7Ra-aEDa.Ua 'p|*Oa"RN*`aVCaCNk|=zaw.rqao_ j7hae8c>>a???a@@@@@@EEEaFFFa@@@KKK>>a???P@@@FFFaGGGa@@@KKK(LLLaMMMKOOOQQQaRRRW@@@XXX^YYYaYYY]]]^^^a___U``` bbb`ddd^kkkjjjRkkkajjjmmmpppaqqq[wwwpppttt\vvvawww{{{F}}}a}}}% _` Ua1aA aPaa%aMaY[aaU `^Fa-5a;aR7a7Raa\[aUaIaUhф@aSMaO{=xaw.p5makWjJhafWfj_[]a\IYXXaV.J.@U>a>L;T9a9:Q$!J aP{F a %[ _ ` F [ a6 !e!!!h&&&1,,,X---a...555444a666[::: ;;;_<<<`===F>>>[???a@@@#@@@FFFaGGGa@@@UUUMMM[NNNaNNNIOOOXQQQaRRR.XXX^ZZZaYYY___U```aaaaLbbbTcccaddd:kkkRkkkannnpppJrrrasssPtttPuuuawwwF{{{F~~~a}}}%>aTLaO _`F[a%aa[aIWa1[aSaMSa:a]F^aaR7a7RaIaPPaFP[z @^`K y9x[v+n9k]iagPe^\P[aY^W9U x[5>K<`:_8A7 #E_b V [ " S3 S a \2L[\F%%%[&&&.---S---[111333555[666V;;;<<>>a>>>\AAA0DDDGGG[HHH[IIINNNOOOPOOOaQQQ^RRR9UUUYYYXZZZ[ZZZ\\\```Kbbb`bbb_eeeAmmmkkkMlll[nnnooo sssFsss_uuu`wwwFuuu }}}A~~~["?^aL Sa\2[[Oa^;V[ K`_A(YaY"[M3[3M[E_`H {'UfU@ȁ?@+ZieqOL<<<UUU@@@jjjUUUqqq:FdIx bP҅"Q \E)tf !҄+5:]? oڈąH~0qhF3ч6zbK0XW Lj>;h 6ąIP)y-EՅNu_lUه@}1NO2CSF*LQ8 ?}SyIx= *^_mmPOHMSKAH M҈ՂCS bR9 T !IR=#X:"""###G%%%S%%%A&&&  ,,,5---P...M....P/ FGGGBIIISIIIGKKKRRR RRRSTTTX]]]ZZZ>ZZZR\\\I\\\!bbb dddIdddSeee>jjj nnn!nnnXooo<uuu9vvvXxxx!{{{+~~~L~~~Q7 @SH#JR<;RK%JX7PL+FSC1NO29X$MX 9QL)GSC8a^^a<[a[a_.a_]M8~V}a{[yaxaumCkaiaj T)OP`NaL[JaHY@$&m/a-[$[a[ aaHT a [ a a  aa[aQ\a@!!!###`$$$a&&&[&&&a'''Y[+++A---a---]///_000a)));222f888 @@@>>>R666sIII[JJJaKKK_KKK@@@RRRHSSSaTTTaUUUYYYSZZZa[[[[\\\a]]]a^^^cccbbbadddaeee[fffagggSooommm\nnnapppCvvv%vvvawwwaxxx%|||/}}}a~~~_\aCVa[a`#aa[aNMa[`a$:aaDa]_a0_a[a\ 8a^^a9#aa(>aa Ia\`a*`a[aZ^_ __ /aB5a=]B&~a|LUx)vavIpmPkajahaj ΅-|UPDNaM.JJHaG)U@,>[ ͅ.9Ut/aC 2 zZa#a N ) a K Ma%Ra 1]aa@"""A###a$$$0 J,,,`---\+++ ,,,000`222\<<<7???#???W@@@a KKK2LLLaMMM@OOOQQQSRRRaSSSaTTTaWWW!ZZZaZZZN@@@\\\&^^^a___L bbbLcccaccc%fffQgggaggg"kkk0mmm]nnnaoooapppCqqq uuu?uuuavvvawwwaxxx%|||Y~~~a}}}[a&aL,aFPa"TaaT aRKaaaa[a[;a8Aa2^_ __ >aaa(Maaa aYaWAa1Da. aQ La#Ka&awf_wCC/`u vasajnIm\k/iagaj ~SP_NaJI+FaED@ @a>Q.)#SMJa& )?a/> a 3 aa aa4a;aM>a@} Մ---a---H222R222at ;;;S>>>[@@@W@@@5"""JJJJLLLaMMM]QQQRRRRZTTT2TTTaUUUaYYY>ZZZaZZZ3```^^^a___afff^^^bbbadddaaaaggg3hhhaiii>llllllammmNnnn>oooapppCttt+uuuauuu?wwwMxxxazzz%{{{}}}a~~~Z#Ca,Aa0 aa aa7a88a<`a @^2\a,aEUaWa!aN aQ La#+a?Ma(F]0_a /aB [a]a%aJ*aI Ea-Ua" yuasaon)f `hafaj /QN NaMaJ I$FaDOG>*@$2 ")'a%Y߁ Sa  bi2aU7a9I a + aa a`-aF %+aDE---a...@111K222a 7999_???.FFF@@@===fGGGRIIIaHHH!MMM MMMaNNNaUUURRR,KKKPPP TTTaUUUaZZZGZZZa[[[+___````abbbaaacccaddd`hhh+hhhajjjGjjj mmm&ppp+pppaqqqCssswwwwwwFxxxazzz%{{{!~~~a~~~R N a '`aa^*aJ a1***a+++T---6...a...=111I333a333)u f222nIIIaKKKNNN NNNaOOOaLLL XXX UUUaVVVa[[[K\\\a]]](```^aaaaccceeedddaeee^hhh'iiiakkkKppp+qqqarrrCyyyFyyyazzz%~~~&~~~aN9a;Qa$aaa[-aGEa0Xa[aaPޅ4DaF8a م6}pan1`caaa` YÅfJ/HaFRF1C\AaA7!4a $X#a"aU  a [1Sa(DaH6aZ  a _2Na7L*8aN2__ /&&&.'''a(((R***1***\+++a...333000W111a333;333B444a555J===nLLL\333D(((}OOOaRRR(XXX WWWaXXXa___ ]]]a^^^____2```Nbbbabbb4fff7gggahhhNiii2jjj_kkk_ooo rrr+sssarrrCzzzF{{{a}}}%HaF8aWa^1Pa.=aL4a^ _a4Ka;[aYa:DaG&aV0YaPaA>aPFa(^a \a6HaA,aT1[aVaaX Qaa\#MzqaoIp`caaa` g@HDFaDaBaA.4!3an#a"9aU 3aaaC Qaa\  ) _ mJaa_&߅0'''C)))a***a+++a+++1///111Y222a333a333U7778NNND```XXX WWWaXXXa___(____```aaaaabbbK^^^fffgggIhhhaiiiakkk_jjj)rrr+sssatttCzzzF{{{a}}}%Raa[+`aaG Laa^$#^aaN [aaS=aaa8VaaXFa(^a \aaQ @aaa4;Ӂx-amqrU`U  FA@ ;3/ D #  #r  8 z''')))+++ ...555444999#V:fffYYYYYYaaa```UUUhhhjjjfff xxxsssqqq  yyywww     "ZS Krx(5ӈAȃ/q gjPwa+V @߂%H5L֌{Cq"Kt(=$<_ez*RN]x{+r}-Ɓۅj S ZGY]Hl;Q]*H[[ NzdI$C6 υ.rz7y-b [B[6vB6FI?@<?+2+/>,3, $!5<&`?3AR!i.Cn}$$$&&&&C0(((%+++555*666=7773999@@@@@@; III5IIIMMM QQQ2RRR>SSS+IIIZZZ YYY;[[[8\\\ddd bbb0ddd>fff/```hhhmmmAnnn8jjjvvv-vvv>www1vvv }}}6<#)=3:C&<6 2>+ ;8,C8:->1 >A)=48C%<6^a+-a-zax_tq +"U\I[aYOMPSNaMaKaIVM?@=a<[1J0a.a,a+[& #!^ aaa?$sR5a b5aana*"""%%%_%%%a'''(Ņ1116555J666a888a999[+@@@4@@@a111s:::oIIIaKKK^MMMOOOPPP[QQQaRRRaSSSaUUUKYYY4ZZZa\\\a]]]`___"]]]bbbWcccadddaeeeafffSjjjmmmGnnnaoooRmmmsssQuuuavvvawwwaxxxX{{{ {{{~~~_~~~aaa8Iaaa[3aa>aaa_ZaaaL2aaa`$aa&aaaa-PaaaX *d&^^I\a[aYOP(PaNLNIFHaF1C@C>a<a:[41a/S,,<)a(="I!a2Dޅ _ _ ԅ eigX333###)$$$Z%%%a&&&a'''(v 111_555 444555a666U = #;;; >>>;@@@aAAAaBBBaN...GGGaHHH[JJJIII2KKKaLLLIPPPSa ^`'aU.\aaWa"a\%aOAa7 @aa^aW9aA9`aa `]0aL&aSRa+BaF_a҅8v[Yr s dH_`]_[FYaXOPJNaM&JHaFS?W>`>E;a9[ 2A0a./-)a'_& !aab>)a GL ###)###a%%%R%%%S'''a'''('111a333,n t>>>K???a@@@DAAAaBBBa@@@ FFF2GGGaJJJ>UUULLLaMMMaRRR PPP\QQQaSSSUUU.UUUaVVVAXXX)ZZZaZZZG^^^[___a___bbbScccaccceee%gggahhhMkkk]mmm_mmmFoooapppRtttGuuuawww)xxxyyyazzzW}}}~~~a~~~_Ca,;a5 a`HaDaa2a>aa \a.aA&aJ Xa3aOVaaV La#Ga,aYQaDa^;a5 a`EaDaa /aA_a #.Ca+5^a WZ^DZ\YaWOPVNaLIFaE^AA<::a8[ 1K/a-&-(a&a$"!aw&1 a: L4 a ?p@###,333%%%F'''a'''(ׅ===;BBB@@@BBBaCCCa@@@GGG=HHHaHHH6LLL\MMMaQQQKKKQQQaRRRaUUUTTT%VVVaXXXLXXX3ZZZa[[[? ^^^Q___aaaa"bbb]dddaeeeeeehhhaiiiWlllClllpppoooapppR vvvSvvvaxxx vvvzzza{{{`~~~~~~aW:a7Fa-aa 9aa a^Fa-aa 7aa :a9YaCa+^a<Sacc]YXaVON_MaIJ FaDa@:9a8[ 0T.a-'`%a"! aUم- 1 a:ȅ> a 7 Ma)/e3an'''F(((a***(+@@@BBBaCCCa@@@GGGHIIIaHHH.MMMUNNNaOOO QQQQQQaRRR`UUUTTTVVVaWWWWYYY a 8 Ma(h1a y'''F(((a***(8T ;;;a<<a7Yaaa  a^Pa%\aaa Da1Sa!Ca+^a &18kajahdcaaQYVaUOMVKaJFDaB]:6cށ ~,(, %a#a a^; 1a:-` 4 a > Ua Ra(aO(((F)))a***( y666c999)777 <<IIIaKKK5NNN^OOOaPPPUUURRRaSSSaUUU WWW'WWWaXXXKZZZ1\\\a]]]> ```Raaaabbb!ddd^eeeaggggggiiiakkkVpppqqqasssR vvvSwwwayyy www{{{a|||_aW 888a9993999<<>>\@@@DDDaEEEa@@@JJJ2JJJaKKK@KKKOOOaQQQSSS\TTTaUUUWWW1XXXaZZZ?ZZZ&\\\a]]]I```[bbba```eeeSfffaggghhh(jjjakkkKpppqqqasssRwwwGxxxazzz*zzz|||a}}}T}}}a^Fa);a6a^aa1a@aa\a1a?&aL[aLaaW Pa Da-aW a^;a6 a^aa /aCaa Ca+ ^a Qpcv`v#s4qaoMj2haeKebRaa^!UTaRO J&IaHRDDLBa(@e!8Ha<k1a:] ]a  ^ a $4aMn +++Ya{R ***F***a,,,(GM222#333`444]666888999a:::Y;;;<<>>8@@@DDDaEEEa@@@JJJKKKaLLL^MMMNNN;OOOaPPPDRRR:TTTaUUUEWWWWWWWXXXa[[[ZZZ]]]^^^^a```%aaa2aaaabbbPeee/fffagggLhhhiiiQkkkalll&ppprrrasssRxxx%xxxayyyTxxx{{{J|||a~~~/Oa6"`^aZBa;aaa_ 9aF8aFVa]a&1aPLaWa.(aY!aTHa2 a^a[@a<a  `_!8aGCa+^aw.vasarap\lhgPeacaaa`I@USaQOUHIFaEaCa̅" Za JB , aaa]:#_v***F***a,,,(///111\222a333a444a444+999@:::a;;;a<<>>V<<< @@@EEEaFFFa@@@LLL4LLLaMMMaOOOaOOOZRRRSSS UUUTVVVaWWWaXXXaYYYC^^^+^^^`___a```abbb^bbbkkkgggNhhhaiiiajjjakkkKmmmpppsssasssRqqqyyyHzzza{{{a|||a}}}Q\aaa.@aaaW a4aaa[ SaaD)`aa^La!^aa`&FaaS a^=aaaWaa 1aa[%#%wts.r+m ]d#b1a!@U R%N@E E1C$ ` (7.+~?0ӃŃ)))+++%...333 111)444/555:::<<<1<<<'CCCUUUEEE%UUUMMMNNN0NNN)OOO PPPWWW%WWW1WWW]]]```.```+``````iii"jjj1lll"fffuuu rrr%tttUUU{{{ |||1|||% )01'%/) %1.+% +.1%%#0'%%/) V3ÅC߆Nt_yԅ8S8y)l'υ&!~Mah/~ׅ́1I-х_WA߄с"(nqS`F TP9Ѕ" f  Ӆ8O-&yx(wf bB9UDž V>]+ (.#o  . } !&. '''&&&'''()))///000'000 666999999.;;; BBB BBB%CCC#EEE UUUJJJJJJ)KKK@@@UUU+UUU(\\\^^^(]]]UUU^^^eee!eee&gggpppnnn(pppvvvxxx#yyy.www&!(-#) #% . '(#.&!(-%.. 9aR{@yaxavasItC[YTWaT4wF/>,<a+)`(a%fʅ  ] aL^ @uK Xaa`'Ra 4""" %%%S&&&a'''a(((a)))/---)///`000a111a333V333999999_:::a;;;@@@!AAA^BBBaCCCaDDD[DDDUUUIIIGJJJaKKKaLLLaMMMDfffTTT5UUUaWWWUZZZ=\\\a]]]a^^^a___K[[[ccceeeWfffagggahhh`hhh'nnn1nnnaoooapppaqqqRvvv wwwyyyazzzaxxx )`aaWLaaa=?aLEaaaE[aa^ Va.Vaa`*1aaaS aa &_aaXLaa>>aOSa4_a3aaR|zaxVv$tOsar*3t]YIXaVaT4'CaD C =^;`%:']y  S aa yV  a W/Fa>+a\Haa 4%%%:%%%a'''F(((')))^+++`--- (((...]///`000)000@222a333B888999W:::a;;;a;;;III???YAAAaAAA0CCC8DDDaEEENHHH%IIIaLLLRKKK$MMMSNNNaOOO$MMMTTT1TTTaUUUaWWWU\\\\\\a]]]Y]]]%___N```aaaa-dddCeeeafff@ggg*hhhaiii]qqqkkknnn`ooo]ooo&qqqFrrrattt9www#xxx[yyyazzzaxxx yyy]`*?aE.aM%Ya8aaL!aT$Sa$Na90aXKaa.Ca@)`^ `^&Da<#[aa \a+>aG-aN%Wa8aaOHaa4WaaYaUaR zCyay-rrapQp ib$5[_Y_WWUaS4ׅ'A3?-<a:Cą]+ a [ [aa B a -aT a`Ha'_`W a"""4 %%%^&&&a'''(((<***a,,,3...*///a000E000333`444`333 8884888a999Y:::^;;;a;;;AAA!AAAaCCCO@@@EEEYFFFaGGGIIILJJJaKKK$MMM&OOOaOOOKSSSSTTTaUUUVVVVaWWWU\\\@\\\a\\\0___```abbbTfff eee`fff`jjjiiiEjjjakkk*nnn6nnnaooo9rrrsssasss^www=xxxayyyVzzza{{{a *aF`a Ua0aBZaVaLIa'$aKaZNa!`_Xa. `aDa-3a<a_=aV`a 'aH_a Ra-aBZaVaO__Wa41aZ]aV"aR zPxau"qqao_kja ~YHWV:TaS4 i?d@@<:;a87rͅ 3 <  OaFa + O a "a`aY ;a5J!!! 7 a"""4 $$$ %%%a'''a+++0+++a---A///8///a000: 333Z444a444888;9996333;;;X<<L<F9a8- j <a̅KOaG<a 6 [ a aa %aN0aB 7!!!a"""4$$$'''a((([***',,,a---N///F000a1110444R666a666$<<8a64  #a 3aOa'aKD a . R a  aa aU8a8"""7"""a$$$4 &&&'''a(((`,,,/---a---D000<111a2227555Y666a888<<>>a@@@BBB2CCCaDDDA HHHMHHHaHHH%KKK^LLLaMMMNNNQQQaRRR[WWWXXXaYYYU ]]]R^^^a```!___ bbbadddajjjeeegggahhhX lll8lllannn;oooFpppappp,uuuavvvazzz|||a}}}a ;a8 Vaaa aRaL [aa\$aNAa2@a.aX 5a=Fa.aa^a 9a:Vaa aSaO:a4Uaa~R vKuas&pmakXhfae\a  o@7>a=9:68a4;@!a *a^OaaSL a $ H a &a[a\@a."""7"""a$$$4###(((a)))a&&&,,,7---a---:0003111a222>666`666a999<<>>a@@@BBB)CCCaDDDJ GGGUIIIaHHHLLLTMMMaMMMPPPQQQaRRRRWWWXXXaZZZU^^^H___aaaa)dddcccaddd[dddgggahhh_jjj@mmmannn1oooa<76a4Z7 1(/aw  `_ЁZOa\a _ ^ *aK;a;Va% [a """7###a$$$4(((K)))a+++0&&&---T---a111333222a333[777 555'666a888P>>>X>>>a@@@BBB DDD`EEE_GGGHHHHHHaIIIYUUUMMM5MMMaPPPCUUURRRCRRRaSSS2WWWYYYaZZZU^^^)___a```Miiibbb;dddaeee;hhhSiiiajjj'kkk lll[mmmalllqqqqqqasssUsssttt0vvvawwwH}}}a~~~a a\ $aS>a;Ka)aL2aFCa2[a__ @a.Qa( YaaV.aI^a a\ $aS;a<Ja)aO:a4Ua~a|RxrVqaoamak\m f&caaa نnED_Ba@a>a=K5G3a1a0a.` =a /Oa1aaa a 6Vaaa]&aaaaA"""7###a&&&4+++***_+++a,,,a---a///K222F333a444a666a888!>>>X???a@@@DDD;EEEaFFFaGGGaIIIaHHH-JJJ NNNYOOOaPPPaQQQaRRRZUUU WWWZZZaZZZUbbb```Taaaabbbacccaddd]dddiii#iiiakkkammmaoooBUUUrrrNsssatttauuuavvv_xxx~~~a Daaaa$\aaVaL XaaaZ .aaaa9@a.#`aaaELaaa_^a Aaaaa$[aaaVaO:a4Ua~:|1mp5nGl:lc`?M#M:MEA<@F=1333-1E/>,D (CA!/:"A C ( 5G;>E-$$$ ###:&&&,,,,,,<,,,F---1333@@@555,444D666?888===4@@@:>>> GGG&GGGCHHHAHHH!RRR OOO7QQQGSSS8UUU ]]] [[[:ZZZ3cccccc4aaaGccc;cccjjjjjj>lllElll.fffsss0uuuFvvv=www~~~::,D?9G6:- 7G9  @C)&:=E.0F=8:+D@9G6:/":3: $ԅ6fMV 6%uHy{jnpZYB @@ 40+@!#    $$$''')))...333666<<< 999@@@@@@DDDGGGJJJ JJJIII@@@UUUUUU ``````[[[jjjfff ooo tttttt||| |||      {+zaxRo.m`kajai5^[aY[Y@?S>a=%51^0a+ '%Y#a!a H:aa_'1aL3aa`0 PaaT$$$Na+ !!!]"""a (((&***a+++U111*111_222a333a3339UUU777 :::I;;;a<<a=%73S1a/a+ $&X$a#?!Oa9%aV:^a %`aLa[9 [ a FaIEaP>a+"""!!!P"""a###a&&&&&&)))^***a+++U---111a222]222:444W555a777#:::<:::a;;;N<<<@>>>a>>>WUUU@@@DDD6EEEaFFFaGGG7JJJ0LLLaMMMSNNNaY1aa:-aT;`_ )aaC!aX:]a}I|az_xavRo?mak1i%faeK`=]a[_YaX[MPPBk 4RDB[@a>_=a:%6/3`1_0a.a+ &$%a$L a^"Qa7a 9La_aLEa+ - a E a\VaXa_a+, _!!!`"""`###a&&&)))E)))a***_+++a,,,U///9111a2226444"666a555N333:::_;;;a;;; >>>L???a???#AAACCCUDDDaEEE_FFFaGGG7UUULLLYMMMaLLLQQQCQQQaRRR0TTT$UUUaVVVLZZZ ZZZaZZZ^UUU___'___^```aaaa`bbbadddggggggahhhVfffkkk\mmmakkkqqqqqq\rrrasss_tttattt!vvvyyyazzz\vvvUUU~~~T~~~aXa_a+^a La#-aCaY.__aa #aO `_Na"4a<Ja_aLBa.+aE@a_aX6a8aQ]a Ia& Ta_a: Wa@a3Oa_aCKa%4a<}_z6x!xavRnQmak!heac\^T\=\YaW[SP\M-L-JaG@D.BSA>F<a:%5F3F3/a-a+ &6$a$<\aaa % a K^3&aL Wa  a V aQ Fa)%V!@a+ C!!!I!!!###[$$$a&&&(((])))9)))+++a...U 111K222a222'888666a777_888:::a;;;[ >>>:@@@aAAA6CCCDDDYEEE&FFF8GGGaIII7 LLL LLLaMMMaIIIQQQ1RRRaTTTAUUU6VVVaWWW< ZZZ\[[[a[[[^^^=___L^^^bbbUbbbadddggg)hhhaiiiH mmmPmmmaooo ppp1qqqQtttsssLuuuawww!xxx zzza{{{Q }}}Ca,%V!@a+a^:a6?a3aa FG^a 5a?Ya_a"aM_4#aLTaaWW;aX Ha' aaa^ 7a9Z(5a:a -aD ]/+aC]a"aN|xvauR m[kajc eaca]]YXaV[UN K+IaG@@U=F;a8%2.a,a+ $@#a"4Ua aa  a U !aL `a   a _+aI >a4 @a+###[%%%a&&&+++))),,,a...U 111U222a222777 666a888a555 ::: ;;;a<<>>3@@@aAAA@GGG @@@GGG7GGGaIII7KKKMMMaNNN_PPP*RRRaRRRJUUU@VVVaWWW4 ZZZU\\\a\\\]]]bbbUcccadddggg4hhhajjj@ mmmInnnappp)nnntttLuuuawww!zzz)zzza{{{I a7Ra aa aVaL ^aa`aX Raaa aV 0aC 4a:aa%aM (aCa aXxvasRmakagacaaa\ YWaU[K+HaF@<F:a8%-a+a+ $F#a!/ Qa%aa a [!aL aa a a1aD9a:@a+$$$[%%%a&&&)))---a...U 222\333a555;;;777a888a999:::%;;;a<<!a!5 Waaa ! aS!aL _a   a ])aK?a1@a+%%%[%%%a&&&...---a...U 222T333a444 333 888a999a<<<<<<<<WWWaWWW5\\\W]]]a^^^cccUdddadddhhh2iiiakkkAmmmKoooaooo&uuuLvvvawww!zzz&{{{a|||L?a3@a+aW 6a=Ha,`a^a ;a8 Taaa aTaL]aa^aX Qa!aa aX3a@4a:aa'aK(aCaaVusasRkQiah!caa`ZYUaS[I+FaD@:F8a5%+a)a+ #5!a< _a_a  ) aI!aL W a  aT aQGa'@a +%%%[&&&a&&&...---a///U 333K444a555'777888a999^;;;<<>>]BBB>BBBaBBB3III7IIIaJJJ7 LLL NNNaOOOaIIISSS5TTTaUUU<WWW5XXXaYYY<\\\_]]]a\\\dddUeeeadddhhh)jjjakkkHmmmSoooaqqqwwwLwwwawww!||||||a}}}TGa)@a+a^ >a3>a3aa ^a 2a? \a]a&aKaLTaaUaX Ha)a`a_;a64a:aa /aB(aC]a#aLusaqRk8haf<c/aa^BSTaR[I+FaD@9F8a5%+a)a+ ! aU+aXHa + Aa0!aL> a 8 5 a< __ [a@a +%%%['''a&&&......a///U2222444a666A777,999a:::E@@@===Z>>>a>>>333AAATBBBaCCCIII7IIIaJJJ7PPPSPPPaQQQ SSSLTTTaUUU#WWWXXXaZZZUUUU[[[]]]a^^^XUUUdddUeeeadddiiikkkakkk\ppplll ooo_ooo^mmmwwwLwwwawww!yyy|||_~~~`|||Za@a+YaTa&aNaQ^a aVaZEa.>a3aL;a93a<aX/aE)aHXaRa4a:Pa%Ha)(aCDa0>>4>>>a@@@\AAAVBBBaCCCOIII7JJJaJJJ7PPP(QQQaRRR^SSSSTTTaUUUVfffYYYRZZZa[[[V\\\\]]]a___.eeeUfffaiiilllIlllammmWnnnZoooappp:wwwLxxxazzz!|||=~~~aZWaF@a+1a]UaOXaT^a%^a QaV\a1a`Ra^ aL_aRaaaX\aS_a1a]UaR4a:%a_SaZ(aCa`Ra^ rqOoCheKc\aO`X ROPJF#COB4895O4(O'O( AZT1! O \KO>  L \ N7VY=4O#'''J(((O((( ---///O000E888666I888\999P999"???0@@@SAAAZCCCCGGG HHH-KKKOLLL-PPP)QQQQSSS[TTTGWWW YYY ZZZA\\\Y]]]T^^^1eeeEfffOfffmmmmmmxxxOzzz~~~7VY=4O#/S[C  E[R+LO @YU3N\KO>L\NOHH\Q$.S[D *O/'P\H O6N\LTRUEVISION-XFILE.java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/picture.png000066400000000000000000000074071445000440600320050ustar00rootroot00000000000000PNG  IHDR@gAMA abKGD pHYs  tIME1;k:tEXtCommentCreated with GIMPW_IDATx흻n#/~33l`N8w!5`G M]fߜTIK-H g 4CRdSUNU9- A-:.11 m6;:}9#>J@NDZw$VGjxw77@8 G"S q@ߊHciZ8dbO3 ^No*Mc@AdNyXjgs|CGN |lvQoHLK ҽ{ "y^|m!$j)ֲsF̉O-@hsi"jm@$IXj{ rTh19 ;VIuhuH l zJ9N :y7v;:zAP<14v6waԎ\̅ fm`$>A+D0ilG Y(J\3%}%RHfogd<kSƑ}qZEi_ K'HAN"{+-D6yղ>`mKxq\DDZPlrH@\~GrzJ[35 ˿!<,'3آƬ@&]xE0sıNZ,2s@Wq G"H F.ѧM&K΅5"7 *y,]%Rorf:y5v*6p))GnfɽW#ALV.e}شu#elo8O G>^ vkYqzgV"R=4tT 3DXo Տct> 1~\o_ԅSv8g؁?QڠgZA)j@US5>9d!SȃF֯Z80@:r x+`ZCl2 *^9i%. εE (yw qM"=JƼ#nn-v~iqB5,ǷPzۨ= &{'E_~2^~̴t D19cWrFJ#Rx5oAs !"4Nm^ (EeZy^y8GYoyB޿z~y p2瓻yex o6&GP ~C$^>Px9ϋ_ACc$#1|/"(0e壇IENDB`java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/remarkable1.rm000066400000000000000000000470671445000440600323600ustar00rootroot00000000000000reMarkable .lines file, version=5 @*|CjnDi=vɠ?%KA,8}Cz(nDK?@%KAU(C"mD@K,@%KACmDu*@@%KABClDu@̲@u&A=s<-̍C({lD)ɷ@@^Aԉ=eC6lDA@A8@{A=؞CkDOAS@zADP>&CikD+CA?(@Ai>ͫCkD7#%A@A>C0jD*A{@F߲AU >TCjD1Ao9@CAi>uzCbjD\[@Aȱ@`lA/>C=jD\>A.@lAQ?tCjD=LA )@pAeL ?~CoiD|WAx@A?TDoiDKA?;A? DjDdLA;A/?%D'jD6AofM:FAbs&?ua5DRiDW@Al@{AwAb?9DBlfDwO=`?AL?DhfD=(@ A7??gDifD#9O>)8C@0A7J-?DxfD";J?[3@AM?dDfD~?B%@~A2?[DfD?!@ǩA>A D?gD3=b@"@gŝAv>A D?gDAZ <@oLC{Df>>p\>&A JC8|D!_@R @&AQHC|DfW@˗ @&AGC{D:?@nAMHCx{DQE?~@hA=HCL[{Dں>@:A".;ICo{D@?+>AV= NCK{D=;@@äAz=QC,{D_@@VAp=WTC{DIw@@ЧA=J]\CzDL@6f@A#> #aCzDd@@nWA%>wlCzD\@@0ACD>ZACzD}@0@Abv>C+]zDGAY@,A+>XCMzD#A@ϼA;&>AC'zD">AX@kmA>%CzD9GAP@bA>j}CyDAAa@`A%>CzDؑTA);KAvg?\HCyDSZA@Am?iC[yD_UA ;{YA &?axD*zD7]A-;jA .1?7DxzDSAt~Dc>Y(_@A\ZW?XFDg~Dl@A@uA4dJ?D}~D@D@AH?JiD ~D3@AH@A^D?DT~D@VH@ZAB@?TD_~DAG@t`Ai =?D~DE3AFH@A8? D~D}^BAdH@A 4?"D,~DMA9I@!(A2?V D~D0gAI(I@Am1?+D~D|A!I@Ae.?}D~D\لAI@mA-?yD~D-A6H@A= -?mDt~D AG@JA,?#_DcDAnNG@gA+?jFDDIϾAF@A$+(?\6DoDApF@ZA$?N DCDAE@uA?(?^CDSAMA@5ApP&?CIDtAA@A?CDWWmA@@^A?aCVODFA@@A?azCD}Aa@@A?LtCD@A@A ?kC.˅Dq^@acA@VA?gCڅD =?@@_A"?<gCۅDO ?G@)A*?iCPԅDIҜ?@dA?7|nCۅDTV@Pc=AQ ?|rCׅD@*'@A^ ?LxCbɅDw@i@Aw ?YCDA@-A ?+CD(AŪ@yA?pCuHD|CA20@IA[?"C4&D$\A @7(A_?8CTDiA @DA?6CDƒA'@zA*?^ECDA @6AU(?:C煄DA~q@AI.?fD~D A@Ax1?}"D˜DNA"<hAC5?{,DkD7A':fAV,8??DDAKd<+dA=?RDÄD.!A @AYD?_DxلDAs;AwH?^kDۄDA <&AsKM?vD+لD?Ac@AQ?ߵ}D҄DInA@K_AR?A,? _DDAIJ@\A_.?JDXۃDnAI@#A^k1?$:DCDXAH@>A~3?s.DӃDAI@A3?o`#DԃD+AH@LA$3? DD1AsG@gA6?4DDA[6G@A8?'C5DwAF@vAA?9ChDikA*E@@AQ??CˇDAtWG@YA4g@ 9AS?CLCRDYx?m=A%O?QCKD| ;@^r@A /C?sYCxBDr@ @A{ ?R_CLDH@{h=TnA>R_CLDUA=@&NC%Dj>29@uEAC ]D?-'@uEA58C_]Dh@m?FAK< C,D@MS?B\PAc=CeіD#@?QAiA=C&DT@?TAf>ʚCDo@H?EAcH>C:GD@?CA^>CDL@M? FAO>CD5@A?HAa>CgD#@rz?xJA ?C锣D@&?"LA8?CHD@8?NA+?{}CѢDW@\S? KA3?C[DB@ɽ?GA.:?'C&DQG?.?iDA-?'C&DOBA?CCDU@N]ChDzt=t@@2C*Dz@@@@CVDq@@@ CD<?Ѝ@U9@=x!C)DW?>n@w'@fS=CDD~$@=Ģ@gt@x'>C|DId9@K@5@ȑ>C%D}G@"@Z@۬>$ChD<[;@!@u@4a>ģC8DKxP@~@U@K>١C D˟h@t@@?C͑DRiw@ኰ@@F?lCODLj@ɵ@x@|?CDZ^@_@@?-CD@ʽ@:@")?CkD@@@.?oCBSDAD?CD\@U>tAE?C&Da@م>[,AIzF?CzD6@W?vAF?;C%ۑDFw@l1?^A_F?XkCDDC@"jH?Aq|F?#IC5D@Nfu?iAeE?HC#D@?A}E?=CDs@n?EYA$>E?C4#D=8@ð?/AD?qCDYn@?AD?=HC7D֢@?2A D?pChƖD@:?AC?2.C`D%@[e@ AtC?C'D*@K @AB?~eC㚘Du]@@AFgB?CAp?C.Dv@ >fAvo?CDخ@p>QAo?2CؗD 9@]?7EAn?LCWD~{@0>3A'n?SCVD@tS ?.Am?CDD@ .?$A'm?HhDʢDq@ N?Am?T=D WD0@(k?NAe]m?"DD.@?Al?{DݐDs@B#?HA5l?DD@VCtbDA3O@@`{?:C2BD@K@W@_~?7CGDD@R@B@:?τC DX@W@A@a?dC>DQ@]@@ۮ?C|DL?>l@}@?FC}$=" A~D;DB@(@" AݴDVDr@w@ A$U<.DDz?I@Ӿ#A~=fDͤDj?0z@@/%AT5;>"zDDZ@#B@}*A>TD“DgU @IV9@,A>Dn D^@*@>/A>D*TD@ ;!@{ 1A??TDD h@N@#2A ?DD@Y@4A?TD]D(A@5A.? DDA @7AM>? D8DL A@9A#L?;e DtDgf@rs@>A_u?FDDm@]>?Aru?DBDn@@`>~|@A&v?sDLDq@j=T?:Awv? 7D@"(@X\A2~?/#DbDAOΞ@HPA0~?}#D~DiA)@KA-!?#DfD%@&)@1KAvM?#DҙD]i@+@6LAq?r#DD$@4@MA?"D1뭣>:#HA&=DDpt@;@HA`==D3+Dy|>[i? TAҊ>G=D-R=D)D@?XAc?=D˖D$@`?SA`? =DDQ@*?MAZ#?A=DDm@?/MAy+)?=D D@4?*oMAF3?A=;@a>Abt`?}:DfD'=AT?I:DᅣD&6A=?@@2-DΗD=˟@@(M,DvDH@Bi@@=",DD@]@x@7y=N,DD]%?L@>v@$G>Ɗ,DDܪ?@@A>î-D?IDi@@@>.DDȊ@G@x@ ?s*0DzٕD-H@@@J?1DDQwAЬ@;@,?7DƔD<A@E@uL?/ A{?TD"Dӗ?5>W AHw?TD"DdAУk?TD"D4AK0?@ ^bDv)DFb=<@Ǖ`AaD]Dѷ@?WcAW=laDDg@j?PeAn% >`DKЗDe@U?3iA".>`DnD@?#+kA"g>. `DufDSA?QkA>%p_DxDX@@i?kA_?2_DHDwt@Q??lA?_D'D@?ԗmAf)'?^DGDq@?mnAO.?z]DsADh@(?cAF,@?T]D<Dߋs@kv?(YAmH?\D,'D-OA?@}SDD~r=4@GAsSDDГ)@@DA-uTD= D@7=@A0>jKVDD]@?@A|A?1YD;D@#(@bA/?\DDA@^Ah1L?O^D~D[A6@A4vV?dDD Aj@ʏAj?(jD[D@V@xpAu?wnD-D8@@}Agz?oDD@Կ@dA-{?"pDБD!@&@)A k|?+rD%DvP@@ħAi}?KzsDګD9@@^RAy?KzsDګDy~An?KzsDګD|AWY?yCtD>DB@!Mx? xAu:?yCtD>DUsA{U?@1+DdD Y=@BcAD賓D@J|@ cA :SDs~DOfD-?'@nAl7>}DǓD[@G!@sAVJ>/"}DWD@m@@huA0Q? {DD@s"@~yA,?|zDܔDQ@|@ zAJV7?YxDԕD@@n}AbM?fwD 4fAky?rDDl@o)>cAz?jsDqD%@Z=UcA.@z?tDD4ʈ@2@cbA{z?uDDA@\߿@bAz?vDDdı@*@}bA{?vwDBD@@{bAg|{?xDѝDj@ @bA{?yDMDp@m@bA`|?zDD@@՘bA|?{D$Dtp@~@BUAU}?|DwD\@ @JA&~?ĵ}DșD|j@8@GA:~?fQ~DD@K@YFA1?~DD@@bqFAz?~DD6_@З@_FA?~DLDZ@jP@9`FA?DDXe?lC@_FA? DD?黔@[FA?fDBDB?@LFA7O?&%DڒDγ>{@QFAZ}?{4DLՒDs>]@EA)y?{4DLՒD%DAn?{4DLՒDZD\D@C?s-AsS>\DVDX@h>?A>D Do@?A ٽ>D~DU+@?kAH?qDȗD@0e@A(?D0:D@6@ /A()?.D~D} N@=B@XAt2?GPDRD @Xu$@SAM:?[,D1D&?7@~- A6A?UDD؋_?}F]@ AljG? DUD S*?O@!AM?*D=DI?@$A``?1DȗDˌ$@&@R%AKj?LDtvDG@@%Am?ʆDZD{@^ @+Au?=D|DФ@u@ ,Aw?MD}Dê@n@3Az?~D"D_׀@U@8X3Az?ED~XD`?@i93AC{?~DWʒD ??-8Ad|?D D?[?8A>|?ňDD#ee@ȣ?J9A|?DDH\@S?y9A{?$1DDYS-@?t#9At|?7\DD)C@>?&9Ah |?qD$D"?&>.(9A|?SD!D?A@09ANQ|?kKDDc@c@B9A&|?=DyD@@@Ue9A}? DvٓD@@?BA~?CD7D1Av>@EA=?&D6Dm@G@sFA>? D2DNF@j5@ :A?͒DFDK@xޯ@?:A? DDy?ͷ@9A?%DDF? J;9A?LDi??4A'?4DDKt@W?3A?Db{Dr@?3Ar?DDV@ ?3Ar?D코D|A?3A?_aDVD@?%3A?ƏD'D2@8?#3A?pDPDJ@D)? 3A?HxDМDon@E?o.A?uDn;D|/v@MI?/+A?2DqD @?[(A?g;D⩞D@p?Z1(A?3DQܞDT?U?(A\?DDA\>,?'A}?D D(6 A_A?@fAD8vD=@+IA@D;٪D>L@~MA=@DhDSr?;?&OApH>i@DDD?걒?LRAz>AD%DfR?(?FSA\>ADDA@*?ԼUA@P?T"ADD@?&WA?ZAD㘬Dr#@Zn? XAO+?@DjDt@\?7\A~I?JL@D5Duf@6?3QA_?8@DDN@y?3FAIm?R@D-Da)@?EA"x?1L@DSDGS@m?CEAy?@DDD"@E?EAj|??DBD|R@ ?P;AK~?u>?D˻D ,>9?),>A?P?DD>H@?A/?Se?DDb>f@@A?Y?DDnݺD9@֋@@AL?>D+D7"@@0?AU}?>D+Dn19Ax%t?>D+DX,A4?@+DD\D=ZV@);AL/DD4Dc@>ї@);AuLDDD> (@>AV9NDDDX>F?;A<.DDũD3?S @Al=nCDbDs#@X@.AI=V?DD @ @__AD>u8ؔ:D(dDi@$@ )A6w>9DIDxE@g!@31A#>!9DDu@@LD6A>D8D{.D\I@@{9AF?T8DiD&@uJ @;A?&8DӮD??=A9?7D8Dp8Ae>@_4DBD&!>I?@EA3D xD*?@yAT=;4Dq}D?>HdAo\=4DmD>@A5 >B4DXbD$ ?@Az>Ե4Ds`D Z?-7@? A>5DED>@d@"A5>7DD[zk@k@ک'AW"?|8D׼D@iE@L)A9(?+:DDܰ@@=P,ApU@?ϗ>D"DaK@U@ A[?@DKDN@@-(A1b?4DDmD0 @ @0Ao?'JD)D@d@;-A{?KD{De?G@jL-Axz?gKDvD>nj@,ApWw?KDrD1 >J@w+AQo?KDrD)ACB_?KDrD&A@D?KDrD!AL?@ CHaC,=c,,>A4(CC]s@u@AC=CTC/ ?l@A >R)C ]CH+?o==?A.s>XC^Cܵ?n?UZAj >7CsuCn?.ag?\A\>DCC?мL?ARv>fCFC%?r-4?BA?UC?3D%CL?rT?AO@?WDÖCA< ?@) CC,{=TW@uEA2C2C8 @Ӝ@uEA&DAC?T@uEAjD'C> A?wEAx9_ DZCI?@p~EA:hDعC?(?oEAf;ڡD=C[ٜ?_?V;AO+<ٜDVCvE??4@A8<D!Ca@?&FAV=GDC:@`?5HA=DkCeID@?HA2=DZCN@?cIA=D:Cl)@r?-KA4>ڗD5C~@9?K'LAyV>[D*C@S?EA>:#DLC @r?FAi>"D;C[ A-?HA>NDCA?|!KAн?GDCQA ?2LA2?6D!C9#A?MA A%?'D$C?A??NA+?8D-\CA:?cOAD,1?K%DDy!AX?PAD6?]DDl"A)1?PAXlD @Sk?VA4j?SDkD?)*?ŋVAXsj?kD^D`>VU@{VAi?D D'D@@{TA[?ͦD{;D3{@ӌ@uRAHM?ͦD{;D'PAf6?gDWD{Ap@GAW?gDWDAA%>@LC7Cv= @ΆA)CgsC8@ @"Af<1C&C@%@rA(=+CڦC6/A>@4AU>CC@A@زA>4CWCP$A7@S"A%>&$CC3,A6+@OW$A>. C+ԿC>A@'Ai&?tCٻCSAq@;+Ag:?bKDCTPA@&,AZC?_cD̺C h6AeU@.A_qR? DsNCZAe@P0AO^?WDCAc@TF3A,u?q DwC A@3A 9y?xD6CkL@5$@BAW{?"D+Cb@@HAD}?:$D̯C8 @@IA 6~?e%DC#6?/V> JA0~?*%DCG?D@IA|?%D CѲ)?nI@kIAx?J$D_Cf>pe@HAL:r?J$D_CFAc?J$D_C>A$?@HOeBD* C= @@ADs7Cz@r@y/@=ADCP?z@@@=ADC9B?B/@@hVAD C2?U@@C׮>@D_C6@2L[@@*v>?D*Cri@S@@?)=DC@H@@-@e? A|{?~1DƕD@l>XAp{?2DD @3;>}AZ@|??4DWDDz!@H9>Aӛ|?,7DgD@y<Ѡ@M}?8DJDO@@mC@c}?Z:DpD )@@t@S@}?N@?BBD#C=d@Nb@C@?ADCaY@[@E@?g@DtCO@NN@@??D,C J@zI@H@{?>DFC*@24@1?@\Kx?>DH9C1G@9&@JA[dj?У=DEC7@2$*?@|GfD&1CȲ>$@@#fDvCIh?DeDrCP1 AU?Ľ@@>eDCA=?J@Vi>eDICP@?_M@>t$fD/CS@*ٽ?x5@n?@? @j?YiDD\f;@ʕ?q<@n?a0iD D?:`?2@p?^NiD& D@??@0qq?`iDkI D@?J?Q@r?iDG DphO???@mq?́iD DF:>I/?b@o?iDJ DM?@Kij?iDJ DH@Ʈ^?iDJ D}@$%?@4 \DCP=@|O@[D؏CN@L@Á@?[DC@I@b@ܙ>1\D-\CGo$@@)0@D>e\D)CMU@ƶ@?y@l>M^D C@m@l@P&?*_DCyk@#@9@L5?x*`DxC ٫@ٷ@`@A?bDC*COa@ @"@T?dDC:@z@@Za?sgD9C@:J@8$@j?iDc C@ѽ@.@q?kDTC̼@@e@Fs?glDظCe@z&@@u?lmDCڮ@{:@@w?cUoDvC(@@f@,y?pD/CT@l^ =@-yz?arDyظCv@/=)@{?sD^C:@Bp,>#9@f|?uD/CM@@> f@8}? wDAC@K>V@z}?OxxDݜC@>@<~?9yD9RC{@(?@6v~?:{DiC@%?ҏ@~?|DMC"@P,:?Ȇ@~?ى}DCF@iX?@?rU~DQC@[}w?$@Q?Q~DGCT@E?!|@j? DCđ@`?@ ?1~D%Ck5@5?^0@~?}DVC |@#@@]~?c{DC@@ T@n~?fyDCA#)@ l@~?|wDoCA)@@~?uDRC An'@H@~?UStDC@E.@@'?rDC@d5@@:?xpDCA:@خ@d[?O%oD{CA#?@զ@x?emD1GC\ApD@@P?iD?C@sCJ@1@¸?BeDCH@J@@G?dVbD["C@@J@D@E@w?_^^DOC 3>2@W@?_D&{@~?r_D'Cu?>ݑ@2|?,_D C\*@1>Z@w?,_D C@0il?,_D C@40?@pID,Dr<)@(ID-D ?@S@V<\HDp-Dq@ @ԗ@Z=IDn-Ds@y ?1ظ@Pc>.ID|.DU8@?@x+>[zIDg2/DRc@z?@>'JD0Dz@ۘ?^~@l#?JD?1Dx$@1Ñ?G@zL5?ȕKD(4D@ d?@YO?LDF5DW@6?A@`XY?LD7D )@?p@%6g?C"MD-:D6@*?@p?*MDkl}?PLDGD??d@p?Y^LDT`GD>k?@o? HLDfGD>H_7@Tw@?2LD8GD6S>8P@N@?YLDM1GDaB>@/@"?QLD GDv@$D?java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/tcppacket.bin000066400000000000000000000002551445000440600322660ustar00rootroot00000000000000`g &DtfExZ@9}dzERAqaoqP4W` [ u~È^桋I >zG#=b^V`F^~a^%u C)MlܻtD_A0?A{~)YpibOjava-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/test.clazz000066400000000000000000000014771445000440600316510ustar00rootroot0000000000000039 !" #$ % & ' ()* # +,-.field1Ljava/lang/String; ConstantValue/field2Imain([Ljava/lang/String;)VCodeLineNumberTablegetWorld()Ljava/lang/String;()Vprint SourceFile Test.java0 12java/lang/StringBuilder Hello 34  56 78World HWorldTestjava/lang/Objectonejava/lang/SystemoutLjava/io/PrintStream;append-(Ljava/lang/String;)Ljava/lang/StringBuilder;toStringjava/io/PrintStreamprintln(Ljava/lang/String;)V1 8Y    * * *   %   java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/test.tap000066400000000000000000002565021445000440600313130ustar00rootroot00000000000000ACEofACES  oP*BG*1987!\PP>2\2H\k \v!ͷ!v>!~ʁ#!X"{\>x2\> 2\6"ͺ$ ͺ$ͺ$ ͺ$!X'!!!!))/):BCBB:^P^ᑑ%""  PPPPP^ j*䄇>$(($ctc< 1[!\!\5>7V 9"23772":#ç;"B.G. REMOVED BY NICOLAS RODIONOV": 9"ACE'ACES$""4E4"::"4E4":"23570","6":"24100" CRACKED BY BILL GILBERT'87 D^%ACE'ACES$ P R;;+@!ZR~#( G~#GN#H @ Ah# h|x@ 7gɄ=߀B <D @xpǃR@a;@@ ㈞&'H0~DPQ P!ٯ|c"@'H@Pр&N!@"2b} //&DP <C5SfA!%"2߼H!# &c&Np #C!@ Ylƿ LD(@:PȀP"DS '@r|@BRCc8Ǐ8P$@ & R 8=2_C!E4؀2)hX@`76A BHRDIB>x'0k& =Ox~@ "], qx -2B 7P~ @B@8(`<~ &@0A$M !΃?" 1]`2 (fP[/^r#50~[ Bx>u@XAZ5 !Ě<&B|>~ x D B"2:~w|wxAB!SAS wo[~A$HDH@\8@( ݹ DIBC@d9H 53 Bt@Tl `W@. \DkR( j@0N&>?e PH& 1`?B @ Dn@"#D  v@%"\@A$HDH8@@hW0bDEAC6|rP[+6@B?:!@֠ 9qT$A}bKP@ OA}꾟*(?-gSjm ?1ǍE/Ť %s _  ?\@I@rfH"8ހb?$ ^O#{/,<YAC@]|@ޫRͼQZ@?`ĀY0 ua @DAuɐdNHCA@_~ ,3o-  IUH>ZCaw^/[(?"3tHf3:Ƞ?v jJ}W{daB@@Q[<6 *UBcij*$@da[@|Q`P@A: ȏ&D A08#b_2pBW(&h"'&5^0Q̐h'd ; Ʊ g&~#v4@?ʌ bg!#"4?) j=D ؓp ? ]𤋮,dTRi~+fK`@ljijוq[t0V B`v.9YJ@73%QAHB8HD' !00(/&=bQq동 zo  *@Ao?f+VS0 5H? d {w_ DOD o׀"]xW0!" @ "7a&%OVcfMA]ẁ0 BQ>]==`$FA% |& 7"P"u?:\ pPhV< (7ԺkP J ?K{E.oM\k?!*K {H]H;JT?F~< @qb2!rTXA8HX9=@@@@9 ` /@A*TDG4F@@,q4:  ? [ T qՉ(+I?@.:#8wH5?0+U}{M "D ) Y!"@ h,QA8Hvr`ph!  @@$*T@ @@juC@ a@ *@PZAB4@ d?(X V? bރsL ?5' "DQ)]@1>" @@A@ ~ـA 1 @?`FQ e}@S,@/*ՠO} E; ?He޳ x)? ;_E@ E-u-`?' E"DQdH  +!" 4PA0H(8B8q!HA H#!ho `(@@@ ܝ XDA Xі1\Oo}} @<$B0 !?jJuD\?" 6D EWp?2h?D qN !)A!D A8p:ހB$HX(A~@@@QK% Q4oGK tȀ ?DPm o 4@҇(?@ 9ER@D'!R G"Q N% vB1d B`>B4A"c _zA|~]1!:\!X'7!_>V!1`!:\!X'\!#]L=L@se_W>2`>2R!"!@"N!"P>W0!+| S0>S0>Ƹ0% W0x0W0>2`!N<yO.xS>>:Q2Qe:Rݬݭw c( :RQ2R:R]2R#z*NR]!/"!D"!Pp|!P# ~7Y>=  ~1ȩ (y/O7:H\xS:: w#= $:<2/= :O:G7gyǨoy/:|H0&X P0&Y&Z :???::w#= = *6\( =*"!":2Ð>22€!: @x ~w ~wx ~@wx ~wx >2€ 7x ~w ~w ~@w ~w >2€Հ>(~w(~@w(~w(~w(>2€Ð ͕!Xw#  :܁>y>y= :݁= Ð!"6\3!<"6\Ð>!2!P":2 ͟!`"!:2~( ͕#Ԃ~( ͕#Ԃ~( ͕#Ԃʋ~( ͕#>2:2#>H2:2#~ POW TRAIN E MUNICHV-1 ROCKETS SE PARISU-BOATS N BERLINJU-88S E AMSTERDAMCLOUD COVERBOMBS/ROCKETS/CANNONS TO V-1S. E TO TRAINNW 6000FT ENGAGE JU88S.NW 1000FT TO U-BOATS.POW TRAIN W MUNICH.JU88S S PARIS.U-BOATS N BERLINSTORM E AMSTERDAMLOAD MAXIMUM BOMBSS AT 3000 FT TOBOMBERS.E AT 1000 FTINTERCEPT TRAIN.N TO U-BOATS POW TRAIN SW MUNICH.V-1 ROCKET SITES ATPARIS. U-BOATS N BERLINSTORM OVER CHANNELMAX BOMBS/ROCKETSS AT 4400FT TO V-1SE AT 900FT INTERCEPTTRAIN. NE AT 900FTU-BOATS POW TRAIN SE MUNICH.U-BOATS N BERLINSTORM CLOUDS E BERLINMAXIMUM BOMB LOADSE TO TRAIN ALT 1000FT.N TO INTERCEPT U-BOATS V-1 LAUNCH SITES SWPARIS. U-BOAT PEN NBERLIN. JU88S E PARIS.STORM CLOUDS W.BERLINEAST AT SIX KNOTSBOMBS/ROCKETS/CANNONSE INTERCEPT V-1S.E STOP JU88S.NE DESTROY U-BOATS. JU88S S. AMSTERDAMU-BOATS N. BERLINSTORM W. AMSTERDAMLOAD BOMBS/ROCKETSFLY SE. ATTACK BOMBERSNE TO U-BOAT PENS.BEWARE ME109 BANDITS ENEMY U-BOATS N.BERLINV-1 SITES SW PARISSTORM CLOUDS OVERCHANNEL. E 5 KNOTS.LOAD BOMBS/ROCKETSDESTROY V-1S.FLY LOW TO U-BOAT PENSENGAGE/DESTROY U-BOATSENEMY U-BOATS NE BERLINSTORM W.MUNICHEXTRA BOMBS.LIMITED ROCKETS/CANNONFLY IN CLOUD COVERAT 2000FT. DESTROYU-BOATS.POW TRAIN MUNICHV-1 W. PARISJU88S NE PARISSTORM CLOUDS NEAMSTERDAMBOMBS/ROCKETS/CANNONSE INTERCEPT JU88S.W DESTROY V-1S.INTERCEPT POW TRAIN.JU88S S. PARISALT 10000FTPOW TRAIN MUNICHSTORM CLOUDS W.BERLINBOMBS/ROCKET/CANNONENGAGE JU88S AT 6000FTSEEK/DESTROY TRAINPOW TRAIN NW BERLINV-1 LAUNCH SITE PARISSTORM CLOUDS W.AMSTERDAMBOMBS/ROCKETS/CANNONINTERCEPT V-1S 5000FTINTERCEPT TRAIN 1000FTPOW TRAIN AT MUNICHME109 AIR SUPPORTSTORM CLOUDS PARISEASTERLY 6 KNOTSBOMBS/CANNON/ROCKETSSEEK/DESTROY TRAINAVOID POW CARSELIMINATE ME109SJU88S S. AMSTERDAMV-1 ROCKET W. PARISME109S ATTACKINGSTORM CLOUDS N. LONDONROCKETS/CANNONFLY EAST ALT 8000FTENGAGE JU88SFLY WEST TO V-1SJU88S E. PARISBEWARE ME109SSTORM OVER CHANNELEAST AT SIX KNOTSROCKETS/CANNONFLY S. ALT:5000FTSEEK/DESTROY JU88SV-1 BUZZ BOMBS S PARISME109S SCATTEREDNO STORMSLOAD ROCKETS/LIMITEDCANNON. NO BOMBSFLY S AT 5000FT.ENGAGE V-1S AND DESTROY!P("ԋTARGETS: WEATHER: WEAPONS: ORDERS: -INTELLIGENCE REPORT-Ð>2'!Hh"Q[͕͉T>G2>2|@ MISSION MAP- STUDY CAREFULLY -PRESS FIRE TO CONTINUE- !":!"[:2=!H( =[H:'( =(!:2=( =H[!(.~22 ~( #*=!"!8 ( =DMv:J[(*H[E:M[(*K[E"!"v*Q["!"!س0:R[( ==DM>2>2>2*6\!"6\!">2! ~͕# :2 "6\ÐdͿs0H0H`hx``hh((( ((( hhh`xxxp8(8(``\X 0HXhxx!"6\>)́̀>2 22N[2O[2P[2S[>2?À>2:(?: < >2 ?:P(?: = >2 ?>2:€(<: `̲́ڏƏʹÏ>2܁>2݁ށ >2܁> 2݁ށ >2܁>$>2݁>2܁ށ:O[*2>`͕!">a͕:N[! 0=(#=~22:O[?W>w!@P:P[}!`b:S[}  >*2!0`">+͕!H`">-͕!(p"͓!0"͂>*2!0">+͕!H">-͕!`h">+͕!hh">-͕!bx"ͯ!`"ͯ>*2!|`"!x">*2!"ё>*2!x">+͕!">-͕!p"! ">(2á!0">(2>2>2!">)2á!">*2 áLOAD WITH WEAPONS AND FUELFUELDONEÐ,8>!w# w#w>G^>?GVÐ]>!@w#     :(: >22݁ށ: >22݁ށ>2>2܁>2!,"͢>2>2! @"!"!(0"!@P">22܁!Xp"!p"ͺÐ!"6\>2ͱ>́ͱ>́>2:(?>2͡>25:(À:€(ͱ>́_ !<"6\>2.: ̀ ͩڜ>`>2܁:2݁Ɠ2ށ !@>w#  !5>w:< >2!0( =>/2!"!"!5:x\?w:<2ȴ x!X"!">2>2 $j! "!0"!@"ÕÀ: :( <<2$͗:@ :( ==2$͗:€( DOG-FIGHT TRAIN U-BOAT>2 $j!"ޘ!("!8"ÕÀ: :( >2$͗:@ :( >2$͗:€(>2 $j! "Ę!0"͘3ÕÀ: :( >2$͗:@ :( >2$͗:€(>222 $j! "!("!0"!8"!@"֘ÕÀ: :( <2$͗@ :( =2$͗:€(: :2: :222! ͉ :222!(͉ :222!0͉ :222!8͉">2>2>͕>2 V-1S BOMBERS TRAIN U-BOATS ACCEPT REJECT BEGIN SELECT PRACTICE MISSION(S)  >@2!"! h"@:0ȵ!0h"!">2:!x( =! x"!("!@XÐ>)2!"!" !H" !" !" !"> ͕!("> ͕!"P"> ͕!""> ͕!"> ͕!"> ͕/ogw>2>)2! X" > ͕>2!(X">͕!0X"> ͛!(">͕!0"> ͛!X">͕!X" > ͕!"> ͕!\X">͕!\">͕͕:2:2Ð!T[~ >0w#>2!@0"n>2!X0"!h0"!x0":\1 >22Y[2 >22Y[3 >22Y[PLEASE SELECT:1. KEYBOARD2. SINCLAIR JOYSTICK3. KEMPSTON JOYSTICKÐ>2!X@"2   >2!"B STOP THE TAPE START TAPEÐ>2N[2S[>2P[> 2O[!xx"Q[>2[2[>2 [>2S[> 2N[>2P[>2O[>x2Q[>2[>2R[>2[>2 [>2S[> 2N[>2P[>2O[>2Q[2[>2R[2[>2 [>![Mw#ÐUUUUAD+ECT* SUKE-CCt *D$ >@MDP@U @YPPTB@*U@A(" *UUUUTUU@UUꪪUUUUUUUUUUU֪ꪪUUUUUpUUU:UUUUUUUUW "u_UW@T j舏U]U `_]('UU>xUU`T@//(UTXH /""^T4EK_J-RU_j4W#ů?UU_a7V?ꪪzUUqUa;Կ:UWUq5U窪OzUUUaUWzxUUUq5UU_S"-UUUUauUUU:ȈUUUUUq5UUUUz'UUUUUa5UUUuUUUUUa5UUUժzUUUUUUq5UUUUUUUUUauUUUUUUUUUq5UUUzUUUUUUa5UUUUUUUUUa5UUUUUUUUUUUUUUUUUUU?᪪UUUUUUUTUUUUUUUC*UUUUUT ?UUUUTUUUU@UUUU?UUUA??UUU??UUP?xUT󪪨U@?T!?C!???W>%v5ہ?k׃uտi}KԷ LUSr+Ii3BL`-[hT+g*6#[JiTE#!)iJ+aQjADT!o*~,(P߼ך@AX ,@w)`>B|wDq18VDF0&_zDOJ0*DEUdVD^S@eDDER,ƑDU@DRb$DE:BP "DZЎ`0DEw(2Ra@U DDES Qtm1 DPE$~LJ@ Ui^̬ )LUP%UUv\^JOVQ_GFwwwr Nw1UBF3βķ9O[=MMdHNlجH+qٙO|1x-ǾPӟs6k@ט/x3U^ޱUU7gU UUUy*UQwUUU<0UP]eUUU:(2UPG@qUUU⊪UT\UEUUU*/>*UUMPUUUUUUTUAUUUU UUUU@UUUUUUUUUUUUU%WjE_J_I~*֪$U|Hڪ SUZEWKj*Wҗ⭪S7_7wo*תOLO*ꪪLULꪪLWLȫLW@Lҭ#L_!L ̟f_ObGgAgIH y$ I(@Ԡ @߮D/Tw^J;us@PA @uw I.=qE`1{Ȟ IawqH !A@b`!H"FF F@a HA!(D@""""$DDDDDE""""$DDDDGE""""$DDDDEm"""$DDDDE$DDDDLC#^DDDDD1"!bDDDD"!bDDN "!cG>Ѣ"!gE刈"%DD}񈈂"DDG<"%~DD_G?"'DDE~"%~DEE*=."#DDOE,"^"DDE, Z"@E Z#DE r?DE!bDE!gDE?!?DE!DDEDDEDDEEDDDEDDDE UUUUHDE UTUUUJ*UUUUU@@@@@`AAAAAAA`BaB~BBBBBBBBaCbC}C~CCCCCbDcD|D}DcEdE{E?|EdFeFyFzF{FfGgGxGyGX8p0x@@(0x@pHppp0x@@0pP XHh8PH@Ph@Phphh@P`XPLNHPXpPhxx`hPhph`XXP`p@P@H@PX`p`@Pxp`H8Ppph`PH8P``p@HPP@0@@HPxhP:Phxx@@Xp|h`@@Ph@PP@P@Xx0Hpzpp0@hp`h`0@P@PHX8HXh@X`px8Xhdd`pX8P`pX@PXP8HXh@Ph8P`plp(@XPX@0@PxpxhXphhXH`pXPH@HXx0Hph0H`phpX0HXhXP`H0HPXxhX0@`|0@`pllx0@`XPXh0@P@Xpx@Xpphph@XpXXPX@X`H`Xppx@`xXpdd@XhH`xPX@XPHP`X`####837h ``@x?x 4``''@@@@@ ? |8||8~?p~?߀c@?/>]?7=?7480pH8<~ n8H< / ~l<~nm0` 0`<q{||||x@<<|q{||||x@|s}|||p~y||x@`p|~~}pp@?@@@`@ۿ0@@@:P@5_@; @w@n/?@ @@-@~@@@@G@O@?@?@?@@?>@@@@p@@@@q<A@?@W@@?W?@?7@?@?ϗ@?@?@?@?<@xp?|x{|||~|0x@?_p~? ??? ????=???m? @<<~8 0 ߀@߇@?@ (((((((8888((((((*0(((((((00000))**(((88)))//((9988(//((9988>999999998888;9;:888888888888999999999998988::$<<<<< <<<<> Ál|>ߏ"??ϟ?ϟ??;UU1uu1[[;[Q?{uuuu3UUUUSUUUUQUUU{;u5su1UUQU5_____1W3WW17WW7WQUUUUQ1[[[[Ququu +!+ "#. $   1EFG$  1 %-=>? 27 , @ABCD/ ) /&***  1/+ 1 / /1/  01/11/127796:;< 1/0 5HIJ13~BBBB~@`px|~~~~~~~~~~~~~>>~~~~|xp`@>~~~~~~~~~~~~~|xpx|~~~~>< $$$~$$~$>(> >bd&F(*D:0  00l00l~< 0`ll8~lll l0hlll 00llll.l0 0 >>0 0| #>2H\!j͵!͵OO!<*:~ibZMTE><͎ͨZ>CiuM><|b-,$|} o|g|d%|/} o/|g||/|Xgͨ*8s#r#"8o&o zͷ "s11_PX >D=H=>PH >Sq͜R]Ε_}zrTnʄ|bp@A2@! @_@AR@!@@`@Ar@!@@A@!@@A@!@@A@!@@A@!@H!H H6-4/ /5535, 5/702. ! 5/13-.(*D:ll8~lll l0hlll 00llll.ll~~f((~|l?|P9n|P g l|P g G!\޷W_w}WwuUZww?]_WUUWUtUurfu[UUUUUUU\UWqVwwg7P|surdMwU>1c]rL_}9srL_W}?UUqq]GoUUWwQ9ϕGUUUݿ]xsWUWwUysSU qqUUUUW[_GSUUUOUUUUUUw]UQQUUU_UUUUU]`WUUUU@UUUU]UUUUTUUUUUUUUUUUUUW~~UUUUUUUUUUuUUUUUUUUUUUUUUUUUCUUUUUUUUUUUUUUUUUUUUUUUUU}lUS3LUs3~C3v\3Vw33[3E[3d[3D[3ݑD[3g[3D[;D{TK7^K3l[;T}KsT[{l[\[s\[3\{7L[;t[3݇s3^uK3W\]\3sTK3|K?\SwL[3lv[U[L[397&&7>I6O777o7U1_wU5w|Uó5T?Q5>|}A34x=~fgff*UUUUOp99򪪪` W WUUUS@ @[x>WSP?SAWS'8`WS  8`WCꪪ8`WUUTgUUT?U ` *WUOOŧp`/ʿWO~?@`/|zW@O=` `/WTpO\x= W?>X>|8}O ~~~(Wo(OW(ը/Wo8WO/WpWOW/UUPTUWO ҩW/ UUPUuUWOҩ/UUPTUWO=Ҫ?/|UUPUO~Ҫ//UUPUG@?Ҫ+*UUPUUUPUUSҪ*UUPUUUUҪʪUUPUUU_UVUUUUҪUUPUUU?WURUUUUҪUUPUUTUUUUҪOUUPUUToUUUUUҪUUPUUUUUUUUUUҪUUPUUUUUUUUUUҪUUPUUUUUUUUUUҪ8888888888888888888888888888888888888888888888888000008888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888880888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888088888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888`}b@ ?UBO =Ԁ3C4c>\UU髏w_?UJO=+;KYtuUUAMBWP@+W/e,?|:UUUU_՗7CMR_E/@ ?.UUUU_????????????????????????????????????????????:P (>:y:P(>:y:P(>:y:Pm:P:*>2$!|" !&""h:*y<2*y! "}\!N:+y=2+y! "}\!dN!Ә!՘wAÐ!F5!G5: !?2G:#= *: = >2R>2}2  >2Ϊ >2ͪ>2#:*( ÐOzazE+z=z:Ʊ͵͢zuOzazszE!" ! ""ah!`" ! `""@fh!" !H""d'!" !H"" e':}>25!" ͖z!(" ͖z>25!""@u'!@rX: >2&:ؘ8>2':2'>2&: >2&Ð:Y[2x>2:N[2>2 2 :S[2  >2 2   >2 >2 2 :O[2`:P[2a>2ͪ2Ϊ2222222ñ2DZ2P2Q2R222л2}2}2}2~2~>2ѻ>0!Sw#*Q["һ!T[Z>2#>2F>2G>22Ð P MV=V \Wq !zv>ͅ|ɸͅ|>2\:\(>2\>2$Ҧ!H"%|!`"%:\R(S z!ZT[S>2$Ҧ!PP"%|!h"%!<"6\   PRESS TO REPEAT MISSIONPRESS TO SELECT NEW MISSIONREWIND SIDE 2 THEN PLAY CASSETTEÐ!}~(5>2Q:DZ : !}5 >2DZ2}!> # >2R: : >2R:( : ! >2R}::}( =2}̠}A:DZ:x\>2}:x\? >2}}>2R:x\?G>!Pw@ >22O >22 : :O}>2R>2}>~>2>~>2͔#:Hv:~<2~(v!(" !""'͔#:u:~<2~(u!(<~: : 0:~<2~ >~@>2: : 0:~<2~ >~@>2:P2P:P@2P2 ~q2O~ÏÐ  > !>w>w!~(@8>2#~(G8>2>>27>2#~((8>2> 2#~(8>2> 2!"%>2$:)??! >>2u>2u0?! "% >? !"% !"%>!~(#>>Ð~v> @` @` @` @` @` @` @` @` @```DD``UPP UUPP8LL8LUTUTTTUUUTUTT'''UTUTEUEUUUUTUTEU????5UUUUUUUUUUUUS5UUUUUUUUU__M5UUUUUUUUR*UUM5UUUUU_UU_mUUUUUUZUUmUUUUUWUUUUUWU*UUrUUrUU*rUUUUUUUUUUUUUjjjjUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU͖w#J:#_:"W w#  > !: o)og:!_ۋ:#:" w#= $: <2 ̐= :!O: G7gyǨoy>>>H! }| = ͐:$|H0&X P0&Y&Z :#???:":$w#= = *6\( =*%" !""h:&2&!> 2 >2!͐ : <2 h(Ð>2y2z!y:}(>@w:x @x ~w ~wx ~@wx ~wx >2z 7x ~w ~w ~@w ~w >2z˜>(~w(~@w(~w(~w(>2zÐ  $0!"":X!Y!e(#=~2Z:Y(: !Z  2 >2!> : ?$0~`( =':!2!:X( :Z! 2 :Y( : !Z2 #= ! ={:ybK( :WH02W@( :W 82W:P:Q:R2P2R2Q:S:T2U2T:x\2S   @`  !"":X!Y!e(#=~2Z:Y(: !Z  2 : >2!> : ?$0':!2!:X( :Z! 2 :Y( : !Z2 = 2 = !>w#>>!X#  !dX >Ow#:X(=2X:Y<2Y:Y(=2Y:X<2X:x\Kb:[ :\(<2\!W4:\( =2\!W5:[<2[Ð[s2 :[>2b2aE   @` !@!#####!""!" >2$h!>2&0>2#&0+>w#:(>w#!>2>0>2+>0#>w+:(>w+>25!""! " @'>25" `'>25Ð:b<2b:b(=2b:a<2a:x\Đ:>y#>y#= := :(=2:}(:22 :: <2 (::22 ::22 Ð(#=~!W2 >25:T`j2 >͏:Q(->252 :T:Q}j2 :Qk>͏y:R:V!a(#=~!W2 >25:U`j2 >͏:R(->252 :U:R}j2 :Rk>͏2Y2XÐ : k>͏22Ð08>!w# w#w>G^>?GVÐ:#:"?~:=(#:"Gw#= >2??0 :2;:#G:"?~=(#:"Gw#Ð:M<2M! H""!Г! ""!!H""!Г!""!pГ!H""!Г! ""!JГ! h""!Г! ""!:LʺЕ:L 3E 3E͞| 3E͞N3E͞>2I>X2i!X" ! h""h!`Y!" ! H""':M :(/:(&:H<2H!:M !" !H""'!:M !" !H""'!':M !'" ! ""J'X>`!`XX>9`!`XX>`!`XڕX>> !I5:x\<2I!X`:J<2J( ~@w#  ~?w#  :I=2I(:x\<2I>>!X`~w#  >25!"":K2!x >2 ':!2!yO x :M :K<2K :K= >2K>25Ð ͸͔#E&& >2$! ""!i:M !i" B:%(h" B:$(h                ͍:"!48"}\:M :!!8"}\!dNͮ! :M !( "}\:#!tNl>2\: !h0"}\:M :!0"}\!( =F#N#V#^ͺ$>2$!hP:M !h" !0""h>2$!h :M !h" ! "" h>2$!:M !" ! ""jhÐJ8@:Ә(!x" >2$!""h:՘(!x" >2$!""h:M :՘!(" !""p':՘!(:ؘ2 >2!!"">2$>͇h:ؘ2 >2!!"">2$h!( =DM*ݘ" !"">2$:!͇h*٘" *ݘ" !"">2$:!!͊h*٘Ð Ð Ě7͚ͶM!u"%\!"%!"%!p"%!"%!("%cdefghijklmnopq: W: /:2:֚٘:"?_?2 :2 (>: '''W>2t:z:  y >͂y@>͂y y@ >͇y>͇y :y >2$&>2$~&q .y k:ؘ(2ؘM@k:ؘ(2ؘM R:y $;:ݘ(2ݘ:ޘ2ޘ!"4͚q@;:ݘ(2ݘ:ޘ2ޘ!"5͚q:y ݙ :٘( 2٘:ژ2ژͶq@ݙ:٘( 2٘:ژ2ژͶq:z:y(: 7<2 7:y: 7=2 7!n: O ~2&:M(>2&>2$>X2%>2u>b>2u0`:*&Ð:MN:Q:\134 M{џ~E:MN :Q:\124 M{ĚH~E!"ۘ!"ߘ> 2!>2!L"ݘ!^"٘>2 > 2">2՘>2Ә2 2O2$2%>2ؘ\!u"%! "%!H"%!pX"%!h"%!"%:W:O /:2:֚ۘ:!?_?2 : 2 (>:'''W>2:z:  y >͇yQ@>͇yQ y >͂y0@>͂y0 .y ͐:ؘ(2ؘr@͐:ؘ(2ؘr :y >2%&>2%~&Ï R:y $ͭ:ߘ(2ߘ:2!!4͛Ï@ͭ:ߘ(2ߘ:2!!5͛Ï:y ̟:ۘ( 2ۘ:ܘ2ܘͤÏ@̟:ۘ( 2ۘ:ܘ2ܘͤÏ70Qr͛ͤP@:Ә(!xH" >2$!""h:՘ !xh" >2$!""h:ؘ2 >X2!!"">2$>͇h:ؘ2 >Xs*ߘ" à*ۘ" à*ߘ" !"">2$:!!z͊h*ۘ:y(>2y (>2y:*&Ð ͢~#?W!(5̓:"(50:"!'2(::)!"!&2(m:)=?2)>2(:' :& >2&2': !ʺz:®z>2'úz:"!l( =DM!""!p" :#'o> ! 2 >2$>2uģ>2u!$5:! ٣2$:!"(00:"('ͭ=2"ͭb:"(ͭ<2"ͭK:!#0-:#ͭ=2#ͭ:2:2:W >@ :!#:#ͭ<2#ͭ:2:2:WH> !!5:#>!W!! :#8GÓmÓ::! #:Ә(:՘(!! ?(>2W:*>2$!|8" !&""8h!L "}\:!N!~:< 4:L > 2_!~:= 5:L >@ 2_:*>ͣ!|" !&""h! "}\:!N"}\:!dN>2$!" !""h!"}\:<2!dN:y( :(<2:y( :(=2:y( :(2:y@( :(2:y >2$!""!x" vh" !"":!wH( = DM!: 2 :#2#>2u* h" :!H( = DM>2uh>2u:*h2$:PʞɆ??!#WÐ:Lʵ >Ù #!I5:x\<2I:J<2J >9Ù>yÙ:I=2I >?Ù:x\<2I>Ù:( >2L>Ù:0 >2L>9Ù0 >2L>Ù>2L>Ùg:*|!dX w#Ԥ͉:V<2V>͠:X:Y:W2 :S`j2 >͏:P(.>25:W2 :S:P}j2 :Pk>͏:Q:V!](#=~!W2 >25:T`j2 >͏:Q(->252 :T:Q}j2 :Qk>͏y:R:V!a(#=~!W2 >25:U`j2 >͏:R(->252 :U:R}j2 :Rk>͏2Y2XÐz_ K͓֡ͭ:Q{͠z:\234 M>22Y2X>2>2#2"22> 2W> 2)Ð>!Zw+!Xw#Ð;Q!7:O ~2&>2%>(2$>2u>b>2u(@>)2$Ҧ!" !""h!H" h!" h!" h!"%> !("%> !"P"%> !""%> !"%> !"%> Wfogw>2u>)2$! X"% > >2u: k̳>)2$!X"% > >(2$%!("%!h"%!"%!("%>*2$: !@"%: !"%: !"%: >82$>(2%>X2&;:%2%!(X"%F!\X"%F!X"%F>82$!(X"%>X2&:%2%!(X"%>!("%>!\X"%>!\"%>!X"%>!"%>  ghrstuvw xyz{ |}~buÐ:( G>2:`( G>2`:aG>2a"0H0H`hx``hh((( ((( hhh`xxxp8(8(``\X 0HXhxx:<2C͈:`_:`(:!2!w<2`:a<2akͤ:C͈=2:!=(#=~2 2!:`?W>F!ѩ:aL! "">(2$Ö!0"">(2$>2uh>2u!"">)2$ÖÐ{͈ϪͿ:z>2$: /!"%:  :y>2 k:y>2 ó #!"%:y(>2 :y>2  %!("%:y:ͪ: >2 ! !@"%:y:Ϊ: >2 ! :z:y(:<2:y:=2@  Ð@:Q:\123 M> > >2>@2>22:0 >2>2: ! >2 :G:2!: Ҭp6n!2 +>2> n:(R:G: ~2~Ѯ: 2 #:: :x\2>22 :2!> Ѯ :G: ~2~ : @8~G:<2Ѯ: 2 #{:(9<[ !>2::( (ͳ>2!:2 Ѯ:(:z( 0>2:y@( :(=2:y( :p(<2:y':y!I5:x\2I':<2!4:=2!5:>2!"">2!:2 @͜>22 `͜>2!>(2 >`2!͐: <2 (: !(= :!_:#_:"W w#  !""!D ( = DMÜ: :HL!I5:x\<2I:J<2J >9 >y !Xw#Ð>2:@2Ɠ2@ 2 0''''2>2 A::±̝?:"(!!8~((5555 ~((4444:#!0 ~55~JI44!5 :x\?<22:!( ~(55~(44!5 :x\<22!:(~(5~J(4::G!d(:G(: DM*" :!???2!!"">25'>25Ð:(AA< >2: 0:z:  !a~:<2 5A !`~5A`!4:  4~>2:???:???> 2A>2ñ2:DZ(=2DZÝ! " !""!D ( = DM'!H:( = " : (4!"">25'" :  :G>(2!$'>25Ð :* ͣ:DZ:ʍ +!" : : 2 : 2 ͐>ѳu !"  +!" : : 2 : 2 ͐>ѳ: !" ͐>ѳ& !" ͐>ѳ !" ͐>ѳ:Ʊ͵ͧ:ñ( >2ñ>2>22±!":x\2ű:± C:ʋ:ű(>2:?p8&>2!">2:?0 >2!":E:* ͈>2:?0>2 l8>2!5:=(2>2±: #:2?n8 >2> 2:*? :2?8:*?!5>22ñ:DZ(=2DZ:Ʊ:???Ƙ2Ʊ2 :*:P>2!͐>wW:*:PrÐ!"6\{x͢>12\:Q(l:\5(e1 &>2*>2I>`2i!X>w#  02 >2*̀3 >2*Ϳ4 >2* aM >2*ͺ|ÐRÐc(ͶuHh:DZ:>22!~r~r:J[(:H[r:I[r~:M[(:K[r:L[r~! ~r!!~ݾ #~+ݾ ~ #>w >2>2DZ" >2>2DZ >2>2????#!#(<>2p`@`0Pp!~2 2! ~( #=!""! ( =DMJ:J[(*H[:M[(*K[" !""J*" !""!@0:( ==DM:*>2$>2uh>2u*6\!"6\!"%>2$! ~# :%2% "6\Ð ƶ޶Ö ͺ޶ÖÖ:͠:Q:\1234 :*  :DZ:!5!>'2(:)??? ( ͋( ͗( ͣ( ͗( ͋͗( ͋ͣ( ͣ((:2:2:2:2Ð֡ͤ+Ͳȱn~͚~}H,y:L` Ð!:Lw͸͔:L2uÐ!@:L͸͔000000000000ɸ:\1234M!SZ(!S~w##:  : ( ͹!W~9(4>0w+>2$Ҧ!8"%!8"%! @"%S!0@"%Z!H"%ʹ>X2%:P(*%>o"% (?#0:Q:R(*!"% L:R 0 ;!`"%!8"%>2$`ACE OF ACES STATUS--- -- ---- ------SCORE: ACE SCORE:DAMAGE REPORT:COMPASSALTIMETERRADARGEARRUDDERFLAPSPORT ENGINESTARBOARD ENGINECAUSE OF DEATH:PILOT SHOTENGINES EXPLODEDCRASHED INTO GROUND SUCCESSFUL RETURN ÐOT DOWNCRASHED INTO GROUNDPRESS TO REPEAT MISSIONPRESS TO SELECT NEW MISSIONÐÐ:2>2:DZ(#>2: >2 >2 >2:(>2: ( :ͪ(>2: ( :Ϊ(>2:(>2:(>2:!溾!" !""> 2$Ph: > 2Z > 2Z > 2Z > 2Z> 2Z:(>@2u>2%!л5!ѻ5>w:::<2(% (~#:!>w#![:( = :*öÐàyÐG5: !o> 2G:#=@@@@@`AAAAAAA`BaB~BBBBBBBBaCbC}C~CCCCCbDcD|D}DcEdE{E?|EdFeFyFzF{FfGgGxGyGQQQQQQQQQQQQRRRRRRRSSSSSTTTTTUUUUVVVVVWW?pp  | p`####837h ``@x?x 4``''@@@@@ ? |8||8  UUUUUUUUUUUUUUUU??P T*UTUU *???@ P P T*T*UT*UTUU ***????@ P T*T*U*UT*UPUT ****?P P*UTUTUUUT*UU **?UL'gDU"UU"'"UU*UUUU"*`@T@UPUTUU`p8}@ P(UT*UT (* ??*UU U U U*UU*UU* **PTUPU@UPUPUPUT??  UU U  @ PUUUUUUUU 82*UUUUUUUUUUUUUU*8@UPUPUTUTUUUUUU               9999<$$ 4$84$ ,$$ ,<<$$9999~~~@||@$<<<<< <<<<> Ál|>ߏ"??ϟ?ϟ??;UU1uu1[[;[Q?{uuuu3UUUUSUUUUQUUU{;u5su1UUQU5_____1W3WW17WW7WQUUUUQ1[[[[Ququu +!+ "#. $   1EFG$  1 %-=>? 27 , @ABCD/ ) /&***  1/+ 1 / /1/  01/11/127796:;< 1/0 5HIJ13~BBBB~@`px|~~~~~~~~~~~~~>>~~~~|xp`@>~~~~~~~~~~~~~|xpx|~~~~>/ _P?  @gg$$$~$$~$>(> >bd&F(*D:0  00l00l~< 0`ll8~lll l0hlll 00llll.l0 0 >>0 0| /lllll~~lff((lllll~ll(|ll88ll8000 0``0 xx >~\oTWToT~\|}x=p` @@8 88@@` px=|8}~(o(W(o8Oſ~|z=  =|~<<<<|o>8,#G``&aC C``d@C``'@ #‡``@@@@@@@@@@` 0pp@@pppppp@@ 0p P Pp 0@ # @D@ $@  @ - P$     $ !P8|:?x.@@P9P8?|:?0.g@P89@/`3@? 8|9|8?@8|?9|8?~@8||8??@~<x<x<xx<<xx<@/7770/  # @U@#@8@???;*!L+ **!:!UUUXUՀիk5 ӀӀ 5kՀժX UUUUUUUUUU_UUUU:X U@`@UՈ@իkU+5    \5+UkՐժ@`@U@X U:UUU_UUUUUUUUUUUUU:X UhU @UՀիkUkիkUkՀժ@U hUX U:UUUUUU888888888888888888888888888888888888888888888888(((((((((((((((((((//////// ((((((((((((( ((((((((((((( (((((((((((((  ((((((((((((((((((  ((((((((((((((((((  /9888889((((((((  /9888889 )(((() /8888889  /)!'!!!!'! /8888889/////8888889///U__aG08: U`j9`Y@x<TH%U( /UUU*$****"**IIIUUUU"UUUUUIIIIIV?TTT?TTUUU_UUP:TTQLTTb"X U"UU *jk:jjkjjIȈhUX UIIKII/ **+**}V""@U"`UIIKIIjTTPTT؈ՀՊՈҒ?kkTTT@TTWb""U"U(k+Պ5 k """U"kՀՊ  """@U"   wUWwhU= U  "X U" UUUU: pUU X U@P UUUU"Հ@kp55 @+p""""""U@k ȈՐ @""""""`Up U^P X Up:P """""""""UUpPU_UU p P """""""""UUUUUp 5k Հհ<?Px X U<pUUUPx<?0xH<<H|?0hhxhh<?hhxhh?<hhhhhhhhhhXXxhhXX<?hhXXxhhXX<hhXXhhXXhhXXhhXXhhXXxhhXX:<?hhXXxhhXX<hhXXhhXXhhXXhhXXBB<`=``B<<BC><B`|```p~?x!B ????????????????  ????(??_U[:U?0j@3a !@: P@BA@@@    x    $ EA@????(??[:U!? 5 j0dpD 6H@@J0H0@UUUU_?ꪪUUUUUUUU_ꪪ}UUUUUUUUUU_UUUUUUUUUUU?ꪪUUUUUUUU?UUUUU@UUUꪪUUPUUUUU?ꪪ?RUUUU_(R?ֵUUU??sUUUW UUUUUU_*UUUUUUUUUU[ꪪ:UUUUUUUUUUUU_!T?UBUUUUUUUUUUUUUU_P ? UUUUUUUUUUUUUUUU_F*5UUUUUUUUUUUUUUUUUWUp0j88@UUUUUUUUUUUUUUUUUU!Tv?BQ̃:99989998899999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999998888888888java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/test.z80000066400000000000000000000302151445000440600311370ustar00rootroot00000000000000~tz0t/2?$+tg0a4F:\@(>```0 (cfx`<||<|<0<<0fx<|<<<|$`C0`80f|`8``ff`f>>fkf`"fff8`f|fff`f`ff|fkfkffff`ff0`fff``f0ff`0f|f`8f0f>ff`8ff0kf00>f8ffff088f0f`ff8`ff>& `<|f><`<ff<>ffffkf<"c|`|<>`f>f`|`f<||ffkfff`fkff|`0>0fff<`>0>ff|kf`ff>0k|00?3|ff>`|08|0f`f`<|>ff+`0`ffff|ffffff#c```ff`ff``ff>ff`ff|``fff2f|`fff0k`fff2 |f>ff2fff`kfffff2`223`f>f``2<`2>ff``fff@<0>f>|<<|<`|>f>><6f|"c>`><|>f<><<|>>f<6<<`<6f`>`|>|`><<|<0c>f<> `f|<<>>f>>c<<<6>>>`><6><<`<|>>f> @H`J<` ``cc>c>>6<00f||><|f<<0<`<<<0||<ff8ffff`f0ff`f>ff`|8fffffff0fffff|ff`8ff|8``f``f8`>f08fff>`f>f`ffff`f``|f``ff0ff8f0fff>8f`8ff`k|8f|ffff8ffff`|ffff>08f`8ff`/c{c{~{>~cc<>00ff`ff>ff<|0|``|fkff`ff>fff|>`f<fff``fff<fff<<>``f`>`|><`f|><``f0ff`>0f`|f``ff<kf`f>ff|>`fk>fff`f|`ff30`>`ffff>`2`ff`fkfffffff>>`f`f0>ff`fffff>f>f`k3`2`<`fff3`fffff`ff`fff`ff`f2f>ff2|``fffffffff||`f`fkff>fff``|f32fffff.cgcg`gc`6cc0>><`f|>><<>>cf>>`<0<<`c<|>><>>><<>>>|><<<`><>>|>|<`<<<<>``>>``>>`f<``<><<><<c>cc>c>> <<<<< < <0<``` <`:(  ppxpx pxpxp # @TT""st\\]Hi]tt itt5t\"f0g XDe![!PX!!@ssPW!{"\ P] B]\`" K SR PISSUED IN OUTLET ALL ENQUIRIES TO CHEZRON SOFTWARE 605 LOUGHBOROUGH ROAD, BIRSTALL,  LEICESTER  ENGLAND  LE4 4NJ  "8E3" "23609","d" JANUARY=:FEBRUARY="2":MARCH="3":APRIL="4":MAY="5":JUNE="6":JULY="7":AUGUST="8":SEPTEMBER="9":OCTOBER="10":NOVEMBER="11":DECEMBER="12":JAN=:FEB="2":MAR="3":APR="4":MAY="5":JUN="6":JUL="7":AUG="8":SEP="9":OCT="10":NOV="11":DEC="12" O:O:ٰ"7"::D;I;٧+;" B I O - R H Y T H M  ":s''''"THIS PROGRAM CALCULATES PHYSICALMENTAL & EMOTIONAL CURVES FROMWHICH YOU SUPPLY IN ANSWERTHE QUESTIONS AS THEY APPEAR BELOW:-" '"p":"WHAT IS YOUR FIRST NAME ?"'A$ (A$>"12"A$=A$(̰"12") 2p=O:n=O:j=O <%'"(Only put last two No.s of YEAR)" F@"ENTER DATE OF BIRTH ","DAY ";A;" MONTH ";B;" YEAR ";C PA<" "B<"13""d" Z("11","5";"DONT BE SILLY!":"F" dB"ENTER DATE YOU WANT BIORHYTHMFOR ";"MONTH ";D;" YEAR ";E nD<"13""" x("11","5";"DONT BE SILLY!":"d" -:T=(((E-C)*"365.25")+((D-B)*"30.35")-A) v=A:"500":f=ID:m$:f R=O̯"" R,10 KR=(R/8)*8U=10 20:R,U:U:.01z# =,R/10 R /"21",O;"1st 10th 20th 30th" 1O,O;" PHYSICAL MENTAL EMOTIONAL" "370":R=̺ U L="2"**(T-((T/U)*U))/U K="2"**("!"-U)*".03" A=LK+L+("2"*)Ͱ".1" N(("5"U="23")+(U="")+("4"U="!"));(A-L)*("#"-""+U),"Z"+A*"<" A:R a#"2";A$;" WAS BORN: ";v;"/";B;"/";("0"C<"10");C,"BIO-RHYTHM - for ";m$;" 19";("0"E<"10");E " q=O̯"" , p=I"320" 6&("5",q)="5""5",q;"P":p=I @ n=I"340" J&("10",q)="10",q;"M":n=I T J=I"360" ^(("15",q)="4""15",q;"E":J=I hq r"23","","!" |+#O;I,O;"DO YOU WANT A PRINTED COPY(y/n)" O ="y"Ŧ="Y""9750" ;:F=O̯"2":F "#O;I,O;"ANOTHER RUN ?(y/n)"  O:A$= A$="y"A$="Y""9995":*  "23609",O o"January","February","March","April","May","June","July","August","September","October","November","December" "`þ"23676""9983" "jS"2":ڰ"2":٧+:ç:"65367":O=ç:I=:"9994":"9900":"9995":SET=O:ZX=O "t "" "~+7,O;" Author not known" ""10",;"Life has its ups and downs!Here is a chance to see how yourown personal aspects of mental,physical and emotional well-being are likely to rise andfall during any selected monthin any year of your choosing." "t"This system is believed to be asaccurate as the astrologicalpredictions which appear in manyother publications." " "8955" "YO:f=1255:f,100d+*3+f*2:=""f:"8",O,,,,:"8905" "K$=:K$="R"* "QK$="C"#O;I,"7";"CONTENTS":"23608",O:D*"CONTENTS" "TK$="N"#O;I,"18";"NEXT":"23608",4:D*"CONTENTS" "NK$="B"#O;I,"25";"BOOT":"23607","<":D*"AUTO*" " "8905" " " "o"23658","8":#O;O,O ;" RUN CONTENTS NEXT BOOT": &!SET;:"9769"+ZX:'':;: & "9785":#O;O,O;"KEMPSTON E IFACE 1 OPUS ZX USER +3 DISCIPLE QUIT" &O:K$=:K$="Q";: &K$="K""9770":SET=I &"K$="I""BAUD RATE? ";BD:"B";BD & K$="I"K$="O""9780":SET=I &"K$="U""9782":SET=I &$K$="D""9784":SET=I &&K$="Z"SET=I:ZX= &'K$="3""9776":SET=I &( "9750" &)1N="55"̰"71"Ͱ"8":"55015",N:"55E3":N: &*"9772":"9795": &,:0 &. &0"U":"9795":;: &4ԧ:ӧ;"B":"9795": &6 "9795": &7'NX="23637+256*23638+4":NX,"": &8@"6",I:"9795": &9"23637+256*23638-0187"::K1@ 55000  !J>!yi7C~>*~ "~"|>!ti*| "|> *!xi= = > 9F,=~ $ >   >{= = ~# K 3 &C °"27";"@";°"27";"l";°"18": &T"23637+256*23638-39":: 128 CHARS *6\$^>ysy# y :\[y & "BIO.R" &3"A:BIO.R"ʰ"8800":"23608"="""d":"T:":"" & &"M";;"BIO.R" &*"M";;"BIO.R"ʰ"8800": &;"BIO.R"ʰ"8800"+("1172"ƾ"23608"=""):þ"23676"P ' ' "55296":"23637+256*23638-148":: AUTOCHARS PLUS !*v\%"6\!?~/wy [v\!P > ^#= 0R6-U^ kk6Y*Q\s#r2>2:t\̇@̇>02>*Q\s#r:\G͛:\W>!2\|2\> *Q\s#r!!8  ~(:\s66:Y >X20 G>2\X 22 0G>!2\ɯ22 :P 22!\˞˦ˮ'''wKI 22!\ˆˎ˖w2B 22!\˶˿wF '>22!\˾˿w:t\( @(2\ >Y2>22 >X2>2 >P2>2 >I2>2 >B2>2>F2>26! 6 &o)))K6\ !~T(Www###:̝:\G͛:\W>!oͨ:\=G͛:\W>!oͨ:\=2\>!2\>2:\(2\>2>!2\>2\ 2~w$#!X >(G:\W>!o:\wc oid`seE" EI }yh$ "]0666666 ?go{s>< ?>c>`>cc> >`~cc> >c>cc>>cc?>0  ??  >c -U^ >cccc~c~cc~>c``c>|fccf|`~```~```>c`oc>ccccc? ?cc>flxlfc`cwccccs{ogc>cccc>~cc~``>cc{o>~cc~fc>`>c>c>cccc6cckk6c66cf< 0 `0 xx<~3|00<>f>00>33>ff>60<00>ff><``|fff8<60<88<600002vkkc|ffffff>|f```<`<|`x``f<ffff<ff<<ckk6f<<~ 0~ 8  xx<>cc>o>*Q\s#r2>2:t\̇@̇>02>*Q\s#r:\G͛:\W>!2\|2\> *Q\s#r!!8  ~(:\s66:Y >X20 G>2\X 22 0G>!2\ɯ22 :P 22!\˞˦ˮ'''wKI 22!\ˆˎ˖w2B 22!\˶˿wF '>22!\˾˿w:t\( @(2\ >Y2>22 >X2>2 >P2>2 >I2>2 >B2>2>F2>26! 6 &o)))K6\ !~T(Www###:̝:\G͛:\W>!oͨ:\=G͛:\W>!oͨ:\=2\>!2\>2:\(2\>2>!2\>2\ 2~w$#!X >(G:\W>!o:\wc/MtMg*/N0+t0ta4e37e3X'e3,Rv>>BB<DHpHDB@~BfZBBBBbRJFB>>>>8>$2>>8"<>B<<0@ >>B>>> 00B FbF$<8>B>>>"8>>)B>">">8>8 >>>>8<> "88>>>><>>2>"<> >.J2"&*2&>>22""$2@ ~>"&8B&&*0 @Fb>$"@&*"""")~&"*" 8">>2"&"$&>"&"">8$222&"$ >".j> "">"b">"" >` "2"b"""0 `~~~b  `""2""2)"""">"""> "" """"">"" >>""" >"2.<<j 2"2 "b& 220 b<< 2 >"b""20 bB@b0 2b"2>2 2>)"222 2&2 2"20" 22"222&0 >"202 >.ff~> >>2>>b>>2>0>~ff0~2 >~>>2 00~ ~~b0 >~>2 > 2)>>2>> 2>2>>>20> >>>2>2>0>>>>0>2 P 0 >R> O~ 7~~ ~(~x~~~<~BD~B~~~~>">(7B>>>8>8>B <>>>B>2>><#BH@@@$BBD@BF@$Hj@&&>""(7B2>"2>&@>$ ">@2"&$#~>B~~~~B~>~jf"""" " >>""`" >>""f>""" #`b`bbb`Bbbjb""& 224p6b & 22 &"b20 22&b2>2"0#`b`bbb`Bbbj~>>> >>4p6~>> >>>>>~20 >>2>~>2>0#`b~~~bbb~~~bb>111111! # @_0^\\\]\^^] _^^^-\oX![!HX! !11"_ K SR P 24570_ 7:1:1:  "SCREEN" )23659k\,1:22,0; 6:6:1 "code"64489,768 236066\,233 236077\,250 /"CODE"24579`,(37201Q-24579`) 24614&`  '"Advent 'C'"1 ' ' "SCREEN" '  ' "code"64489,768 '  ' ."CODE"24579`,(37201Q-24579`) ' "" &` N1`CODE N1``N1MO ! X'Bh M-&/DPh?`+-e3 Rv>T QUIT!`6#!"*`0!6uku5!`6WANT TO RESTORE A GAME? !U`zhIhYghݾ(Eݾ(5:kuݾ(5 EVERYTHING IS DARK.I CANT SEE. !`zhݾ(5`hn&)^#Vzh6!ku~(Aݾ 8ݾ I CAN ALSO SEE : !`zh4!(q ^#Vzhͽh# !aݾ(5ݾ(5TELL ME WHAT TO DO !Fazh͍c6ͬg2` 3~ I DONT UNDERSTAND !zazh~ #(#6~ (a##ͬg2` ~( (#^!u^#V~(ݾ #~w}`##!r6~ tݾ+a~ ~ 86I CANT !bzh+aI CANT DO THAT YET !'bzh+aI CANT GO IN THAT DIRECTION !Hbzh+a( ݾ(a#~(ݾ(+#N#F# [c2` 2`!Ic^^#V:`ݾ(Z##6a!ku^~ݾ(A0=_ݖ84!ku^~ݾ(0!ku^~(!`^~(Ób !`^ !`^~¹b!ku^~¹bʱbbbbbcc(c8cN#F#6 a2` 2`!c^^#V!kuN ~!cÇhQUITTHE HANDLEUs]rscddEexeeeeeeffff1fg/g?gRggMg1aMgMgMgI HAVE WITH ME THE FOLLOWING: !czh6!ku~(B8:6!(q ^#Vzh~ !Ddzh WHICH I AM WEARINGͽh# ݾMgNOTHING AT ALL !idzhMg͂c("I AM NOT WEARING IT !dzhMg~ (I CANT. MY HANDS ARE FULL !dzhMg64 f~ $I CANT CARRY ANY MORE !dzhMg͂cݾ 64 fI AM ALREADY CARRYING IT !!ezhMg͂cݾ I DONT HAVE IT !PezhMg 5~w f͂c 65 f &I AM ALREADY WEARING IT !ezhMgI DONT HAVE IT !ezhMg!}yN ^#Vzh*}`+a~w!`N 6 !`N 6cc͂c#Fw+pfOK.. !!fzhMgDO YOU WANT TO SAVE THE GAME? !4fzhIhY g!`+READY CASSETTE !mfzhIh>7!ku5>7DO YOU WISH TO CONTINUE? !fzhIhYMgDO YOU WISH TO TRY AGAIN? !fzhANSWER YES OR NO !fzhIhY,`N 2`!`N w f!kuN ~w f!kuN 6 f+a:*`G:`'2*`#:+`'2+`MgYOU HAVE A SCORE OF !lgzh:+`͙g:*`͙gͽhMg͢g06h! "&`"(`&`~ ( (#!v>6&`~((6#ݾ ~#READY CASSETTE !gzhIh!`)>7V!ku5>7V!\66~η(0 8` W>8 >8>zɯ~(6h# 6Ih > (+>6h> 6h>6h6h ( (w#> 6hͯ 'iUiiijCjqjjjkEk|kkklClqlllmBmjmmmn7nennnn$oWo}oooo"pOpppp qYOU ARE IN YOUR SPACE CRAFT AN EXIT IS SOUTH YOU ARE IN AN ALIEN AIR LOCK THERE IS A LARGE ROUND HANDLE AN EXIT IS NORTH YOU ARE IN THE RECEPTION ROOM EXITS ARE NORTH,SOUTH,EAST,WEST YOU ARE IN A SHADY ROOM EXITS ARE SOUTH,WEST YOU ARE IN A SILVER ROOM EXITS ARE SOUTH,EAST,WEST YOU ARE IN A GLASS ROOM EXITS ARE SOUTH,EAST YOU ARE IN THE WEAPONRY EXITS ARE NORTH,SOUTH,EAST,WEST YOU ARE IN A LONG CORRIDOR EXITS ARE NORTH,SOUTH,EAST,WEST YOU ARE IN A CORRIDOR EXITS ARE NORTH,SOUTH YOU ARE IN A LOG ROOM EXITS ARE NORTH,SOUTH,WEST YOU ARE IN A FLASHING ROOM EXITS ARE NORTH,SOUTH,WEST YOU ARE IN THE COMPUTER ROOM EXITS ARE NORTH,WEST YOU ARE IN A COLD ROOM EXITS ARE NORTH,WEST YOU ARE IN AN EMPTY GUARD ROOM EXITS ARE NORTH,SOUTH YOU ARE IN THE RADIO ROOM EXITS ARE NORTH,SOUTH YOU ARE IN THE KEY ROOM EXITS ARE NORTH,EAST YOU ARE IN THE MAIN CORRIDOR EXITS ARE UP,SOUTH,EAST,WEST YOU ARE IN A BLEAK ROOM EXITS ARE SOUTH,EAST YOU ARE IN THE ANDROID PLEASURE ROOM. EXITS ARE SOUTH,WEST YOU ARE IN A PASSAGE EXITS ARE NORTH,SOUTH YOU ARE IN A HOLD EXITS ARE NORTH,EAST YOU ARE IN A STORAGE ROOM EXITS ARE NORTH,WEST YOU ARE IN THE ANDROID REPAIR ROOM.EXITS ARE DOWN,EAST,WEST YOU ARE IN THE CONTROL ROOM EXITS ARE DOWN,WEST YOU ARE IN A WINDOW ROOM EXITS ARE SOUTH,EAST YOU ARE IN THE MAP ROOM EXITS ARE NORTH,EAST YOU ARE IN AN ANDROID CONVERSION ROOM.EXITS ARE UP,EAST YOU ARE IN A PIT ROOM EXITS ARE NORTH,WEST YOU ARE ON A LEDGE EXITS ARE UP,DOWN,NORTH YOU ARE IN A GALACTIC BAR EXITS ARE UP,SOUTH YOU ARE IN A LASER PRISON THERE IS NO WAY OUT YET YOU ARE IN A LIFT SHAFT EXIT IS WEST YOU ARE IN THE COMPUTER ROOM EXIT IS DOWN YOU ARE IN OUTER SPACE YOU ARE IN A METAL ROOM EXITS ARE EAST,WEST YOU ARE IN AN ALIEN BANK EXITS ARE NORTH,SOUTH,EAST YOU ARE IN A STONE ROOM EXITS ARE EAST,WEST YOU ARE IN THE DEATH ROOM EXITS ARE NORTH,EAST YOU ARE IN A TOWER EXITS ARE DOWN,NORTH YOU ARE IN THE PICTURE ROOM EXITS ARE DOWN,SOUTH YOU ARE IN A ROBOT FACTORY EXITS ARE UP,SOUTH YOU ARE ON A COURSE FOR EARTH qqqqqqqqr!r-r8rMrTrmr}rrrrrrrrssGsdsqsssssssst"t8]:0O1:Y;DbE>]?DaG@]CC[F>]K7W\)HN&<ljdbsekRvVlGf8uIX}U}UgwLge]Lp}UyqzniýXjr (z­͸BY`$-[(U20s7/{7.~7.~9-vEAt;7j7=R8BB8@9,3-(+1<;RDEPJKV8C@:B8:B8:G/,<();+1D3(>: "FtW[UKE6QCO@Yy7Iȿze#>]6I#( %.4%(.+#@c^iZId:/I DZ5>R2;U/?Y36X10U28]::_<8X9E\??V9Ga;DdB9^C5[?B`ERtYLnS=cGO*:qRtN}lgd`K~xXQfan/^AD'9#>/k5znOr1D^3TNOj 38&{?`R;^K}oOw;10LC[pWg}abghmo}asQcMv^M}]DzT@vPEwRIyYScQcMrWDbEFdGSpKOoDIh>Cb8>`9<_A<_A<^C>aC@cE8[?3]Q%Nf:RL]maaVePeBsF^e1pNvVpG}U^yOb]Vaomjblhj͸Ű˿ſƽ9qCX|Q W88w93w6/6/;-~F8}H:F@mPObBIR*0:'2D16\/3Z;$fyģvSkL~qZ`^z>tZ#zSZ|a"E)3;r׾`ƬPL<<P9K=ZpżOF7&\wjQvk`Xy'$~i3I/)A,azdlrho[m]oXmQcQcQcReUa]iM|_Sw`KpXJlQAlO=gN?hPFhMJgMJgMHfI>c@?iM7bE1\?3^A;eI;aE-TPCX1Gvoruaq\pGc0o9WqC{KRJQRWyH_MRp_ce{{gvzſ74cUN \k2Ij"!p;1G8A/5$~L1L4P8D1LB~B3a2#Q0%S2#]D5o1+r*$tE;G=KHwJH\:8L2328K1/D+/D+&A/-ET)n)nsmQHUKF:VLA:C<L=XRHAGOWX\_423/.XYQJ;,'%#!+/8;2543XT[T[TKJKLIJ[X`QmZnb[2N8$+ +8_7PvOTzSSoNMiHFY=JX=DW;FY=AW=5O53N32S<2R?6W@2S<A[A=Z8C_>C_>A^<IcGIcGXvYGnO2fE.bA!aA'låYqUmVu`d|oCf#kai/[Grų\ôXo[..<D8C5Mf{:id+SL}gVz ZxjyRar4CT9CI9[I9[IIx[S`UbS`ReQ}]GsSKyTW|dMqZ;`H4P:4P:LiRJmONoIDf?LnGKhFFgALnGJkELmJBeG<_A<[=3^A7bE/]D/SXCR;J~o[kWeBX+l@nDpGtMtMj>yOuI{R^^yOTi`odszt}fwtD&.)$(!9a3Ls5B^-Pq@U&%t"!pA2G8?/3#y?1I6=*D.J<?=u32i-.`+&g/*k1)u9(C=GBBGxDRf@Nb-@<TyVLqNTuO_}We]c[EZA5H77J98F:BP7>P7@R9=X=6Y=8\=:^?:jbþOb^4=>:CDLZN9O3E\?=fB7\D=bJ;`HIkYF`O8SA1I77M@HUJ>K@;N='L47\DOy];jMCiKElM?dIAgKAgKBcLAlO;bC9_A9^C<^C6Y=5\I:_l"k*tQA5%{3&1(x"!attVV;5|+$l9*|?-3$9)?+J->!D-C2L8>*t-&b+ e/$iA47)zKAKAC8}??rKL~U[p6OB%L-1Y97`<!E.3Q4S#'S?WDS=eFS8Q7RARBZKM=4(aX]SeQ`GJ9;6,.$9)TxIP|DTHQLQEIz=QFSH^Oj[sm* '4C$:b62Z.1\33_2EqDO{NQ~Mdchg3Z=1J43L67K69Q<4S5:Y;8X9:Y;=Y;9T7Ca;<^7<^7HiCInSGlTOt\;eI*]?8!O6ħoN}lhl^y\dvunxvMmoGx=5{*A9+.9?6(,Minj}fOI}5jlCF+MWaySw|)CJ)::9JJASC;Q7EZAC_>C_>>[AFcIE_EA[A3N3c@6[89[I:^n%rEPp_H[Dc?S+k:m=^2yO}Um=yOXo@^am6R]|GxR\xTmḾ( 6RIHNy/)m1,o<47)1#9&-%q$$`pqTY?6-%q<(:$:#>'<%<$@(?)9$;)1 z2u4sD+D1<5@@<<=B@?@?Qa3TUP1NcXl>pSMbSNX|MM{FWPOJOFM|DWHUF^I\Gzj3 #4H(:\56X17b93^5/Y1=h?WVekSY=cG7O=9P?c@ChM=dEAhIMxY1`CD' =Ug̭\~TvldcS|q_~xgOnh[~zLwr>.,4VWeqtVXfw_T)ZY%VU 1.P~}hZ7RX+4>>IC%}P8J4<53:3:=D::>>;Iu/P^(hTBnWXq5Wa?Ib]VUOC9NAJ:F6TBRILDAC0Cq;BrIsuOlWwLPtEX|MQJQJM{FI}DG~?UPUPoaBY3=W18Y64T2-V2*^=$X7#U0AjFfab]A[AAWJCXL3I<.N;,O30S7AY8FY0HZ2L_6E]<8Y6>^<6[89a>;d@7`<DgIEnV9bJ6'cAbwrȧlb}LnSwmQvkRneDbT@^PXwe\~f\{lbrlZPyyH8{4<6@HcUQ[hX@AUoUIz/#:-Ywgaz9WR&;:7G?7J95H7>N:DV=H9DQFFSHDO<BN:FO7Q5/I/J-Ix[ApSGrUFiKFiK=h?@g?FfD:Z86]53a<1^:7\Q5Rl!pWYe`Y?Q7a@]5^3d;[%yHs?p6xBzE~K[}Nm6~=XI_V`cedrwnuEJ@D-,B40rF+~2j8v<w<w<'u6'pScZmjIA%f1!w8z>#: |.u.u2H)P2L27 E/C,P8;0A6NJ0Np JGVSOUH|4oh=XaNFRDI6K8NCJAZQ##׸"+Mjt`[tMVi]UxDNu>^NR~FM{FUNIJE}FIHMLSGvkdY01P):\56X13_2+[.3d6J!QzVog[ySE`CIbIA[AEYDAW=;X6Eb@MeALa@E]<CZ:C]A:Y;@^A,Q.7T2G_>CZ:E_9PnQTrU)O3O1XoVmXmȳS~nMn`Tu^FgP@]CHfIHfILmJUrP[pW]rYil\weVq_f|OsxOUA/CR?FFZI?7:cK)Y\0\TTx^W}}=Y[!=?4IH7G?3L65M8;O:9S9=X=?WBKdNBQA>M=:B8M==Q<'>!,L*^~\HkMOuYGmQAmM?eGFiK<_A7_?-]?-]?*[O0Rg,u1>uqW@M5d=j>h:d5_*m=o@q<yHqCsF~KRc)wEQMVTj^j^rahzvy[U-%B7, B5y<$n@%x:r8#q2!o0m:(z6%sWXVnqPN" c2nH*:!y>%}8z8"t6r8'x)o d4z<&I6I6A23!@2>0F8?-M:FD5C|'Nk!HeMN8zn6pm"\Y/UPMaSM]IU@YCJ=PGJAv&I-TXZz~Sj/?}tM9{JkERzNPxLRzNMyIMyIQN>wE>|I>|IMNXQ|u-O8'R)+U-4Y)1a('W6a,qcfQ`yKLd:YrMOhCOhCSkJKcBMdDNnLNoIHiCNoIHk@JrFLtH=h?DjCBgDHmJ8X9:_<@eBIoQ(];5qO[uQpjbz=dTF_RJfPB_HKeKIcGKfILkMa~\VuWbca{aazd[t^hqfr]~pi|i\\EA><F8:;caEuBhqa[Otu9VT/A>7JF7H<=NB5J>7M@;L@;L@ARF7O=;RA2?48F:1B6=NBARFDLBLUJDG>HKBEE<:G<>K@7J9 9,61X;KvYOuYChMNqS9dE;iP-[B.^I)W>1_F1]I,R[CQ;IgYaEaEhAV,i9m=[+r9t<wL{K{Ks?s?Th,l)_}Na{Rd^n~cun´A^8suy5kL+ \1MC0_;'Y:-o2#l6'p4"t6u2q6 o8+oOBE{evCK(b*,r/!o;-~)l6"y6k>#s2h(^4lB'zB(H)J+<<P3B&@%A+K5K;FA?GAJ5Ne9660:4@U"x#+E?D<J=D6H4P>L:`;վ%;1GPW^Qo@wLg;ļQqFLuENwGKzDO|KKxGIJO~HE{=KCDCC|B]\UL ; *Q)8_75T*Bi40V"]TYU\}ZXxVSiM^mN`pPRfFWmQLkMJhKOiOWpKXpC\uG\wDKn:UxDYzKYyNRuJNqFEoG?jK;fG?nQEnVOvcmY|iWzgKn[JdSMbGOhCOhCBf:@g?JpIEoSImVRnXVs\c~aa|_igomkjsr}|wwlsnuri}d`YYAt{`bV|6C8BP7BU;9O3;R5CVBASC=P?@MB8F:@J;@J;@J;@P<JZF@O?DQFBICBIC@FA>E?FMG6C8.:3%:.*?(UaKwW;kK;kK=hK=hK9^C9_A?eG1ZB0X\;X)FA=_HaJkKZ2_<W4aC_<eBl@sFrInDm=s?e.d)M|G]yOyO^m}Usjq_pXT1u3C(6!|J4"@:-{@)|8"t8m6e:"i3_5d8 wH/N,>!@#D&B%J,@">P6R8G9F?@A@AED{AAh;?Y9.R?UAUAO1b DYtQCo@a+O$}Dg<Fq<Fq<ExBMyICp?P|DQEC~??z;FH=w<SR_\9d88[38[3A`97Y)QrCmb_}Whz_ViMP`@dtT^mNbs[WkVWkVQePN_GTfKXgH^mN\qP[sR[sRYqP[sR]uT[sRSoNLoQTwYGmQckoOr_LmVVq_NhWPmSUrPQqFQqFIc=UpSgeQeP;QDZhYbpaSgRcwbi}hbr^jtel|hvrsrpyluc|fsowwmmfi\vhTm`?]XMtpo[|}[yXjrBU\9JJ;JC3L63L65O55O55K1;Q7?U;9K;9K;2>7@FADEABE?DLBBPDFSHAPI;JCICDG>DG>JND:A;(4-+:3#3+3!I&OxTMzV;mH9iI7gG5`A7cC;fG)R:3WP5Hd5Hd7Az1<}RKw]W6T'pGrI]&h*l+f&wExB^&d&v7r1Px;Waf~crhu^zExBk3["QT/]@8$b0ZC8P>=MBBQAAh9/q- m/!o3%v6"y0s6r0f#X70lz}_\'$`2#l@&u.g<'u4p6k.c5dL/6xF,=):)4#6*9/7"1;'6,@6B9<0C4E7E8A4E:G<F<E7G9=5KBK7S>S=`InW[PW|In!'O?vOb1W>e=Fp?@j9Lx@Lx@Jv>Ix@R~FAv<I}D@}BFH6s8SRQR;k>3d6VgCTcDbqR^rRbtLj}TbwSe{_SlSUoUWqWKqUGmQImVry`~cJgMKdNQcSQcS[oZVtYHkMPsUjois7]A9ZL0N@DbTRpbLgUNm[Tsa]u`_s^ascewgmlpgri`sWl~csoeza]u`]nbYj^Ud]Qe]RneteyqKZS9GAFTE=T7:Y;6U73N3?S>>N:4D0(9C'M.C,A+D2<26/81B<@5O;E1R6G3O;S9H7C9WNLHbNp]ƿrtXqKWIDs| YNRrPGqIMvO?m8Do:Hs>Fr:NzBG|BCw>Az@8z>@FGFHL?tFEzL4[3\Tph]|RXvY[u[WqWI]HM`LWkV^l]VhOViM^pU^nKXhERc?ZkG\qPbrO`qMdpMhwXUjQ[pWOcNJdSLgU\ybpvZv`RnXUnXI]H[oZ`sWOjMVuW~x{DcQ:YG:XJ/PB>]KBaOQjTNgZVpbWm`QhW]q\au`e{_rcvgf{Zjx]j|cbs[]o_WiYUgW[m]NgZQqg]|sTjf]khCPN>ICFVB9P?9P?7O=5F:>F<@H>4<24A6@MB;L@FR>J[CHYADT@LYAHU=MS9OP7HQ7HQ7#S57gGGrU;eI3bH@cE@cEE_E;MLJSu~srzq"#ZZ\9f;`5^4l0a.g3s?e3[)H n/t<QuIxS~\vOpN{csWoQkYsWIMP)\6P7 E68N<>T25;8:D8:D6)OB/iH0wJ3yD.}<+y<+y0m8%n.#`0%but]X+&g0c8!g4 j2h:,q:,q$[*gC3+z7)2#9#=(G/; = E(B'F,F,2!Sy\XeS_M!#Eg7LwBLwBFq<Lv9Hq5Jt4N{2E{0Aw,A|0<~5>76z9>|I:pJ4iD6[8kcme]xNdxX_wV[sRG]A\nUXiQ^oW[pWSgRUiT^iV`nSVdIZhMThHfwS^nK^rR`sW^iV`lXShOGaGUoUor]v]WmQe{_nbYwQSpKNnLhnD`JB]K9ZL/PB-Q=3]D<^CDfK[pWUkO]sWQlOYmX`mU^jSrbdxXdxXe{__s^[pWYnUXhTXhTVgRSlSVtYNhWD^MevjkjWkV3K99K;8H7F<:A;#M4+T<ClTHiREgX?`R?WrQS}Z]]v{ 4`'k;],[+_*e1a-r>r9b)Go:k:uIi7xYrI|XhAw]gEcF{cG;Q';#;#4#N=o>:F85@88:DAI64;PP_baqdjtPV`Q]n?Rn9Lh?LyFM46m.1gr|fj.3d+$]4%_2#]>/i*NKL~EFxY90}gA2@#D&F(?&>&;=!<%H+H+I%I+="C'R5F+N.N.R3V5Z9T0R5D1F3Z:_FLɠ`V~SVaC| |_R>FgAGtCIuEIuEEqDGsFGtCK{B=t5;q36u5.k06s84u::{@8wC4s?+`2d\h`UuJe}\WmQ[qUMaISgRQePbr^_t[YsYE_ERdIdxX`pPXgHVkJbvV^rR^kQ`rYYnUQfMWlSKcBsjg__wVg|ccx_[pW`sW^pUgg>ZDD`J@\S3ZJ+QB3UF8SAC\CE_EWlS\nUVhOfs[ht]fs[fs[rel~cl~cr~gdn_`lX\hTUiTWn]OfUYmXSkVOfUKbQQbVp~rz|Wm`?NG.:38E=6B;,9.0=2*7,4>/(7'FTEK\PCYIGYIEWGN\MCSH=NB?PDBPDDLBBK@>GH,7T,G?fWYDWBaNjZhW`YpO]Z ;):TCIo`/Ji9Tsf\}X{KTS`erI0k>a4e1_+f.f.`(_-MOi5zE~KyHqClGhAaCgRaJQ2@ JK%<'@+"lLLjFFH@@BIK=FTEbpa]leWh\Yj^M]RSb[IXQIXQESPFX`JVoFRkhoed10g#"Y8/g-*Y=:iM*"V>6j81d#]#p=0;)>-@/8(23#5$;*B-B-@-=08'D2K2J-T1R.P4V:T7R3O<ULOFq|3Q]zqTY0f8OlJPqKTuONqFNqFLpDNtMHs>Hs>Hs>?s:7p65n43m20o;+nF-pH.fGVy[^cRrPUrPWrUYtWG`JSlSSlSYoSWmQYoSYoSWlSOcN`rYdu]ViMWmQ]sW`sW\nUSgRSgRMfMfgbcZx[cx_g|cg|cav]UkOsmLiRKbQCYIC[FAX;?V9A[5=Z8E_EKeKKbQ[m]WiY`q\ht]fx_hyatitmpi]q\cueQcSUgWUjQWlSYnUMcSQgZLfXF_RCbY>XVf~pgu~V[i<@O.0:*.06;:0540544;5DJEV[MRZAP^CP^CPaIG[FCVB3I<FQKBHF>CB67BHA=pv_cDV/`9c?eBW4`?QA?R{0Y*FyWV?>^6{Kx;e~D]Bv50hjfX.f=]-g3d(f*j3W%Y'Oh/{Ko9g-e1l@nDcFeIcF]<~c<@O)b].)08^.]6&U*I,!O.$Q4,`(#o=.?/-G2E0;%6 08%9-@+F1?#M/L/T6H+J(F,N4V7X4X4V3R?RGuvkA9:sEKQJ[AK(I%QnLPvOLrKLtHHs>LwBErALpDFp?NwGDo:9m4;o6,m4'j50m>6tD3d6gYcUQlBdwN`uTRfFOgFSiMMbG\oSYnUZkS^oWXhTYmXTfKbtYnzWbrO`qMfwS\qPOlJKhFa~\up_|Zezal|hfwbn~jWpZiiOhRI^RAWJE[N6UC'W9-]?5^F?WBEZAWlSThHVkJdxXduQr}^ly_retpqsk}mhvgjtebnW\iQbvVSiMYoSUoUNqSFhMFhMRnXJiWRq_ru}~frkBMJ$+%:G<8F:>A86>4FODARFK]MNYFR]JXeKH\<H\<DV=NQHKDA93/$"6W[Ws\_8Z-`4a:_8Y2]7U1Q<I3sT1N+g-Mp/SFSG~PvH.G(b5^1U&_-^)`,f5_2Y,EUn2p6Ye*lGpN`5aC]<Y=]<C#9;4GLMQOZnapijshpynpynhvgjyidrcZjVOcNUiTRbNP`LJZFN_GGbEGaGIbIQhWK_WM`YSa^ZagXVjBAT(%I)*YI" c?1/}A-5"E1G30#*7*7*@(>&D,>(G.K5Q;D,P2P4R5P8L0P4Z9OB4MB\ ^x/iKxzuT#.$JD05Nd?TkBPg>]{HMo<Qt@MlBQlBUs@Qo<Or>;i7?l;?oB3i:7jA;nEDjC`[FgAKhFdv[XjOZlQ`hQRaBHW8L\<XeKPaIVhOJZFTdP^kQXeK]sWYnUav]`rYbtYOjM]w[sjc{ZayXfx_ly_j^nb_wVmkusJgMI^R@YL8X9IbISlSOiOUgW_qadrclugp|hr}jj}apdthfx_`o_bk]bk]\mX]rYWlSav]RuW;bCAhIKu\ToJzeWratsGQZdcv64H7FN/@@3ED=QIASCK`GG\CGbEGaGA[AAWJ>IC/;<#00FOq}p?1Y7n@Z,b4p=Z+f8d.V(`4R%.Vd%s C{3}6|:ICFp/QxBMa+Y#]&`(d'b$b,f1b,JX"j+j+bn2wLo@eBy`MlGE@"Z<]KOMyQO{`gpjurpwqnuorzpr{mjtebpa`i^VdXVdXR_TK]MGYIG`J?WBQhWE\KP^OPZKXaSVdXVdX^d_\c]`efTVlNPf&*\-v>B$F(A&; 1=$?!A#C"E'G)E*L0P1N.L.N)Y1Y1[3X3Z6V2b:Z1xNB|r8HUOA1%)$-)5Ig4Fm6Jq:Mo<UqCQn?Eg7Nt@Km=OpALtHDl@Bj>=i<CnB9c;9c;NoI^~\<\:Bc@]uTMiHYvTQnLHfI>]?6R<HdN>XGB]KI]HJZFFX?FX?WmQXiQ\nUbtY_uYkisqav]cx__t[_t[ic[vYa|__y_usqoPmSG]PGYIEWG=V@@bG4W;5[L+RNGV^VgR\mXXiQftYrerexhwscx_cue\j^`o_fuebmZbnW`mU_t[PmS4fI4fIKeVjTh_oVx]{~wYmKa[qp~T\N(:BHUJK_JG[FK_JG[FAU@;L@CQK*E@&A<*#D#f/\$b)^-j-n1b#XU*W,* >X+f#y0{,H2;A@t.PI\l$h!b!d)`$d(h4d3R!DT n8p;i4uId;hA_@j\D#z]>4 VAdbKTmMUoffhsppprmnpknwlpynjsh`nbfodZcX`i^TdPM^OI[KR`QTbSOaQOaQK]MIaLMePNhWOaQVgRRbNTbSR[\\ff^Wn8=nh75:0/%-$2%:(:(8'>,?)=(H+F+Z6P,N/R5J3P9\AL5V?T7^@B&T6GPuUa{gX_J,:bh/5("Nh6Mr7Kq5Sv@Mo<Mo<Mj8Jq:OpAGi9Dl@FlEDjC9d8Ao=;i77e3RsMXvY@^ADdBWrU]sWUkOSlSUnXAZD?PD;T>E^HAZDQfMAW=I^E?U;MbGZlQXjObtYUoU{usmav]eza_t[fx_c~aa|_[vY[u[x{fiFgPC[FC]AGbEC]AFcI>[A6QL,;L_`LTYKZSXfWtin{cfy]l}el}ebr^drcdt`fwbfr^bnWbnW]rYLnS9iI/`?Yp^rXlSePrWextvvSC$x$$3DRCP^OJ[CFY=6W@^>T#FMU"Zmday}kj]Tig9%)9#?]7>c3Fk;@e5Bh7Fk;Di9Gi9Fi>Fi>Ca;Ed=Ed=Be=;i7=h?3^5ia[vLKf<WpKgoM`lIbmKRcKG[FI]H[m]J`\@UTNcbO\\O`TN^JHXDLa@UmL]uTPpNhkeza]rYav][vYa|__xba{aa|_UpSgb~`~cG`JE\KF_RB\NCSHI[X?QN?Vf:@zylw|Wdddn_hqclwdlwdlwdkghyahz_fy]i~eYmX[oZ]q\SlSEoS=hK4lKaxq![@HgUWrxkYdkeˍ۾lUB,QTr36T?PDVY-h7;m;;g:7j71c1d`]zUIfAUmL]sWYmXWkVK]\(2_LVN?M+e?Y3b@[@t_R<`Znn|m^l]YoSuo[pWOdK^nZfx_hyafs[fvV_xSk_trpqdeC[FL]HVcKR^GI^E@VRDZV7PjTO|wkk|ݿD`J`lXbmZrifx_czie|kctharfk|puwdncq]kYnf~~gyN{o\}Zz^bzdvopC ]S![:MQ?QAEWGHS@FdV/9YvK>-!_3`)d*l1d%g"k'k`VVJE(C%P(f1[ky0s'w-Lkv7x;|:x4RW%S)O&R808!K'P!Y*i:`1iH^2hA9oXG&V5S@5:]agWgk^k`Zg\\j^\gdfljbgffj`uwiqrenxidi[hpfRZPN[PM]RO`TP^OXcPN`EViML_COhRMePHdNSgROcNI]HOdKAW=AW=4OJ7R#m#,uND;6+&@36*?(I2?)P6T:N)X4V7R2L+J(\5\5U+^:Z6g?O(N1X;\=R.N*Z)d,y:w7SLnK\5P6N4*#@Be=,S+tl`UV{KUrPXiQZcXX`V_ezOEA7eAe3n2\r+m"f+n3yQ]MsWHmr|zp|hQePWkVXfW`o_bpabs[c{Z_wVypwvo\nUP^ON[PT\RNVLLUJBYTJ`\.RXSyil~^vί|MLaMClc{|okjc}ciiY~f"SJ2cZplr~ej_^kjmlwOlnY²|~L:XAT>!H#8/3H?>M=;NJ $C:lJd)h/`(f.d']c\ ^ d+Nb(e.EF^dl!gs u$<g}6:x4@gV.b49g8>k4Af-9y5@'N)^6b:T&lG?7]<WC+4+PX~WihGYXE[Nbodbod^f\dmbfmgdkedkeqokwuqptj`eWZdUPZKXiQG\CMaIN_GVcKN^JTdPP^ORbNXhTN^JSeUEVJQbV?S>=Q<7K6-C6)?R7C)4u-(6)>1:(?)L/B%F+G1K5L-P2V<H-J/V2V<P5N3R3V8^:L+V6^>R<R5X;`<\5a6e9c0V.^6^;UCGISTBL)9DQT>^<Dg<Dg<Bi48d9d(o#e.|"k!k!n2h5d2nH~qddf^^`py_bvVdxXayX\}Zjolqvwa{aWqWXfWKaTJWLR_TM[UD[bD[b%/e{exvfmm@W`^tpf}xn|mzuso{Km^5gBлuK]ya>lqTz&֙}`{z{K+_6mD/)m#,9;CQ;IF(`E^BZDUHAB[\^g-Nc8J8[3@b;:\5:d38`44\04\0;[0=]29X.:]28[3<^7<^7:\5=Z87T2ygqcaS[xVUnXatpQd`@Fq<.`SGT`#Vo!~%|)rz&em`jm*k)c)h=`6S3f<V1d=rK^EP8XOxgttI\UCVOIXQ@Qi:KcTOz^hdlɢnEc0kbbs[pipie~hboly-3˹v{zdqoc|s^йDƝAB(>#I>5:]/>C3NT(`unKKp(abbh[gf%l-j2f.r:k3m6l/p6t.t.r+y*oz&iqk}6x;|AJ50qU/6TL\xRb~8;}2(;'ePb@dKaZ3-29oBRbP_pUfZSgRQePOcNNjTI^RUk^Wm`crkfqnbljfrkdoibmgbk]`j[VeUVeUWiY[m]]o_^k`bj``eW^cU^iVXhTTdPMePOcNSkJG_>=U47S5?Z=8VH0DT#Q2(D)D)B*D.}J.N1O/O.U4N.V7Y0[3S/Y4W3X7R<B?HEL\4BJY`iA`9<^7>`96^2:]24W,8_73\84Y62W44V/7V/=\53S(2Y16V4&F$UuJtob]YvT_s^\snLc^xjD5o`dvOSmEIckcsgfbsgN^O\zkxxzmxktkmk]t!_8v|w|sc˔x˹xW;;?116PN7>1={aXN+H%])YZ^^Lb$s?o@{Rk:T{KuCo9Rv>~Kv>Qt<Mn,d`dj-|Gv>cm` X"(ShvP_p4BVC>rJ7}TAJ7}jXppPP_JXIMiHUrPHmJHiRRnXHdNDeNFeSHgU]scarf_uhawj\tkVq_d~m\{l]scZxjWyj^}k^}kkqfx_`mU\iQ^oWQkQWqWQlOMiH_wVUmLFZ:I_CCY=B]K8KR==dAAhIJy65u..m/)mC>BD~01l=;v?8tC;xKBL?>/xF6F6N=D1F3B*F/J2J0L8B2F5J7J6F2J/T4N2T8R1L1R6J6N:B?@=3>WeIVhyKcBEe:Aa6Cd5?]*?]*9Z+5V':\5.Q)-E$5K15K16H/7Q+1N,5Q0-G!wki]\tJ\qPUgfM^^\a[;O/m+x{ujkpt }/r~,nr((s(rtxs"vkis ci)y:R48;}BEq^dgYoz_,re|}xyjw_mgqkiiyw5Z%tJ}bYr7U]RiVW;S4H%I;C#Z3L%d4S[&a,nDg-{KFz>Pz7UOOy0Fr8]r8s?k3xSm6|Gd#`q5u+|AesC$HisS]r2GF2NEB\N>WJ9Q<\ybdjB_HE\KI_O[raVeU^l]OhRMePJkTFgPHiR`}fi{kb|kPjY{а#T3lD{]F{eUmUmYpp|zvVgR\mXZjV\mXQcSMfMJgMPmSI_CDbEBaC?cL;ZTGPiCKeABt/&s;3=2;+SGC7?,;(G4Q>I6>&B+G4F/F.F.F+U+S)T)S0F,L2H2L1N2T4R0X7P0T5R2X8U-Y8L:N;41xDX\q[u;X6>`9>`99W1@V1aJplUUN(b>KCZaghoupirivmwsl?LjrwVЂDiO8F-Q1XJ_rKEN*L([(a&j-|Gs?n2TLx4K:KNK}6YxBTzEr8t<wLq<z>t5n%gs w-FxVV4!V|8GZ4JF:TCD`JA/E3F.B+YLSE4(A+?)G8?/;%A+A-L5D,F.L0O(M&R%R2L2L2L2T4R1W5P0W4U1W5]5W0]8Y9P9T<DI1mh}by4T2:]28\06\(6Y.@c89R-9R-0P.,L*.Q5)C)5O5-F0-F0&D)2P5=_TshQrC[qU[koUei86|N(^7Y_bVd&MqCuIRCLt|"(-y$ur{m}my$jxpj%MwFmD]VxvkV*iEZILS\jqzpxdzfyyp{\6_#ļ\ybWZnSўP»V>IM$U(\GdN@JwD@$ ]6yOFJI:KI@K<BDB{3Xz>S@x;x;~Dr1wEo9t<@kf~=vAr>O@ov7?\6@>;RADaGB`E<[=JjHqqYsYWkV^oW`rY[pWXfW[m]SeUTpZ_lgtWrToOrchmĝoƟuOT]MzHiHaPiXmg|mzanLgUShOQfMUoUOdKKeKMfMOdKIbIGaGHeK@\S6JZ8L\99lG:A4H2K8A3K=V@E3C0I7H0J-D'K&T4V5P/P1L2F,N0P3P3T7R6L1V;R1R6L7T>P:L4R9Y8[=V?R:PW 7VvhBY3Jc5D]/?]*2W'4Y),S+2T-1P)3R+-I+3F53F5.;02A1-A,)<(&F$7T2upYoSSeUDSdHWhLE[8E#c!gZXm=uCd;hA|Xk:wL|Ax;v0|:q$}/{,rva s yt nt r.h e u$w&T[mepRp1lnn}ryfvfvft9¸tgyji^[_cb¹omM+W;JH"0 9@^GB}xvO?G8?9N@~DIz7PF~=z7Rv7Jz>t<j-wEo@q<q<l/t<xBSg3wEs?wY<:}VU8MY-E3AW=I^E8X9NkQy|_xbXt^Tm`^wjTm`Rk^Pi\^wjn{ao[iMi>e*^4h3kC~!\+bTRH{KxMoY{RqdqeuIaLPaIZkSWlSRbNTeMTeME_ELjOHeK?WB?QNAN[CO]+/V3*kA8yH4A.C4;-QD[IG5G3H)H+J-M&S/F'J+H,P/P/P0N-U0Y4W3T4J*Y1]5[4[4]7]7W2R4X;L?F8})N9R*JdbUs@;^*;^*Ac3;[09X.,S+2V7*M1*M1/I/3I/3I/3P.(G)/J-%@#$C"(G)fgSmQMcS>L`DRfF>W8I)^%Ve-[#e1yHi7sF{Rl@~\jE[3tMxSe1r8zEp/Qp/YT5v!Hl2jvmu$TnpyyigwirirdghkOuY%Ľowk]k^Vo}oJK6,D)^QnTSr@p3UCZQK3O}6KLx;Iz7C@F}6|:r1~DJs?g-{Kk3s?i0_(s?uCd#l(t<v>|NeQcOu;3QDOBM?N=J2hPP=9+F/N7R2H*J-P3N5L2N3R7N.L2P6P:P6N3P1X8T5V7X8U2W5Q1Y>PClB5^$1 +(/QNAo=>h7.X'8\0;Z37V/9P03J-/I/7R72E)3J--D'5L//J-*L%*L%%A FrqNsPDbTJXlFTh"!p9Y8TP n2^k:v>s?g-}UsFsFo@h:e^a$xB|Gh)~Dj-~KuCE}61g@2pW g>m”ͱufNEHfikm=eQ:lR S5¿to^v~`8ZCmQ~Q~MzW[~Qt_~e}gwm}\weQcSM^OTeMZjJRgCRgCKfIBdIBdI8[?6R<:TC8SAF[Z:M`0CVDMoP`|JXyLZ{BK|XK:-oid_M@%P6N3N.Q.M*S/O*W3P0N3T4P0W4W3U0Z-U,U2U2R2N0V9X@RFw,,;-+@4$T?@+n;g:Lx@6b*8b%;^*=`,2W'.U-&K(0U2(M*$G)*N/(K/.N/3N3-H-+G& Bb]_|Z9[IM\CSx6$Vd+`\t<\o9s?m6i0Zz>x;xByHxSpG_({K{Ko9yHsFuIl@pNb8[CIigR r+|Ghܴð½bdJO}JahU~s)ZY9ji$Ow(9E[/Elk\HT>wdsrZOu֎ܕɫýR]WS:P~Kx4R?9<R{3O~Dr+CR}6I~=|:Mt5Fx;Jt.t5z7z7z7Lr+p6f'n2l/d#o9m6b j-_!g-wENn=<'u?J\9ZL3UF6UCKdNy{molxjv\{lWkDv\NfNcHaLeEdIe?_Aa<_:_6[;a:n8q>wAvDqJwfi}eu_oat]u`IaLKfIFdGWoNSkJKcBDf?Be=>]?:\A>ZD@\F<[IPg_RiaFa\AbcGglAafCctMcKbQQQPPLB?D.R2O,S0P1L2P6P1T7R2P1V6R6T8X7U-Y1_7P4N9L8TEB8X%&09:=`M,`NR@6fQ\d=v>;s</](&I4W,(O'-X/(J#0R+1M//J-3O1)D'5P3.R3.R37P:#>#A[Aaq1F]%KCwFn%f`a$q<_!m6i0n2l/zEp6MMv>VxBuIXqCq<{Rj>^l@yOyOT|Az7Fh TSGÃlغn:ZqiE`fCbh6Wp.Oh,KEIwdvnuClTcj>xS|_mS%}usk\FpqTW-\k@2I.8~,A~,D:;?.@F|:v0Pr+:}6@l!}6s'y0@r1h"z7p(r+r+iv0p(n%p(l!fCo,R]38^9/6RI<]Fay*odh)e*g-v>n2p6n2p6uCWR}No@g3{RWo9wE|XrIzVd4o@wL|Xs?iIRq,ZSjn|dse|9[X=`\Cce8YrDe~%@(&lwjuhtT[lGxYg8PZGYmu+=ɕ&h`,W@vD525Ax#N.3B:k:@x4Cw-{,<y0kx4k }6z7p/j'v7o&n)l&iw-{3fd#l!fy0p6j0l@`WCTT;ML:V@1L1j{fqmtzpvElY:lR=yYC~_AaPgLcKbBcDgJmHoRuX{SqDeIuU[`t\}[{[zEdL~dMsUOvWYr\QePTeMTeMVhOC\C?Y?@_M7cO/ZJCn^@o^0cR2fT2fTBq`;R$;3/U9?dI>c@9fB7e@%S:Jmg{]qktUU!S!)T(/Y1/W4)Q.,S+*Q)(O'+U-1Z6-X//Y18Y61N,OlJYyo3?hMhM\%^^i0V yHg-k:m=m=e1r8qCbyHp6MbwEm=yO]xShApNZ,`5nJbsFR[ DTafdqWf]j9Sv2_b2_b7jd:fj4`d3Uye[vVz]xY{Kg-eh:xB2z,s`ü'2]h"T|Ar:|".u+Dk?}/s'<8.tp(:?w&y0z7v0dr+l$r+|Av7\p0b"h/^%Z!j*j*`#e*x4hp(z7v0j9aTFYl:M`6OBmYGl;uZ/iN6mR,cH,cH$XC#[EWA!YC_G.rQ>aKpOt@t0d0d:q^[^`NoSrNfDv\Sx]^zdYmXWkVFcI:\ARtYKpX?cLCqX;iP:jU2fT6jX:jU;fV?`RQsd@p[:iX:4T2,LFQKVgCGb8Mg>Kf<8\=FiK+VFTUv]~kzgdol]]MHOJ;o6)\&+Z1#S),S+6^2+Z1/^5'[1*`FVr^%O&uMx;l(f"h)^+sFc.i0p/t5p/|Aj&MXl(MSzE}N][Vjz7p/g-]bl@Mr8RzEcLZekgv7`X"Q>h}4H&JZE^i~¾s{MlFL~=MaTu+g6UʇvMg| aJh"n,s'3z&|)z&}/m{,u$~,2<w&y*r+c}/{,{,{,o r!s-m%}6y1gi o'f*l*Zp,o&o&fp(q$w-u$o$y0RfI,8Q:F_0JH^|njQsdIj\5YO5+6-"?51B66F26F2>P7/i69L0?/BQACZ:Gg<=]2Gg<Ca;HhF@`>;aEXzNvX]xdypshsji`_kf^T J2^&:e01c15g5+_5BdIv}&T#p@n.g-e*e1e1Qd#q<l/g3g3wE["sFyHm6uI}UqCW]yOwLc'q<`e1wLtMc.T}Nh*m6]ZOG~%w&r1bc'd8E4QAA7+#{Įʵðc?wLwLb}NRb]Fx;hTX>Cyo@Z%Sa$bx4l(r1v0t.by0u$s'}6|:q$u+y0q%j*\~Dh'h'g%l-b)t<Z$f*^!h3Z \'Z$\ `#`#d"b#h#j&dh%r1NbG1:S5=Wh~dwVriOqb7aH/X@+O83!0:+4>/.9)4<2I6?UE5L;=QI>UM:QI7YJ SBN5T;R4Y5eA{S'^*^4hLvQxMtWoQaSw`Ot\UzbQv^KpXImV;jM?oO?oO:tN8pQ8pQ0dO9eQShGv\4eL:pV8oT+kK=iX>l\Fud?`nny.8e,6ctp<(D0P6G7J6L9K;E6M=E>KGzHJ`24J690BY3RgCJa8Gd5Gb8Gb8MeADdBDdB7aH)zHD:{@(j.>wEwϱyE/Jr9^`h)c.m6m6i0o9v>k3g-z>m{3p(p6yHuCwERRk3{Rg-a$]!i7k:wLq<JSt5j!PXZjt+y*mp(\h"t5l(< X$ʽr1hFJ|AFxBMxBW|GtrR|"Zy*bNYc'MO _y*m8ms'n%@cw-kks't.hx4q$l(q<_(N s+m%g o,n1Z^(m*i&d%g iiik&g!bj(j!`|)*tg/jUH[nwIikIlf7[T6RIB_H>ZD2B.,/&98077.26,.:&>I6BS>CTECVOK_WGUR?PP=ON';30I<1J47P:6R<0O1:Y;-V2&`:1oHAXd?gE(qO6y\AaNcNcS~_KvYGrUvU7wW7wW9uS8qO:rQ:qS>tZ8pQ4kM+eJ0gI4kM6lFErNEkOKqUKpXf|zI_/EeYdSF5H-N5B*F.H8Cl@WY]&h;h;tT_9f?xYuIN zEVZCUr1l!bl([l(r1v q3=qR(J ms)d^>8HGmU;c?\y*u$y*j~=^ l(h"f n,`bf bd%e.Y"e3S'GR d/R V%[.[3O'^,V#\(\(\(_*_*]&Y$c.Y#g-q=i5U1]NyRdlI^RF`OJdS>[AB`EZQCXL7H<)9.1E0/I/.M1JgMZ}_X{]Z}_W}aWhUxe]mgzkx;`HB*(`A5tX?~b7qV>uWHa=yY?{YL`@{TCuPB{Y0hG0hG6oM>pSCmQMw[IsWgwf~Hb`*9J=>aSTwGMo11X>>YBC]LTYJPK8@9DQ9>R20R+4V/Dl@HiC<^7C\7Gf?@g?>e=BgDO|XU^H\kgxBt5p6T^i7q<m6o@m=p6~KXVC;mfpvx#d{,p(q$}6\Qqʾֽзſż¼r(sW=?J'"<s m{3eh`^f j&bj&f d r+h t0n/j+_!OL!\0P!X\$\"Tag$j(n/l,Zh)i0a&v>j-v8xJsg[wA^l2^V9`MGmQAgKBeGB`E6T9/A1)9.-=57G?H]\;YVC`^7ZV1UK6TF8VH;RA/B.+>*2B.(K/fmTv[QjT[pW[pWcwbilhxdrnlzkcx_}rx/S< ;"%S:%S:9hK?nQBuWCrU=yY?z[7vZ/kX/kX1iS6oM4nH0jD{L?~ShdOOSSLV=OASnAUM=V@:L3&D)*H-!E.`mQv^Qv^Rq_Po]Nm[Yp_c|fcuek}me|kgxluz}||5\=8/S<(I2(I2'N1)W>1`CEtW=nMMxYGsSIvRAsNDxWqQ=pJ?qLErNK{[Ix[Kz]Sx]Ya\cZ|aKcB:P+DZ59X.4[3e]Ed=7V/?]77V/+J ,O$0Z)HqAEqD9h?7g=7g=)^0IWÏxCT1rBEVh`YXUTQWD{QJW;~V#f>0wK\w\wc~YtF]7eL9]F5YB@YLBRb$2D;3iGk7e1e*h:l@`/b1nD`/j>d#v0p(p(\hdQg-m6Vd^f `Y&k:^6`4X+pG\-j>f8s?O q<Rqea q$h^ s's'h)v>l/~1v(x:ſpXQ}fK Cm6no@]]a+WSc'a+We*h)_#e1Ui7c.[&Z)^6O2K.G3F<:0J<F/Z(T"Zr-l&h'b&f+n2p3h6f5S=^`Jah56-9Q<A[A:X=8W;.K4=)#L79ZLZygRs\Rs\\vhNl^Pn`Xrab|kcylawjZxjczisynq|";.7H<7R79S9&J+%P3H+-Y;;jMKvYMw[Oz[GzTJ~]@tSBvUCuP?qLEpQI{VJ}_Hz]KwWQ|_Qw[U{_TtR;X65Q0KfIDbE@V1Nd?Ic=;X61N,-I+&F$9c;EoGAkC=kF7e@9kF*g8Q`qXm,wA6K7~Idc\U`YXNVJTHOF>wE+iB%c<wpJ3'RB1XE7ZG;^K4PG?VfKcrLHFPn/r8t<l/p6e*uIa+o9`b l/o9[![!L iSm`dba fb$p6j*h,d(h,c(n2j-l/bj&Ogl rv}ku|)5~,x#+4B 'JΛĶȹIShŷŷO7U*j&h8XhM#l/^f'r1f l(f'`!l/f'c(j-h)h*h(f'\#^/S*U,O4:.RFK7Bh>n6f.`(b'r2h*l.l6`7V\S:F_:SF@YLBdI=hKEp`Cn^;[]?Wc7P[8L\0BJ7DD'44,81.7,6>4@F7@O?CVB=Q<)A,&B,0L6knTn]]rfQgZPg_QgZWm`\vhZtcSucWzgd~mmmoox{jw{'8,>H9@J;DT@7M3,M6*K4!G+H+/Z=Mw[Q|_EtWEtWKwWKyTGuPKzQK}XBxRDzTGzTAqQOz[S~_YaElM-U5=dE:Z8-P+M7h:z}45O)9Z+Fq<Bm8Di9ClE7b95gB/mFD_@[7{O1pE3sG5sLfbYX]\\RVLR}HM|DI|FAsA1c1oq]b!I&DeN=QI@UTH]\NbrDI\b_JZ)L ^{Rj>h:j>f8m=a+m=a$p/n,p/SW F TJ ZTb\\j^"l0g2_+])d7Z*\0\0BuIsF'T gmijXchz7z7w-u+<|*NyҪƙdZhFg\[m"l(v/Œ]M]M[7e1e*a$f"`"d%c(Y$m6Y!KV+L!X(e0LV(TH $_8RQujMDOGa@h2f1j1n6_0a2iA]@gJ]W:MQD`WPlcLgbJ]aBY`>U\J\d6IM&:=&.30464MjEKiCBj>FnBFnBCsFDU5tI9xM6qJ:pJ8nH.fG\~Wb[\U\R\UXQQWCpL9^CmwBdI!G8;aRGhiUiSSIIULq^UBN1M)IS&f8l@hA^2qCUo@i0f'f'p6Zj&NF\@`kaco!`h l'h/^%g2Y$a-W#S'^<CXTN UWEdT kEu+y0<irjb\pKobz{vtm_qCU;M xջ^a+f8PZ%_!h)j-V`]a$W^#j<rC!d+C^)A\#@NGf{q[l[]o^pS`dcepBOtfsfuYr}Ebm;Xc=Va3OQA`Z9WR8PG&-'*1+97310+3/21-0'#&$+%.6/8E=?NGEVJ3D8"=+/JiWW|dOuYPi\F_RKaTSh\Wm`esp[qdg|p]rf]rf^zdb~hpvBIC@FAJQ>JW?E\?7N11J4=S9=S92B.,M6!G8_vToIuaIuaM{b>uW?z[=yY7sQ@tSKwWItUKtPLqNNsPQnL 9 k]}o"LI!EJpIO|Xe=Bj>GqIQ{SArDCtC=v>;s<4l>5i?3h=-X/^YAE4jcd]S|U?gDemgo7VP2B^P`|ajnb\HF2[EpPbAP-W7U+Q']5oQa<b1g3a+}N_(p6f'l(\j-G33= U`V\bXd)d,_2W*`5U%]#[!V)KI T _X X `ZL^R`!zEp6p1f'Pôd`zwqx{rxGceϩ˻ do6a+Ss~äкbmLXQ5h:^+\)P_(Y["X"TJ[0aLM7!"Q@UT6KJ#L7LK)Vb;ht~{;Z`Kkp=[e9[eA]_A]_:VMKdNYk[SeUNRE33*;;226,*2(&) (,"+*%--$--$2<-FVBCVB7K6)C)8NlOlsXraSh\QgZMcSQhWcziasccylg{fg{filknortlӾϺBZQ(@7CVBSkJE_9A[59R->R2pSAqQ4X97T%A^//Q5P&5P&Ke?QqFIqNAjFFlELtHJrFGtC;r?AsA;m;=i<6^2,T(C ααikGqIAtK]fQ| >6CE`N`F[:_>bDjLbCc:`9@V%Ǿg3m=a$l/x;f z>r+v0r+w-[cdj(#UZ\\^h fl$b$b,`+Y*T'a)W`"[ YZcV \a`VbRqw::YźźǼĶl}˥b1zVlˍf\+^tMh)p/h"v>t~3ҙo]mNP7_9["_!_(Sg-V!RV%[1D-dGJh7DD7DD3L6+T<I@B::MQ|}Mpj7[T..]LOaQP^OI[KGYIAU@7R77R79N50N3'P8H04U>o}@sULaHy`Dv\EtWEuU9fBirRe;jMCrU>pS3`?(H&(H&;X6/J-+F)E`CPuRGpLIqNGvMGpLEnJAjF?hADg<>a63S(-E$)@ 'B%ýjo=vlA`ZWRXFaOYGaLX<`DhJ\4FT%o9o9a$f'x;dj-t5oe x#QaXd$T n'R ``Xd$\j-b'd+b([/R(R+R+g-YW N_ZZec] Xj'fϫ?JVLFǽɻëѯdky}Jy`mN~V&T#{Kx4v0p(x]KusO[QM/*eIU*W-I[3T&P"`8_D1*f!VCLV7J97J94R7$X7N0P2]W06ɳvl|HgUoowsok[ramvYobKaTKbQOcNUiT4D0"(,"&) ,4* (&.$ *:B8EWG);+)A,/G2gjqwNgZNgZPi\Ztcg}me|kg|pysyym}h^z2odSvpJss4\]HWhE[NKaTKdNDbEMbGMbGKaECY==T79T75P34P:Td4b1\*EJ@>Cq>Cq@IJHU=BP73N3%Q1V:V:_MFB:6ҺrltZvmPlcEd^cyln{DcQ8YBDeNBcLAZD -"!&(0&(+%*,'&.$#H0GlTisO|XEuUQaLcC]E_C]?z[FzYBvUU[*T#0V"0V":_/0X,2Z.GqIM{SAtKExO?lHArDUX-]31\32T-8[3;S21I(3K*.N/FX9\?bMmGa]wK|{LTjreYdD`@lCf=`/nDg3qCi7k3_!r8`zEbo!_ pMYNh)Mh"Nf)RTd.S%S%R,R-P+R-V1T+V.T_[^lffpwmu sy <Klľ.*9`[k{^j%x4E:CD`gpbhrWTeKj^ң9lsia}]n3579gGR b8T#`/^h)f.:#v).D7=R>ICUM>UMM]R>H9""(&+)( ''(5*(5*/F51I77P:}NhWPi\g}mqworwzsvsv{}}]mGjW=hK-[B;aE=cGBaCC\7QjENe<J`;Nd?Lc=J^>CZ:?W68X92Q32U98[?0S7-Y;!L/YjYnNfHy`H~dNfNaPcUcU|]go9cG*M1N`EL_CCZ:8O)M.:I*>M.>Q5]TSLUNTSWSYU C fvdtXReHoR|_qTa,g3c.zEn2i0f'z>d<iggc:DFc.]f'Zo#Y `d%T^$V#O&M#L(R-U*Q&h.e#Udt%hnp!r+fr.i)QwCx=A3OcſXdTB=xQƫ]Sb3zEĻe=MQYha]le`ci^Rxlxlt>M=kx[huJ]C;QJW-k3j-xBK6J@*m.1D9LE=QI6Q?.N;0P=/S<0S7/W4+S00i:"W5DxW}L_fm[qyOvr?g`KdNqt^k`DT@HXDThHNbB# +*-$&) 26,4>/-@/,>%j|cpieyds|w}w}{u{u{s||WyjAcT@ZIA[A?Z=E`C:^??WBE^HE\?CZ:C\C=X=?U;?U;;Q7BU;HYA=T7?V9;W99^C%K/=hK^vToN~iNfDwYWc[gen_m9cG3Z=>c@6V46V4TyVHk@6Y.X~WRwTIqNMuRAjF1Z6+S0$G)A]?7T2)E$/K*1N,7T22T-LSNYLWQT5I?S)qYO_JPncH<w]nLjCjCj>f?b8`/a+m=_(YP55H ;=> pNg-]N QWR^(V)F\@sdxtqVKK2M3l?j4Ra o&k!k!o#|#r-*H5r auacd];1T<B+uƓгzI*~axMl@ _LC}o>/Zvmerr]jjcehn:{3KXXj~`sAQC#[3zVb1f)?v5M!kL;MBF<TXN9N57T2;X66V4-T7+Y@'U<5XRǼEgX>0l\Hrnass_ ESP`j[XaS`pPZmD6D) *0$0&2?4@MB+>*0Q:~}|{r~x~|zcz\{lFdVP[<DX8;X61N,@^A<\:6V4:\5C\7=T79O3=Y;5[?/U9)T7ZmYkYkUaM|_[e]gfmIbI#>#=S9;X6<^7:\5>c@;W9igSoNHpDEqDUTKuM;d@)Q.*H-@O0-G!wkDX8&F$FfD<\:SLTXTXV\=RCXU}itseE8~^]7xVb?c3[)c1SUFLj7V'JJQ2@i9[e+s?TRP`/S/9+[N <W`MUB+Yg&l)tt(.rz"oCEZ(Gs|-Kwr_gϚ}qR v}uC[PBTv0n%LABĦ֋SK\)X8hHKt.y?|4Sbgsz_fx}Un2o@yHa$`!]-Y*Q/YB[WWS\_tWh\CSH&F3EhUkkCglQ~aIfq=dT V<$Z@N0 SWuF~Co~|pqV\ZEZAi~emiWqW[t^ZjV*:&(3 ';&3G2)M6%Po]v||tzluUoU]v]C_>KiCIfAEd=C_>@b;<^7:]2AX;=S93I/VhODW;>Q5DW;?U;7S5=Y;>]?8YB,M63X@asesYgQcYeQaUeAqQE))O3(L-,T(9d87c65`41Z63\8rqFp?LqALqAIh>IfACa;C\7MjEH\<@T4;fGHiR>_H<^CWRUQWSLO2].Y$9Re}S?M']7b5Z+N^-JQ-kGmMfCX-L"J;;;!mQnNxYb?='67)zSWqmqxn{p/>7#;JCJqyOCh.f,m#4x%n2d"d"l)s+w-@s h41 uEO"uBSbTt5MYFWqw&In2zEhs'~D|ARb p6^ r+m*WsK_S`bb-aNh:r{rIZ,pGX"l/b"j+f,v=\9\9cT6Chfs_tRc{CWo-BY2GS7VP9WR9[I9]F-YE)UANATTFF FR^FyHzSeUQlOsqgb_z]^k`bod8<2 ((0&"1!  yfoG`Je~hFcI=Y;KcBG_>=Y;>]?:Y;<[=4Y61]=+W77aH7\D)M67R7AW=?W6?W6Eb@3Z=#J-Cn^kQ~ic{Gt_Qv^NnLDdB9R-1P)7V/5S-0W/@h<VY?XX}sw]9a=e$}7s/k(z4}.s$88u1@j!bn,|:GN0==NAi<WDOVvz&G.J^(@O^ LT _ 8~pFBc.+T!j>R[}`!e1sFYk3_(a*g6oF_EkQgXUUEFx:Jf;V\5TK/NE7\D>c@:_<<_A6[88Y6:Z8.S0-W>%N6M?7BUw$^hciBtg7_Kurunoc~aigeza9Q<2,1VucJdS^xg\we;V;AW=K`GA[AE\?G]A@S76U7<[=@^A7]A=`M1TA4M@?UEA[A;V;DbE.N/>]?ry[hEjRҺ_l>ZD4S56U70U%,X 2^&8c.;i71e;/c94iD6mC@wM;nE;mHOz[Oz[Ku\9eQCo[EjREjRKpXGlTJkTTrdNl^jv]\URYVQPIWO]ZnAѯƿƿ¼Qd`9KHFNt-0~oqcN4 }[a?vCMHF \"OU?wNjqU3J*J*@9i+.I3:LAHZMXj;Sb7O^1L^5Ne2NuB^GJAY5u596w-98o(t'5w*<ir2~,zB9Phtk{,:KEQHaBNDX:~%zF1IRq??u+p(bBX k!gZxhfgE-|Gr8i7v>h)f bz>\`)tDn=_;R@FAVQZ_5=WIQk8RP:TC>ZD:V@AZDA[A=X=ASCDV==T7?V99S92N82N8)TDNP"0\`}3yi+eWFvaAcT[}nkqqwsyou{wz}hkAfN~}ZsfcylPqZRs\pzCYILXDFR>J^>CY=G]A;T>?WB@\F>ZD2N8BcLBcLh@N%&U-3H>BQ>BQ9CI?NSK[_4NL1LR/Gb+C^EG[>I,_|*y2u.~14x*|*2x+9t&nz&2~,Dw&]"ed:{3ZKKEaCZ{3I:''E<BFw@@v] cXNJX#>1KYd^?_6d,n.l+d)a,n9p<c<Q5M0N;RFZOZO^e'1F7BVD[SG`J?WBC[FE^H?S>CVB8SA=X=?V9CY==T7?V9?V9,K-A2!A7&Wo?{hV?IkShStfY{lZxje|kqsqs}|mzivgq~z}rNl^VtfVtfVtffoVs\:L3FX?FY=E\??V99Q<>[A@\F>ZD4P:+L>=_PB^URl[PqZPqZTv[XvYFdG@cE9O3=U41I(6J*2F&1K%;U/:]2CnBCnB,b<5gB5eE7gGAlOUcWe?iMAgKAgK?dI?nQKs_Ks_JiW=bJ_rIj\LfXUiaK_WkqZaM[M[IWK~UQ[PZ A&¸skEYQ9LE\ivw|}T?J+_6XuFJU I\6zEzMee\My;i*7r.)HJNPFKLAC5@K8FR>=V@:PL4HX,@P,5f6(@2@dm&o)v+u/s,|1t'x(65mo s {zl .@fZF{3TEIEX1G8EB" F6>Fh io!klnaTAGb:pCy4@:F3=gs_lesNS*-6:GBK?WKZAiJ[@cHaU.4GZ?^UBcL:\A<^C;^K<[I8WE6TF?UE;O:CVB:J63G21E0-A,0@,/F&Qc{K:.]LEi_ZxjRpb]rfotkte{njvnzjvov{xgw]uKwcWk`jRs\Vq_otFTEBQA;T>A[A;V;8X9I^ECX??U;ASC=TC;RA;^K=eQAdQ=`M8[?)T7#O1D'!H)(G),K- D"2W45]:=fB9n@;pB;pB7l>2hB:nM.34$$3dza]Q:I1U5[qAG"fJsHuJg7PNH ? Km9x?Tf[uF_f0R60-Q87=>;CKKB>F<BK@DI;FJ@@A@@A@28B''Z!!TU:YU [ll"v,x1y6q0w6}3.jlnmol(r+Rx;<vC$0*EE!8:}y(\ Xh"Xz X@Ui\ VNF\*k4pz&GK#/GT0-7#\8H#X0}UsFi9tH~T\:"i(PSfE\KBaC>`9Df?@c8FfD0P.!<:+F)/E)-D'/J-)D'=Z8JpICpLKyT?jK;kK=nM0f@=pJU_OYKwWJpIRxQ?j>ApG;jM7fI;dL=hKAlOAgKInSWqWKeKWrULkMJhKNsPY\JQNUNUJZJZO\[dBdI?#/" ,1") 53~m1H6MH5]J9l@V'VR R P R Bv7oBVo]Sq9e/X=..I:?>@EDDN?>H9BM=DN?JSEDLB.*:'F,^DDKY$c.]VQxKzGPX7f/00KFSHIcGKhFC_>IbI<^C<^C+O8 <&)<(-A,);+6C8,9.,9..G=>..i}eyQyeB`RNgZVpbLgUWpZ^l]n|mi{kmssy{}w}jwvW|dZw]VtYWrUooqh/F&CZ:E`6C]4>`9=Z8;W99T7=X=6T98W;9S9A[ADV=8K1&6".@'6H/8L/7O.*L%Be=FlEM{SGvMGvMExO;hDAoJEnJU~ZRwTPuRKwWCrU?nQ?nQ?iM;mH9kF?lH3f@;mH;eIHjOJmOPsUTrURuWJmOTqWU[R_R_JTIMMQaajl)A, )!(%:.SkJٽ<^C.,*L2T).$d_J-:RJRJ YDX[wOjqe;uH?2\BIR@FABIC>I6CH1OT=P^C`sW^jSFT;6@14A68F:/>79CI-0[7:e+|:`,bh$t2BEAg$s2t;Av6Jp1h(cPk<qp(r+y0r9u <0ys@v.3^`TTj!l"\x;ZLTiq$s?q<XZZ`%cQTa|MzTbEjNF&&8@9QfMFfDBc@FdGG[F?S>*9)&.$.6/293,814A6*7,)>22% D59+E_Kz]SeVs\LiRHdNDeNQjTokShOe~hi~e}y{{}hnd~maqQtaGlTdk.K4;T>=V@C[F=Q<NYF@N3HV;>P7BU;@R9AX;?Z=A]?-I+$A'/B.5I45I47N14[3HnG@vPF]@vP@vP;mH=kF;hD`^TyVRuWNqSPqZJlQHjOGpL?jK4fI8kM;jM5eE9iIGnOHjOVhOHYA.HSuB4W,YYXWNMJIQPSRkgtrBS>")9"#H0ɕ{ >9,;+0?/#>D$_Tpo]yi^>EEZ!ZO K_U YQ[#|A^!vIV<@&u@:NHIE>K@6C8:G<FPAT]Ohu[tdfvVP`@BQ5@K84?,6;-(,"1,3/)1/>N"XO/Ue.q:j7g-k2|Dy:|B|Bz>zE`%j-Tn2n2t.o!o!t.m6e1M a {{:u$y*Dfq_;,fNX ISQ`/pNbd5C1e][~KMq<p6r8p?QrRV66,%: .&.$E_ERdIL_CRdIDR74B'/1 11(#"A@;2:0'<01G:2.30 +&50ŤLnSRtYbeYsYWqWYnURpSZv`RnXQjT`}fpv{p}ZygY{lSucCeSMv^Y~f|:TCCSHJXIBQAHYAAU@;QD7M@7O=KcB;S2;V,If7C]45P&1K%5O)5O)2T-/Z.?oBK|ND{Q>qQ@vP@vP=nM=h?UWO}UV{XTrURpSVwT5d;CqICvM4iD;.{sh{cV,HWKNH ZHN Q`_m*;~8,QL@eRSO@M5FR>>I62A18H7P^OascqsaxgKbQ7H<*/.&+*'$$ " " *&2#!M`d^a@;A&gKf:n6r9Th-n2FSdi7Wj-l(h"Fft5dt.hPTd[Q]IYi~,u]dm]a an \{,km"qa OaVYo@i7q<t<XNj;eV7 $/)1729):H-OeII_CG[F4@9(!*,',.)/+,744;:58;58;5/>7C7QRοοqp_uYicayXav]Yr\WpZcx_kkoosoux}}|k}muwascYj^_pdXqdVpbbrNl^2IACVOCVOBPDDN?FT;>K3?V9R20D$0B)5K17M3-J(1Y9EwRI{VBxRDzTAsN=pJ7bEHiR\~fLmVMrWOuYKqUAoJ;hDMzVCqI3a<KuMIrK=h?DjCLrK?gDHkMPlVVs\VtYNpUHjOar}ŌS`CPVY[W]Ya^{}SgR"*9)jeiw % 9,*6/6>408.&2+$0&#1.'5203F 4qG2:m]P+<USPJ ZP ZJ\V \&]7YNfE:RCB=NRE@J;8C3(7'0?/R`QgyivugxlIZN-;8%!# % $!#'")($3'3)?Mf}aq0"F8nFl1Xf.[+~@\:bYv7fp(t.@mAt(vdNMRD>6<@6@T Wa cme yetel)[yOA Ex4p6k3Qp6f)\_6xVtW( c-2&*0#("%6A.K_JOcNGXL*/.$)(-**(-,,,,000=9<;88;886>4.A%Fm~]q\kgqp]u`Yr\hkilorqpz~|~ixqqsi{kWiYTn]Rl[IkYMo]]~pWyj,J<:TCD^MEYDBQADT@@P<%#4(+:3-=5%4-*/.25/25/,64-9=)49#*<'%Q/(p1+r/?d#BS Y ]P`+_7?TGpJNPDHJ@O?@R96F2.#JS;pxaumnf^80(%)".&#$$$$$$!1(,/&*((*!., +5':EoSTEGS7^,q5e)xDrE([W[}N^ oy*AxB}1.yw+cYRFBH^*C OGWZ^\ s e x#m\l(i4]1CrQhAc.Of'U#_-k@cqL88 G5)6/#0+((58&VdIL[?>K3.4%,1#,.)10+*/..42>@;4<2P7@R99S96F2GJ8CF4:C)7N1/E)/K*@h<K|NEuHpS4eLJpIFlELwBJo?Hm=XxVPmSPmSXu[Rs\IkYSuc?iMt|RURU_We`a\ilns1D3,  t}.N/(&$",*"&(,.)461(,"761()(23230032-/-)54/+((32-;:5@FA03934W34W5) D"L+>'JAjD;d>BD@O?BQAFPA:G<*9)#$2RdItk~wjoaSNF! " "! $(*"!$,+.*),((*"(2(.8%:=5DyXM5dX)b3\7"mJ}SMr8d}6o!A!?8Q{B*5jCCZ Pn2bt.``U`5lG[v>d#l/c,\.,yK}NzEwEsAi>[Q4F,0/+,/+,--$ -".;0@MB,9.&.$$,"$-,*),/+,300A@;:G<4A6:A;=ZYeRr\VLJ?TDO;WOme^wj\vhVucPo]PjYZtc;T>/F5McSJRH4C3*:&.>*-D'9O37N13G2I6$&+)'#%#"'&$-,(&*1+$(*$)(*/."&(0/2..0*),*.0&-'*1+.=-@H>/<:3A>1>K3JY:HW8HW8FU96E5*#&2+TYKjvb~vfr^HQC "#"$#&""" "&( $&(,.(,.$(*,3-*/."&(&*,&+!9!9GgZwgUEP?V<R7N.uV|;Pdw-o!3s ~=t.kh j<{,{,ArJ~,b`l!Ma+\)d4[RR]uCa%i0e9G1 i!s`jEt7r4HqL# G)M#(47.,/&,1#!1&#3+%4-(5*$-, *((-,,,,000444:B8>K@8F:=NB5F:4SA*J7H0J+U:Y>\HUN$_X6KJXfWӹUnXqpmlutuix{::Q5iOQ8iqPjgHV?V?R=N>bXZPwzdv~dv~`|saxg\k[bpaN^J7N1E\?FZ:4H(.A%.A%;Q7?U;3I/:L38L/>Q5:M1FU98L/>Q5?]75b>?rI9lC9lC6jI;bC3[;Zx[^YXyS]v]YsYTwYNqSJmOFlELrKHpDEqDClEClEJpI\^TyVPsUXvYPsUfkNpUXz_LnSRuWƖGVWf`daZg`zo?Y3&0!$.--$+((# "$&!! )$(***$$$)'#&) $+% '!$+%******$(*&/0"&(&*,6:<@@B666666@C=8H7@O?FTEN\MR[MLVGHQCFR>HS@FPA':)!EVJdt`oq_qaBNG+'*!##"'#&+%!-'#1+')%&,+.&%(..0)$(/+,# $")!&+ 4NL{iH^Z?JOCMS$-[.~KiZk{,8tJ~2OEYNpaw&Cr>(i[m6m6o@j>h:uCT~Kq<akbVr2x;Q4S1U1_;\2o@^aS*'C+&---$--$*2((0&&+**/.,.)11(11((+%,,,)$(3/2?;>;88GDDED?:A;>K@:G<5F:/F5"@2(F8/R?7ZG;^K>_H;dL>0stzy=?cLeBeB_@oXd=nJvVN1F)L1T:`L\HSCacce^pxZqiUk^_uhH^Z(=<ZonOa`*2((8$.>*HYACX?;Q78C3AD2Zv`i~Q`Wf`be^mfLqN$5%&13%94,/&*+"&%+(((&%&(+1*-3-(/1-0)%&)%&+((*),*),0/2+'*&.$.7,HPFMKG:?1>C5P75H75H78C34>/FX?GbEE`CBaCPaI-I+8ɼga\5/]E]C[A[AuaiHc?hfAF H,N5bGT9Z@YEgRQMVg_~=\qFZONSQYV ,0*K4LmJDdB8Y6&K(6X17T25Q0;Z31Z63\8.d>1mK4gG2fE6lF7e@2Y18_7hiRwTX}ZVuW[vYXxVTtREnJEoGClEKuMEnJK{[K{[8oT@P]7LG@LA:F514+((.34(,.0/2.1+*,'*/.*/.***000071-=53C;4><@FABK@>F<@D:@D:4C36E5;N=F`O:TC7YJ/X@+iBQ*7=,0o^bO@-[AaCw]j>rIa<~cJ"L*J0V9R4R4C7OCNBdS<'<'J-Q>-$z%:2B.8I4WrHHiC6X19S95M89T79T7;X6:Z8Bc@DjC;f=5gB5gB/a<3dC(L-2V7ljZzXZzX[vYTrUJmOHkMInSInSInSGmQEuU\yb,H2ƞȩ¹ſȲЭeͭ>vHQ\Wb_`qs?hA$A'9L89N55K19L85F:9I> (""%$'/-)+*%'&!-),1-032-/-)$0&$(*&*,((*&(#2B.L]H@]CI_CRfFDX8PbG;Q7CX?OdKI^EG]AG]AG]A7P:'?*)81054&+*6;:>E?,81"+#$!+((#"# %!"$#&&%('$$/+,+((*),*),*),./.7/0) "+*% '!,3-'7/+,'$-*w9WR)DG5Ar5#q<E{3vF|6OfG4:g3{R\r1fs'VVVj-T~,'48f'Yc.Y]$h:Z'\<^rİbd~to[{fpe)+J,0200002-*,'0001-0224*),0:8+:3)810=2@FABNG6C8EYDA]?GbE$A'.M1VzNFfD8Y6:Z8>R2F[-F[-C_1Bh7RvG?mE3h=%e9+k?,a?5[?&E'"A#vqbdX}ZXvYRpSPpNRrPNtMJpIRxQEpQYcQ'ȼ̶Ӱ|ėmQ}uUXU`U`imY[%O'3L6;T>3O1;W9?U;CVB?S>,6'"(,"/+,1+'-'#4*'-,'--$11(,4*.34*.0((**-$CX?QfMkcp~ivagX[xVSoNSmQI_CKaEG]AG]A6F2*:&,9.8@9.342681..1..# )%&)%&%!"# " "$#& $&&%(-),1-0+'*1-0-),(,.*42((*((*"$(/),3-054+,039]nbat9;):j71C=5v0r+\ſ[i#0yQ8zEXjd4z7dv0|AU#Od#s'w-5ih"r1i0Uc.Q ]$o@k:`*@[fNSdj]Uf]NKo#$(*+*%32-25/*/.,10,10,648C@2<:6>48;5BF<DG>HKB@C=6C8FSHIZNC[FE^HCXL>UM._V&VNIW`ę~9?!'iWKO1K-Q+qZiHoQnDq<k3PR3N5P7y9'4B J,K7WD^P5#9L8VxQMgKGbE;V;=S9DX8@T4Ih>IrKAkC9kF3h=.f;2j?1^::\A:)D'zfa^YXxVVwTYvT[xVRxQNsPPuRWeA^<oŽ˻ʯӫӫЛo̓e‰ZbZOFYPea\~W(J#5I49N5@T4BW6LW8MS9UZAJOA%#11(,1#$," (&-'&(#3/2-),3-)? 7{imv_y_IcGGbEMaI0=2+ 54/436;7:73654/+((# % $% $-%&) "'#&#")$() "'#&-$(3*.1)*3+,1)*1+'/&*5,0'"+"&-$(3*.1-0(12-1)9= uRgs#0` [%DBomqOB|Xh?f iI{3v0^j&J B]2e:l4r8Jr+efl!q$[gda$p6`"W&b5kWbvWapcjGV3{*-),+'*-**,+.*,'*,'8<21A9/>7(20->22?46C8BMJ9CX26hnr`]YZQRW]OZ;Nw[oB_)H~5S%-9K"AH Z%Z%NoQeBgEeIgEkK_;3>->-CxQC/B+@.:(>!0(M(4-@KEYoSMiHEb@?Z=:Y;8Y6>^<?hAGqIAkC=lC:a98]:2W42W40N33P.6jhwuYtWZx[VuWPsULoQX}ZOvWU|]Vq_ljNſĺ˾˯̭ί֨s̐mƊVΊqf_QEg7Cb8=T79O35L/1M/4X9:^?8W;G[FEYDT]O&-'-),..0***000+((/-)/+,1...42(0&.7,9K;8SA&<8Ƹxíajm_i_i}ZeF@K,08!05'8<226,(4-%$ *( *(#",10&*,#"-,'&) (0& ,%$/)7613-)3-)1+'3/2..0,+.*.0./.()(1-00/2'7/)81( <37Pj z za/{+Xd_~[^C#c'Ws?n2v>n2cx4fB \3]3[1yOf Rc'MMY]A]]Qc.S9u1AE½ÿX^Ydqdhk`ODjF*$6"0744()(,3-(/)4<28;5>@;77.25/.346:<CFaI<`N^L[@gEY0a<[3^4f?nIuLoEU$C QOG W]$NiH_9iHc?gKcF|XJ@a޶vu@5*0)H4:'C'HBo(4-@KEdxXQoIGf??[:DbE8[?<^CAlO?oO;kK3dC7cC4X9*N/%L-2R0'S3#P/C'}OuYX{]RuWMsUMsUS}aUaUaRCǽijƵɷɴȭȭΥӤҢtčqeytC_>=T7@M5BP78L/3M'9R-A[5DW;LYAP]ETYK(,"#"+'*-,'*-$(,""%,.)10+;:547.08.>F<1L17fI@<_~˳ɱŭ|zqii{l}ePZKMMD??611(!!%$&)!&0.#"((*'#&% $&&&+((1)*7/0?6:7-5A7?8&/D287.21(,1-06*,8-.$$$,02#54..2;&4./4;@7ĭRY`gum}fSQn%j&j&~Dq$5;|"j!ZkCd>k3WJw&[j\^ fZX f ^ ffXn%_9E jeK_W?RnFGXYbHZRz(H$$)(*1+&-'0:8.864><2<:046#/35@ESPCrQ\<I-[AY=iH\0[3_9oQf?hAnD[3F8M Y]^c.^+pGlGeB_9d;N~jcc)(R?EC!AB(J&N*dMKG *5b>.S0*O,&K(*I++E+)O3#J-(1L1wwVwTZx[Zx[XvYZ}_W\MuRк͸±˶DzʴѱЯȟgeViM8G+@G4BI6@M5@M5FU9HV;DS4HW8RaBFX?,1#&(#/-)''11(-/!=?1BI6HN<DQ99L81E08RD'T?1pT+jNaG3"I;Ow˴Įtt}kirevZygJiWETM4><&$ (,"&(0&(/)*),"!$-,'#1.go~xkup^\Ox^UqB9U1*6:*86&4>+54&/21787='44=8;6(PTxd\Hw4Eԧhp^P@`P}v>_eRx;_!v0I~=v7WwEf8MRXJbNf T R^d]me`kWZW[WY>c}\\^JXy96]Z]@oKyU<-[ $$$232.2(aC8_78_7Bj>@j98b1<_4,N')C)'A'-A,*:&.@'&8.B"egQ}]Sx]W|d[h[~kaktɾ˹ʴ͸ƵDzɯͳγγɭvgviZ*94=#DM3IP2>J'P7J[CLQC-'##-%&-),+((956CC:GI;GJ8MP>BP77K6;O:9I>7J90I<D]P;]NO9: S8&kS'cP;wdSyNwLv\{yrtrL~q`{vjttNWXA=@-%&3+,) "3-)+%!/)%(+%'-B/hfnxThCq<}NJJl=yHwFi=X1e?iCuLe.i2c'uC^g{l|_nJz]Us'"$z77k:t<~,SQBz7x4fx4^\\k]`gcu$u+Ws cefLPK ?NqtU>QdpzE9E9P1nNhH7_9*@!(+%36$8F-8F-8H7"9@CARad]OX<^B_BpNb;f?h:Z/`5xSb8hAlG]<gEgES&CO!V WV Z%\0f?b8hAG_([h)Gc.R=CEK"V/@B*_O5`92T--E$0>#2;#2;#/3,1#(,"*-$6;-4,BxvRuWg}me|kejQ±ʹDzî˹ʴưʯĮɷ˹ƺȷūlZ{pW,8?F(=D&BR22F&:I*BR2MV3R_8N[4FU9"%#"% $/-)0P="F'=5b>0h=@wM;kK7cC7cC7cC4fI6iK8kM>tZ^}]qnseXmlX\^JND,/&()($#&&%(%&,@-^]xqHqH|GWWuCi7k:uId4a+uI_(yHq<a$Hvtt{in{R]bz>d\r1b~=Yx;YV~Dz>j&We1V ^+V&^2V&V&\)["]$d#TjZ_ THJ C IAP!$+8;m'Y}l/R4xVR/"Q(+>"'& %$./.-=5-=59EFC#+Ya[OdJfE`?`@jFhC_9b8[5c?[:[5]6mUY0W-S&G_9KX)W{S'`5h:d4jE3G[:?6D&:AGAE#<*4!5%VMSYn+0F?UEAY8G_>?Y3:\5>`9@b;Bc@8[3.Q)4*4%/.&33*&) (/)(/)*1++=-9"dkV|UYgEoSǧͭʫʯʯūɯί͡XJw]Ƌr`VoA9?"=B+AF/>K38G+:I*8H(QZ7NZ7Vc?>F/ ""&(:<7HS@*H:,H2.J7"=+/F59P?3G2%8$=X=:^?LoQCkH>^<FfDBc@?gD3dC-^=.aC$W9+jNgznqh~zXc]DOIFGF,+.$#&$) /HcvLY[xBzE|Gm=uIqC`5qCt<f'Fj&n,^cup}uzfkZXn2v0fdq$`z7IdTewEn2\Yi0Z%^+Pd;Z,Z,e1`/^+a+X^HFL HMAG5;#!TSGix-;j_^S(z <%--$)( *-$+:3+:3 1=")_kcaYZ?kIkGiEV8gGiIiH^2a<Q+_@]5gE]<Y0aCGQ$Q+S-Y0jEU*a<uZcFS-Y0@JT ;=#C(7I)C#;E&=(9#M7B<.:V&1NF`O;W9=Y;?]7<\:@g?S"IP%MO2UW:GM3@M5DQ96C+>K3FX?DV=4A6 26,?QA;N=;N=:L3:H-C52B.0@,'=#DX8=U43K*7T28Y6RrP@b;BaC>[A>[A@]C<^C@bG=dE1c>a:#f>Z5Oo{{qi`{vZcdBLL!.;).&~|rs\[qKv@zF|GVz>PF~Dm?t2z&~,3l,xL~ztstb Vq$?e cko!KJql/Zc^f _!e*_(g3]$g3c.g3o9h)t<|Gr8f'[%UG=E@@8D9=U.jBtLieA=}nZ>3p H($?232$&!.1+()(#/3-9=(I6BWI_Q]N[JfKx]J4jYkassFU nD^+j>qCl/Y_(i0Z,^2X)hAX)`5d;d;X)c?FJR >HN!DH!?";A&:*0 0*ACXf4Bc,D;2R0>^<<\:?dIJfPD`J7J9*4%$.(/(2#"/$ -"&-'03**-$26,$,"47.&) ;qRnXZv`Ys'ySh7O!J"?<d4vZ$U<K2%V5,a?Q/&[9rTo6O!BR2HS@FR>>L1DU1:N.6J*6E&>F/FO79;*/0#AA8UXFCI/CI/Q5FV6>M.BY3D]/Lf7Gb8E`6E_9?Y3Ke?DZ5J`;Ic=8]:7bE3^A,^D!]=O/1rOhxn}_gu+$]9"u`}Z{QqGo?~E?T<DW{3f Cdf ~DSjv7l3zE]|eo7d#R r1r+a a Z_(wL~\nJe1bzVEe1T#d;U*]5]5hA\0a+l@i7wLrI{RY"\)W!],MGAECF8BI%W4Y6aZiZCnM BF01<,3-$+%(/)",*->&5FT]ZK:eTfRC.JI|w tMh:vOvVf?L^2U*S-_@Z,c?Y6_@W;U7M+J3M+K&9K$9C=!?#C'="?'=*3 ;+B9ei16\4KC9U4Eb@@c8Fk;Gg<Gg<.B"(.*/!--$.4%&.$,4*&.$,4*(3 *5"(8$95>!h;jMewRn(_D6:$@#C'"C,!eq\w+VF9*;,2S<6U74S5-V2jXg^+B"JT9DL5:B+6E5:I96B;2>7>E?:B8@H>,3-:A;(-,498LSM@C=>@;H98D0/08.>H9@M5<\:A[A?Y?K`GE\?CZ:Ia@>]?3]D/X@W7?$5Qq}yeJg[Ya_yUuMqH|G}>|AM|Cv7p1Hz&@o!ir+Fs'C{5@N}Wc2RQkx#zqm\@Uo@yO^xS\)M]$i7d;R V&X)f8j>k3o9m6WrIUi0g3_(i7[%c.d"TOVNbN GI_/_8xqVH%17%C'#&*-$$+%*1+ )*.0F "WsjjTjTs[m_qlwJKCa<yOpGf8vOo@Ye1V c.k:X"tMT#jE_9_9W-W;M+Y6?<A"==#C(?#99#=(;%G4M=EEl##JGURC]AA]?IfAIfAMjE?]78 (&.$11(-,',/&*-$/.&03**2(,4*$/)2<:lzkCqX8- 2'#0002-$-, *(_uhyF_R:$*'8,':)*F04P:'N1e~YYtW)D'>N::G<4A6DJ8M.6C+/(.CB@C=BF<690BK@/EG9KM?IK=8D06D)>L1FZ:H\<DX8H\<BV9CY=?V9CX?C_>?[:?[:=Z8;V;?Y?2M;!G8C2=,;uZ{{uw|Y_nSuOkEl=vH|NXtBr8H~@v7Chd v0@y0<h-YV|>|>bL C C \qa ]PxBzE}UtT^j>GT\)T^2X)\0U*gEW-c?m=k:uI_#e1e1b1^2jEX)`/b1b1UI;MMA?U,M1{h$0+;0+;")#&.$,/&(," )*&.T"84rcjThQlOUKaHu]aUW;b8\0m=rPlGkKwLa$YV h:d4TqC^+h:b8W;O.T;I%O(U13<@=A'9%3 ;"=(=(E2G8C@m53_JTRJ\AL_CVlGIa@KcBG_>$1&+.4%*-$&.$,/&,/&.2(55,03*03*7E/ ~8mW5+&.$2:029&%8$ +4!8VH~Tm`*J7($3 ,<(&991P)*N/aYOjM)D'8C3GI;IK=DJ8DN046>4?>9GG>8<2>A8690>C5DO<@K88K18K18K10L6:[D:YG8WE>XGJQ>PWDHM?:F?);+0:$0 E7qz)3jPvIj>pCpBxBNxGxGxGn=[&f$\t.Iy0io)XP~Kt7t7Pc__Owo eJt<j-ZaAO["T`/JY0JT#]5jET#`/sFZ%V |Gl(n,r8i0q<k3e1h:`/b1g0A EREO$]9jI>0uS=*Y-(/-(/+((3-)-,'54/.27$J*lg`P\DbIhEcC]5kK[3cF[:jEb1_(e1uIi7uIR}NO["^2e1_(V q<c'o9j>b8T#_9IM!Q&^6. >?!A%C&;B$?&9!>)NH^d8?iANNMePQjTIcGA]?G[F5I4)+-0/1 ++"*-$$,".7,(,"*7,6%#;)%S:н۱)L9>+&2B..>*!4 &5%$ontNl^2PB !08.,3-.6/11((0&,4*.=-pZD26<*%&!#"(!(! .4%^cUPUGOM<GF4GF44>/4A6*7,0:86B;6B;6B;6B;2>78E=+:34><HPFBK@Q5BY3?V9E\?BN:hAT#V&k3Ok3k:S_)`/Z'_(]%hA^2Z,i7t5^ F VHZ)a?E41U+O=1>33*55,,1#*4%,9.0=2/3 I1`k?6S<]F_C_D`5b8f8l@`/jEvOZ%Z%pGpNlGqTrPV X"a$Z,X)U*l@["e1\0W-W-_9MU*Q&T)T,@A';%7#7#C*B(B(>*LCgi?B`IXQSlSQkQDdBE`CL^E6H/+(1&.(//0#33*++".2(11((2#2<- 975 & 28)4D0(8$%8$UyrcQunGZV ,4*05'1638!(5,7$*5"';&g}a0&8GJ8@G406$ %"! )"% -( ) "# O^WIZNIZNO`TDEA6906C8,9.0=28C@0:8:A;4;58<2:=48?,R2DX8>R2BQ5BN:EE<++"'$$$)( %$&3'H?jbbJbAvUh2V)XXpAzCxAp8b3j4Z$PZd'KI{3u-s'93hhq,aTS;[\\v7TW^`E G UW!a,Z(^,T"H`/UB a+g3l/]#e+a,^+i7[%d4e1c.c(p6j(TNLPpDQ/5#A-9+'*32--,',/&54/761;:5!,1'2g'e]^H`FjQ[>`6]5W-U*nD`/f?m=v>`n2t<xBqCRi0W_!i7a+Z%f8V b1d4^2X)hAU*GU1?O.O.-;&?-1C*?*=)P?H@2>WF/%:!/D+ TpZi|Km^AcT'':),2 9;*.5".5".7,2:0,4*690VgR$,"11(11()( !!!'!!!+((-%&!!CSHM]R?UEMcS7G?:B8690:=4F<3F55I47K69S9?WB9Q<?S>HS@DO<HS@HU=FT;@M5BK3@K8>A826,,,, &%(!/AI7_N`IeCiGd6\0|MSnAHz@p5n4t=l4X!JLg"p k$q)?|)^ _ fXp8^(BH 4Fj&r1TOQUOOSGSL P#R%BF9V/[;iIfBh<f;f:^1qCc0f8d4f8c.f8f,ZD N#lAbD)S,-6%7;5111(/.&.7,26,4;54;5$+1 H6|`FX;`CnGm=X)Z,X)f?Z,pGk3n2xBi7g-uCrIrIm6U_(k3a$]$o@Z%b1k:`5R l@O!IY0LN!N!J)@$D*>$=%B*<$>0LHjyHWhPi\QlOGbE9T73N3,4*$++")( /.&&.$,/&*,',.)$,"%7'?(3(п0&##%57)0>#1D3 5E=sYyo=]S =0 ((0&29&(2#*9)0?/.9)6A.4?,4:(G[F"'')'#'&!#" # '$$+#$2)%2)%* +%! %HM?N^JOcNM`LFT;8G+6I-.A%6I-7M33I/4D0CE747.:=42<-BQA6T9*H--I+9L87K6;N=BPD:J6:J6BQ5DT@:J6?PD5J>6OB4M@tEvAl=~PrBv>t=zBf0^Gu=] QSU^v(Ak!Kibc"w6h5L@ < Zl/h*zVpG<J#LOO==IA$//7"P0S6_CmDqFqFk<o@xB~KuCjE^6_;nDqDsF3O._E6C/#0?3@05'6;-49+4>/,9.276276&,6: D|s\B^@jMjEf8Z%d4\)b1\)jEX)j>j>Rt5x;k3sFs?Wa$n2b d4i7Yk3l/a$g-^+CY6K"NP Z*O"9 <<=*?25(8.V]t^qJf]E^H9Q<7"4#+B1.(2#(,",/&*,'33*(+%02-(/)'7/,4*r|.;0*7,5L/:#?!frLgb$>:#1..6/29308.'4<%4?,4F-2D+,<(  " " ,%%  #%)"-( (+%&(#(!!"02-LNIRbNN\MJXI4<28A64@D:@F7DI;:=4d:n?Tu>yAzCp9t=xG~M|Of:f.u9M[&OU S S[_ mif p*a#VRVh d(t<}f]E6N.W,B6&.8&H?=I"\=^:b>^9V/P)U6S.d;Z0l@l@pGh<hCuFe5DvOR+&UC3L948/*.973;6.=80:?16C8.34(,.!6]9ul\DgGqTP[e*a$]$uIa+q<s?e*a$x4|:~=v>s?sFCc'i0i0\h"f p/_!h)Zc'c'e1OSX+X+L$5?!5C(=*G4ED^]MWlakqhsmJUO&3(,/&.2((,"(+%)'#10+.2(03*+ (5*0$';3 $886-8PG +!7[T[~zCa\1?<+868<2A?.CA0CA08?,2B.,<(/A1"1!*9).=- +((!,.)!$$$$)(&&&'$$*1+(/)."2&>N:BS>OjM=S9AW=#;));+9Q<1J49L88H78H7:B8H98K18K1?Y3=T7HU=N:FVB>L1BQ5DS4BR2?U;8I4>N:DO<:D5@O?aVV{X_xvqZU`;pAh8iBqJlApFtC|Kp?xRXn6`(E@w6~2s*VTE;AJ y?d*b'\[RLj-UuCyOhO1( 3*&2+*!zH7#n3!~G':D!N&J&O0kLcC^4d:_<`8`8X0rPl@l@nEvOr>^*\6o[%$L65E,1?.2AZRz^M^MdR_AWJj3%F"CY;vibFxYjEHa+_(["a+f8RnDm=i0g-uIk3s?r8p6VD[v>h)`l(_!g-X"RJa+^.P V'IR&^2H"E+5"=*9);5A<EGfsVjzFZjRips~mx}rrtBK@*7,&3(*/.&&&,,,.2(54/***000!.,'BH.IDq -KHJB2^V2^Vm;c\"694;56=7?D-AG-=C)4B':G/.9)4>/,6'13%79+38!  #"! /.&$&!(+%%##%$(-,(&(&';3I[KUgWN^J8D00;(,6'",2B.8I49N5?QA5H72?46B;1B69I>-E35I4DO<@K8I6BS>@MB:G<:B8N:n%x/<:C!r&"aZd&2Tk%YV fS ?J [!o9ZvpQLPB=>p34f/.V)-T `PP(X*4KV.N&d<mBg:g:oCf;b;X1i<wEwEk>yOqG_5|WO/wA!i6+L*!J0afUZ<a=a=Y0h9oGW/7-qB bwdC`8T,V&e1a$i0a$r1bn2t5l/f'zEp/x;z7l/WAa$h"b^p/`h"dl/Zq<MW UT!U"])U(S&:@C#L>0!CI?UuOkzOkzH]iM[dWfnluvntr^d_6=7$+%/.&55,-,',10*.048:'07DQO+BW68K18K1BQ5BL1FQ2FQ2>Q5>N:DT@=Q<,O3D 5b>EqDQL[Vg_dc\`dh^e_fah[dWzv]e;E~TBeFS3h>l=`:zTvGn8h2Lg+q5q5=D?k!e^vngay S`&h.FYGE>k!]j1~v mCCv68N46L25;0:8#,E74,K0/7j4a4g:\8lGjFbA]<jE\6g2v@|GsC}U|IMH~)GE1c%;;&\qcYKU5h=f<^1Y3]6[4e3H"sshJ`1V'X"e1_!g-Wj&d`r1f'b t5t.t.q<e*iL\^^`_!Zn,n,l/b \)MQ[!UV-\3L(L(>#:=.IK+-6@m!C5BH9AC5AC5?A3AC5?A3>C5LPF@D:>F<:B8:B8>F<:G<>K@BM= R.%JA3H V6\=`<^6h@b9f=sFg9l@m<yHsJvWbE& zD@BK`UXA^GeAkDa:U/^;W/W/]0\*LpO[l@`/Ua+_!a$["_(We*j&l!dr1h"t5j-d#LPVl(^\b Ye*a$[%W!SQ"X/L$P'P-N+M$L,>!F)K6KB_V2BO$;3,D;*B90HKN]nRarAL`Rar]iw_lyhyaiwW_m75aJ*n;tAz@p6mDyPZPPX8ArŲ˹/"*.9)35'*0&,$.&.$.7,25//.&8>/,1#57)57)35'35'# # '!-'#'!)%&(#,.)'$$""%>K3Pb=RaB:I*.755,++"(+%*,'*#8E=:F?6;-8>/H9FPA8F::J6>N:4F-5M83I<9NB5L;*J(PpNb[ab^f^f`hjqhodhmj]^[\UYUVSTU\ScQm~MhzVQW6K*^1h4h0t<s3q0u5y:s;r?xFJ^d q {kyps k~gsip*W;?L T`!d#]8"t(H;1F9.D51448:*27"*/)+J:5&"z&> U9_C^>W;aEfFaBpIlDh?o>sBsFWmJK 0N$X@kXeJ]Ad:pAl<`.c9U-W/a8Q&Ii9jP!V'X"_(_!]["a$_!_(j&h"h"r8Wi0l/h)MNFh"\\f'W]!e*]%[$Y#KW(a2N$N+N+T.N2.<(6-YVkhIUc(-,^ic< %6;-35'(.(2#*4%,;+,4*,4*54/55,26,26,33*;8+A>1<6)-%&#&.$05'&+"%$,"$,"&.$$"690KP9T]CBS>*:&!1&(4-".'(5**->2=NB5J>4C32A1:I975E=0<5FSH@J;BM=>D2>K@4A6?NG7H<P7Aa6UPc^fhmlodk`lhdlfn_mVd[XYVOPSTUV]Y\[Y{lUwh^hbS8*W0^/Z,`3zF|Fj4o7`4n<f5Shnl ygum m]vcyun b!M=[][`^xHZ+D0B=i.(U./=429,/525;39N43s0/o&B#Q5]@YAY=eIgMeNlIjHh@qDqD~RQ-% L-T!7 4<liYAjAf=d9j=h:a8Y.\:\:V1],],Y(g4Y a+QYY["Uk3W]j-`Vp/f'r8g-f FPQbfV j-[_#[X\f#SS"Y)U&Q%[/V8K871-'C@|z}fj|k{VXbKIE4><7DQ7DQL\lJ^nJ[sL]uCRT4o@uCi0v>uCyObi`P8lk{PP_4:(:@.,9!&6")%7'(7'26,47.,1#49+,6'&0!&5%'<0%:.*7,"!$ ()(&%($#& '!" '!% *@J;I[K=NB>E?$+%&+******* %$%*@H>:B8<7G1/[ECo@4,J,^@`@\=bBdDhCrQfFpQrH|R9 (H *"8 ,I~]C^8c?eAh8l9j8^0n;j<`2c8]3[2G`/xYb8Q_(JV$T&X%PT RUMh)bl!f ^v>V9P\[#e2SURW UY$W%[.S%L%R-X3L0D.58UYjy^u|XovOaVSVAlb=_la}wjKsFt<r+][o@eKeKiYxZKyRBq,-803**-$.2(35',408!,7$&0!,6'*4%.9)(2#,6'.5"49+05'D2DEA8@9>C5@F7DO<9GA5F:3D8BM=>I6BN:5K11c>>vHXbklkjwvhlkyftn|gnOO]]]VbVaZc\[[_[_[a]ZS^ZPxL_u[fgraJY2`4b5h;p@n>q:zDd2`.]/z>p4T {,] q$am k{h u&aW ]VF ]WUi*N\PJI[g'dC:-o&[65T:4T60P8.j+2^@Z7]4g>c=oI~RtHo<Fx0b>O/&7(,*>Yya]F_Ea=d9d9`7f=\3];[7[7]8^;X2Z4FHs?s?_!["X%Z'Ne-WU Y#T ^f^f"^ Qe1a-7Ie4IQWYY#X(R"V(W(_-U#M N0T6S?%#CMoz]u]zMjuKYQHgK[@̺B`5e*p6ZZt=qEqElNuVcrYGe224*2(.2(6;-.4%*9)$0&.;0&5%.7,26,69035'83".)99#&(#$&!"$ "# !! ! 26,BM=LVG>N:4<2##)( '$$*#$/).7,.;0DRCDRC$>-+?7/C;4@93D8$;3.G=AOL>BD6=7dy7>aDb:f?b5l8vBn:d3s9c)l4r9j2p6r1V f ioe y*_oc_g>Xb&[_]VV%T"@ 9=IW'H$\9<"q':E7X.,B2*O5,m;2s4*G5I6I7P5C#k- U0>- 1+/ "$ 0(ZIT0jJeBf=jAeDnD`7^4h9b5^1]4^-d3Z%QSg-T^+V!X"Na+Ua&c)`Rb&P` bVXZ 0FZ&Z"f&Y![#W$S)W.b8DP(R*J'L.R3WCtx`q}\my`wrbwvf|zoPZL2żÿx{cl/~D~KzEt;zDzDe8zNxWhFp`&4@:N11(29&*7.<#2?'&8*<#(8$#7",<(*:&*7,6'*4%,6'-,'55,/.&(+%# '!' '"!BH9HXDHXD@F7)'#''##,4,=Rc?BZ0Ja8ZfCR^;8?,,4*8A64A68F:6C8HPFe.h8`1l4n4n4v9t.f `p(o!imd nd n^Vlk)_k)T[\$VR>J VVNZ'B#$P6.b:>M0544<2l1M~Kr=zItCpBrExPjB{[bX.&N25;/.&26,,/&26,.9)2<-,6'.=-*7,*7,,3-.6/,3-05'-'#,/&*-$-,'+(()%&%!"!&(#JNDLPFBF<.;0$' %"'@;HIE@H>:D57K69L8+F)'U#somnijspmjxlwkma_V]T_X_X_[^Wd]__[^ad[^QWSZW^W^JTCWMaTdej_dgWe?)]GlHi5m9\.f0f4^+f4`2h9R*nJT%Z,d4l(ba`mY` Y]Q `c_a UV#T V&JV%=I51 U<,T(P3,8=<708.6>44A64@98E=@FA>>>>@;02-/-)'!-'#(+%#&&&,,,! &$+ %:Xi>oKiG_=bAc=a<[6e:i>i>U%Y%Qa.OZ%QS[!W$OT$SLJURXZPYC.b&b&Rj&Zf(]$Z%Nf8g3g3YT['e14#Qac|gfrWsuWsuE]jm"#.,fcouvkԽ¶RGsv;Fc2uIoBl=tGc@mJvYt%H6#:;:528).4%49+*5",7$,6',2 .9)&0!/0#6<*29&.:&"$& (,/&.1+&(#$)(054FKLHGJ<<>/+,&&&BH9CZ:Kf<WrHJo?Ee:KcBIa@.@'2=*H9>H98D08D03L61J4%L-'V-ouosbfunyrxijXbPdRdRfWbS_VdZ^T^RWSO}U]cU_[bW^LSN\O`GXTa]cW]anazpqddO>rOb?N&X&[1S)b/^/^/_1jER$R$l@f'Zidhf _ cS W^a[]ZXZ"\$Pd*RJBB O&(>8;P;7:<@6*7,4A6->2)9.3D88@9@C=683*Z\X^QUb1X"Pe1`/d4LTY_(R/b]`rTgzf~ubz}foQ_@a,}cF[ԎŵaXF-qAo?g7m?oAn=xEmGgA`BneF5(E'#&,1#.4%05'(3 ,7$.9)(.28)05'06$0;(,7$.9)(00 -""/$"/$(,",/&>A8FL=:B86>4&+#-I+IcGIqNCpL?lHEqD@h<5d;;iA*M13N3/I/.K47J9>N:@P<3G25F:/D83I<*M1P"Y`jqqqvg~on_[RcZWX]Zc\e^aX[T_XW[IMUYae_\STSTRYTXYQ_WWVVZZ^Y]W^cxi~abg8>x3$2*J0K'FJ!@I"(d*x;a$M n,] jf hn` h`TXn3bq+o)k'VVB@@1k6'a-%5<4,@70?:2980==4980C>6690>A86:<:CD6;:FDdqdzbx_7ĒaR@*qfn;:b<`;m;l6n8j1p?tBr?fAz[i7&P4.2*":1*14"57)/0#77.&2+(!,8125/*2(2:0,9!(/)/$*&0. '!*1+ (&28)>C5@D:8;5,.) "K3DV=:M18L/DQ9/4A66C8+?74@90<56;:22M6'p<-v2FvB`_ kc gjcmd"`!Z\Tb!X^Zf'XlDD/n@G=R3.5729514:<7:B808.08..:3,81.:36=70714;5(20..0$#&1..10+03*.2(11(35')*05'"* "1!-/''+gv{goGNI5I5`9Z.R&h0Z"f.r9NQJNPSMTSY%OKRTTLWM(HV#T W#Qi7OTF Z%HQ_(MI c.LV+P5`FsoqO^MQX8ڷț*|)juGNipnM`?n3m8i4p6~Fj7tAtKlLlLH8v2,@68325/03*/.&/.&,2 .4%*/!,1#*/!4<2*2(*7,5/+3.&1,$++"/.&33*&*,'/.&AA803*-%#@VO=tAKO8s?1f83i:9k95e84[3BhA@b;5Q09U4=Y;=S99O3=T7N:3L6#S5?!L\u_XYRg^bSj[fSl\aZc\g`_[eaWZO`AVWlWiSTOPXWUNUT]\[ZcdghZe4gG'U<1_F6R<;R5;R53I/3I<-(C13L61E05I4'>-3D8$5>2DL,asL;wJK`hko!aUmv7d%d#f%caf$ZZ`(nH8%_ EM?Q;267.2C=9?A36908<297332-;:50004;5;:532-7/054/'&!&.$,3-.2(26,690,3-,3-$," -"&1&1TqWTkcQc>76/X>Z2P(a2g2X^%O@OMSZ!T`#Ef%VZ"GIECA?HTRTYe*M ^L_!d%C _*]!TRLQ$R1N-SIfhPQAf %\ukwy*hbžteyOoBl+g/o9j.z=v=zBjAcO_KWK6+L232<=<==4/-)32-,4*&0!,1#28)6<*4<%:B+2@%(5*&5%"1!&.$.9)(2#$)(0&/+,956*-$- /W4URI}DEy@Cq<=k9=k99d87c66]54[3Ed=@`>:Z88W;=S99S97R7=Q<@K8HU=DQ97O.-^0%U(LSe][S[ScTg[cWc\j^dX`Tg_a`ONLTJQYZWXTVRVVZ]ZSTTV`bfjRc8tI44>/4D0.>*/B.1E0-@/3F5/<:2:`$+R g?+b3P!Vn%XZ ``Xd!d^#X\Xb(U 8 w,#L4+T,(423266603*?<<=<797352288:22494822474474425/&(#(+%$+%25/*,'02-02-(/)")#33*-'#",&0! eyo9dT?chE\MdGHF(R4`7_/KU SSC ODR%N!P$S"IMI=ICFPF.8JX^SW^VN W[(^.X(MO[RP!P >P(pGd4o9n%q@DP T_htlq$B'z_oS:qyOf j-b$_2i@a;Y3a:gHiJ~d=*Y72911(32-10+(0&(0&*-$,/&57)4<%2;#(52<-&5%4C326,03*47..1+,.)8;5.1+ -"/*IuESKQEM}AExB7l27l25i0=q:`9Qn?A^/;V,A\2>U,Ja8DZ5CZ:5M87P:,H2/4D00@,0?/-B9';3-E$n-4b'd!V\XVb&\QMU(P!d5R03e5(EA4Q-),9737613008<2BF<:=42:0:A;07102-4A6*42.86,+.02-(+%,02,0230030010+/-)10+-**%#50(-( #:z^cAfN?^ULcjNelMX-*]YZ7W%Z!Z!Z^"U"MCU]%U\%PRQGKIC?C'1?AQTZU?AT)DL!FO[([(OFT"F`5i0m6z7t5_ MNV <nd |d +Pc@c@enhb e*i0['g7l8d0j:d9h=gK1h729"* 05'05'05',2 58&-0:I96@1&0!.<#5/+?B09;*2<-.5"4:(461,/&2:06>4);+ /.R3WS^RQESGKzD?j>;g:>h78c.HiC@b;Bl;IuEDjC>e=Ed=Gb8Id:A^/F\7BU;6H/49+;;2==447.,4*4<22:026,08..7,03*.1+.34046 'TMjUjUM EK`+T`(^%R#P&h<N D(Y) 1/&730046146147.461@C=>@;4<2:B808.4<2898.1+*,'*#*/.&+*(+%26,(.*/!,/&%")#$)($)(* /S{XGpLLqNHeKNkQGjfDY"7s7-a8Y)],EW"QY$JJBJ S&M!K Q!EECCDZ)25E 2R(OQO: >P!CAO)E@H@M(N4k:q<c'x;w-IMY[_YS e] YcgdQq ^PgPr8j&l(p/l.h+h+p6b.`-lElQO9d.90493/5/+50(72*55,55,(8$#6%%7'*9)6A.2D+0B)+@'&6"4D0*<##>#-A,-A,(G) 89f5WRFEMNIJJsCDo:>i49g2/f31d.7k4Ac3?`1Cd5>a67V/9U4;$C"?'V Ap:]Uk_g[_ZYVkde^aYXZXZbjW^MTY`NUHPLTJRKVIRGPZ\abQR1`79a>?hA9c;=n@;g:?j>?j>JrFPyIDn=Ap:;m;1c1?hA:Z8A[A5O5)A,);+);+.;0.6/*2(08.08.8A62:04<24>/"@%$A'*LFQJ7?n0T"^0R$k5\,8wG4c./=,10.42.1+47.,/&6A.8H79:-IK=??6:=4<@6980=561)*/'(+&*-$(,"/-)--$*/!$)"* )'#'&!--$$+%4# +V9u~GuPErNKtPInSAgKEhUSsuIik&?{ROQ2K,M\$Z^!RV"JIOMU'N&GO$IDJF82hE: V-MEC- + G'<J!DN$DJ#J#L#D6 d;i0]:iFP^P\O `[aLD LV!UMiZWpfY@jXt5^`f,[)g5a<pSw[ -9,=76132-&) 49+0:+*4%,1#.72;#090@,-D'-D'+G&0)D'2D+,:0? 0? 3R+ J9S*5M,/F&'?(J#FIx@[Ri`h\fZn^mil\l\_S\^dfV]JQFQLW]\LKRQHNETNVV^aeQ}PGsF=iIChMAkCGqI;k>5i??rI/a<=pJAo=GtCCq<Do:Bm8Bj>@c8@`>>^<'=#,>%.@',4*.6/25/4616>4.42.42?<<.7,9P?'>- 5K&˽Ue;,G9C[6V;J._DC%u/a1'<,1026,03*32-03*BF<6;-@D:??6AA8GG>;;2AA8:<7444+*%! "#%..0'"'&!-,'"%(/)#"* -"1!]w[~}PvOEoGVzNTuOLnGLoQOufKrbEcy?B5 I3^4T'Y%_+KU)KQ&MKQ#K"DJ"ICE"7@(/]Gsi=!&P$;)1A.;R,L'M!L(H$;R/@BvVi0i0w-gKaQaY[O Tb"'9Q)Q); \bYr`qp(r+`ff"^d([&e0g6\/`=. S#+/.&11(,1#.9),7$*5".:&*5"8D0,:4A)@N38G+CH1:B+4<%,6'4<2&6"2B.$C"M 7h:e==n@>h7@e5LrKNtMLqAJrFHpDUpFMi;Id:C]42E)*9)*9)47..4%*5"0;(.9)6;-6;-,6426,.1+68398057)9:-49+==4:@.:@.BM=BH98>/??632-"""% $!%!"!%$$$"'& %$&-'$"/zupr\^NnLTtRUpSSmQNlORoUPmSPlcGYy_eQX3!J U%W'SY'QS"W&IGU,O$GQ(?D<:6T1K23G-AD(.F(@Q&IGHDH"<"7'iX[Ol@Jd] CU TVRWOPX0Yi*3}fWI f8X"l!t.\Xfq(]l)u9W8f46C=91+'55,&) 08.*2(35'*4%6@1,7$2F&=D&?F(8A)B?)*>,@ $FLw6Kr2Ms4Qw;@j9/a/9k9/^&+b/R!X%,4<%BI6@G4% $%%#yx_eGvMSy[OxTNoXFgPTpgOkmMbMb04A,K*M+FV.M:.&9*5(M?=1%/'=2=)E+C(B"P2T6E0&+AHw~P[{e='S"N;MF [[[Q^'B (95pwlZB#cNM+s?c'`i0IQRb#XY$l?{V;{1!G?<<744$0&.9)08!08!(;.@',>%&6"4H(CL)?H%;A',9!$15m6;r?5l9ArD9g2Kn:Gj6Gf0-a*MJ_^e]e\i`__MQefcdQNSTQRTHRyBXHNwGOxTGqIKuMHpDHpDFnBHnG@h<HpDJrFWxITzF:@.4>/0:+2?44>/8C31B6-E3!E.)M6Mo]@- -".>*.@'$5#6%$/)*1+4;5:A;;:576B; " o}coMxYJlQTv[GjWIkYFgPMqZNvzFnrVoSMC(M2K*R.?+E0K>SDgYKD5)3(A5I2H,N2E+C-O9I6IIYmavXua]qlV1=H TD :8FDR#*$8 |MţnWuhM:U1uC[["_(\XSOSY&HBzW_Nx&%(&%(32-.5"$3 *:&4B'.<#.<#$3 :I*>U,DN04G++D;t-@o.Bn6>j2Jp<9o1>l/>l/>l/eSeTkVbPn\`TdfKY)l7P[W^K}KQQP{FVCY@U|<V@Lz=Dr5=l6?l;OL=q:IyL/A13F53C;.=-6E58I46H/$F+0S7U{_!G+3+4<2,<(&6"&5%#4(6=7HNI8A6A8'&!$"_iMW<V,$ MS}ԿoRbqqH_(_(S_(P!@HO7[&Lj%:c'Pw@ZP\XHpD*M1+>*1G-3J-7N15L/1M/,H2.Q5@~N@(#3+55,2:02:0,/&(5*4;5DJEDN?@J;8A6%!'""""&$)($)((!X~WVy[`gMqZGlT5_CChMJmOVy[[eMpjS`IV7/H)Q,G"[5[LaRQIMNJUVabuykH`RYulSI<:F@D>LCS9K8I5C4ME?79B]uic}Suq|1<cQe8,@: @<R4% * , z]_!s?CdJv}tlk:OUV!9G =E?P&S&S<W@mnwsDQF'&!28)28).:&(.(.$'F_1>X&>X&/M'5g&7i+7l23l4;s<3e'-e.%Y/-a7-Y,gddU`KdO[JUVWXWXIZG}N>f:HpDIj;Kk@Aa64V/)Q.^RG~Ka`MvO]_PxLO|K=k9AsA?oBIuEO|KQTT}M`XNqF,L**I+.N/-I+7N19O37N1:L37M3!G+(r@D'#;)->2+=-+=-':),;+.1+2?4>K@I[KHPFOPCBU;%$*#%A;7&(#% *(-5 U~Z[`[u[_t[OdKIbISlSSlSSlSf{Z[yS[u[SlSQmoM]TFTFQFmj}z_ezcqneuGjWdqsu%/t[SSJBCPKHBSGUIQAE4G8WVONgwc|ey[ogmv5=YJs?- 3E+A'?&( ) wdj>e*wEt.{xxl_CN?C!H J"H V+N+RC>/dkx^hf|VhO%,7$2A1 0$3 &3@Z(;\!;\!8T3N$1a(5d,8f)=r+=z67s0Cr:/c9J$*_:+Z1ebaXlZZHkYR\MQSWLYCsFLqALqA[zDWuBSq>Be=0U26]5Lx@NzBDHSOPyI^WVJM<7r&5q.=p=I~PG}NYXZYHnG-I+-C)1G-1H+/I/3O11M/8M,8M,:N.7N15K1)O3h63dC9'(A4&A/*E3%=(.@'*6/HPFJWLNSR@K8."6%*%#&-'#/)%'&!(+%&(#$&0!0[aMzVPrWX{]PsUDfKKfIUpS[vY[sRWuOXvYRpSVw`Wwm^px^pxZtr`|sh{jw`~c[eAgKryƸur=LuVd,:S\LFNHXPYMOASESL7Dtuwiz_seycQZYa|iNkLQ+Y3D/uhW;a<pGrIj-Ùjy}m|nJLG)C$?DH!P$L*9&M:JP{_ll_llv*7,"/$08. *&0!"/DY(9U'9U'9S*Fm6Jt45k-7n/1g)3f$cUUNTW`cZb]UQPSRHk@PvBNyDLwBLuEPtELqA@g?Bc@1Y9+R3#L(%P$CnBNvJZSO|KUQR}HXLJv>Nx8Hy3K~<SD<^7)?#.@',>%6>'0@,)@//F51D34D00@,8H(3K*I'Q%]q <&.;02:04<22<-0:+$-,FLJJTRLWT.2('$$%!"!$&!&(#"$"""$$$ '!+ .enS{XO|XU{_Qw[EjRMrWGnOMsURuWVy[TwYTwYX{]X{]Xu[VtYXu[`}fZv`_lerInSSx]s}[~k[|}tJc,8aUUIfag\abIJhpoyksfs^}kc|xtFAqQtGZR#SQIpGkKkKk:yO[p(RƙΤhYQ&/R S K[$S*Q<9#FNIizGhxQvzlbfh07111(#";Y&8`(4[$=\&:S%/g0]""d(*k0?~2E{0Aw,Ex6*+>*+B10L6T.=T^>&0L6%?%%?%-C)2B.*,'*,'JRHM]RIXQ(/))'#'&!&(#(+%*-$$'(,""""&&&'(*#y~TyV\^QzVQzVGuPKqU\^LqNV~RS~RMxYMxYQw[Zw]~[hi|dHL?fa]zSlXnYrųϮ~gqavav_~uOZB4qhCpL+ ,T"xSjEgKgKc?^nq<RĕΤrsfWyn]C;G!=L#Y0_Qb.;`Kkp?^dQqvc}yvHO{NV{KNrCSv@RyB%P$3^5Q}PS~RUTUQSLO~HExBGv@Ax9?u7G~?K9.2(&2+(4-$/)%20%20(5*X+8 -sB*7,6H/.@'+>*,<((/)&-'DOIAUMGYX*,'$&!()(()('#&u~O}US{XW~_Sy[KvYEpQKz]Kz]O_S~_X{]Z}_X}ZZx[isgqlsbiiv^yb}^e^Frjɲåu}]ico`{YtyWUySh?Z1[3f8h:tMeB}feBjEo@o@FOaUʥıɾøúkRs^vVf)R\)J H5Z:++Kpq,UUFooewr}w>S"9V$7U"@T'Ha3Bl/3h!1f)e"7p6=t5Cy;5f)$_(;k2SJm^cTdRhVPVbf8[3:\5:\5Or>[~J[H]JV~FR~F)V%#QBl;R{KXQUNKRI~P/c9KHDs2Fu4Ex6Ey@I}DAo=%5*(4-*6/*6/293.Q5 T32 $`5֣(:!4B'2@%*=!0C'26,,/&L[K461 &(#*,'***-** N^fvI{VYcZ{UVxQZ{UU~ZIyYM}]Hz]QaYeYeW~_Z|aưfkmwkumwiueqgsgw^spGt_̯]r_t^|YwkahbMc7qEl@kKgE_@]CxqXAaCoQoQe*_\sżһư°rhbWs^eIF$R:TSr``~5SN-KH86Ad`;`aMjhy{?Y37Y);]-5V'%X")\&5c.=o1>l/@n1;m//f$]LcR[MOP[Wkgx_i7b9)V2+X4IuEZ~OT}MXQ[|MUxD0W/%P$#T&Cw>7l2Cv@Do:Hs>Bi4Fk;FfD.O,8A647.*1+*1+0718%T33Kʕ%:!1G-5K1'?+G&,4*(0&EYQ2:0$/)%.6/&(#,<(,<( `UR{KV|US{XY^QxYU{_QxYSy[^c[cY^chX}Z^YIoQSy[TwYTyV^`]bKwW[im~escqguktgmkqkx]m{׾Ÿwuusݽ§b~hpv_y]w~IMp[pNxYZ&xSvOc?Q+{ckKnJ{Rmm6~=_ĴƷ˱ŷɺN@|w\H[M{]w~QlrOufIuaO{g1hV2]X7_XCkdwɿ*U 4_*2](?a.(5%S-\&5c.Gw>8f)>l/;n,I>eVeVfWe\Lx@^R}^Cq<8b18d,*VG~KR}HZL^P\LT|P@h<NvJS|UHpD?`12T-(R!?hAClE5e86r:>yB5m6=k9GdB/K*#7"4>/2<-.@',<(2B./F5/F5'A'.B"4>/*4%$,"GVOGVO:G< &) *-$(0&".'(+%(+%1,$--$--$&) (5*4!!D1) 2eG~YaSlG^`X}ZV{X\aTwYVy[_dbd\abgbgOxTPqK\~WTwYW~_]eYeO|Xgluz~}gomokpmrirgsiukybcñPi\[e_saOp~IWaQqa_GW-pGpGxYKrPpNwLtMvOpNyHaܺƵ°ʻɻHMfnRONJf_u M8c^XvL{jMxhJyhIo`/UFų1P)3U%7Y)!6$.%<3b,:f.=l6:f.?j>7b9@g?:Y;0O1(7',1005425/,812<:0:8.:&6A.2D+/B.,9.&3((-,LWTFPNHHH%!"(,""%(0&"$*,'*,' %$ )*$,."+,$-,!4 -A,9M _eMvOTzSX~WX{P\XPvONtM\~Wjhb]hcto^~\NsPTyVOz[Sx]]g_kO_m}iyaokygupwrqnmvuoteuaq]intſѼQtaYjgxcwWxew{_EQyH]$nDl@vVT#f?xYnDl@l@i0_6O͗ήҽ³ʻʻusiOqZ[3^C~d+7u',FoIhKs_1XE̶;X)9Z+/1)G!:f.3e'j2PSSRONWVYTVGtet_Hw6:d35b1+X'!X%\JVDSKE{IAwEVOUxDV}HAkC1Z66[8F"#Q,7jA@wM@wMCxJGsFEvEDl@:]28Y6$C"(:!08.08.02-6>40<52>7';&-A,)>%)A,1B6#4( %$PZKDG>+%!%#)'#$&!%#,.),.)$)($,."+,"+, *('7/#3+3&8}irMvO\^Z\_TRxQU~ZMuREpQbgd_b]xpdbMsUOvWSx]OuYYcY^cho{kwes[m]o]mmsmowyamcqao_kblĽĽɾ}WrOzj\^Bd::wLj-~Kg3`/l@h:a$f8xSvOpGnDa+}Uz7yrḵĴıȵl_S-eEkK`MIMt 6<_RnL2ez7R(Ha3$5,B20V"Hn:?p30S(,O$.Q)Dm1Js7R~9MBMF;w4@h<6R< <&.:32:08A66C+0@,,640:8$;3 ?!R*$3uDvz1E0(K/&F34A6,9..1+][WYWS@C=(-,+*%%#%#$'47..2(10+*-$03*-( 2*"(,"26,+=-7lneeO}U\[baV|UV{XX{]TwYAgKU|]`_`_lgfaItUGsSTzSZ{U^Y^]aneu]mgoaiai_e]`rqxwbcdihmQxYY|iλr\9`}Zg8EA+\0X)vVa<`5V'c.X"^+j>^qCsFd4}Uk:bl_ɿleٺD$[A]CePf\* |%T3Ii=yf;iP7l_>V,<(*:&@(d,%Y/S)I!-[)?l;ExBM}PIyLEuH0@,:E2.@'.M1*H-&B,D%A8 e/ҤÔ8H7DO<2D+*:&(8$&.$$JaYH_WATP,64+%!'!-,'+'*(-,&+*(/)(4-*#(0&*2(*#+:3#E6*8jPuKzQXxVb`b^hj]e]gs{mu_k_g_gOuYKvY{ǾƼƼ||_|KJEDY0c.i7o@V&h:f8`/L`5Nf8sFwLb1{R]"uC[ʶwOv{NѶvnP5s?Z1^46)49$ . <* J6<|h+gT&.$08.+F)!R9s(G6<2Cy,=t&Dz$]HSAo]aO?q?KwJItHBp3I}7E|:K@/a<+W7/[;H7l>9n@PvBFl8@f2Jp<Mn?:`,0V"J,V%49m';p)Q?SNURc`IzICp?Aq8Eu<As5E{=M|DK{B2^&1]05`4#Q1d.SPSNOJKHOCK}?IwBDj6Dj6NzBKzD?l;+X'*O&Q.Y$6e$=t5?x>?x>=tA7j7-`-)<(:I9@G4:@.>F/5M,9O35L/5O5)P1O*O*"Ң\p2:0':)28)49+&+.'Uc`Uc`TXNEE<$&!(+%,,,/+,/+,10+*),..0(,.*42 *((20*:&:4 vea\aV{XVxQjlnp[]mskqioaumRu0IY\imY]ksunwdmNJO/G(\)c'^l/Zn2l/]X"Z,L`/j>j>V'k;Y*g7sFuIwLuIt5@:k}fZ!~KQ,4*"* (7'SriUuk>hd(5!7#:'>!'?-E$2U*#R5k-OG;z.?t*Q<E~76v20o/>|=YQUDUDG6[MWP=z6;t:-a*3g0=p=7n;=lC3c9?j>HpDMp:W{DY@V~FZJ@k61_-1c13f3IIx@SJfWXH^NT{DN|?[IO=G~?MEO~HIwBEw9?n6Aq8I|FEvECp?)V%'U#0U%.T#2](;k2Ix@Gw>Jx;@k64_*4\02R0%<3K*:N.4H(6J*3K*(G)0O1&E';X6&F$(H&4R7.M10Q:*K4.O8#P/U3O-z|ߤkdGaG&) *-$8;5!4 /B.:);+,810<58>/(0&"0<5F]ULd[JaYH_WESP,*&%() "/'(10+*-$(,"(+%55,03*(,".1+(-,,1025/10+.2(03*6%A#$ q~cmRxQhgX~W]eYaMw[joVy[X{]=hK?lHKzQ_eU~Za~\e`Y^U^S`O\W~_U~Zqvotamhmlqeja`cbee[^on{zqy`fd}I@M+mNkK_@gK_@_9b8d;nDvO^+`/`/T#X)a+c.["d4S\)P`/`5\0c.g3_(_(n3~KuCi0t5z>r1x#E@3o{G!gKQ+[:_.q@;&HPF\eZWm`Mol;]Z9]b2&6"$3 *<#&:0D$9V$0_5j K6H}+R.J{&K4?v4?s-YGdJN.V6SB_WHt<@l4Fo3Dn.Bm,>m,Aq8=k99f5;i77j71h57n;=q:EvEGyGHqA8b17h:-^0N(+Z1KzQdZ`QVCZGQ~MKHCv@P|DPz:X?Ly3Ay6/g09q:AwEGyGClE5_7L(!L/J-4Y68^->g+Hq5Nu>Ju@>i42Y11M/,<(4D0 kL^^yOWeOWeFVc@Ua:O['96!8'!8'!8'+@'+B"1I(5T**U 2a VDT;N|0N|0M85q.Ay6[PSE\B\B_QEu<>i4Do:Ag+Ci*Gn.Bp3Fp?>e=>e=@h<1c11_-7e3Bn6Cq<O~H?j>8_71^:5b>O- T3J~]U[XNZKVGSNKzDGv@V}HQw;Jt4T~>Bs-5m6AyB=sDMxLEqD9d8.U-%L-'O/+R34[39f5Ao=GtCEoG;f=<_A7M30=%8F-4F--H-1L1$C12N8*H-*H-,J/3N31L15L/6J*DU1BT/5P&6X1)Y,)Y,>1i2|TuO0@,,<(>K3#;)@ +@'(7'461461461$/)%$-,BNGSa^WebZcdNSR>CB"$.1+(+%*-$,/&&&&232./.***,/&(+%.1+/-).42,02046".'/A1!3#;sY[acacbd^`X~WX~W\[ZYOxTOxTGuPMzVegS|UY[`e\aX}Z\^SYQ}]myk{gu`enssn^]nmekcikmaccfghwm]O$W-j>vVz]b1f?j>j>o@}UUh)n,df `f'h)a$Sa$M g-g3a+Y"a+UW!]&c'b f'Uj-v>l/n2k3c'p/j-l/o@V f8FN/_QQC@EsMWlJZgBS_7RX.HF)(".'+A%9(H&+F))E$+G&:]2*T8nZ:E,?-G5?y.C}2G6]LjRWEUCGy;=l6?s:7l27n;7e3Cp?Et>Dj6HpDBj>Cp?;i73f39k9#T&+X'O|KT{DTER~FJv>QHT|AT|AU|<Jy8OFEu<RyBBl/Nw;Ms4N}<QJGv@-[6.R3*N/(O'0X,-a*3g0;s<AsA=p=BhA3O1/D+1G-@M58K16H/:H-/E)CVB-A,4O=!B4)J<$C1-Q=+O8+O82S<-R:$U<N4L_&ߦ~W`/F5/F5+=-*I+(G)"E)QU&<840#3+."!1&#7"/$F+"E)&E'/F&+B"5V'8f)-d%KCG<I}7I}7Dy'E(YJK<>|==v>9q:Av<7o,7l%9m'9m'Du/Du/W;Pp-Tt1Pp-RC@j9Jo?@e5Jp<HqAFp?;iA3h=3f@3f@ T3!R1;kKEsKClELtHFnBXHHw6L{:O@Cv@Et>O~HNu>Fm6Jq:@l4CxJFkHDiFEb@7Q+3M';X)3W 'Y9j-;n8GtCAo=@g?&J+%=(1J4-E3/D8/D8+B1+>*4D04D08K11E07K61J4/G27S53O1=X.=Z++X'5b1UHҝȝ[e3I</D8:D5>H9.9)/B.6%':)':)'<0/$*#.E@@ZXLfdSebZe_FQK300% '!&-'-**1..+*%444"!$436+((/+,3001..*1+(0&08.(4-2&'<09-Z9|^Z\U^Y`[V{X]_V|UZYZ\V|UbddfprUsM]zU`[NtMYeUaH{[MzVgpqsnmgoemho]geocpcmYq_wk[p_tK_!b `v>g3Xo@wEx;`z7s'`r+ckcR_!b8\0^2X"\)[%V*P'L$Z2T.]3U,Y.W%c1Y&`#a'i0o9c.Uk3PB!P.KBCFCFPVCI\,10$)((,"03*&) &.$*<#6E&2A"@N&.H.H?a./](-d%Cy;=y*@24&/.MT@}BBDIz=Bq0Fu4=o1;i7;j47f0?n6TBL{:P>N|?Nx8Lw6Kr2K}??s:Ey@G{DBl;LuE@e5Gj6Bl/Hq5*Y2cP;@q+Du/Dr5TEM|DExBI|FM{FT{DY~COu9Hw6@n1Dr5Cq<Cp?Ap:M{F;i71\3%O''P,'V-R-d10k7;nE=qG;hD(L-/D+5K15M8+E++E+4C3/F5(D.0L6!E.0N3.M1*N/*O,6V46V4.S0%R.$[1"Z/!b2;Ԓgc$J#&M%9T7.M1*H-'?*(A4';3!5-'8,2&*'":1G[SOc[Qe]\haLXQ461 "* &.$ -" -"!.,$-,&) 47.(0&(/),3-*,'26,.2(26,*4%%5*+;0#H02usPsUQaUeU|]WVU[SYY^W`]bejsuW\Y^I{VIyYO~aWiK}XOOiiehqzkumwmrclaj_lZmRtPr`jrQ*`t.|:|AQYz>t<r1dz7k_}/bkgHf o9a$c.S_(a'_+M!S'Y,\,d3R![-S'c8X/f>[+e5j-j&`[8LT&`4[+g7|PheR8d^RV31 31 14"java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/zexall.sna000066400000000000000000001400331445000440600316170ustar00rootroot00000000000000?X'6!D+->:\b~888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888 6 ! # <@8\\\\\\\\\\\\-\ X![!@P!!88 K SR P;:32768 69,0:32770,128 '+-'+-'+-'+-'+-'+-'+-'+-'+-'+-'+-'+-'+-'+-'+-+-e3 Rv>! ͑!:~#/+͟"ל ͑7€!߁>[xׄ6Spχ.K hNJ&C`}܎;Xuԑ3Pm̔+Heė#@B,O+9~cӉ^F8!Ԋ hl,...$ &PXfɛ0!٤add hl,.........$ ݔ[c3vj h60!ߎadd ix,.........$ Q> Qq0!9Xadd iy,.........$@<~gzma[) f8Q.aluop a,nn...................$>:WML f;?Ǫaluop a,.$݄nϬG("58K# 9Daluop a,....$݆2n@En 8Jaluop a,(+1)..........$Fu +1)............$@>zazP$?S^bit n,...$Ǵr0 Kb-cpd.......................$HMJkqNj| --cpi.......................$'A! `Y[y)m-............$<J՘+CDZ0 a..................$#-CazZXwZs b..................$ͫDɍIM*!Ү; bc.................$ 5 ['ҕ` c..................$꠺_e8C\FQ d..................$.4( g.:T!, de.................$/` L$ 2%Yʌj e..................$$+&(V^ h..................$#åmO*W! mJ hl.................$#<  !M1 ix.................$#zc1Z鲴m!P]Q iy.................$,1 VC <> l..................$4V| >~X\7()^ (hl)...............$3o4iѶޔvS[!]' sp.................$4n(,WP3o(  (+1)........$$8l1>XYBoF6b ixh................$,M`tv2 ixl................$$6(oa˂s-l iyh................$,b9p~>"6u iyl................$KYohنKMEld ,(nnnn)............$*c0xw `_$ld hl,(nnnn).................${Wa!ʅ'`zΡld sp,(nnnn).................$*L$އ¼cL mld ,(nnnn)............$CM]a?dld (nnnn),............$"rwSr?d-5`Gld (nnnn),hl.................$sZVگlD ?X_ld (nnnn),sp.................$"l i *kld (nnnn),............$\F-x`tF009ild ,nnnn........$! SrQ "}%ld ,nnnn..............$ *BƱ5ld a,<(bc),(de)>.............$ĝ=9UtS U8ڵVld ,nn...$6EaĽ &G~ld (+1),nn............$F`B9J ld ,(+1).....$fRI *Mld ,(+1).........$~МC dld a,(+1).............$&S<@Fyw] $肋ld ,nn......$@r$aǂq?tKld ,.......$@ż/; ?Gkld ,.......$2hDCSO&-ld a,(nnnn) / ld (nnnn),a....$Rhfh 'ildd (1)...................$.*G9=ldd (2)...................$0X``&ldi (1)...................$Jnˆ-Юldi (2)...................$D8k_4WBFCZ 8jneg..........................$gˑb y@>w....................$Cm S @|..........$+1)..........$J]0Cx=?W%X3shf/rot .$ˀ,9KѲjS'8W n,....$D h2^g xc n,(+1)......$p ':{p bjld (+1),.....$tdf j1ld (+1),.........$wgOD׼P_ ̾Zld (+1),a.............$; l!zLOld (),a...............$~#fo~2(#(!6, ͑@:v:vHl9<0!T ͑\ ͑\ ͑##>22Ӛ!"!"Ԛccl#cN~ʍ>ܲO|~ʨ>֚O™y*F!~Ow˚*#"x>*ԚF!Ӛ~Ow*Ԛ#"Ԛx>6T] (^{yO#zo&z;w!4~gGc6#T!~ʔGxŽ6#w#xsP1{sN1N{P*"@!L~w@!T2~n#R~n#aw ڄ'0_͑Gy( ( x ($( Z80 instruction exerciser $Tests complete$OK $ CRC:$ expected:$ $T# o&))!XFw# 1!T>w#Jw0a, Qmpjc5dۈ2yܸو L+~|-dj qHA}mԵQӅlVdkbze\Ocl=c ;n Li^`Agqr<KG k5Blۻ֬@2lE\u ϫ=Y&0Q:Qa!V#Ϻ(_ ٲ $/o|XhLaf-=vAq *q3xɢ4 j m=-dlc\kkQlabe0bNl{WeP|b-I|LeMaX:QΣtԻ0JߥA=ؕפmCij4ngF`D-s3 L_ |Pq<'A  Wh% of a^)ɘИ"רY=. \;l  tҚG9w&sc d; mj>zjZ '}DhibW]egl6qnkv+zZgJoC`֣ѓ~8ORѻgWg?H6K +گ L6JAz``ègU1nFiyaf%oҠRh6 w G"U&/ź; (+Z\j1,ٞ[ޮd°c&ujm 6?rgWJz{+ 8Ҏվ | !Bhݳڃn&[owGwZjpf; \ebiaklE x NT9§g&a`IiGM>nwۮjJZ@ f7;𩼮S޻G0齽ʺŠS0$6TW)#gfz.aJ]h*o+ 7 Z- P P P 3\3\\3\\GWq !v>>BB<DHpHDB@@@@@~BfZBBBBbRJFBJ@NDZw$VGjxw77@8 G"S q@ߊHciZ8dbO3 ^No*Mc@AdNyXjgs|CGN |lvQoHLK ҽ{ "y^|m!$j)ֲsF̉O-@hsi"jm@$IXj{ rTh19 ;VIuhuH l zJ9N :y7v;:zAP<14v6waԎ\̅ fm`$>A+D0ilG Y(J\3%}%RHfogd<kSƑ}qZEi_ K'HAN"{+-D6yղ>`mKxq\DDZPlrH@\~GrzJ[35 ˿!<,'3آƬ@&]xE0sıNZ,2s@Wq G"H F.ѧM&K΅5"7 *y,]%Rorf:y5v*6p))GnfɽW#ALV.e}شu#elo8O G>^ vkYqzgV"R=4tT 3DXo Տct> 1~\o_ԅSv8g؁?QڠgZA)j@US5>9d!SȃF֯Z80@:r x+`ZCl2 *^9i%. εE (yw qM"=JƼ#nn-v~iqB5,ǷPzۨ= &{'E_~2^~̴t D19cWrFJ#Rx5oAs !"4Nm^ (EeZy^y8GYoyB޿z~y p2瓻yex o6&GP ~C$^>Px9ϋ_ACc$#1|/"(0e壇IENDB`java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils/testwriter.txt000066400000000000000000000007131445000440600333160ustar00rootroot00000000000000;Comment1 ;Comment2 .0x00000001;It's header ~-------------------------------------------------------------------------------- ;It's body .0x01,0x02,0x03,0x04;Body ;Next comment ;One more comment .0x0A,0x0B;Part ;Part line2 ;Part line3 ~-------------------------------------------------------------------------------- ;End ;The End .0xFFFFFFFFFFFFFFFFjava-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils/testwriter2.txt000066400000000000000000000014101445000440600333730ustar00rootroot0000000000000000000000 .0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F // End of line 00000001 .0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F // End of line 00000002 .0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F // End of line 00000003 .0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F // End of line 00000004 .0x80,0x81 // The Last Linejava-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils/testwriter3.txt000066400000000000000000000021531445000440600334010ustar00rootroot00000000000000.0xFF ;Complex object ~-------------------------------------------------------------------------------- .0x01;Header .0x00001234;Another header ;Body ~-------------------------------------------------------------------------------- .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ~-------------------------------------------------------------------------------- .0x0000001234567890;End of data ~-------------------------------------------------------------------------------- .0xCAFEBABEtestwriterbin1.txt000066400000000000000000000041111445000440600340050ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~-------------------------------------------------------------------------------- ; START : some class ~-------------------------------------------------------------------------------- .0x01; a .0x0002; b, Short field .0x00000003; c .0x0000000000000004; d, Long field ~-------------------------------------------------------------------------------- ; START ARRAY : arr, some array OF byte [128] ~-------------------------------------------------------------------------------- .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 .0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 ~-------------------------------------------------------------------------------- ; END ARRAY : arr, some array ~-------------------------------------------------------------------------------- ."Hello String"; str, some string ~-------------------------------------------------------------------------------- ; START ARRAY : strs, some string array OF String [3] ~-------------------------------------------------------------------------------- ."Hello",,"World" ~-------------------------------------------------------------------------------- ; END ARRAY : strs, some string array ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; END : some class ~-------------------------------------------------------------------------------- testwriterbin2.txt000066400000000000000000000745431445000440600340260ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~-------------------------------------------------------------------------------- ;START : Png ~-------------------------------------------------------------------------------- .0x89504E470D0A1A0A;Header ~-------------------------------------------------------------------------------- ;START ARRAY : Chunks OF Chunk [8] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;START : Chunk ~-------------------------------------------------------------------------------- .0x0000000D;Length .0x49484452;Type ~-------------------------------------------------------------------------------- ;START ARRAY : Data OF byte [13] ~-------------------------------------------------------------------------------- .0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x40,0x08,0x06,0x00,0x00,0x00 ~-------------------------------------------------------------------------------- ;END ARRAY : Data ~-------------------------------------------------------------------------------- .0xD2D67F7F;CRC ~-------------------------------------------------------------------------------- ;END : Chunk ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;START : Chunk ~-------------------------------------------------------------------------------- .0x00000004;Length .0x67414D41;Type ~-------------------------------------------------------------------------------- ;START ARRAY : Data OF byte [4] ~-------------------------------------------------------------------------------- .0x00,0x00,0xB1,0x8F ~-------------------------------------------------------------------------------- ;END ARRAY : Data ~-------------------------------------------------------------------------------- .0x0BFC6105;CRC ~-------------------------------------------------------------------------------- ;END : Chunk ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;START : Chunk ~-------------------------------------------------------------------------------- .0x00000006;Length .0x624B4744;Type ~-------------------------------------------------------------------------------- ;START ARRAY : Data OF byte [6] ~-------------------------------------------------------------------------------- .0x00,0xFF,0x00,0xFF,0x00,0xFF ~-------------------------------------------------------------------------------- ;END ARRAY : Data ~-------------------------------------------------------------------------------- .0xA0BDA793;CRC ~-------------------------------------------------------------------------------- ;END : Chunk ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;START : Chunk ~-------------------------------------------------------------------------------- .0x00000009;Length .0x70485973;Type ~-------------------------------------------------------------------------------- ;START ARRAY : Data OF byte [9] ~-------------------------------------------------------------------------------- .0x00,0x00,0x0B,0x13,0x00,0x00,0x0B,0x13,0x01 ~-------------------------------------------------------------------------------- ;END ARRAY : Data ~-------------------------------------------------------------------------------- .0x009A9C18;CRC ~-------------------------------------------------------------------------------- ;END : Chunk ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;START : Chunk ~-------------------------------------------------------------------------------- .0x00000007;Length .0x74494D45;Type ~-------------------------------------------------------------------------------- ;START ARRAY : Data OF byte [7] ~-------------------------------------------------------------------------------- .0x07,0xDE,0x07,0x01,0x08,0x31,0x03 ~-------------------------------------------------------------------------------- ;END ARRAY : Data ~-------------------------------------------------------------------------------- .0xBF3B6B3A;CRC ~-------------------------------------------------------------------------------- ;END : Chunk ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;START : Chunk ~-------------------------------------------------------------------------------- .0x00000019;Length .0x74455874;Type ~-------------------------------------------------------------------------------- ;START ARRAY : Data OF byte [25] ~-------------------------------------------------------------------------------- .0x43,0x6F,0x6D,0x6D,0x65,0x6E,0x74,0x00,0x43,0x72,0x65,0x61,0x74,0x65,0x64,0x20 .0x77,0x69,0x74,0x68,0x20,0x47,0x49,0x4D,0x50 ~-------------------------------------------------------------------------------- ;END ARRAY : Data ~-------------------------------------------------------------------------------- .0x57810E17;CRC ~-------------------------------------------------------------------------------- ;END : Chunk ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;START : Chunk ~-------------------------------------------------------------------------------- .0x00000E5F;Length .0x49444154;Type ~-------------------------------------------------------------------------------- ;START ARRAY : Data OF byte [3679] ~-------------------------------------------------------------------------------- .0x78,0xDA,0xED,0x9D,0xBB,0x6E,0x23,0xE9,0x11,0x85,0xEB,0xEF,0x1B,0x2F,0xD2,0xCE .0xCC,0xEE,0x18,0x7E,0x02,0x33,0xB2,0xA1,0xB8,0x33,0x01,0x06,0x1C,0x6C,0x60,0xC0 .0x06,0x9C,0xFA,0x11,0xF6,0x11,0xFC,0x00,0x4E,0x9C,0x38,0x77,0xB2,0x99,0xDF,0xC0 .0x21,0x1D,0x35,0x60,0x47,0x0C,0x4D,0xA7,0x8E,0xC6,0xEB,0xDD,0x1D,0x5D,0xC8,0x66 .0xDF,0x9C,0x54,0x49,0x1F,0x4B,0x2D,0x89,0x94,0x48,0x0D,0x67,0x86,0x0D,0x34,0x9A .0x43,0x52,0x64,0xF3,0xAF,0x53,0x55,0xA7,0x4E,0x55,0xF7,0x04,0x39,0xE0,0xED,0xCF .0xDF,0xFD,0xBE,0x93,0xE3,0xB6,0xD7,0x2D,0x1C,0xFA,0x09,0x1E,0x41,0xB0,0xDF,0x2D .0x3A,0x2E,0xC1,0x31,0x02,0xC8,0x31,0x0A,0x1C,0x01,0xB0,0xB3,0x6D,0x36,0x99,0x04 .0xF7,0xD9,0xFE,0x3B,0x3A,0x7D,0xAE,0x13,0x11,0x39,0x9B,0xCF,0xDB,0x23,0x08,0x3E .0x01,0x00,0xA8,0xE1,0x03,0xD2,0x4A,0xEC,0x40,0x10,0x00,0x80,0x4E,0x44,0x5A,0x77 .0xEC,0xCE,0xE6,0xF3,0xEE,0x08,0x82,0x8F,0x10,0x00,0xB3,0xC9,0x24,0x56,0xC3,0x47 .0x6A,0x78,0xFB,0x77,0xC0,0x91,0xC6,0x37,0xC3,0x37,0xFE,0xF8,0x18,0x08,0x8E,0x40 .0x38,0x20,0x00,0xA8,0xD7,0x47,0x22,0x92,0xA8,0xD1,0x53,0x00,0x20,0x71,0x40,0xF0 .0xDE,0xDF,0x8A,0x48,0x8D,0xBD,0xD2,0x63,0xBB,0x69,0x5A,0x38,0xF2,0x8B,0x0F,0x08 .0x80,0xD9,0x64,0x62,0x86,0x4F,0xF5,0x98,0xA9,0xC1,0x33,0x3C,0x6F,0x40,0xE8,0xF4 .0x31,0x3D,0xDF,0x0C,0xBE,0xD2,0xC7,0x25,0x9E,0xAB,0x5F,0x12,0x04,0x87,0xB6,0xF5 .0xF0,0x28,0xF2,0x27,0x39,0x9B,0xCF,0xBB,0x5D,0x01,0x37,0x3C,0xF1,0x04,0xCD,0xB0 .0x66,0xF8,0x4C,0x44,0x06,0x7A,0x1C,0xA9,0xF1,0x53,0x44,0x80,0x04,0x3F,0xC0,0x3C .0xDF,0x40,0xB0,0x50,0xE3,0x2F,0x75,0x2F,0xED,0xF5,0xB3,0xF9,0xBC,0xF9,0x0C,0x0D .0x4F,0x1E,0x15,0x9C,0xAD,0xCC,0x91,0x8C,0x37,0xD5,0xD3,0x22,0x6F,0x5F,0x0C,0x00 .0xEA,0xF5,0xF4,0xFC,0x81,0xEE,0x43,0xDD,0x4F,0xF4,0xB5,0xA1,0x1E,0x23,0x7D,0x1F .0x23,0x40,0xA3,0x47,0x7A,0xFE,0x35,0xF6,0x05,0x40,0xF0,0x62,0x91,0x00,0x8B,0x7F .0xDF,0xBA,0x74,0xF4,0xC0,0x1D,0x7F,0xB7,0xA5,0xC9,0x18,0x47,0x71,0xBC,0x8A,0xE7 .0x45,0xEE,0x54,0x8B,0x48,0x3B,0x2D,0xF2,0x6A,0xAF,0x00,0x50,0xAF,0x8F,0x9D,0xF1 .0x47,0xBA,0x8F,0x01,0x82,0xA1,0xBE,0x6F,0xE0,0xF2,0xBF,0x5F,0x48,0xCB,0xFD,0xE6 .0xF5,0x57,0x22,0xF2,0xA3,0x03,0x41,0xB9,0xAF,0x28,0x80,0x30,0x1B,0xB9,0xCA,0x85 .0x5E,0x16,0x99,0xD1,0xF5,0xDF,0x02,0x00,0x6F,0x54,0xB9,0x6C,0x41,0xA0,0xFB,0xB8 .0x53,0x82,0x7F,0x1B,0x38,0x68,0xF8,0x52,0xF7,0x46,0x44,0xAA,0xB3,0xF9,0xBC,0xDE .0x96,0xDB,0x84,0x2D,0x10,0x1A,0xC3,0xBB,0x07,0x6A,0xF4,0x91,0x7A,0xFD,0x48,0x9F .0xCF,0x00,0x80,0xC4,0x79,0x54,0xA7,0x3F,0xCA,0x16,0x76,0xA5,0x8F,0x97,0x1A,0x09 .0x2E,0x75,0xFF,0x51,0xC1,0x70,0x6D,0xBC,0x60,0x97,0x51,0x00,0x86,0xE7,0xC2,0x07 .0x3C,0x8E,0x60,0xF8,0xE0,0x8C,0xDE,0xC2,0x00,0xF6,0x5C,0xF3,0x14,0x10,0x20,0xE2 .0x24,0xD8,0x53,0x5D,0x43,0xF2,0xAA,0x14,0x51,0xC1,0xCE,0xAB,0xD2,0xFD,0x1A,0x0E .0xB4,0x7A,0x0A,0x08,0xC2,0x43,0x6F,0xFA,0xE6,0xED,0xB7,0x01,0xC6,0x4F,0x91,0xE3 .0x87,0x22,0xF2,0x85,0xF3,0xFE,0x31,0x08,0x20,0x17,0xB0,0x85,0x0E,0xD0,0xC9,0xBA .0xFC,0xDC,0xE1,0xE4,0xCD,0xE8,0x3F,0xEA,0x7E,0xA9,0x91,0xA0,0xD2,0x54,0xD0,0xED .0x28,0xD4,0x7A,0x6F,0x8B,0xF4,0xBC,0x03,0x16,0x3B,0xC2,0xB9,0xFA,0xB2,0xD5,0xF8 .0xCB,0x0A,0x8F,0x9B,0x6D,0x40,0xAA,0xC6,0xA7,0xB7,0x0F,0xE0,0x3C,0xA9,0x1E,0x47 .0xFA,0xFA,0x00,0x7C,0xCA,0x36,0x5B,0xAF,0xA5,0x88,0x5C,0xE8,0xBA,0x5D,0x01,0x04 .0xCD,0xA6,0x20,0x48,0x1E,0x2B,0x91,0xA6,0x22,0x72,0x9E,0x17,0x3C,0x99,0xA1,0xF3 .0xFE,0xB1,0x3B,0xC9,0x18,0xC6,0xAD,0x7A,0x40,0x20,0x8E,0x14,0x0E,0xB1,0xD0,0x16 .0x11,0x46,0xFA,0xB7,0x8D,0xF3,0xBA,0xE7,0x78,0x7D,0x8C,0x3D,0x75,0x15,0x4B,0xE2 .0xC2,0x6D,0xE4,0x80,0x60,0xBF,0xA5,0x05,0x3F,0x89,0xC1,0x63,0xC2,0x6C,0x32,0xD9 .0x86,0xAF,0x10,0x00,0xE4,0x50,0xB6,0x96,0xA7,0xFA,0xDA,0x08,0x04,0x3B,0x45,0x3A .0x2A,0xF5,0xB5,0x4B,0xAC,0x6F,0x0B,0x8E,0xB5,0xF1,0x5A,0x6D,0xD4,0x0C,0x9A,0x16 .0xF9,0x02,0x27,0x62,0x27,0x3A,0x76,0x8C,0xDF,0xBC,0xA7,0x41,0x5D,0x4F,0x92,0x77 .0x85,0x70,0xB5,0x40,0x2A,0xF0,0xA4,0x72,0x0C,0x8F,0xB8,0x11,0x95,0x10,0xBA,0x9F .0x5A,0xAE,0x26,0xF0,0xAE,0xB1,0x46,0xB0,0x53,0x11,0x79,0x25,0x22,0x5F,0x8A,0xC8 .0x57,0x22,0xF2,0x46,0x1F,0xDB,0xFE,0x46,0x44,0x5E,0xEB,0xFE,0xA5,0x1E,0xED,0xB9 .0x53,0xDD,0x2D,0xF5,0xA5,0x9A,0xCF,0x37,0x39,0x9F,0x14,0x1E,0x6F,0x4E,0xF4,0x5A .0xCF,0xC5,0x3E,0xFB,0x0D,0xCE,0xCD,0x5E,0xFB,0xAA,0xE7,0xBC,0x4E,0xF5,0xEF,0x6F .0x2A,0xAF,0x4D,0xCE,0x63,0xA3,0x08,0xE0,0x40,0xF0,0xEE,0x3C,0x2F,0x7E,0x06,0x0E .0x40,0x0D,0x20,0x80,0x24,0x95,0x08,0xED,0x15,0x10,0x69,0x1E,0x65,0x20,0xA2,0xF7 .0xA7,0x2E,0x02,0x0C,0xB0,0x48,0xA5,0x4B,0x21,0xCF,0xC9,0xB1,0xF4,0xB6,0x01,0xF6 .0xA4,0xA7,0x74,0x0D,0xAE,0x74,0xED,0xA0,0x53,0x2C,0xF4,0x6F,0xAE,0xF0,0xDE,0x20 .0x22,0xDD,0x6C,0x32,0x91,0xFB,0x88,0x2B,0x84,0xB3,0x18,0x60,0x1C,0xA8,0x21,0x4F .0x10,0xFA,0xED,0x38,0x70,0xEB,0x6D,0xEB,0x1C,0x83,0x98,0x2E,0xF5,0x7C,0xAE,0xB6 .0xB1,0xE7,0x56,0x11,0x00,0x20,0xF8,0x77,0x4F,0x78,0x14,0xE4,0xC4,0x05,0xF2,0xD3 .0x85,0xEE,0xFF,0xD3,0x9C,0x6E,0xC7,0x0B,0x0D,0x5D,0x2B,0x84,0xD5,0x16,0xE7,0x43 .0x32,0x14,0xF7,0x18,0x64,0xDB,0x1C,0x6B,0x0B,0x7D,0xAA,0x8B,0x7C,0x02,0xCF,0x7F .0x05,0x4F,0x7A,0xAD,0x8B,0xFE,0x05,0xDE,0x3B,0xC6,0xE3,0x53,0x7D,0xED,0xC4,0xFD .0xAD,0x79,0xF1,0x4D,0xD4,0x52,0x2F,0xEF,0xDB,0x62,0xA4,0x1E,0x03,0xDF,0x2B,0x44 .0x54,0xEE,0x19,0x76,0x9F,0xA6,0x32,0x47,0x16,0x63,0x18,0x3F,0xDE,0x1B,0x00,0x14 .0x04,0xFF,0x04,0xFA,0xCC,0xF0,0x86,0xC2,0x6B,0x35,0xF0,0x0F,0x30,0xFA,0x7B,0x11 .0xF9,0x1E,0x2C,0xFF,0xBD,0xBE,0x97,0xA2,0x4F,0x04,0x23,0x27,0x00,0x58,0x22,0x4F .0x9B,0x59,0x60,0xC5,0x32,0x76,0x61,0xF6,0xB5,0x0B,0xE1,0x23,0x2C,0x38,0x53,0xDA .0xD0,0x91,0xB3,0x41,0x4F,0xF5,0x73,0xA2,0xE1,0x78,0xE4,0xF2,0x75,0xE2,0x41,0xE0 .0x6A,0x7D,0x23,0x7E,0x27,0x3D,0x1E,0x3F,0x80,0x51,0xC5,0x55,0x1F,0x1D,0x88,0x27 .0x23,0xD3,0x93,0xB7,0xE4,0x29,0x7F,0x34,0x2D,0xF2,0xBF,0x9F,0xE7,0xC5,0xB9,0xAC .0xB7,0x76,0x3D,0x18,0x96,0x28,0xF1,0x5A,0xA0,0xB5,0x71,0xDE,0x99,0xA0,0xEE,0xB6 .0x92,0x2C,0xC3,0xB9,0x45,0x9B,0x80,0x00,0x3A,0x05,0x49,0xDE,0x08,0xDE,0x72,0x0A .0x43,0x0D,0x90,0x66,0x22,0x17,0x65,0x58,0xF7,0xF3,0x9C,0x3A,0xFD,0x9C,0xB6,0x47 .0x9C,0x61,0x93,0xCB,0xF8,0x8D,0xCC,0x26,0x93,0xCA,0x55,0x2F,0x0C,0xFD,0x29,0x00 .0xC3,0xF3,0x14,0xA4,0x4C,0x33,0x70,0xE2,0x88,0x73,0x89,0x68,0x5B,0x22,0x9A,0x76 .0xDB,0x92,0xE5,0xE4,0x19,0xE0,0xB9,0xD6,0x2F,0x26,0xF1,0xAB,0x51,0xBF,0x2F,0x71 .0x62,0x35,0x7E,0x6C,0x0C,0xC2,0x67,0x3F,0xBA,0x41,0x88,0xE7,0xC2,0x6E,0x62,0xF8 .0x04,0x91,0x82,0xDC,0x21,0x81,0xF7,0xFA,0xDC,0xCA,0x70,0xEA,0x73,0x7D,0x83,0xEF .0x6F,0x1D,0x7F,0x09,0x58,0x33,0x96,0xB5,0x01,0xCE,0xE0,0x3D,0xDF,0xAA,0x83,0xC8 .0x55,0x22,0x19,0x08,0x64,0xE6,0xA2,0x5D,0x8D,0xB5,0x8D,0x60,0x2B,0xD3,0x51,0x4C .0x42,0xBF,0xD0,0xE3,0x02,0xC4,0x5B,0x5E,0x04,0x00,0xD3,0x22,0xFF,0xC7,0x79,0x5E .0xFC,0x1C,0x4F,0xD5,0x30,0xBA,0x45,0x82,0x1B,0x4D,0x7F,0x36,0x99,0xD8,0x02,0xAD .0x00,0x8C,0x06,0xA5,0x1E,0x39,0x45,0xC0,0x1E,0xB9,0x12,0xD2,0x1B,0x9F,0xB9,0x72 .0x84,0xC5,0xF4,0x9E,0x35,0x02,0x48,0x82,0x23,0x9E,0x0D,0xEA,0xEB,0x16,0x69,0xA9 .0x73,0x7A,0x41,0xE2,0x22,0xC0,0xA0,0x47,0xE1,0xF4,0x21,0xBB,0x43,0xF8,0x8F,0x1C .0x30,0x53,0x38,0x85,0x01,0xDF,0x22,0xA6,0xE9,0x0C,0xE4,0x47,0x1D,0xCA,0xCF,0xA5 .0x03,0xC1,0xCA,0x1C,0x71,0x1B,0xF5,0xF4,0x39,0x11,0x40,0x34,0xD7,0x13,0xB5,0x15 .0x3B,0x7A,0xAE,0xA1,0xD3,0xA7,0xA2,0x71,0xC1,0x98,0xF3,0xE4,0x3E,0xA3,0xC3,0xF8 .0xA9,0xEB,0x41,0x64,0x4E,0x92,0x8E,0xF4,0xDF,0xC1,0x79,0x58,0xEC,0x6A,0x67,0x9E .0x73,0x85,0x1C,0xDB,0x02,0x7C,0x43,0x47,0x4E,0x09,0x88,0xEE,0x81,0x7C,0x6C,0xBF .0xA1,0x76,0xEC,0x9F,0x51,0x80,0x00,0x6F,0xE1,0x48,0x4C,0xA1,0x8D,0x4B,0x93,0x0D .0x9C,0xED,0xD2,0xBD,0xB7,0x7B,0xA9,0x14,0x20,0xD3,0x22,0xFF,0xCF,0x79,0x5E,0x7C .0xE5,0x3C,0xE9,0xA6,0x41,0xD1,0x23,0x8C,0xB4,0x30,0x32,0x81,0xE0,0x0D,0xCF,0xF7 .0xDE,0x67,0xFC,0x01,0x8C,0x6F,0x2C,0xFD,0xB5,0x03,0x40,0xEC,0x16,0x38,0xB8,0x4E .0x64,0x8D,0x72,0xB5,0x44,0x3E,0x6D,0x21,0xF6,0x04,0xA4,0x0E,0xE3,0x00,0x24,0x6A .0x29,0xD6,0xB2,0xED,0xE9,0xDE,0x05,0x84,0x73,0x46,0xA7,0xCC,0x89,0x4F,0x2D,0x40 .0x68,0xBA,0xC9,0xC2,0x81,0xB2,0x73,0xA9,0x69,0xE9,0x22,0x6A,0xB5,0x6D,0xEF,0xE4 .0xB9,0x11,0x40,0xF4,0x24,0x49,0x58,0x1E,0x6A,0xE3,0xFA,0x3C,0xEF,0x65,0xE1,0x16 .0x1E,0x15,0xFA,0x40,0xE0,0x84,0x14,0x2B,0xCB,0x4C,0xA8,0x39,0x05,0x63,0x4F,0x1D .0x29,0x62,0x8E,0x2C,0x51,0xCF,0x97,0xD0,0xD4,0x59,0x9A,0x36,0x90,0x89,0x8D,0xD3 .0x9C,0x38,0x05,0x93,0xC4,0x2E,0xE8,0xEB,0xB1,0x6B,0xE7,0x5A,0xAA,0x68,0x5D,0xD8 .0xF7,0x79,0xBF,0x41,0x5D,0xBF,0x72,0xFD,0x90,0x95,0x8B,0x30,0x2D,0x52,0x96,0xA5 .0x89,0xEA,0x29,0x8D,0xB3,0x5D,0x00,0xA0,0x76,0x44,0xCA,0x18,0x79,0xE7,0x58,0x35 .0xC3,0xAF,0xF4,0xB4,0x38,0xFB,0x22,0xC0,0x1A,0xC3,0x56,0xE3,0x5B,0xFD,0x6B,0x61 .0xFF,0x8D,0x88,0xFC,0x04,0xE5,0xD8,0x18,0xE2,0x52,0x0B,0x62,0x6A,0x9F,0x63,0x0B .0xCC,0xF6,0x33,0x17,0x7A,0xE5,0xD2,0x52,0xA4,0x86,0xAB,0x5C,0x83,0xA8,0xD3,0xEF .0x8B,0x41,0x0E,0x23,0x07,0xEC,0xC8,0xFD,0x06,0x71,0x25,0x65,0x82,0xF7,0xB4,0x3D .0xD1,0xC8,0xB4,0xFE,0x06,0xB2,0xBA,0xB8,0xA8,0x59,0x6B,0x57,0xF2,0x49,0x52,0xF9 .0x2E,0x00,0xC0,0x5C,0x19,0x3B,0xD5,0x2E,0xC0,0x03,0xE3,0x1E,0x41,0x23,0xEA,0x11 .0x79,0x9A,0x7B,0x1E,0x73,0x20,0xC2,0x72,0xBD,0x09,0x32,0x5F,0xA0,0x39,0x35,0x40 .0x38,0x2E,0x91,0xAB,0xD9,0x39,0xB3,0x46,0xD3,0x25,0x8C,0x5F,0x22,0x32,0x58,0x04 .0xB0,0x32,0xB1,0x86,0xC7,0x33,0x52,0x19,0x88,0x53,0xFC,0x8E,0x4C,0x5F,0x1B,0xE3 .0xB5,0x15,0x00,0x3D,0x82,0x70,0x14,0xB9,0xF0,0x4F,0x19,0x7D,0xC5,0x10,0x7F,0x36 .0x9F,0x57,0x10,0xB8,0xE4,0x39,0x46,0xDF,0x29,0x00,0xA6,0x45,0xDE,0x9C,0xE7,0x45 .0xEA,0xBA,0x5B,0xB1,0x23,0x23,0xB6,0x58,0x43,0xC7,0xCC,0x87,0x30,0x50,0x03,0x23 .0x57,0x8E,0x2F,0xB4,0x38,0xDF,0xA4,0x47,0x47,0x1F,0x41,0xA4,0xC9,0x5C,0x48,0xBD .0x82,0x61,0x2D,0xB7,0x5E,0x02,0x04,0x4B,0xB0,0x68,0x33,0x7E,0xAB,0xF5,0x7B,0xAD .0xD1,0xAC,0xED,0x49,0x4F,0xFC,0x6D,0x6C,0xD7,0x66,0x4E,0xDE,0x1E,0xEA,0x77,0x08 .0x9E,0x67,0x04,0x60,0x05,0xD1,0xC0,0xF8,0x9E,0x90,0xDA,0x20,0x4A,0xBD,0xCB,0xB9 .0x88,0x64,0x47,0x9F,0x43,0x79,0x33,0xC1,0x8F,0x0F,0xC8,0xA5,0x31,0x98,0xBA,0x6F .0x24,0x75,0xCE,0x68,0x54,0xBC,0x1A,0xA7,0xA0,0x65,0x3D,0x75,0xFD,0x08,0x0B,0xCF .0xEA,0x62,0x05,0xB1,0xE4,0x42,0x6E,0x87,0x4E,0xDE,0x23,0x0D,0x2C,0xE1,0x75,0x7D .0xBD,0x7D,0x2B,0xC9,0xC4,0xE9,0x03,0x3E,0xC4,0x0F,0x7B,0x88,0xE1,0x09,0x72,0x7F .0x8B,0x54,0xC1,0x68,0xC8,0x31,0x39,0x0A,0x3B,0x56,0x49,0xED,0x75,0x68,0x75,0x97 .0x00,0x48,0x20,0x6C,0x0C,0x7A,0x4A,0x39,0x4E,0x11,0x0D,0x9D,0x9E,0xDD,0x3A,0x79 .0xB9,0x86,0x37,0x76,0xEE,0x3B,0xFC,0xB0,0x84,0x8F,0x3A,0xFC,0xFB,0xA5,0x7A,0xFA .0x0F,0xE8,0x41,0x50,0x3C,0x31,0x80,0x34,0x1C,0xA4,0xE0,0x76,0x36,0x9F,0x77,0xAA .0x61,0xD4,0x8E,0xA3,0x5C,0xF5,0xFC,0xC6,0xCC,0x85,0xF6,0x94,0x1E,0x0C,0x80,0x66 .0xAE,0x14,0x6D,0x60,0xFC,0xCA,0xE9,0x24,0xDD,0x3E,0x41,0xB0,0x2B,0x00,0x44,0x30 .0xEE,0x69,0xCF,0x6C,0xC0,0x00,0x47,0x8A,0x20,0xC6,0x03,0x1A,0x59,0x1F,0x13,0xAF .0x1C,0x08,0xD8,0x28,0x4A,0x5C,0x33,0x25,0xE0,0x7D,0x25,0x52,0x48,0x89,0x66,0xD4 .0x05,0x3C,0xDF,0x72,0xFF,0x4D,0xFD,0xFF,0x58,0x1F,0xFF,0x6C,0x3E,0x6F,0x67,0x93 .0x89,0xAF,0x64,0x3C,0x19,0x6B,0x1C,0xD8,0x53,0xB7,0xC6,0x91,0x93,0x96,0x7D,0x87 .0x71,0xD5,0x13,0x01,0xEC,0x5A,0x89,0xBD,0xCD,0x45,0xEE,0xE4,0xE2,0xD0,0x69,0x91 .0x5F,0xE8,0x0F,0x1E,0xA1,0xD3,0xF6,0x06,0x0C,0xFD,0x4B,0x11,0xF9,0xA9,0xF6,0xB3 .0xDF,0xEA,0xF3,0x27,0x48,0x03,0x41,0xEE,0x4E,0xDD,0xD0,0x03,0x22,0x80,0xCC,0x7B .0x99,0x91,0xB4,0x2B,0xB9,0x1D,0x2D,0xBB,0x10,0x91,0xEF,0x44,0xE4,0x9D,0x36,0xA2 .0xDE,0xC9,0xED,0xA4,0xD1,0x05,0x17,0x79,0xD3,0xC5,0xD5,0xF7,0xD5,0xB2,0x3E,0xE3 .0x60,0x6D,0xD8,0xF7,0xE0,0x15,0x4B,0x78,0x71,0xDB,0xD3,0xE0,0xEA,0x5C,0xEF,0xA4 .0x44,0x44,0x5A,0xC8,0xFA,0x50,0x6C,0xF3,0x90,0x1E,0x72,0x48,0x11,0x40,0x5C,0x7E .0x1E,0xC2,0xCB,0x47,0x72,0xF7,0x7A,0x81,0xE0,0x4A,0x1F,0xAB,0x7F,0x5B,0x88,0x33 .0x35,0xC8,0x20,0xCB,0xBF,0x21,0x3C,0x2C,0x82,0x27,0x95,0x00,0xC1,0xB5,0x33,0xF8 .0x95,0xAC,0x0F,0xA4,0xD8,0xA2,0x06,0x94,0xAC,0xC6,0xAC,0xBB,0x07,0x40,0xD0,0xCD .0x26,0x93,0x06,0x86,0x5D,0x80,0xBB,0x78,0x45,0x30,0x73,0xC4,0xB1,0xC5,0xF9,0xB6 .0x4E,0x88,0x5A,0xA0,0x2C,0xA5,0x32,0xD9,0x3C,0x67,0x14,0x6E,0x93,0x99,0xC0,0x64 .0xC7,0xD1,0x24,0x75,0xB9,0x3E,0x73,0x40,0xF0,0xCA,0x57,0x05,0xB2,0x16,0xE1,0x71 .0x0D,0x0F,0x10,0x47,0x22,0x87,0x0E,0x48,0x0C,0xFD,0x46,0xEC,0x2E,0xD5,0xF3,0x17 .0x00,0xC0,0xA2,0xA7,0x96,0xF7,0xD1,0xA7,0x9B,0x4D,0x26,0xAC,0x02,0xEE,0x4B,0x07 .0xC1,0x11,0xB4,0xCE,0x85,0xF5,0xD6,0x35,0xBB,0x22,0xA7,0x14,0x1A,0xDB,0x37,0x09 .0xD7,0x2A,0x13,0x82,0xB8,0x79,0x2C,0xF7,0xEF,0xE2,0xE2,0x90,0x5D,0x02,0x80,0x25 .0x52,0x82,0xB2,0x6F,0x88,0xBA,0x9D,0xA1,0xD0,0x72,0x66,0xA5,0x8B,0xF5,0x1E,0xDE .0xD1,0x3A,0x01,0x89,0x79,0xDF,0x16,0x35,0x76,0x2A,0x9F,0xD5,0xCC,0x36,0x8F,0x70 .0x29,0xB7,0x83,0x29,0x15,0xE4,0xD8,0xD8,0xF5,0x05,0xA4,0x47,0xCA,0x6E,0x66,0x93 .0xC9,0xBD,0xB9,0x57,0x23,0x41,0xD5,0xE3,0xE1,0x4C,0x13,0x56,0x9A,0x2E,0x65,0x7D .0xD8,0xB4,0x75,0xF2,0xED,0x12,0xD5,0x08,0x23,0xC0,0x9D,0xEF,0xDF,0xC7,0x65,0x6C .0xBB,0x04,0x80,0x6F,0xF6,0xD0,0xCB,0x38,0x92,0x95,0xC8,0xFA,0x05,0xA1,0x89,0xAC .0x4F,0x0B,0x47,0xAE,0xCD,0xEA,0xAF,0x3E,0x1A,0xBA,0x5E,0xFE,0x0A,0x8B,0x76,0xE9 .0xEA,0xFC,0x6B,0x59,0x1F,0xE2,0xF4,0x97,0xB1,0x71,0x7A,0xA6,0x82,0x67,0x56,0x22 .0x52,0x3D,0x34,0xE8,0x89,0x74,0x10,0x9C,0x54,0xDB,0x20,0xBF,0x33,0x02,0x44,0x58 .0xA7,0x1A,0xC4,0x6F,0x09,0xD5,0x8F,0x63,0x74,0xED,0x3E,0x0D,0xBF,0x0F,0x00,0xAC .0x10,0xD2,0x06,0xFA,0xEF,0x31,0x7E,0x3C,0xE7,0xED,0xBC,0x54,0xEA,0x3D,0xD2,0x8C .0x3B,0x42,0x33,0x66,0x80,0x28,0x42,0x09,0xB5,0x81,0xAE,0x7F,0xAD,0x91,0x64,0x89 .0x50,0xDA,0xC9,0xFA,0xF0,0x05,0x39,0x04,0x35,0x88,0x0A,0xE4,0xB0,0x04,0x47,0xB8 .0xB7,0xBD,0x0A,0x10,0x34,0x3D,0xDD,0xC5,0x12,0x40,0x0D,0xAE,0xE6,0x6F,0xD0,0xBE .0x5D,0x2B,0xFD,0xA6,0x45,0xBE,0xBC,0x31,0xBC,0xE4,0xB2,0xEF,0x6D,0x97,0x00,0xE0 .0x58,0x98,0x19,0xCC,0x16,0x23,0x95,0xF5,0xE1,0x0A,0xDF,0xE8,0xE9,0x20,0xA3,0xD2 .0x58,0xE6,0x51,0xA6,0xFA,0x0D,0x9D,0x86,0xDE,0xF5,0xE4,0x54,0x9B,0x4D,0xA4,0x7A .0x38,0x00,0x2F,0x19,0x43,0x40,0x0A,0x8E,0x7B,0x18,0xAB,0x8F,0xD1,0x37,0x68,0x67 .0x93,0x49,0xF7,0x40,0x24,0x68,0xB5,0x47,0xE1,0x89,0xA0,0x11,0xBC,0x0C,0xE7,0xD1 .0x21,0x65,0x11,0x04,0xF5,0xB4,0xC8,0xAF,0xE4,0x03,0x6C,0xBB,0x8E,0x00,0xD7,0xBA .0xB0,0x0B,0x5D,0x64,0xCB,0x75,0x23,0xE4,0xE0,0xCE,0x89,0x3E,0xF6,0x5C,0x02,0x6F .0x5F,0xB9,0x86,0x10,0x95,0xBF,0xD4,0x85,0x53,0x76,0xF3,0x38,0x67,0xD8,0x81,0x3F .0x8C,0x51,0xA2,0xDA,0xA0,0x67,0x86,0x5A,0xBD,0x41,0x29,0x99,0x6A,0xF5,0x40,0x55 .0xB2,0x53,0x10,0x3C,0x44,0x0C,0xBB,0x1E,0x00,0x44,0x72,0x77,0xB2,0xE7,0xE6,0xFA .0xC8,0x69,0x91,0xBF,0x97,0x0F,0xBC,0xED,0x12,0x00,0x15,0x22,0xC0,0xD8,0xA1,0xBC .0x44,0x78,0x4F,0x9C,0xDC,0x5B,0x23,0x02,0x58,0xB5,0xD0,0x21,0x82,0x18,0xA1,0x3C .0x41,0x08,0xF7,0xD7,0xEC,0xD5,0x4E,0xD9,0x63,0x1A,0x31,0xE3,0x73,0xDA,0xF7,0x04 .0xE1,0x59,0x70,0x9E,0x7E,0x58,0x84,0xBC,0xE6,0xC1,0x79,0x3B,0x05,0x87,0x4D,0x3E .0x35,0xB2,0x3E,0x87,0x10,0x39,0xDD,0x64,0x21,0x07,0xB2,0xED,0x12,0x00,0x8D,0x53 .0xB3,0x16,0xC8,0x83,0x06,0x8E,0x46,0xD6,0xAF,0x02,0x5A,0xC1,0xD3,0x38,0x30,0x19 .0x40,0x9E,0x04,0xDE,0x3A,0x72,0xED,0xD3,0xC6,0xED,0xAD,0x0B,0xB5,0x03,0x78,0xFC .0x2B,0x00,0x60,0xE4,0x5A,0xB9,0xA6,0x43,0xC4,0xEE,0xB7,0xF0,0x06,0x16,0xCD,0x6C .0x32,0x09,0x8F,0xD5,0xE5,0x96,0x2A,0x14,0x08,0x5E,0x39,0x94,0x69,0x91,0x1F,0xD4 .0x25,0xEF,0xBB,0x2E,0x03,0x1B,0x10,0x20,0xCE,0xB5,0x95,0xF0,0xD4,0xE0,0x3C,0xB7 .0x41,0x34,0xE0,0xBC,0x5E,0x0B,0x00,0x0C,0x7A,0xA4,0xE3,0x12,0x0B,0xCC,0xC5,0x66 .0x37,0x2E,0x93,0xDB,0x8B,0x2E,0x38,0xDF,0x3F,0x74,0xEC,0xBC,0x01,0x70,0xD6,0x2A .0x01,0x10,0x34,0xDF,0x9B,0x78,0x4C,0x39,0xF4,0x93,0x4E,0x07,0x79,0x37,0x92,0x9D .0x01,0x60,0x5A,0xE4,0xB5,0x5E,0x43,0x58,0xCB,0xDD,0x9B,0x40,0xD8,0x02,0x66,0x78 .0xAD,0x43,0x8E,0xAF,0xE1,0x89,0x19,0x2A,0x86,0x80,0x90,0xCF,0xB9,0x81,0x1A,0x3A .0x3A,0xAF,0xB1,0x63,0xD3,0x26,0x81,0xF7,0x53,0x9D,0xCC,0x5C,0x03,0x89,0x1D,0xBD .0x13,0xD4,0xF0,0x4B,0xFD,0xFB,0x15,0x22,0xD5,0x93,0xA5,0xD9,0x43,0xBD,0x15,0x4D .0xB2,0xE3,0xCF,0xE3,0x0D,0x20,0x82,0xEB,0xCE,0xC5,0xCA,0x0F,0x78,0x85,0x2D,0x27 .0x5C,0xD8,0xEC,0x49,0xA0,0xF7,0x0F,0x64,0xFD,0x12,0xEE,0x06,0x11,0xA5,0x84,0xA7 .0x57,0x00,0x93,0x29,0x85,0x27,0xCE,0xF8,0x1C,0xE2,0xAC,0xD1,0x47,0x60,0x58,0x1E .0xA2,0x84,0x5D,0xE2,0xF3,0x6B,0xD5,0x06,0xB6,0xBE,0x87,0xD1,0x21,0xDF,0x87,0x28 .0xD9,0xD3,0xE7,0xFA,0xEE,0x18,0x3D,0x87,0x2A,0x18,0x85,0x18,0xB6,0x76,0xAD,0xD4 .0x33,0xF9,0x78,0x88,0x10,0x5F,0x82,0x47,0x58,0x7E,0x1E,0x83,0x71,0x0B,0xD2,0xC6 .0x00,0xBC,0x22,0xC1,0xB9,0x94,0x2E,0xCD,0x90,0xBD,0x27,0xE0,0x1B,0x63,0x68,0x03 .0x4B,0x10,0xD8,0x4F,0xE6,0xFE,0x45,0xFB,0x88,0x00,0x7D,0xF2,0xB0,0x38,0xCF,0x6B .0x9D,0xE2,0x95,0xC9,0xDD,0xBE,0xFF,0x18,0x00,0xC8,0x9C,0x76,0x10,0x50,0x5E,0x36 .0x28,0x23,0x97,0x72,0x77,0xFC,0x3A,0xE9,0x21,0x8E,0x2D,0xFA,0x0F,0x41,0xD6,0xE7 .0x17,0x1A,0x59,0xBF,0x7C,0x9C,0x2A,0x64,0xA5,0x64,0x70,0xE3,0xFB,0x15,0x1C,0xFA .0x5D,0xC8,0x76,0x7A,0xAF,0xE0,0x69,0x91,0x97,0x0E,0x08,0xD4,0xF5,0x79,0x4B,0x96 .0x16,0xB9,0xBC,0x02,0x38,0x62,0x59,0xBF,0x14,0x2A,0x95,0xBB,0xC3,0x23,0x3E,0x45 .0xF0,0x06,0x0B,0xA7,0x28,0x15,0x79,0x77,0x0D,0xB6,0x71,0x4D,0x22,0xFE,0x01,0x3D .0x03,0x4A,0xC6,0xBC,0x80,0x23,0x95,0xBB,0x17,0xAA,0x6E,0xBC,0x6E,0x1F,0xC3,0x2D .0xE8,0x76,0x7E,0xB3,0xE8,0x69,0x91,0xFF,0xD7,0x71,0x01,0x96,0x42,0xC1,0x35,0x8B .0x2C,0xC7,0xB7,0xF0,0x50,0x7A,0xAC,0xBF,0xDB,0xA8,0x1F,0x17,0xE7,0x3D,0x0A,0xFD .0xB5,0x86,0xC1,0x95,0x9C,0x26,0x13,0xDB,0xF1,0x7B,0xB9,0xBD,0x27,0xD1,0x05,0xCA .0xD7,0xDA,0x45,0x9A,0xBE,0x1B,0x5F,0x7E,0x32,0xDB,0x5E,0x7E,0xCC,0xB4,0xC8,0xFF .0xE5,0x74,0xF1,0x0A,0x44,0x8F,0xB7,0x9B,0x31,0x86,0x9F,0x39,0x63,0xDB,0xE2,0x57 .0x72,0xB7,0x7F,0xCE,0x46,0x4A,0xDD,0x23,0xC1,0x52,0xD9,0xE3,0x78,0x35,0x6F,0x41 .0x73,0xA1,0x20,0xB0,0x89,0x21,0x1B,0x22,0xE1,0x34,0x4E,0xE7,0x00,0x1C,0x6D,0xB3 .0x5E,0x1F,0xCB,0x0D,0x28,0xF7,0x45,0x02,0x65,0x5A,0xE4,0xC5,0x79,0x5E,0xFC,0x12 .0x79,0xD7,0x18,0x38,0x47,0xA8,0x1B,0x59,0x6F,0x96,0x98,0xB7,0x79,0x15,0xAE,0x42 .0xDE,0xBF,0x99,0xA0,0x99,0x16,0xF9,0x9F,0x9E,0x7A,0x7E,0xE7,0x79,0xF1,0x0B,0xFD .0xFC,0xB1,0xE3,0x06,0x03,0xA7,0x07,0x70,0x32,0xE7,0x93,0xBB,0x79,0x65,0x78,0x09 .0xA4,0x9E,0xE7,0xC5,0x6F,0xE5,0xF6,0x36,0x26,0xA1,0x47,0x50,0x09,0xB2,0x7E,0x43 .0x24,0x5E,0x3E,0x1D,0x14,0x50,0x7F,0x78,0x89,0x05,0x39,0xCF,0x8B,0x5F,0xA3,0x3C .0xB4,0xF9,0x41,0x8B,0x10,0x37,0x53,0x3B,0x0F,0xCD,0xE4,0x7F,0x4C,0xB7,0x9F,0x0D .0x2F,0x75,0xC2,0xE7,0x79,0xF1,0x3B,0xB9,0x7B,0x39,0x14,0x73,0x3B,0x4B,0xC0,0x9B .0x3B,0x77,0x4D,0x8B,0xFC,0x8F,0x87,0xBC,0x80,0xDF,0xBC,0xFD,0x36,0x1C,0x01,0xB0 .0x39,0x08,0x7E,0x23,0xEB,0x13,0xB3,0xBC,0xE2,0x35,0x12,0x91,0x68,0x5A,0xE4,0x7F .0xF9,0x18,0x43,0xA9,0x01,0xE1,0x43,0x1B,0xBF,0x0F,0x90,0x8F,0x02,0xE0,0x85,0x41 .0xF0,0xB5,0xDC,0x36,0x7C,0x6E,0x3A,0x66,0xD3,0x22,0xFF,0xAB,0x1C,0xB7,0x17,0x31 .0xFA,0x07,0x05,0x00,0x80,0xF0,0x2B,0xF5,0xF8,0xBF,0x1D,0x4D,0xF8,0xF2,0x86,0xBF .0x03,0x80,0x8F,0x2D,0x77,0x1D,0x8D,0x7D,0xE0,0x65,0xE0,0x71,0x3B,0x6C,0xC3,0xF7 .0x46,0x80,0x63,0x14,0xF8,0xBC,0x8C,0x2F,0xD2,0xA3,0x6C,0xBD,0xC4,0x97,0x1E,0xB7 .0xC3,0xD9,0x8E,0xFF,0x71,0xE4,0x67,0xEC,0xFD,0xF7,0x02,0xE0,0x18,0x05,0x3E,0x9F .0xED,0x41,0x43,0x1F,0xF9,0xC0,0xA7,0xED,0xFD,0x8F,0xA6,0x80,0x63,0x24,0xF8,0xCC .0x23,0xC0,0x31,0x12,0x7C,0xDA,0xDE,0x2F,0x22,0xF2,0x7F,0x28,0x30,0x65,0xE2 ~-------------------------------------------------------------------------------- ;END ARRAY : Data ~-------------------------------------------------------------------------------- .0xE5A387DC;CRC ~-------------------------------------------------------------------------------- ;END : Chunk ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;START : Chunk ~-------------------------------------------------------------------------------- .0x00000000;Length .0x49454E44;Type ~-------------------------------------------------------------------------------- ;START ARRAY : Data OF byte [0] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;END ARRAY : Data ~-------------------------------------------------------------------------------- .0xAE426082;CRC ~-------------------------------------------------------------------------------- ;END : Chunk ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;END ARRAY : Chunks ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;END : Png ~-------------------------------------------------------------------------------- testwriterbin2b.txt000066400000000000000000000754371445000440600341730ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~-------------------------------------------------------------------------------- ; Start {} ~-------------------------------------------------------------------------------- .0x89504E470D0A1A0A; long header ~-------------------------------------------------------------------------------- ; Start {} [8] chunks ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; Start {} chunks [0] ~-------------------------------------------------------------------------------- .0x0000000D; int chunks.length .0x49484452; int chunks.type ~-------------------------------------------------------------------------------- ; Start byte [13] chunks.data ~-------------------------------------------------------------------------------- .0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x40,0x08,0x06,0x00,0x00,0x00 ~-------------------------------------------------------------------------------- ; End byte [13] chunks.data ~-------------------------------------------------------------------------------- .0xD2D67F7F; int chunks.crc ~-------------------------------------------------------------------------------- ; End {} chunks [0] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; Start {} chunks [1] ~-------------------------------------------------------------------------------- .0x00000004; int chunks.length .0x67414D41; int chunks.type ~-------------------------------------------------------------------------------- ; Start byte [4] chunks.data ~-------------------------------------------------------------------------------- .0x00,0x00,0xB1,0x8F ~-------------------------------------------------------------------------------- ; End byte [4] chunks.data ~-------------------------------------------------------------------------------- .0x0BFC6105; int chunks.crc ~-------------------------------------------------------------------------------- ; End {} chunks [1] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; Start {} chunks [2] ~-------------------------------------------------------------------------------- .0x00000006; int chunks.length .0x624B4744; int chunks.type ~-------------------------------------------------------------------------------- ; Start byte [6] chunks.data ~-------------------------------------------------------------------------------- .0x00,0xFF,0x00,0xFF,0x00,0xFF ~-------------------------------------------------------------------------------- ; End byte [6] chunks.data ~-------------------------------------------------------------------------------- .0xA0BDA793; int chunks.crc ~-------------------------------------------------------------------------------- ; End {} chunks [2] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; Start {} chunks [3] ~-------------------------------------------------------------------------------- .0x00000009; int chunks.length .0x70485973; int chunks.type ~-------------------------------------------------------------------------------- ; Start byte [9] chunks.data ~-------------------------------------------------------------------------------- .0x00,0x00,0x0B,0x13,0x00,0x00,0x0B,0x13,0x01 ~-------------------------------------------------------------------------------- ; End byte [9] chunks.data ~-------------------------------------------------------------------------------- .0x009A9C18; int chunks.crc ~-------------------------------------------------------------------------------- ; End {} chunks [3] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; Start {} chunks [4] ~-------------------------------------------------------------------------------- .0x00000007; int chunks.length .0x74494D45; int chunks.type ~-------------------------------------------------------------------------------- ; Start byte [7] chunks.data ~-------------------------------------------------------------------------------- .0x07,0xDE,0x07,0x01,0x08,0x31,0x03 ~-------------------------------------------------------------------------------- ; End byte [7] chunks.data ~-------------------------------------------------------------------------------- .0xBF3B6B3A; int chunks.crc ~-------------------------------------------------------------------------------- ; End {} chunks [4] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; Start {} chunks [5] ~-------------------------------------------------------------------------------- .0x00000019; int chunks.length .0x74455874; int chunks.type ~-------------------------------------------------------------------------------- ; Start byte [25] chunks.data ~-------------------------------------------------------------------------------- .0x43,0x6F,0x6D,0x6D,0x65,0x6E,0x74,0x00,0x43,0x72,0x65,0x61,0x74,0x65,0x64,0x20 .0x77,0x69,0x74,0x68,0x20,0x47,0x49,0x4D,0x50 ~-------------------------------------------------------------------------------- ; End byte [25] chunks.data ~-------------------------------------------------------------------------------- .0x57810E17; int chunks.crc ~-------------------------------------------------------------------------------- ; End {} chunks [5] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; Start {} chunks [6] ~-------------------------------------------------------------------------------- .0x00000E5F; int chunks.length .0x49444154; int chunks.type ~-------------------------------------------------------------------------------- ; Start byte [3679] chunks.data ~-------------------------------------------------------------------------------- .0x78,0xDA,0xED,0x9D,0xBB,0x6E,0x23,0xE9,0x11,0x85,0xEB,0xEF,0x1B,0x2F,0xD2,0xCE .0xCC,0xEE,0x18,0x7E,0x02,0x33,0xB2,0xA1,0xB8,0x33,0x01,0x06,0x1C,0x6C,0x60,0xC0 .0x06,0x9C,0xFA,0x11,0xF6,0x11,0xFC,0x00,0x4E,0x9C,0x38,0x77,0xB2,0x99,0xDF,0xC0 .0x21,0x1D,0x35,0x60,0x47,0x0C,0x4D,0xA7,0x8E,0xC6,0xEB,0xDD,0x1D,0x5D,0xC8,0x66 .0xDF,0x9C,0x54,0x49,0x1F,0x4B,0x2D,0x89,0x94,0x48,0x0D,0x67,0x86,0x0D,0x34,0x9A .0x43,0x52,0x64,0xF3,0xAF,0x53,0x55,0xA7,0x4E,0x55,0xF7,0x04,0x39,0xE0,0xED,0xCF .0xDF,0xFD,0xBE,0x93,0xE3,0xB6,0xD7,0x2D,0x1C,0xFA,0x09,0x1E,0x41,0xB0,0xDF,0x2D .0x3A,0x2E,0xC1,0x31,0x02,0xC8,0x31,0x0A,0x1C,0x01,0xB0,0xB3,0x6D,0x36,0x99,0x04 .0xF7,0xD9,0xFE,0x3B,0x3A,0x7D,0xAE,0x13,0x11,0x39,0x9B,0xCF,0xDB,0x23,0x08,0x3E .0x01,0x00,0xA8,0xE1,0x03,0xD2,0x4A,0xEC,0x40,0x10,0x00,0x80,0x4E,0x44,0x5A,0x77 .0xEC,0xCE,0xE6,0xF3,0xEE,0x08,0x82,0x8F,0x10,0x00,0xB3,0xC9,0x24,0x56,0xC3,0x47 .0x6A,0x78,0xFB,0x77,0xC0,0x91,0xC6,0x37,0xC3,0x37,0xFE,0xF8,0x18,0x08,0x8E,0x40 .0x38,0x20,0x00,0xA8,0xD7,0x47,0x22,0x92,0xA8,0xD1,0x53,0x00,0x20,0x71,0x40,0xF0 .0xDE,0xDF,0x8A,0x48,0x8D,0xBD,0xD2,0x63,0xBB,0x69,0x5A,0x38,0xF2,0x8B,0x0F,0x08 .0x80,0xD9,0x64,0x62,0x86,0x4F,0xF5,0x98,0xA9,0xC1,0x33,0x3C,0x6F,0x40,0xE8,0xF4 .0x31,0x3D,0xDF,0x0C,0xBE,0xD2,0xC7,0x25,0x9E,0xAB,0x5F,0x12,0x04,0x87,0xB6,0xF5 .0xF0,0x28,0xF2,0x27,0x39,0x9B,0xCF,0xBB,0x5D,0x01,0x37,0x3C,0xF1,0x04,0xCD,0xB0 .0x66,0xF8,0x4C,0x44,0x06,0x7A,0x1C,0xA9,0xF1,0x53,0x44,0x80,0x04,0x3F,0xC0,0x3C .0xDF,0x40,0xB0,0x50,0xE3,0x2F,0x75,0x2F,0xED,0xF5,0xB3,0xF9,0xBC,0xF9,0x0C,0x0D .0x4F,0x1E,0x15,0x9C,0xAD,0xCC,0x91,0x8C,0x37,0xD5,0xD3,0x22,0x6F,0x5F,0x0C,0x00 .0xEA,0xF5,0xF4,0xFC,0x81,0xEE,0x43,0xDD,0x4F,0xF4,0xB5,0xA1,0x1E,0x23,0x7D,0x1F .0x23,0x40,0xA3,0x47,0x7A,0xFE,0x35,0xF6,0x05,0x40,0xF0,0x62,0x91,0x00,0x8B,0x7F .0xDF,0xBA,0x74,0xF4,0xC0,0x1D,0x7F,0xB7,0xA5,0xC9,0x18,0x47,0x71,0xBC,0x8A,0xE7 .0x45,0xEE,0x54,0x8B,0x48,0x3B,0x2D,0xF2,0x6A,0xAF,0x00,0x50,0xAF,0x8F,0x9D,0xF1 .0x47,0xBA,0x8F,0x01,0x82,0xA1,0xBE,0x6F,0xE0,0xF2,0xBF,0x5F,0x48,0xCB,0xFD,0xE6 .0xF5,0x57,0x22,0xF2,0xA3,0x03,0x41,0xB9,0xAF,0x28,0x80,0x30,0x1B,0xB9,0xCA,0x85 .0x5E,0x16,0x99,0xD1,0xF5,0xDF,0x02,0x00,0x6F,0x54,0xB9,0x6C,0x41,0xA0,0xFB,0xB8 .0x53,0x82,0x7F,0x1B,0x38,0x68,0xF8,0x52,0xF7,0x46,0x44,0xAA,0xB3,0xF9,0xBC,0xDE .0x96,0xDB,0x84,0x2D,0x10,0x1A,0xC3,0xBB,0x07,0x6A,0xF4,0x91,0x7A,0xFD,0x48,0x9F .0xCF,0x00,0x80,0xC4,0x79,0x54,0xA7,0x3F,0xCA,0x16,0x76,0xA5,0x8F,0x97,0x1A,0x09 .0x2E,0x75,0xFF,0x51,0xC1,0x70,0x6D,0xBC,0x60,0x97,0x51,0x00,0x86,0xE7,0xC2,0x07 .0x3C,0x8E,0x60,0xF8,0xE0,0x8C,0xDE,0xC2,0x00,0xF6,0x5C,0xF3,0x14,0x10,0x20,0xE2 .0x24,0xD8,0x53,0x5D,0x43,0xF2,0xAA,0x14,0x51,0xC1,0xCE,0xAB,0xD2,0xFD,0x1A,0x0E .0xB4,0x7A,0x0A,0x08,0xC2,0x43,0x6F,0xFA,0xE6,0xED,0xB7,0x01,0xC6,0x4F,0x91,0xE3 .0x87,0x22,0xF2,0x85,0xF3,0xFE,0x31,0x08,0x20,0x17,0xB0,0x85,0x0E,0xD0,0xC9,0xBA .0xFC,0xDC,0xE1,0xE4,0xCD,0xE8,0x3F,0xEA,0x7E,0xA9,0x91,0xA0,0xD2,0x54,0xD0,0xED .0x28,0xD4,0x7A,0x6F,0x8B,0xF4,0xBC,0x03,0x16,0x3B,0xC2,0xB9,0xFA,0xB2,0xD5,0xF8 .0xCB,0x0A,0x8F,0x9B,0x6D,0x40,0xAA,0xC6,0xA7,0xB7,0x0F,0xE0,0x3C,0xA9,0x1E,0x47 .0xFA,0xFA,0x00,0x7C,0xCA,0x36,0x5B,0xAF,0xA5,0x88,0x5C,0xE8,0xBA,0x5D,0x01,0x04 .0xCD,0xA6,0x20,0x48,0x1E,0x2B,0x91,0xA6,0x22,0x72,0x9E,0x17,0x3C,0x99,0xA1,0xF3 .0xFE,0xB1,0x3B,0xC9,0x18,0xC6,0xAD,0x7A,0x40,0x20,0x8E,0x14,0x0E,0xB1,0xD0,0x16 .0x11,0x46,0xFA,0xB7,0x8D,0xF3,0xBA,0xE7,0x78,0x7D,0x8C,0x3D,0x75,0x15,0x4B,0xE2 .0xC2,0x6D,0xE4,0x80,0x60,0xBF,0xA5,0x05,0x3F,0x89,0xC1,0x63,0xC2,0x6C,0x32,0xD9 .0x86,0xAF,0x10,0x00,0xE4,0x50,0xB6,0x96,0xA7,0xFA,0xDA,0x08,0x04,0x3B,0x45,0x3A .0x2A,0xF5,0xB5,0x4B,0xAC,0x6F,0x0B,0x8E,0xB5,0xF1,0x5A,0x6D,0xD4,0x0C,0x9A,0x16 .0xF9,0x02,0x27,0x62,0x27,0x3A,0x76,0x8C,0xDF,0xBC,0xA7,0x41,0x5D,0x4F,0x92,0x77 .0x85,0x70,0xB5,0x40,0x2A,0xF0,0xA4,0x72,0x0C,0x8F,0xB8,0x11,0x95,0x10,0xBA,0x9F .0x5A,0xAE,0x26,0xF0,0xAE,0xB1,0x46,0xB0,0x53,0x11,0x79,0x25,0x22,0x5F,0x8A,0xC8 .0x57,0x22,0xF2,0x46,0x1F,0xDB,0xFE,0x46,0x44,0x5E,0xEB,0xFE,0xA5,0x1E,0xED,0xB9 .0x53,0xDD,0x2D,0xF5,0xA5,0x9A,0xCF,0x37,0x39,0x9F,0x14,0x1E,0x6F,0x4E,0xF4,0x5A .0xCF,0xC5,0x3E,0xFB,0x0D,0xCE,0xCD,0x5E,0xFB,0xAA,0xE7,0xBC,0x4E,0xF5,0xEF,0x6F .0x2A,0xAF,0x4D,0xCE,0x63,0xA3,0x08,0xE0,0x40,0xF0,0xEE,0x3C,0x2F,0x7E,0x06,0x0E .0x40,0x0D,0x20,0x80,0x24,0x95,0x08,0xED,0x15,0x10,0x69,0x1E,0x65,0x20,0xA2,0xF7 .0xA7,0x2E,0x02,0x0C,0xB0,0x48,0xA5,0x4B,0x21,0xCF,0xC9,0xB1,0xF4,0xB6,0x01,0xF6 .0xA4,0xA7,0x74,0x0D,0xAE,0x74,0xED,0xA0,0x53,0x2C,0xF4,0x6F,0xAE,0xF0,0xDE,0x20 .0x22,0xDD,0x6C,0x32,0x91,0xFB,0x88,0x2B,0x84,0xB3,0x18,0x60,0x1C,0xA8,0x21,0x4F .0x10,0xFA,0xED,0x38,0x70,0xEB,0x6D,0xEB,0x1C,0x83,0x98,0x2E,0xF5,0x7C,0xAE,0xB6 .0xB1,0xE7,0x56,0x11,0x00,0x20,0xF8,0x77,0x4F,0x78,0x14,0xE4,0xC4,0x05,0xF2,0xD3 .0x85,0xEE,0xFF,0xD3,0x9C,0x6E,0xC7,0x0B,0x0D,0x5D,0x2B,0x84,0xD5,0x16,0xE7,0x43 .0x32,0x14,0xF7,0x18,0x64,0xDB,0x1C,0x6B,0x0B,0x7D,0xAA,0x8B,0x7C,0x02,0xCF,0x7F .0x05,0x4F,0x7A,0xAD,0x8B,0xFE,0x05,0xDE,0x3B,0xC6,0xE3,0x53,0x7D,0xED,0xC4,0xFD .0xAD,0x79,0xF1,0x4D,0xD4,0x52,0x2F,0xEF,0xDB,0x62,0xA4,0x1E,0x03,0xDF,0x2B,0x44 .0x54,0xEE,0x19,0x76,0x9F,0xA6,0x32,0x47,0x16,0x63,0x18,0x3F,0xDE,0x1B,0x00,0x14 .0x04,0xFF,0x04,0xFA,0xCC,0xF0,0x86,0xC2,0x6B,0x35,0xF0,0x0F,0x30,0xFA,0x7B,0x11 .0xF9,0x1E,0x2C,0xFF,0xBD,0xBE,0x97,0xA2,0x4F,0x04,0x23,0x27,0x00,0x58,0x22,0x4F .0x9B,0x59,0x60,0xC5,0x32,0x76,0x61,0xF6,0xB5,0x0B,0xE1,0x23,0x2C,0x38,0x53,0xDA .0xD0,0x91,0xB3,0x41,0x4F,0xF5,0x73,0xA2,0xE1,0x78,0xE4,0xF2,0x75,0xE2,0x41,0xE0 .0x6A,0x7D,0x23,0x7E,0x27,0x3D,0x1E,0x3F,0x80,0x51,0xC5,0x55,0x1F,0x1D,0x88,0x27 .0x23,0xD3,0x93,0xB7,0xE4,0x29,0x7F,0x34,0x2D,0xF2,0xBF,0x9F,0xE7,0xC5,0xB9,0xAC .0xB7,0x76,0x3D,0x18,0x96,0x28,0xF1,0x5A,0xA0,0xB5,0x71,0xDE,0x99,0xA0,0xEE,0xB6 .0x92,0x2C,0xC3,0xB9,0x45,0x9B,0x80,0x00,0x3A,0x05,0x49,0xDE,0x08,0xDE,0x72,0x0A .0x43,0x0D,0x90,0x66,0x22,0x17,0x65,0x58,0xF7,0xF3,0x9C,0x3A,0xFD,0x9C,0xB6,0x47 .0x9C,0x61,0x93,0xCB,0xF8,0x8D,0xCC,0x26,0x93,0xCA,0x55,0x2F,0x0C,0xFD,0x29,0x00 .0xC3,0xF3,0x14,0xA4,0x4C,0x33,0x70,0xE2,0x88,0x73,0x89,0x68,0x5B,0x22,0x9A,0x76 .0xDB,0x92,0xE5,0xE4,0x19,0xE0,0xB9,0xD6,0x2F,0x26,0xF1,0xAB,0x51,0xBF,0x2F,0x71 .0x62,0x35,0x7E,0x6C,0x0C,0xC2,0x67,0x3F,0xBA,0x41,0x88,0xE7,0xC2,0x6E,0x62,0xF8 .0x04,0x91,0x82,0xDC,0x21,0x81,0xF7,0xFA,0xDC,0xCA,0x70,0xEA,0x73,0x7D,0x83,0xEF .0x6F,0x1D,0x7F,0x09,0x58,0x33,0x96,0xB5,0x01,0xCE,0xE0,0x3D,0xDF,0xAA,0x83,0xC8 .0x55,0x22,0x19,0x08,0x64,0xE6,0xA2,0x5D,0x8D,0xB5,0x8D,0x60,0x2B,0xD3,0x51,0x4C .0x42,0xBF,0xD0,0xE3,0x02,0xC4,0x5B,0x5E,0x04,0x00,0xD3,0x22,0xFF,0xC7,0x79,0x5E .0xFC,0x1C,0x4F,0xD5,0x30,0xBA,0x45,0x82,0x1B,0x4D,0x7F,0x36,0x99,0xD8,0x02,0xAD .0x00,0x8C,0x06,0xA5,0x1E,0x39,0x45,0xC0,0x1E,0xB9,0x12,0xD2,0x1B,0x9F,0xB9,0x72 .0x84,0xC5,0xF4,0x9E,0x35,0x02,0x48,0x82,0x23,0x9E,0x0D,0xEA,0xEB,0x16,0x69,0xA9 .0x73,0x7A,0x41,0xE2,0x22,0xC0,0xA0,0x47,0xE1,0xF4,0x21,0xBB,0x43,0xF8,0x8F,0x1C .0x30,0x53,0x38,0x85,0x01,0xDF,0x22,0xA6,0xE9,0x0C,0xE4,0x47,0x1D,0xCA,0xCF,0xA5 .0x03,0xC1,0xCA,0x1C,0x71,0x1B,0xF5,0xF4,0x39,0x11,0x40,0x34,0xD7,0x13,0xB5,0x15 .0x3B,0x7A,0xAE,0xA1,0xD3,0xA7,0xA2,0x71,0xC1,0x98,0xF3,0xE4,0x3E,0xA3,0xC3,0xF8 .0xA9,0xEB,0x41,0x64,0x4E,0x92,0x8E,0xF4,0xDF,0xC1,0x79,0x58,0xEC,0x6A,0x67,0x9E .0x73,0x85,0x1C,0xDB,0x02,0x7C,0x43,0x47,0x4E,0x09,0x88,0xEE,0x81,0x7C,0x6C,0xBF .0xA1,0x76,0xEC,0x9F,0x51,0x80,0x00,0x6F,0xE1,0x48,0x4C,0xA1,0x8D,0x4B,0x93,0x0D .0x9C,0xED,0xD2,0xBD,0xB7,0x7B,0xA9,0x14,0x20,0xD3,0x22,0xFF,0xCF,0x79,0x5E,0x7C .0xE5,0x3C,0xE9,0xA6,0x41,0xD1,0x23,0x8C,0xB4,0x30,0x32,0x81,0xE0,0x0D,0xCF,0xF7 .0xDE,0x67,0xFC,0x01,0x8C,0x6F,0x2C,0xFD,0xB5,0x03,0x40,0xEC,0x16,0x38,0xB8,0x4E .0x64,0x8D,0x72,0xB5,0x44,0x3E,0x6D,0x21,0xF6,0x04,0xA4,0x0E,0xE3,0x00,0x24,0x6A .0x29,0xD6,0xB2,0xED,0xE9,0xDE,0x05,0x84,0x73,0x46,0xA7,0xCC,0x89,0x4F,0x2D,0x40 .0x68,0xBA,0xC9,0xC2,0x81,0xB2,0x73,0xA9,0x69,0xE9,0x22,0x6A,0xB5,0x6D,0xEF,0xE4 .0xB9,0x11,0x40,0xF4,0x24,0x49,0x58,0x1E,0x6A,0xE3,0xFA,0x3C,0xEF,0x65,0xE1,0x16 .0x1E,0x15,0xFA,0x40,0xE0,0x84,0x14,0x2B,0xCB,0x4C,0xA8,0x39,0x05,0x63,0x4F,0x1D .0x29,0x62,0x8E,0x2C,0x51,0xCF,0x97,0xD0,0xD4,0x59,0x9A,0x36,0x90,0x89,0x8D,0xD3 .0x9C,0x38,0x05,0x93,0xC4,0x2E,0xE8,0xEB,0xB1,0x6B,0xE7,0x5A,0xAA,0x68,0x5D,0xD8 .0xF7,0x79,0xBF,0x41,0x5D,0xBF,0x72,0xFD,0x90,0x95,0x8B,0x30,0x2D,0x52,0x96,0xA5 .0x89,0xEA,0x29,0x8D,0xB3,0x5D,0x00,0xA0,0x76,0x44,0xCA,0x18,0x79,0xE7,0x58,0x35 .0xC3,0xAF,0xF4,0xB4,0x38,0xFB,0x22,0xC0,0x1A,0xC3,0x56,0xE3,0x5B,0xFD,0x6B,0x61 .0xFF,0x8D,0x88,0xFC,0x04,0xE5,0xD8,0x18,0xE2,0x52,0x0B,0x62,0x6A,0x9F,0x63,0x0B .0xCC,0xF6,0x33,0x17,0x7A,0xE5,0xD2,0x52,0xA4,0x86,0xAB,0x5C,0x83,0xA8,0xD3,0xEF .0x8B,0x41,0x0E,0x23,0x07,0xEC,0xC8,0xFD,0x06,0x71,0x25,0x65,0x82,0xF7,0xB4,0x3D .0xD1,0xC8,0xB4,0xFE,0x06,0xB2,0xBA,0xB8,0xA8,0x59,0x6B,0x57,0xF2,0x49,0x52,0xF9 .0x2E,0x00,0xC0,0x5C,0x19,0x3B,0xD5,0x2E,0xC0,0x03,0xE3,0x1E,0x41,0x23,0xEA,0x11 .0x79,0x9A,0x7B,0x1E,0x73,0x20,0xC2,0x72,0xBD,0x09,0x32,0x5F,0xA0,0x39,0x35,0x40 .0x38,0x2E,0x91,0xAB,0xD9,0x39,0xB3,0x46,0xD3,0x25,0x8C,0x5F,0x22,0x32,0x58,0x04 .0xB0,0x32,0xB1,0x86,0xC7,0x33,0x52,0x19,0x88,0x53,0xFC,0x8E,0x4C,0x5F,0x1B,0xE3 .0xB5,0x15,0x00,0x3D,0x82,0x70,0x14,0xB9,0xF0,0x4F,0x19,0x7D,0xC5,0x10,0x7F,0x36 .0x9F,0x57,0x10,0xB8,0xE4,0x39,0x46,0xDF,0x29,0x00,0xA6,0x45,0xDE,0x9C,0xE7,0x45 .0xEA,0xBA,0x5B,0xB1,0x23,0x23,0xB6,0x58,0x43,0xC7,0xCC,0x87,0x30,0x50,0x03,0x23 .0x57,0x8E,0x2F,0xB4,0x38,0xDF,0xA4,0x47,0x47,0x1F,0x41,0xA4,0xC9,0x5C,0x48,0xBD .0x82,0x61,0x2D,0xB7,0x5E,0x02,0x04,0x4B,0xB0,0x68,0x33,0x7E,0xAB,0xF5,0x7B,0xAD .0xD1,0xAC,0xED,0x49,0x4F,0xFC,0x6D,0x6C,0xD7,0x66,0x4E,0xDE,0x1E,0xEA,0x77,0x08 .0x9E,0x67,0x04,0x60,0x05,0xD1,0xC0,0xF8,0x9E,0x90,0xDA,0x20,0x4A,0xBD,0xCB,0xB9 .0x88,0x64,0x47,0x9F,0x43,0x79,0x33,0xC1,0x8F,0x0F,0xC8,0xA5,0x31,0x98,0xBA,0x6F .0x24,0x75,0xCE,0x68,0x54,0xBC,0x1A,0xA7,0xA0,0x65,0x3D,0x75,0xFD,0x08,0x0B,0xCF .0xEA,0x62,0x05,0xB1,0xE4,0x42,0x6E,0x87,0x4E,0xDE,0x23,0x0D,0x2C,0xE1,0x75,0x7D .0xBD,0x7D,0x2B,0xC9,0xC4,0xE9,0x03,0x3E,0xC4,0x0F,0x7B,0x88,0xE1,0x09,0x72,0x7F .0x8B,0x54,0xC1,0x68,0xC8,0x31,0x39,0x0A,0x3B,0x56,0x49,0xED,0x75,0x68,0x75,0x97 .0x00,0x48,0x20,0x6C,0x0C,0x7A,0x4A,0x39,0x4E,0x11,0x0D,0x9D,0x9E,0xDD,0x3A,0x79 .0xB9,0x86,0x37,0x76,0xEE,0x3B,0xFC,0xB0,0x84,0x8F,0x3A,0xFC,0xFB,0xA5,0x7A,0xFA .0x0F,0xE8,0x41,0x50,0x3C,0x31,0x80,0x34,0x1C,0xA4,0xE0,0x76,0x36,0x9F,0x77,0xAA .0x61,0xD4,0x8E,0xA3,0x5C,0xF5,0xFC,0xC6,0xCC,0x85,0xF6,0x94,0x1E,0x0C,0x80,0x66 .0xAE,0x14,0x6D,0x60,0xFC,0xCA,0xE9,0x24,0xDD,0x3E,0x41,0xB0,0x2B,0x00,0x44,0x30 .0xEE,0x69,0xCF,0x6C,0xC0,0x00,0x47,0x8A,0x20,0xC6,0x03,0x1A,0x59,0x1F,0x13,0xAF .0x1C,0x08,0xD8,0x28,0x4A,0x5C,0x33,0x25,0xE0,0x7D,0x25,0x52,0x48,0x89,0x66,0xD4 .0x05,0x3C,0xDF,0x72,0xFF,0x4D,0xFD,0xFF,0x58,0x1F,0xFF,0x6C,0x3E,0x6F,0x67,0x93 .0x89,0xAF,0x64,0x3C,0x19,0x6B,0x1C,0xD8,0x53,0xB7,0xC6,0x91,0x93,0x96,0x7D,0x87 .0x71,0xD5,0x13,0x01,0xEC,0x5A,0x89,0xBD,0xCD,0x45,0xEE,0xE4,0xE2,0xD0,0x69,0x91 .0x5F,0xE8,0x0F,0x1E,0xA1,0xD3,0xF6,0x06,0x0C,0xFD,0x4B,0x11,0xF9,0xA9,0xF6,0xB3 .0xDF,0xEA,0xF3,0x27,0x48,0x03,0x41,0xEE,0x4E,0xDD,0xD0,0x03,0x22,0x80,0xCC,0x7B .0x99,0x91,0xB4,0x2B,0xB9,0x1D,0x2D,0xBB,0x10,0x91,0xEF,0x44,0xE4,0x9D,0x36,0xA2 .0xDE,0xC9,0xED,0xA4,0xD1,0x05,0x17,0x79,0xD3,0xC5,0xD5,0xF7,0xD5,0xB2,0x3E,0xE3 .0x60,0x6D,0xD8,0xF7,0xE0,0x15,0x4B,0x78,0x71,0xDB,0xD3,0xE0,0xEA,0x5C,0xEF,0xA4 .0x44,0x44,0x5A,0xC8,0xFA,0x50,0x6C,0xF3,0x90,0x1E,0x72,0x48,0x11,0x40,0x5C,0x7E .0x1E,0xC2,0xCB,0x47,0x72,0xF7,0x7A,0x81,0xE0,0x4A,0x1F,0xAB,0x7F,0x5B,0x88,0x33 .0x35,0xC8,0x20,0xCB,0xBF,0x21,0x3C,0x2C,0x82,0x27,0x95,0x00,0xC1,0xB5,0x33,0xF8 .0x95,0xAC,0x0F,0xA4,0xD8,0xA2,0x06,0x94,0xAC,0xC6,0xAC,0xBB,0x07,0x40,0xD0,0xCD .0x26,0x93,0x06,0x86,0x5D,0x80,0xBB,0x78,0x45,0x30,0x73,0xC4,0xB1,0xC5,0xF9,0xB6 .0x4E,0x88,0x5A,0xA0,0x2C,0xA5,0x32,0xD9,0x3C,0x67,0x14,0x6E,0x93,0x99,0xC0,0x64 .0xC7,0xD1,0x24,0x75,0xB9,0x3E,0x73,0x40,0xF0,0xCA,0x57,0x05,0xB2,0x16,0xE1,0x71 .0x0D,0x0F,0x10,0x47,0x22,0x87,0x0E,0x48,0x0C,0xFD,0x46,0xEC,0x2E,0xD5,0xF3,0x17 .0x00,0xC0,0xA2,0xA7,0x96,0xF7,0xD1,0xA7,0x9B,0x4D,0x26,0xAC,0x02,0xEE,0x4B,0x07 .0xC1,0x11,0xB4,0xCE,0x85,0xF5,0xD6,0x35,0xBB,0x22,0xA7,0x14,0x1A,0xDB,0x37,0x09 .0xD7,0x2A,0x13,0x82,0xB8,0x79,0x2C,0xF7,0xEF,0xE2,0xE2,0x90,0x5D,0x02,0x80,0x25 .0x52,0x82,0xB2,0x6F,0x88,0xBA,0x9D,0xA1,0xD0,0x72,0x66,0xA5,0x8B,0xF5,0x1E,0xDE .0xD1,0x3A,0x01,0x89,0x79,0xDF,0x16,0x35,0x76,0x2A,0x9F,0xD5,0xCC,0x36,0x8F,0x70 .0x29,0xB7,0x83,0x29,0x15,0xE4,0xD8,0xD8,0xF5,0x05,0xA4,0x47,0xCA,0x6E,0x66,0x93 .0xC9,0xBD,0xB9,0x57,0x23,0x41,0xD5,0xE3,0xE1,0x4C,0x13,0x56,0x9A,0x2E,0x65,0x7D .0xD8,0xB4,0x75,0xF2,0xED,0x12,0xD5,0x08,0x23,0xC0,0x9D,0xEF,0xDF,0xC7,0x65,0x6C .0xBB,0x04,0x80,0x6F,0xF6,0xD0,0xCB,0x38,0x92,0x95,0xC8,0xFA,0x05,0xA1,0x89,0xAC .0x4F,0x0B,0x47,0xAE,0xCD,0xEA,0xAF,0x3E,0x1A,0xBA,0x5E,0xFE,0x0A,0x8B,0x76,0xE9 .0xEA,0xFC,0x6B,0x59,0x1F,0xE2,0xF4,0x97,0xB1,0x71,0x7A,0xA6,0x82,0x67,0x56,0x22 .0x52,0x3D,0x34,0xE8,0x89,0x74,0x10,0x9C,0x54,0xDB,0x20,0xBF,0x33,0x02,0x44,0x58 .0xA7,0x1A,0xC4,0x6F,0x09,0xD5,0x8F,0x63,0x74,0xED,0x3E,0x0D,0xBF,0x0F,0x00,0xAC .0x10,0xD2,0x06,0xFA,0xEF,0x31,0x7E,0x3C,0xE7,0xED,0xBC,0x54,0xEA,0x3D,0xD2,0x8C .0x3B,0x42,0x33,0x66,0x80,0x28,0x42,0x09,0xB5,0x81,0xAE,0x7F,0xAD,0x91,0x64,0x89 .0x50,0xDA,0xC9,0xFA,0xF0,0x05,0x39,0x04,0x35,0x88,0x0A,0xE4,0xB0,0x04,0x47,0xB8 .0xB7,0xBD,0x0A,0x10,0x34,0x3D,0xDD,0xC5,0x12,0x40,0x0D,0xAE,0xE6,0x6F,0xD0,0xBE .0x5D,0x2B,0xFD,0xA6,0x45,0xBE,0xBC,0x31,0xBC,0xE4,0xB2,0xEF,0x6D,0x97,0x00,0xE0 .0x58,0x98,0x19,0xCC,0x16,0x23,0x95,0xF5,0xE1,0x0A,0xDF,0xE8,0xE9,0x20,0xA3,0xD2 .0x58,0xE6,0x51,0xA6,0xFA,0x0D,0x9D,0x86,0xDE,0xF5,0xE4,0x54,0x9B,0x4D,0xA4,0x7A .0x38,0x00,0x2F,0x19,0x43,0x40,0x0A,0x8E,0x7B,0x18,0xAB,0x8F,0xD1,0x37,0x68,0x67 .0x93,0x49,0xF7,0x40,0x24,0x68,0xB5,0x47,0xE1,0x89,0xA0,0x11,0xBC,0x0C,0xE7,0xD1 .0x21,0x65,0x11,0x04,0xF5,0xB4,0xC8,0xAF,0xE4,0x03,0x6C,0xBB,0x8E,0x00,0xD7,0xBA .0xB0,0x0B,0x5D,0x64,0xCB,0x75,0x23,0xE4,0xE0,0xCE,0x89,0x3E,0xF6,0x5C,0x02,0x6F .0x5F,0xB9,0x86,0x10,0x95,0xBF,0xD4,0x85,0x53,0x76,0xF3,0x38,0x67,0xD8,0x81,0x3F .0x8C,0x51,0xA2,0xDA,0xA0,0x67,0x86,0x5A,0xBD,0x41,0x29,0x99,0x6A,0xF5,0x40,0x55 .0xB2,0x53,0x10,0x3C,0x44,0x0C,0xBB,0x1E,0x00,0x44,0x72,0x77,0xB2,0xE7,0xE6,0xFA .0xC8,0x69,0x91,0xBF,0x97,0x0F,0xBC,0xED,0x12,0x00,0x15,0x22,0xC0,0xD8,0xA1,0xBC .0x44,0x78,0x4F,0x9C,0xDC,0x5B,0x23,0x02,0x58,0xB5,0xD0,0x21,0x82,0x18,0xA1,0x3C .0x41,0x08,0xF7,0xD7,0xEC,0xD5,0x4E,0xD9,0x63,0x1A,0x31,0xE3,0x73,0xDA,0xF7,0x04 .0xE1,0x59,0x70,0x9E,0x7E,0x58,0x84,0xBC,0xE6,0xC1,0x79,0x3B,0x05,0x87,0x4D,0x3E .0x35,0xB2,0x3E,0x87,0x10,0x39,0xDD,0x64,0x21,0x07,0xB2,0xED,0x12,0x00,0x8D,0x53 .0xB3,0x16,0xC8,0x83,0x06,0x8E,0x46,0xD6,0xAF,0x02,0x5A,0xC1,0xD3,0x38,0x30,0x19 .0x40,0x9E,0x04,0xDE,0x3A,0x72,0xED,0xD3,0xC6,0xED,0xAD,0x0B,0xB5,0x03,0x78,0xFC .0x2B,0x00,0x60,0xE4,0x5A,0xB9,0xA6,0x43,0xC4,0xEE,0xB7,0xF0,0x06,0x16,0xCD,0x6C .0x32,0x09,0x8F,0xD5,0xE5,0x96,0x2A,0x14,0x08,0x5E,0x39,0x94,0x69,0x91,0x1F,0xD4 .0x25,0xEF,0xBB,0x2E,0x03,0x1B,0x10,0x20,0xCE,0xB5,0x95,0xF0,0xD4,0xE0,0x3C,0xB7 .0x41,0x34,0xE0,0xBC,0x5E,0x0B,0x00,0x0C,0x7A,0xA4,0xE3,0x12,0x0B,0xCC,0xC5,0x66 .0x37,0x2E,0x93,0xDB,0x8B,0x2E,0x38,0xDF,0x3F,0x74,0xEC,0xBC,0x01,0x70,0xD6,0x2A .0x01,0x10,0x34,0xDF,0x9B,0x78,0x4C,0x39,0xF4,0x93,0x4E,0x07,0x79,0x37,0x92,0x9D .0x01,0x60,0x5A,0xE4,0xB5,0x5E,0x43,0x58,0xCB,0xDD,0x9B,0x40,0xD8,0x02,0x66,0x78 .0xAD,0x43,0x8E,0xAF,0xE1,0x89,0x19,0x2A,0x86,0x80,0x90,0xCF,0xB9,0x81,0x1A,0x3A .0x3A,0xAF,0xB1,0x63,0xD3,0x26,0x81,0xF7,0x53,0x9D,0xCC,0x5C,0x03,0x89,0x1D,0xBD .0x13,0xD4,0xF0,0x4B,0xFD,0xFB,0x15,0x22,0xD5,0x93,0xA5,0xD9,0x43,0xBD,0x15,0x4D .0xB2,0xE3,0xCF,0xE3,0x0D,0x20,0x82,0xEB,0xCE,0xC5,0xCA,0x0F,0x78,0x85,0x2D,0x27 .0x5C,0xD8,0xEC,0x49,0xA0,0xF7,0x0F,0x64,0xFD,0x12,0xEE,0x06,0x11,0xA5,0x84,0xA7 .0x57,0x00,0x93,0x29,0x85,0x27,0xCE,0xF8,0x1C,0xE2,0xAC,0xD1,0x47,0x60,0x58,0x1E .0xA2,0x84,0x5D,0xE2,0xF3,0x6B,0xD5,0x06,0xB6,0xBE,0x87,0xD1,0x21,0xDF,0x87,0x28 .0xD9,0xD3,0xE7,0xFA,0xEE,0x18,0x3D,0x87,0x2A,0x18,0x85,0x18,0xB6,0x76,0xAD,0xD4 .0x33,0xF9,0x78,0x88,0x10,0x5F,0x82,0x47,0x58,0x7E,0x1E,0x83,0x71,0x0B,0xD2,0xC6 .0x00,0xBC,0x22,0xC1,0xB9,0x94,0x2E,0xCD,0x90,0xBD,0x27,0xE0,0x1B,0x63,0x68,0x03 .0x4B,0x10,0xD8,0x4F,0xE6,0xFE,0x45,0xFB,0x88,0x00,0x7D,0xF2,0xB0,0x38,0xCF,0x6B .0x9D,0xE2,0x95,0xC9,0xDD,0xBE,0xFF,0x18,0x00,0xC8,0x9C,0x76,0x10,0x50,0x5E,0x36 .0x28,0x23,0x97,0x72,0x77,0xFC,0x3A,0xE9,0x21,0x8E,0x2D,0xFA,0x0F,0x41,0xD6,0xE7 .0x17,0x1A,0x59,0xBF,0x7C,0x9C,0x2A,0x64,0xA5,0x64,0x70,0xE3,0xFB,0x15,0x1C,0xFA .0x5D,0xC8,0x76,0x7A,0xAF,0xE0,0x69,0x91,0x97,0x0E,0x08,0xD4,0xF5,0x79,0x4B,0x96 .0x16,0xB9,0xBC,0x02,0x38,0x62,0x59,0xBF,0x14,0x2A,0x95,0xBB,0xC3,0x23,0x3E,0x45 .0xF0,0x06,0x0B,0xA7,0x28,0x15,0x79,0x77,0x0D,0xB6,0x71,0x4D,0x22,0xFE,0x01,0x3D .0x03,0x4A,0xC6,0xBC,0x80,0x23,0x95,0xBB,0x17,0xAA,0x6E,0xBC,0x6E,0x1F,0xC3,0x2D .0xE8,0x76,0x7E,0xB3,0xE8,0x69,0x91,0xFF,0xD7,0x71,0x01,0x96,0x42,0xC1,0x35,0x8B .0x2C,0xC7,0xB7,0xF0,0x50,0x7A,0xAC,0xBF,0xDB,0xA8,0x1F,0x17,0xE7,0x3D,0x0A,0xFD .0xB5,0x86,0xC1,0x95,0x9C,0x26,0x13,0xDB,0xF1,0x7B,0xB9,0xBD,0x27,0xD1,0x05,0xCA .0xD7,0xDA,0x45,0x9A,0xBE,0x1B,0x5F,0x7E,0x32,0xDB,0x5E,0x7E,0xCC,0xB4,0xC8,0xFF .0xE5,0x74,0xF1,0x0A,0x44,0x8F,0xB7,0x9B,0x31,0x86,0x9F,0x39,0x63,0xDB,0xE2,0x57 .0x72,0xB7,0x7F,0xCE,0x46,0x4A,0xDD,0x23,0xC1,0x52,0xD9,0xE3,0x78,0x35,0x6F,0x41 .0x73,0xA1,0x20,0xB0,0x89,0x21,0x1B,0x22,0xE1,0x34,0x4E,0xE7,0x00,0x1C,0x6D,0xB3 .0x5E,0x1F,0xCB,0x0D,0x28,0xF7,0x45,0x02,0x65,0x5A,0xE4,0xC5,0x79,0x5E,0xFC,0x12 .0x79,0xD7,0x18,0x38,0x47,0xA8,0x1B,0x59,0x6F,0x96,0x98,0xB7,0x79,0x15,0xAE,0x42 .0xDE,0xBF,0x99,0xA0,0x99,0x16,0xF9,0x9F,0x9E,0x7A,0x7E,0xE7,0x79,0xF1,0x0B,0xFD .0xFC,0xB1,0xE3,0x06,0x03,0xA7,0x07,0x70,0x32,0xE7,0x93,0xBB,0x79,0x65,0x78,0x09 .0xA4,0x9E,0xE7,0xC5,0x6F,0xE5,0xF6,0x36,0x26,0xA1,0x47,0x50,0x09,0xB2,0x7E,0x43 .0x24,0x5E,0x3E,0x1D,0x14,0x50,0x7F,0x78,0x89,0x05,0x39,0xCF,0x8B,0x5F,0xA3,0x3C .0xB4,0xF9,0x41,0x8B,0x10,0x37,0x53,0x3B,0x0F,0xCD,0xE4,0x7F,0x4C,0xB7,0x9F,0x0D .0x2F,0x75,0xC2,0xE7,0x79,0xF1,0x3B,0xB9,0x7B,0x39,0x14,0x73,0x3B,0x4B,0xC0,0x9B .0x3B,0x77,0x4D,0x8B,0xFC,0x8F,0x87,0xBC,0x80,0xDF,0xBC,0xFD,0x36,0x1C,0x01,0xB0 .0x39,0x08,0x7E,0x23,0xEB,0x13,0xB3,0xBC,0xE2,0x35,0x12,0x91,0x68,0x5A,0xE4,0x7F .0xF9,0x18,0x43,0xA9,0x01,0xE1,0x43,0x1B,0xBF,0x0F,0x90,0x8F,0x02,0xE0,0x85,0x41 .0xF0,0xB5,0xDC,0x36,0x7C,0x6E,0x3A,0x66,0xD3,0x22,0xFF,0xAB,0x1C,0xB7,0x17,0x31 .0xFA,0x07,0x05,0x00,0x80,0xF0,0x2B,0xF5,0xF8,0xBF,0x1D,0x4D,0xF8,0xF2,0x86,0xBF .0x03,0x80,0x8F,0x2D,0x77,0x1D,0x8D,0x7D,0xE0,0x65,0xE0,0x71,0x3B,0x6C,0xC3,0xF7 .0x46,0x80,0x63,0x14,0xF8,0xBC,0x8C,0x2F,0xD2,0xA3,0x6C,0xBD,0xC4,0x97,0x1E,0xB7 .0xC3,0xD9,0x8E,0xFF,0x71,0xE4,0x67,0xEC,0xFD,0xF7,0x02,0xE0,0x18,0x05,0x3E,0x9F .0xED,0x41,0x43,0x1F,0xF9,0xC0,0xA7,0xED,0xFD,0x8F,0xA6,0x80,0x63,0x24,0xF8,0xCC .0x23,0xC0,0x31,0x12,0x7C,0xDA,0xDE,0x2F,0x22,0xF2,0x7F,0x28,0x30,0x65,0xE2 ~-------------------------------------------------------------------------------- ; End byte [3679] chunks.data ~-------------------------------------------------------------------------------- .0xE5A387DC; int chunks.crc ~-------------------------------------------------------------------------------- ; End {} chunks [6] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; Start {} chunks [7] ~-------------------------------------------------------------------------------- .0x00000000; int chunks.length .0x49454E44; int chunks.type ~-------------------------------------------------------------------------------- ; Start byte [0] chunks.data ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; End byte [0] chunks.data ~-------------------------------------------------------------------------------- .0xAE426082; int chunks.crc ~-------------------------------------------------------------------------------- ; End {} chunks [7] ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; End {} [8] chunks ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; End {} ~-------------------------------------------------------------------------------- testwriterbin3.txt000066400000000000000000000001251445000440600340100ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~--- ;START : TestClass ~--- .00000000;a .testb;b ~--- ;END : TestClass ~--- testwriterbin4.txt000066400000000000000000000002201445000440600340050ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~--- ;START : TestClass ~--- .00000000;a ~--- .See on array;Line one ;Line two ~--- ~--- ;END : TestClass ~--- testwriterbin5.txt000066400000000000000000000022471445000440600340210ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~-------------------------------------------------------------------------------- ;START : Parsed ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;STRING: str1 ~-------------------------------------------------------------------------------- .0x31,0x32,0x33,0x34,0x35 ~-------------------------------------------------------------------------------- ;END STRING : str1 ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;STRING: str2 ~-------------------------------------------------------------------------------- .0x36,0x37,0x38,0x39 ~-------------------------------------------------------------------------------- ;END STRING : str2 ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;END : Parsed ~-------------------------------------------------------------------------------- testwriterbinfloatdouble.txt000066400000000000000000000024261445000440600361540ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~-------------------------------------------------------------------------------- ;START : Klazz ~-------------------------------------------------------------------------------- .0x-1.EE0F18DDEE1C5P749;d ~-------------------------------------------------------------------------------- ;START ARRAY : da OF double [2] ~-------------------------------------------------------------------------------- .0x-1.0EF69BD2607B1P255,0x1.513C114B5DBD2P358 ~-------------------------------------------------------------------------------- ;END ARRAY : da ~-------------------------------------------------------------------------------- .0x-1.2E127EP-125;f ~-------------------------------------------------------------------------------- ;START ARRAY : fa OF float [2] ~-------------------------------------------------------------------------------- .0x1.AA04F2P67,0x-1.2521A6P-93 ~-------------------------------------------------------------------------------- ;END ARRAY : fa ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ;END : Klazz ~-------------------------------------------------------------------------------- txtwrtrjbbpobj1.txt000066400000000000000000000010341445000440600341700ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~-------------------------------------------------------------------------------- ; Start {} ~-------------------------------------------------------------------------------- .0x02; bit:2 a1 .0x37; bit:6 a2 .0x12; byte a .0xFE; ubyte b .0x2311; short c .0x45DA; ushort d .0x82A0337F; int e .0x99041045BDCAFE12; long f .T; bool g ~-------------------------------------------------------------------------------- ; End {} ~-------------------------------------------------------------------------------- txtwrtrjbbpobj2.txt000066400000000000000000000012241445000440600341720ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~-------------------------------------------------------------------------------- ; Start {} ~-------------------------------------------------------------------------------- .0x02; bit:2 .0x37; bit:6 .0x12; byte .0xFE; ubyte .0x2311; short .0x45DA; ushort .0x82A0337F; int .0x99041045BDCAFE12; long .T; bool ."ABC"; stringj ~-------------------------------------------------------------------------------- ; End {} ~-------------------------------------------------------------------------------- txtwrtrjbbpobj3.txt000066400000000000000000000014611445000440600341760ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~-------------------------------------------------------------------------------- ; Start {} ~-------------------------------------------------------------------------------- ~-------------------------------------------------------------------------------- ; Start stringj [2] ~-------------------------------------------------------------------------------- ."ABC","DEF" ~-------------------------------------------------------------------------------- ; End stringj [2] ~-------------------------------------------------------------------------------- ."GHI"; stringj ~-------------------------------------------------------------------------------- ; End {} ~-------------------------------------------------------------------------------- java-binary-block-parser-2.0.6/logo.png000066400000000000000000010612771445000440600177650ustar00rootroot00000000000000PNG  IHDRsBIT|d pHYsgVgVڨRtEXtSoftwarewww.inkscape.org< IDATxgdU'Si2Mhi(t $Q48:qL *U#L ** 3MUurvթ:u M3~_]gשg{,767C(-` 2R*)n͐LČs1LdHcfΟ$10SIᏎ"oTOY 5޿)ɼMi|qyR|6Np^;>LČo! )Xg7O^Ԡ*%6K,=33v|eH"R>wk(ykߐgwPq%5R¥t0=wN$||d32#0M ޳ l }5/fT~YFj)+\76I(zJ}G{7Ծ1<}<7Ϗe{cjB:r.S*#cVSkh]d4fąH^oݤZJ(fӓg5 #Outcm$cFϛ@}\m/LF4.mAZyJfn%i枩LGboiݯ3tZg ciGֹ{Y44[ӧ1CADr%R5%^3ف^ƗzBg %-5S&N\w#%9dC=ۜ,j7gpJpOj렴dF*bzרwDL>}hM]S$Uߕ;v`a[{4!tOoS&TOv HaڵsYC #f>3E23Q<-a)cIo~yj J<~,2tWCvq: 9dqDmM*e?'{ȇƱhgyT/ch!OD;ElRa=GcHal<}jix*Ĥ2q6Mq3hw }n :6ڣ*SZnO3jhn cF2>q2gu{<*  :ђwE.qy_Ws=I_3 ?c #1}V %e~x,=Fq:UONّo7 뀕R֡_ܿ+F8T.a@$v9[T,}*ojwL GKy]9hkD;DXcjE{{D#d~w<=2ML&CZ 5#]mspGMul]R,'SXojFC\C,tmC’фM?x>BRv iC4aٓhoq_ %t]"2C;>{=&U1nWVy4ϛewHDOp9L]Ok4.|s%7 >}vs'p:O(o60</iye% yc  >0CGu$_"_%scјOo5hpe㭵"ƗϘW[ԾUy*k*o*o?/rvyxλP&F7JKC{C/JQ;=sWPavƿm&}aԁ˟IFc#C5moKgܦBiPIw)S6olQKႁ-w~~KGJAIF#Wmg>ޫl2o ~&^R5q 5es iu+_SIo~0 śAI4ôƍELd(iK#Ԓϋ_)x0Kꅨ@Hݵoc^ga]Q>SMG5K0AII_Bg=]u2ԺB{'GT*/&&;? LUP vA4eU4>?,/"I=;B9ǯ+Gx?>[k7 bF>V|Fàt蓣hT8Hv 79'[LqK4ܢqR9y]J0[A}\_>%ȳ /<<ުOiVSB%C?)S~h" V׵I* PWu^IEx'&T@n-+ZV_ήʷh!Zfx!Lӄ+{O@jHxq[`a|hXTI_6^>"{cFǎTR_PV^z:&= ភ(6i5qp/$'n^POOij) -{wt2L+WVQRw WzbM ؼH >I`ZP.tR%!;!ѓb xwU'-WzmI!~d~srӯ7]\{BOXolv|V:.ww)Ӝ; q -ܫkii5 J/'eT%QrK77!]? PJ'#vwow pNov I)>>"&-$_"^D:6*9&X ɓPW!P&oklRvV$C 浕%e>ѮR  VY~R2>)~XBkH$nYda *9E/)i+>#%Mz'um#]}E1"W# 8o`4$'S'UH@TqNoC(cDۅXWW.21ʌ~S)}nexLSD,Dwuq_o&|)E=[(OD|R5żBޱZ`_xdK{JCj""TFO 0u;>>J`xaaݧ#'ĸkmg4*z/^?#; o+tL_o !!92`yӇ4; % *ϢL+xyM%o2ᲀhɕ{u~'Ք;D{ttiHCYsxǺ%ɄT}ZƊEom- II&Lo@8QLE ;> reO&Jq'`eM -&ʆ>Ll,$ɖP60D(/Y}=hp KY2!.N]JH-&s RX`^gBqLUE/!-'Dx9X#ƟMgzzd/2[=!yO''8OkwdM }:F ppxALMGO0 ّIx(՞>af"O*H1(UȶBX/1~o=Ӈ@$pht8!X#e R$!ʴj<Y:+Tn)D;E"y/=LFcg2ۜ$ m`dq9'٨`<#cl}Zz/NTr% ޑ}>jf R|=}aG tٖ w &]WG@"Pg7Q6(D;uFyp\2{zjam#mޠ (-'U=H2s6p_hqD2cӐ))6S኎ηdz<- gE|h6bK)A7dARCȶLcHp@<[:b9|}. ~A~TPS[ZS `qo!ݫgxQѮpB,$(k$5>^6GE"% i$|?cG;i͚5ig@RyR INEO\yF)UB8]mEDgq53~ߤ(J!mϏer SCo ܒsahiHL9/, G_χ\$q*9aېxA=et %gCe^=0}ZCy:" Hj%id?hLs1-WF.ыc,/=L.Ypo-{ $7`[k()$+>=Lˊc)3J.I8= `ڷphk}R_Ch<ڃz.9DH=cIYN_\1I+tO>C"k($XF7Jd8!\:}+^>UԸnyqG{Hc8xrxq6֐~y;"O*h{!Ā=+5 =z %`VH Hn'1ٿ[~)o/g<ʔۇJp[C=)O cPPHvXOi`2|Y婗C,z01`܇Ҳ2By^E}b`tkhЇ>D65k^$blUV*[e$dlUV*[e$a<$O.\gYZo /£PRHUUsa[$_KT$c I}`ջvHW8{yi%!>*R)%N ?xrŤ1M)DY٤%Q. RFJl)M$Hf`:g{=VQGۆ p`Th@yB]EyF)˅0c*oRJ0 BD8%nտi"7+*dE0U K-9sYCR,'{i!<** PZ ;"RL k #{R x}]V:-O+>_Oj">{ {0 "m#[yNh*`JAɅ³.IdNLjUQ{_Wd[.Ytjm*[4!y5b8&Bk%k(%$)FBku8!~9P6 .txrS.oC ecM)9~Vi]!ƹ%]1G/jrc *8Ee5ԝ0-JRuEJ\X7oj6> "~*c10W /yk\(B<)p`G{o}qHNk)>qjxQ-ڷ{Aq'(R}ArMv7~JлTS$""bQYI=![/H)%FL[G DH$'*XWUPT%h:_ƤՄW43GDpтٕ]rLy @%ۧ: `G(=gӣ"Q-̋ߡSˁ WzH\ Gj^NDuuJQY (:UhL\O_8T Wx7mr%0wh~JTW#ˁ w VD;ңv, UEKBgU?rUxR N&b}}C3=: 9j) D`?/K`U5tPZ rʏkrF>X&po4 D rÕ>A.^/,k?Zk%q}}@j9}!2^`K2.0br(?Ҍ{ L}t]DpHƺuu5w qI<cnS˞[!OԾg/޶cG`?ƿ jf]7;!3~Q@t Srk(gaɁ_ei2-h|a8 V {X FM=Ow"@C/AG;zDa£uE@XEҬL; P}Hp#!,A'1>AR7>D{Dr4 8_偙}#\lRy5kWzC}.Ad<!3:?%k`\ken `ؿx7bd$}:]J凵|.b z$ՄuyϋV}+ kY`us4zB]DÅ:`_jvgTvk_By?ŸBj2"7P]DcDCDH} .ݠ y2H)\&#QtKuqZָ( :bm0h!7evFN?4HeqJsNA8ƿXN=oSXR~~4'YMAH00qz?E_o̰/ IDAT(DkK">5GlP VyV~A:2Ս*sFFT$u¥!pB:)m*? ostFFez*x:%Π>wQ9Ux > g~S:ڽ:]XolTvИ@ D;扠];k9Ms'X>eRMF?2uE{%u-{XLu׶\^ݪD|,iT#Gz afl^<%ýZ2wb~\Y*Ϗrdl(%s@T``^?EGGyN-%v_c0􉎞)FpE@2'ֺW[HTJmEj4>?'BR'vN$D\J qCUWTXGJN'8Կ \<gϟ¼ޤz *w(ϩ8'yS{cH[AO_T/=,* 9M?T2'^S0)mk\j1.ƵR(Aoj6yEj(51ΠlR_0qIQ}AE{X#5iFٱ#m%'xA~| 7hwHr52g?m$xri|~Db 6Q:>!'-&5V#>=V`h˫6%YM˫H[p5&i >y"hv&&L7#ZyCKǦ}{Ӗ<toܖ1lAqh1ގLmDo_=>AǸx cGq. vz 40"OJ-_bCj6Jӑ 72qq A& 7h"{2Ƹ$5:>1.UK{M; BR(Li~x^E%>S [Gf7`??YPT0|P(wyJE@+T~RC}LFJ2M[- /.%N‡pއ T5T./YV~]l5JJi;(ɑQm*C,:;z-@hWyȶ 7W THOSR^u4X!n(Mr-&a.%A'f10PA=#E( pf?4~!ZmTW$s1cef%iS/.)))m A ^B7dWhDKOs:/*4?4~y%n_(*wpXqNjad$eoz,B$Obh!&5f.װ_zzNċIhw7UPSJ#]ґ.# e JZKfj OYzKxaD}3 9T7G!YM q2H|%t%񢛍v^H拶e-&-&-XszNc)6<,B1襝/^z_:sf3zz!¹2q s~qqNk[}v.Q'Gz=^r?%(ڝ"ʱDhǎb7[HX]Dݳh陃NFeA3~qMLJ(!W{$z>{>JZдM\\DOĸ>I̦n4Q^thwY,Kk֬I&^$$rXVc.Nx@Ī= UR5%5dSS(d$[l8&ϫ"۱O'UAuEEcN)VJkԿnD{HE'50ۇon ?ӻn<3O)tc<##Mhi4.,ҐٷOOĂ_u,w').qc"?#;\HYm_[HQ;?9*rx88synu }N]E?SyJ@*?nK)4=A$Qh).36 +;EDۇolr*?Ȓ!7%I Ut6@Ci-r7PP]d$?اY{{8wەy3E9<*mӣ"=y};i^oTyBUZyFrY5+:`(!Xfh/nB}DB*Ռ,.yC`kUV&rf,hS}C*}F`_. =Կ6F2:o&[w k\"qfvp4eeRb%bG5?2M<'3;gcH$_ZϪ}UM+3$U:͉ }g邙v D[.[ZKdgBdGA(<bI}WUa~]<0nqx&GΝ!&Y(FJ$ߩ>ehG@,q>"ق`^ب!a*)J'rcHD0=NXw{ V3WD0"asӠ<#A9k[X iY/ d@WU2[bEc#ȯ^ZGO =1Ξ'eW[?MkHݳ$CqOvqa-@BZo9-@ԖכT/F EH DEwr_e`r?V0n1~fRJ,AJ 0K"Syug]QEC9fV"$Zߥ> }>MZ8vQf3kjݫ4|q(M!Q>%x6KכTce@fxɕDzV8F癮,O=|6a^++|Dȁ$ Φd>#H}6JQI 4'Ӵa}:yL뚂Ϗ3Y!'5Ѯ`;gsm⢫NRSO:ZWcч+a^k~LJtч֬YܜK%5RT\eT<#R&$iPe 1m~>{}G*Tw2L7g+Ŵdt+V*[elUgzlUV*[3QAWJJ>7_Z•2RnʅBhPȠnS)N7)4EH !BO@+C$W.MMBV3-$Z!O˅ݪiV7JIP\Ҿ\($*;RKGZKQT ,7IJ>˂BEyU$UpыDI?AIi%_[$"HMTH@K UkH}J-gDJn=ZFZ韬\89'jDIL)1^@JýqG[V- az!Jc "ebTK sJn{Pm\oϥ^դv^ QxBHFľ*/<%R]~s}+ψ~oF)鷿#5SGR뗹 W er?!.!.Do,xhɑs=!5$X, < \ys.R#=&Yd}W;`G"}ft8ARz@nHE:H:'W45> 0HݜlϏpOgwCs/ڂD0w7x,;^̑}F !u8]DaH'%L KOqN{WOpO]==#/6h2ѝċ#bܖ_LuMR]pɳl urQq:Da(~kRT]rdB#=~=}C,@ [dh۰NH%'DB,dcbM-@l"C]a,'@Oh3 &_J($)c^>jx&K<Dp.!z] !C|K]DMQЗx'8;EJ%wK{~7N:_C6\_ڿLO<"u 'eS2/~S=Wt'8o#(|s[y!TZ. Kj5;\_]E{ڷ˹JB{]O:! Qj -=.#Ԕ *?VSl!x^L&_<*ʡ1OiWyg+;Knכǵs| f kJ`ѹvq ɞG@XF}Fvq05~V:О1rPvqI)o7 |Q-cXI iZ; Pp'ϳJHLu[J>Qy ; iV|g}f fSzqYvf/Y3?%m (OXWVikfI.I5^"0|F[X˔lI>RNuu\I` vSjIK6dH]C^OOIzQ=4M-[&rSp\5EA09BO.4y\ʜ4*!.PL߫ NׅdXؑŽ<=cd ɡp˪e_G8=]Tj8'ppN + u0=M4w G!Yn#A\elSw=u[?ze! %&v0}LD??oaeC"k+ \hHtoٵ =\R+l\XQM[juƪ}Qz`BԼG^Fk2 El^mZ^>X݆KDK"MKR##ֵ3z*ڃzcZsWN?1|D;G9Xr[*JMkpπ`yo#(=#61wzI]i%ߔB7d]LJQӄ.χ(zOW q0}FE@9{<ﮬ)lwC65I~Qڞ#>(;Z )zj"׷GŠt&Lo3'V" ߕp#Vӟw}>ĺf/9MqyuQ!ԧjk,10lu:wf=ӤzGAk0(_Ұp|t`3z3Gu1V짦PECbCH(O+e"6Ƹ,m Mi`Rf`pUnA"֯*={AU7e`1le@g42hb\g|4_W7%Gp7 V;0xFS IDAT"?y3 rv["3DyV|}s`y][,l.qNqk%>)Z:gni}ԗ7 =ΜveӤz"Pα4?8#ƺܬ\9E;5s`:Սhwv͕͒0u++[E4f;sI3$*]2wzFq#O!ȢrIK 6UM9n^mP oѲ/Pu pBZ!+3: fBHpGYjzZMJ.ŀ6`[6PAZ4zs%-dS@oՕF g|/lB}TjJL]ϖ!渐_"ߣvi}&ˆOk/Ef?< 8y&QV!cN>&&I*ʌ|pɑФGd3ȮDv2$pTIO HQ's1/ۖEnƋCv [g%rR3A|O#"Kpې/nDn5PF;)Hlя||Q. */l)BPSQjs' qʒ=4K=LQpuNT>56yL_FKɰϋ7R12%3 1>)_5Qqёx,f˛:`'fj~`}y"3ҐNuIbqB6O+ )]=ueF;xa{h";NF\G+T=/͸n>7}yqT=b%czd8a+>1>Lϝ%X1q ?wdNO+T.f:V7{oGU?gN?$kB/ҋ K"EQQDT*Mzb -!t!Scϙ3sfn~~d9Ϭ{UxS] =á|f`X-4ϬE0{SڣZ,(^9ĊS$ #ʍrzMrÿZN5?".!e|G`^d<1| x lD}^EB8F\֋7zyNz}g8 2=@~QEF8 o joOįwadwϠkC$SaH@Ցsnv8~-mNߒP# p͗c4K1|@o8eyvszBN[oQNa&z]2 ߏpzN &Nh@;v*R8h|Z@O9Osk n;=*SX{F4+po㷽޽#RlޙoiWyv cT >['59OAU~O lW|8f#ڣdnֻl>I4ߥ z '2Nf1NoA)Ծ;ڈQ:77<#igf`mrC}07"5ЏP]ڻ1;-];%>FLYސQcͯXO76nMO;]&^t]l)b};:݇"Tt01῁e˖aժU`_x1a c`Lp cxS?FV~1a cc,0 믯3j.01fEڻpg>BinVB=zE,={S>c:!^-&: ?:X֑<5ʳgV6(eTafLB3|Yĵ"{GZ+37&q8GdsuB6ғu%+Jj gI/`&L<fX{KTr~qJj w a@L,G&)-@F&ʉ<vzK>luR#ַVPLTy[ 1"R)[ !ɂRR+"U%D=G}FKM2Mq!^n氼2 25"Rt7f(Z* з!%V߷P}7 e%8j/g aY5tZ]vae$g4c:V/2r-$D )! x;ZlٛR86W(;ЙCBUv{sO`Sy5Qmp:-g{!dn˥ u_gsbaQhwgR#[E% t_[9% c Κo>MЙC3jjaFEM!Y=,S9>*1QiX^_H8|1~L+֋C3gμurT$f.ԇS&+)3G21-,c [S_Obț$-X L"q'xhрL_cca©r JjdxWK8ЏjA^8;XZ1h ܙnt^S`|iZ]:(F>9BCuH3NeBwok|oAyq})K? w .hS{g ֏mU"ΣuzT1@&WkސaoN A7O#lk,^? 2هچ λwhbc7As8uYBΎ6+<Ȁ3Gh~d3됖)y'xa~t@\yg€!ң9!k_|-)ʴ .&IMAct!%A|?d_XTc?كLgq0dγ[pr9upo13v 8wńIA5H)]&*մpy7Y{Dr8 鵎m9[2[R!j_@ @# ;]`U)2 /IK_cM$PVHrzz L͟A(1lQ:͘XR)4Rtd?&c/ds?D\|o!1b~B;(_/t | 4C /[ =>hoAv`/2! Ho1sL`Ļouٰ!-wĵ# ׻)̃ Sp |Σujd*Bp C*>_CzMqi,!}ou>Rӽ؜-p,we y-MyAe ?C' 1-@}J 4ϩzO&Cv#8q祘𴷹(0Q^4Kc;AzE3!K.i> CˇNW3 uSӠ=<ĈgO3N?);N]vW1u Զ`|6GۜNw!-B{0k_R=(cnaw|665/*0x8s_1䈝f"s[jnpCLds9 R$(\Vb6?ݛ(kp,SZ&!E5jp=/ HEƽ`?{[ |3Xah_Dųˌ*̷(x[0l-4Aoͻ@\)5' iE)a f˄7/,NBn9d[2ˌCBW 1كlByUa6rok(gg aΖ6ʋ{̷@Sa&봾Zd@z#·ۜFNG?;H8FbLv!b|gG5X? u @&$N?ZUv0؋n놵 {w+JLhmU^M};{ ~4yF닂*uqVr'z ̵@p<#qM?|!#TZ뉴+4A&+w,;Pt޼).ߩ#]7Lsߐ}>~X !m@ q0ԇyw'aiRZ-x]8bCcigD\25,a)C# 3nJA R! N80t:.++vW)&4\5J聸NPz5) (Ϫl!<1h+s6P ܿSh Hs"EB'@FjJPY">8uhsJqNU(h$>~ a+J_AU 'v4|iSGе*D X` ?.ru> @Th\.YF<`9`49#山[yL7fmxVzwA $ CA3qN%F$lF|p>Łs8pL(ñ ̿E~rLB0tZibkf;Sq\9.N+> UN:g-Ň!wA P,I}spʻYHZ D)@tJ5l3Iu7I0{hE# "|0?(.ș$e>^[VcsVt c}WL6T᧜rD琽glׅD~5"?ۼka\x-L4wv}_"7v?qZx$F!4`v6#M!C9DzL]suRnWR,UEߍC]nm' V\-kphcBcol矇o@8̟?0t01_8XtR: 81c01 ⥗^*~? k׮Ìa c"4PUpM>d~Z.Y58 ) ]w5aw Rh~K< 8pwXL_Per)w.aa?#U5:ofoʧ Ɔ;h˯][5hUG\6.X>@8((sCץDtPtQOv^TPЏiʫ,^RNVj˔sqYCrs~֗f^Hil|/_'8_Q%?Qx$.o✚p4Bl*hBOLȫ,|k~@\"' `y@*Z nT<J+*" o3r9! 0i x}>!T[HD|7b/$WiqNed6VBzpY9!M9I}PJV-l2kQQN)Z1?(r: }㜶_ ol IDATatU|:/TiڷGv[6؆~BL|{Qxa<*PM i!M)!|t_?m׆/vv`|͡wIkC\&#ǔ9ńm׍kCyVM4#Џk4^62\Uҹ[;u [90#ƴxg; o iF!n(%"/kRΟ RFA>_s*=$D/+I1uq^ݔK nQIq sG!jk)B<\H; )R8 {WZ'x' -~gv AW "+lqg^FThBMty&Щ ab,)?c2Dg׫{3lWNmنR Ie֞&IGh~LJfh[EZ^VRU.w3'U~| GB$OPz ?p*1NEq1TkWߝC2gc bȻ:؀ Gr!?8_Q7a-2! #_<´ /U ߄=߆\toڅ#2bWw>AyyDzb\PlkC?Ni@[\Wf?D<̓#u؋Lf?4`k"s{']1O:`7\΄k}g Z1\\Ic6&!!]$2weNhņBǛv0aC\!'W AyB8は 0m2NW< ;Y!> kwPTR02hŸ֏kŸ!7a/YL!+B^pZ0loCptIzS ǟaװk߀>xzw1<f]MeZ Mܜg6o\-)Cu8;4ڝ|R6!onϡ?lkyȯt%OټI ;R(~o9tM' I HPU,U!{ y ɇ/|=L;/t7ѱ2vl<؀ .Bi<\o6bC}0&·*@y#A-X&I>1=s? ׾7 e#ͯ ge_/-X{ AG ~(iSj]$#[p7%G4`j[;? IL`VC| gN ϐw;׏iB^<2)nuF_FlnIo?Um曐^xɛ-8JJRz 3P ~qt槴P=ނ~!ӛP A1MM nVmTN7͋bRxYyTAR*PxӘآVx^NDG38.A!TX93q(/TU̟:,`yU1ig">oJoX@\< m oV '8 J&PʨBd Z8s{ՔG5\JjʔzONya$sG ֌|U`9&yt‡"|o خY|WB(c޴@9w ֮&z>4J~IA;IN׈e6'?*J.&Gl~*@19&TZ!'@B&zAPO<8x jrnYhZT|Z{O8 EIE4ί2GDYKͤT(ĕ%#([R!3?" QWNةzw RNvROXuc[ޗk'zLGHA f$}ڨXêNOUl~6\Ty.Y+J7(yVdWbU8L<ӞH3ʯ*(8cV_1Ns4vCįa~D|WU՛삫sW0wĄN")6E4Ϊ!{S3jef"Nz\R?"0; M ~#Mc " 4 +̏D>AԃS"vzD 5cO9~7,3?? uJU9O5ozЏh2Nxak;Zd>!k)q5W ̨B@5V%- ]2KW (OjC,|ģQ^g;TW琻reqd|x'6 n8(=`C?qoV|[B_ܩ.`s8pvsR#RKC]1 8/va:"-zuXgAGe z=PW}TIR  MX{=~\Xi}Wc}8%ҰIԱFCӏdVKq<Աh@;$ᤅN]~}G@ ȇ ʣtQOo ԟA˄MT1S#8B? ,田TL`g m>}>#[ ʗ>&|-p:J2ZgQ|8Bh@@`7aw~Fʛ8vϨpLrʹ0@fundr3}/r[,t]K/^6ń&+׆RY zb.2 /Q`}8Z Jߑ`ƩT(- ̽ CLR%׀IxBvԋC H+doJ'z, -4^LJ;"K %&7xpl`}q>8uÙoEO9h@z]ӷ?cE^ºl{w $C> BB~Q nBlƲw <(b6qpvmB@?уPsk73b.c3<Ǵex* owKWyfY_L2·wZ&ab*Q>Z6ݓ_gcq"v:Ƀ;ہ?݃MF,6y,;i@Zǟp{S={<@Y x`e ,4hA~I.S<;MP l^dA|Ob"UDO҄~|3O2N| ,yͤ@ /! NvO?&O+$l1O 8D%Tp t \m7fc.+z:c(U:R!p90ґi)׊T wo w~Uql8[XCB|Wdbޝ }\,݌} 1Hu]&, ›u: 87Gl.ڛą7Ӆ7L(Yl'% aK֛ή0*g)gU-DX Re8p:و=zzpu`b!LOslcÛl䇹mdg t[1TemP[7s:byTtL8 {3NVdc M>16fA!?_g0-#C8 -#›N%ީ.CXVBOAzMTQB3}Aoc'OLrʯs٬Opx+NoINX~d+ZFBpٛ?y#( IE?̧~X GyZ6XHᕬd+!ޜ)Y$琹o~Q7C`ܡ1vVL8䮍}o 8cZ,ZʥRëgv5z,E\%B]V!-)e"WH\so.;%K0<oy,\B?v(`o7xJLF,]H޾7G6ԭӓvʏ|akO]F6ls_X) sg0$jpCih˛~w ~mmػtm 3/{3a>*)DsN!?+Ɗ<G揈!8c>)C Aa c(xװr:u*f^OS1a c0Z%>y co1a cCFFF|'_1a Gl 2 O ,Y SfOȵ*])!iCdI7j8HFD6%jË45>Uv)zR%"Q.6e]> o]Yd!0Z^ a $߷qx az\wSֻ?ݕޝڀȄBBҤ@s*8g efΜyQaO3</`D}Lcl?C{( @F`_fՃ̝9Oi0<;Iÿ@S]X$WehB}V &DHh]G 㐹=o&E{G+&,j 0) |;!mœcXOdO`h0AHbW /nҀ^6S[Wc(Ύ65I!ݛE%hd!,^dA?0>BN7a` z&,@zKbtqzB_p:ہyZ ZW)Ahn2No·z Lpxq Mdi.}GN@ԇ3<^&#Z{&7`|rq/PJ/mI!hulCX{XD~n.B`h@\}-yYHoKXhހV9h~3؄x&zfxDt+5A=vu& [֢qqL8:%D/ '5{2[0`P>BUHlF•ew*Xv2X#ׯR(T1塩@X'^d2,8C5qs::[0. #7 m$KR]4 .tq&OL- \իwx6`c@&n)w`#{k=LPGm߬pvBq""9}YH+;lSj9sm4^C0mAyNoZ`(~_kDػޙph}oYyZeIJ|%+D7UpE7[]Cw #G2::%gyPRDuYH)t8=4;I'8ڨ]E˺vC<*F)-43NŮ?aX`\ofS{/Q-?.UorחjPאI9+~ Tfc5Ϊ!_Ƒ-?, 'ƚB? RO7yr7_sB|GB)2FA&0a/0SO%JNŽ Eh ϮtqRJ/ hC&!*}D5NG}_Ë￈,f0L駽D#p1N!#iDkhޑbQ8Oj@X#~YH=uJk 7 ?$NCV΂k?3O~E4$!:n/J™8-zWlQyRPHO+UdB'VŸ}[xք%qxwl 2@~]9p8zPi s] )qb/@Pb 0Cpu.qpn)*~\𝜉0hw1oVgM5JŮmXD FOHه=75=)'T}D [<8ODorbocݧZayy]Uf#/U+yTuCy3a+tR"': A{ $R _GYgnt!k3 Lئɛ%lˉ+h *o wfWO'Ԙ<)}R8mpJV xOiS?1?{6V.[%[Hewll  8C -Ns^ $@y t\17 wY$[ޙ9ucϝ33זxyD_cݙΞYgwܮ{uzGHM|6=<"yvm:u Yy:[h *@pwl(cc 8<sK z];e|Ot)OgqN;Q=+9ϥ٫9YLiչz#=tہx  7 ڏgJ~=Ы }!z՜)Z fЊA/Ɉ+J#6sEzU ϔyNS41IX½G6~5O^~#Sgh7VZYJ(y WN{C%IT?"Kԓ4ABjjjFtfH:r|Ok1[ =MSD?ͼh4|~SVV贏}Hv2y~9:A5IN R]S; l~JQkJ/e75ɹjukU [jXos.OFOUg ϰAi KZjxcw*MJl^Fcvu`l'n ޔ ┱!1y~Bh*OPAd?j&Ɋd32@p[1gϨwȘJ]t/j"L#gE dƑC﬒Mtix5+c#؋tqZE5K1)4t5>{>2 / gf޼y~LfSs3 -تvY/n^ zBJWx{6,H.ϸ8ϻ$GGT/eoLӲ钄xu#W ϯlq /`VhO,ǦXY #YrD5 w9#!vql~NrD!lrɿQ^ LıyW1'+"}Ivv uF0T?/ !KQ[@4a2RS)JS_}S&/Xf Rݻw"‡B(ؒV߷=hrū-;"O=}Tч.iz{qv9 0JhϫrƓԋP>bla41)Yo)Zy.|ѓ1'Dx]LtB; ӷ[qCh.B jFFpgI~[44(A.m$ W AW?lϢ\LsY*|}~5?C WFl78HpslT@kj2xdc"տ{ҺqgѾhDpG!:NtrjtP='9Fv53L*jvMӲeRh( 0wtcdφ)^_BoΩ#,t0cw1<}G$03"ODg]/3mcoLk {MކP]1{ ]!Ψ36"md%cwpYt1ԡcfA lS9O](W@mɫHOM0ݤz+ ;"G%}IΎPkS Ujz[Bpe{b§ FUg+0{U'~mDpOb ti4!ck*pu)fkR) 2}tlܸN4@BpEs5zb[p"K?NߓXFU}M:&>% *MP?ݤlw|J=CBtzHrB]aԯ+ ^):8tOƮ*Ck$kc*8гl%k":?3`7l#qdS3G \9_prP0z(u4l&hͥ`N OL2cPZ~zj"l#n4tЎq:dYluArrN/[Z&O{4+/Omǟw1^P١DPS3g5!zCtrHtB2?-l 6o@G!jnC±6ĵ% vWX_W:l{ӿI$o%#3B>{|AރtyF v /Q Crlȭ{V45@AUDMR~[g/=v@k-gJߨ CIrd][4 2 6dɈd ;BCr]/8M~PFd iC 9.8l{4SO^C[!BMS8ϻPöy[=̤ KŪfe/gsk=s8-pГ:;<*vtzT RBI g۳+:&K?]7ff o"@bc{bYi3ۢ.>.>MȚ2ngwmEs YŶO|uQЌlW}7D@ 8z^#'%8zûŷ1צDTz>:&ꨔ䂈pf0j M!-! !m4{b}ù5bԢ 3YS8wgjScwD4<"O_ nͯ ˗/gܹ]߱cw}K`P9FrlOz9bٱO`<vKwXvM0]= IDAT[IQwˡ5MV69σ@` |mjvUWG8[\].u[3x'=Lhf@p͡Usm'k&K=ޠeN>mD&ܫTNۼSc߇ 4a F)z% 7U-vg 0Qٔm'YS` =Eȇ} }6q98/8fŶ͢)c=uo3gk_ڼ)Ԝ 5뾿@%邔a.z<<w|sD30]P?F$Υx5w[]t&9&f{. 9b(lݯIW$ϊ/=&+">"q9]~3|Щg9[: ٬څc1yء݆{=CST>/94%tZ4C!*Tuگsg \:1~3jLrw[!Щ# ݗ1?3%h겫Z^ֆȝ RpAA0޽c~=Gݔ)SXv-B /r.YٳgPq? -0䡇rUV0}t~Nlܸ ?\p3 <DdA-h_CϷDlmc>ke9K=BbLr4zcs !`3UhDbÞ] 0!vP"3)/0e,qG^r"g:7IcFID#t# vM w9=t0]'u_e,3 6) fNh=GJ6[z>c\K\ҳ2 Z~.8rsE&Cji+|DԡkGШa eWjS6c\HP  WB+qr| hl+}˅ZR߰u]֬Y|"؊ݿ^/2ȩ*dsq {>VQӳPs^=BP2O\~SS =FyuU?!qQ8;&j;'[# :!`h{kV:F8^]*(İy=fs )zCp{ L;;.F+? :W7 T4bD6{) ji%[h z/\[O#iUZtAliZ!w(|TZSVVvlJ4` ut /R>\VdSRE 5ju:%K!Kpڊ@ ve;_Vj~"ùſ3ce ͡'-JV ^ cjZUzmFs|RH4&^W﻽g9<%[R>w0_S0o!+Wo(zhg< @6&Hshlx㓚0eM}9=,KHFLyK$kbUQQ$+b”)o쪩tY˘L%x[."YcƤ.9h E 8g'KmEG\c&c_Ø"NصڌxuDkJ"!ɲtUE xMW%$bc#&_8+?C|rD,|}7(C+bңԴq={QCN؃&]4ߓ'f5tږ&}̧rOVV."PW_U1E Wr/Iq!鲄xe$v+j.GntЋzBlWD͂S ϕIOZ?,ʽMr3hUAٱ??69u\:TQ$qj= $|%\K^-nsi rD_Qj34䐼NQǧ?mQzn|J5JՏ|U&Q(@YqR6(+"w{z%5]N4R!=/{?1j4m:Dkܛ^$8ƽݣ"fHm/=B씸;|aie&HNփL8:8:D쾧ZPDCmq32;04lnvh7~F:}MJx~iEgq$Kbo ?fA. xmDrTO~~xUDxNF48s7^MG5 > AvԽ@CoaiVZCITS`5\?;)sk;Dg["xŅӏ],T}(o;Rz*vez( {fgoYGo)fDE\F?=G GDGMc.|CtEDZfe7#xuO ixfi|\ fd2J?,}SFT%}0@|loj!ѕ:]2F"َ q;T/eѸv~8޿SXw{mtuLtB4s8/$Gϯ1^96{\'qD'詪 c#{e'esşT .88z]nl::w޷Hdlh,/7\"Rs|!YQ5۠U1%C#F${U~vKL`H"6Kk}1V(]afiģy%lrExu~)_wIU^z&;'A>/ ~`\(TAA է:>El zs[>|nErF~37ZNt}!`|ƠwTS>\"ՓoofȐ\"7J '?Kϋ3;l7KA, pYncLjˬ^!;: rU-Yno&-F]ekvhF66,;Bn? 0+Kt|-ꙚM:$ʿ72_sy~###</9_3f`…ڙ3gORvZlV8yZGU> 96&Sd_<]:j.<]7̂zHz|p7uA:?)Fb<}L̙fJp7W-C{GgQS6"]n(\uie])3]!WYdEc)u;z5wb@ZO o=K"ܭ.u?w^ PuqS'GÊU܍K=uRvoHW">"]*26/'KiFg+Ɏ}ufX[YWPeVWĿ]ǽ]fH&;\ͮ  =FM+".΍E9@SRmb~qxx{Ͱv5>C60s%#bϪ~amMQ f&)I yr/8AVWD6Z |!Hz"KP}r+;̠!>NzY5 ^#?b&k̠}JTXb7ƹ}gmc`2C얈ɖw }SLzf fgד'py'1nZ_O.qE_\#Ғw%Qlq)kFxN䂁7n/b lA/E }̧\~cdžAOӶ ޣ>lHGýĹWqd=>ʾ#[e)8: g'>ܶvwn*6s)^k99P~ƾ %g2".[ό 씽_e:uͣ/}t=UQJxjӶ֏o߰lVMՈ-Zo{fdT;JzT+l|eIYT) 9&cInUjV+!;k">:#n*Ry?؃ E<!ExfH1m?)Qq.LE5HA(ЇYu7Vl=K( rNS{%rL3KGIVy^WPMVNg(D6pr,ЍxUgl>n&ZQľ~n)R٦݋ m?m`w&#G$zHNc}Wiy|n7z@5ƱEF0X `4yO׆>!Zm9S4'1I=ehIB~J,nzef"];z9#D(ZdI9Hhvnhb?uJNsl]:-c :_.=Nm>^: [CJ`0=a.ZNcվMOiij~m vO ih-Ĵ&Z 7xk N;%!W uon׎ Z۹ٹN=ǓRr10iҤ"olذǪW1i˭ST{]C]HkfּB$·@ujq']~:lGo[58uQ#d8ɑ@zV .8\5)G_]vջĦ$SNH1m;{=>ePρ:8hhSrN0}ӓlZ,i/I8暲Wn{`HI'iZi{Gw rucnpmeL4 9Ęl6ύmF ,Iԝ"E]N0ww# yon^g1`A,"078>7OO ~װWcOר- !gs^!4^bUC|9=IKܟݩz}m=w' e0<<=~̝;z(zWƞb5= ó# 8`>;Cɮ!:+luǑ,Qs~VIS!= wz;Gv͂]:E͟Iޣ^13X75K!wȞjNF<#w~[I'iN:)iG}p{{U IDAT@xv,gI ⵽ C~ORxM{*j~,IJl>.㞕AO5Ynqw”tqNwtoMIM)iS;.sɏ-Y:.!ow[*nIpgu:3#Yש$znG|ts{ՙH@x~d&P2_|B># &GU!{gvl ȌS8NNPGfk(\j&up ~Odg$Kω5µrLҳg/fR7$G ɛb.=!F69x}C6)v:ЖU+1j%/Bgǥ(wĶnltF" }U@wEoG/7(tYoNK }5] jSA73'0IQR4M}=_Xj!Zs`I&8CL_z";-9 Y jY>s#<^Vfx7Zr7 n7$cפ'}o*XN$Ő!(F ټ\ui|LXhiYƿ'>v w ]yX= H@c007 HѺg9K hsM >N!@7FcAhpPt y'yShZ#SbkD[g3`H/l|M<~F ල`d{"=J!@JɎ;hWaU6c}alc1YuL䌖d%Ճڥ[T-5ِ OjNf N`*e)zHSh ! [.Y,ÀAGh^HryHP ps ]٘Vc\U#RE5Ԍ̒.N)ܘ'$~oCːU3ka !z&xIN [BjEO8.55~Aq$k"SBK/VZ♊ULvHQ{ۘ{Ɋڥn('uS#]byUM-7P3-A}$xC8'~al^:-BlvzP6BMS]/)ɡ%Rn饣 X ;t:":%c#ҕ jzF.l^Ftnǚ?)$Yk))+~A5E5uj&VA4EvlKFsKCRKV]cIu:4Cڌ۫aeLE {>y΃`"5(ʬ*JhtY!0Ļ&[ v ċ>mǥ{M`\f[ F˽&T&} Y1s ϕɎKгZD*p8Ψ[R\W+o Lh ^0{|Ad% \"Ic z;AAfȂ@mU=_c F'^GbĽ#Ҥ$x x {<\;K_x.W mwqYzP^oy;Fd=G5e9 ]iu- 0O|Q"8S!0%ʊ}gCk՘P|Ώ3`np^Kd N`nq|N*_}y @;$7}8Ī 7 K@~|UTl87ǾKzY4 a<7 aU$qΑQIPK~a {>"b̶4,L[g~VKVxFgCi]55sCDm|g" M x tqBzxln n;gRzJ*b;k?jH& s7y.>L_kҺ Jd ٢AOфZ:M{ǻ'`. 7N\.Rp7zSUΫ>c=cJ :$Gq)UeqIեpc oCo+ԴNcuZfgHwԥxmٶcc |i] ~ytaJzXUc&|sN#..@xnWpw@Y)IaS 3ѩ-Oxxn Ni !"Oi]%W gKgP,F̚Y|]$GutC\жýCw,ŋFiD`P|y= pa}dFOݗ#poDvlJ|IS&V#]x&BxVūn*v$8mٲ !qM"7Ncjk<=b`!"7J^%\ƶp/gς!zQ9 31 RG>׽1 {dMԿ0w8_QkJ-uVE68>9OFHG`wVoO&, I/Cwie0mwށrUӧ~! 4"tRTi" O"E|(6,k_|>qq,{whfNM[Xk`gAcN>@Ɇ u87fwRvll'n{EbUov.5SDc"'!'+ṀET427fzNۥJӟ1EU4d;6={cÂ癱b+˺bD1at2͹.cOɈxXIܜa[l'#!12pIIb3hFYJ jZ UCl^2tNO*.cɈG~in,z~Lœ6ɦM7u(553S c/NhXm{pTTb=Kw1K.ss{oˤ)u +X?p0o"T %˕pr~f9-,~IBNT6,bϔ?>F9r?(g`c_QGٴ6ԓߴʦ7+_+KOpg hDCeS%咄x;d~^pBBGһK.fW55  $1 bR?p1l#6 n$鋣_\ }hW$g*wB) wMQt9ىw;U7h :$'~Sa_FDO@Kנ0 Ybū&V[1k֬WeN5kO=A;駟fwL^ ,C M0^@d|S? p=*MVWc6_c0y ģ .󽈍+uIcv^jG+׺SZsiׅ=Oy^*g6Osh/ }n/ڐ}3q_D%=ܦW(\656Iehl19Vz}"g.G+CT{z}iA,YDIf(GُLjWv6fK[7cJt-~ZB.A2% 1$gZxE RUt]q,1Fs:uw`J'6d#acaabB?N4;_ DY($ Oo'31n(|-hktϦOCwN"^A<2 5j#GxpI{0XN=I__ |#c}u`2?=;݂ym[!]+Ǻ}0<@oIk7zF%4 vٵY2gi$$>>h|ƪgJ?8%&K'-cC,-}h:2 .48uaF\v ̗5dil]"J IL}e{5Tmi9c(]9n4swf͵X,^{,@x'6k: v?w1]˚RJ7Q< w PZ1IaEvsooPzWoI~x f,d*\\Q7-t/b}OSi>hGy27RxF񌿯KSZW[ȷ7LC'=J뾠7AdlzC᫓lRqťX+Oݦ]_l·c O" ?+]Yi^]`{?8op)6ɦj|c܊bëT1ɦbctnpt_|nhdH==H.| lՍ$=))La 6Xr%O<ī2eYd|!7tff>}: .lMa S))La '_ޟwe`&gެc[mMa SLm0) VXё9`^"̙axqK+;ULa SB,{Ph}N|1cս{'R} J `2Y!JLNF[I%O7yaѾ!um* Q 6zzĈnq:VoD jB%ShؿuRZuQچn^IϬwO- 1$RAd ĵ5$tdDGG!Ѣ4Qcj l0f?>whpnz~)ބbh ; mᴀdC4GtxO"߱*2I~ְjDUBtz~J+Y7C~G[_KwTc 1aV^F$DΟ4篰vu?8>Lē!LS>7׆,&'y$$Xt: 'UQ6pn=J<;3S3Vi޸7kV[}8{plz0Ə36.D8I1z$V{h4#j۝߰kH_U ޒ.!&I毜TJfg0nhku1>㎺{am^'z\ TE) {j)jR;V]񸉾@˄ ǭpi<+@c gӓ;m01v Fiz2/fzCl.mV> IO *˭vX+ !FM!dW%;Y:|h)tS]1sڄtW*m&ћ%X:H?:V0۴T)1.D跛w)QBȂ;[;Ыuݘ?w9T[[}ef$sGW(vjhϐAtd0!8EX+kr$~s4j'ȜD{Hk|N2 4HaQ菩,JɁi!ΞHo1!mozUSb¼F$U¹!}6Z;*;hU-%,띫P0boaj ˱{i!bUVmL%3ZhϨu-'$D*_*n}֠zJp}Xolk^/ r8$K)%!jѱ!5,[}5:?~dl>CE.!LUorU`/o?tH T?4p5hv!h=-*>WIW=?S[ĸom❷A2_RxfD#طdm.qlnݷT GtܣmB+%I"O O(߬D B,w` Xdæ`>ja<>#mpQ!zm}ӈwᢀQ*ܜMI$٣{d vDh U%|ܢzR=D4#|XڦsCCjD3TW vF6!p e7!fz\?AwZ[2-[RúŦzoP˭rJk()&; AvU,^S"l9>5^'l" 6,z} OY.`-,Z!!,(~Di>`c<& a,71$"O3EG]@[n 4|22]RbU7!*HM{. . RU:Uyb }*f8g呋T<Ol困)GtsM[KkJ}\[n  O έWMk{&#-MM.ӊ*fY0AT`tG0@֫ A]7Z4ckXa.YI]H'{H\*܏;;@HCS { " %?l`FO,~}L&k#H6S-mC%rsZ/Fشޏv215 >$Ts~Yhyr%%^]#ijjH[b4K8@$:mIA'LW65W6MIuS[\kM!2# |]tvoU=~sWhW ]u]7+8l֛ug,'%l_2yM" L9¢B2'?_żAa IDAT 5X/oG)?&tx}T-S:We@&;I=P7$hI/4PvUvd!aQ;dyiKR-Ế۱Ѕ=C_oώ{ӿEf$5 _Ff:HRYGV"~ͦKyd~þQٱcuM#45e)\֍zkOKGSW_?0TgD$oD Ky!c{ZM߫kS=ԝ$7Qf9Ok̆&KG AF5D,0V _eD1WOCu% j"D[hUՒKS5cd- 12/ϴ8uѱ;$1 `H]IC7NnRfhP֟ѩ|l _爷P7VԲt$ $aV^=wUU,2 UZ29I2\(JĸH*#9=Q)U-lDo*;XSk0J2KM$#ɬ 3NqLe#:Y~%S]YQ\dV htk}[ f_ZR.C"Y1&&WNZfxa\E*Vl9JV֔x"Ȯ2ac=`OHz%\6Z#&xQO=ieIgF<F68r+fΜ3m l3fn1}m=3LS\u5O{d Hj?! r3H!AYf$G j͕=&BÏO۔3y :5xlG15[?5^΅,88ͫqA2-z8ݟEfO|(1L=6h4/y*~e ?@?14G$-Hᗣ}úZC@`ut݇Q#!s Ƥ,e# 4Ϯ\7^Nm\# qn̐MV+[1."FO!-囉EJXe2z0Tvhw=DceqM]G7=˺0/\վHzM_<3"QTzNW6=M }RԤ/!)ӔM :Քkbh˨Ń2b0GNQĸF3tFݦRuEEjy 1:.^~_5OCLkKdW5K,M$/ivE~[Ho?5K1H˃:m忕&Uxȃw 6z߇n*+#0~Nӆyvw2::,Z$13dPP=smfǕUpTF |jG")T/)49TX(c؜᧼^BFzhhjd\i=ݴ|=5.p$FD{Nz,62VWBJt h;+Dۧ꺠'EgFTޗ&@7gRʩ%` >80iPPiVBuh]nb}yc~b4n1W;.y*~%>^ kmnP@S;v#y]z8_̫u$3bOڣz%xKڏnP;iS6; 6gq̀6Q,-9G?*`>vLg\b_ J[nerg3lSֱ^c>j$DDUL5'ό2-Dlа֜>9o_C'h?ܞIjrO܀8 Ey~]688ŞF,o+~4Q&?sؾd?%?;-lŵ?0/ƣF;pse.ɩIpHc+'_?D HK2vj`B@#c0 ?d0f}.XF?~(IWs j>.6:R V_C2d 4zO$~)xݦxh40|/y@t%yX;cTN.aԙ/PywpAW{yԎ g{yGLV^ƍ@OOn2aÆK뺬<)C_i<܃kx)!0p.ˡ0OϮwG_+2wr)qWuۦsfE{Y`<9FW;菙$Dx4vhKV+*V)4 +,WuXCopUEϼ}k{h.F=m=&|Zm/6[&|lTKǟ܏ m珐Z5)|}mzN_4$cLV@kE=E.FZ59m0ZTb4'I6}x3UOZM-bJǻtB&ŦMtK{лo/Qr82hc:1U܃kdn6*`<').Q"7'Pf Fdĸ-[`ws"xGО֑s/yXϠ/3їJp S9I8U#wuzS8],,͋p͏pZ3Ewy]zbzoV_82!!r I9. JmLlc)=VxG0f7FK7`HLd1WsV <#<8>2'X)'̊}L|tK#<=>SE vThL/b#]G4}MWyeJzB !!_^jGV1֤'֓@wjuj\[Uͣ]P}O/so.27gqeF2!uN NބySy{O6֓q51RH{sʦ:Q1,%3GG6?{q!sIpK|`6S%Y!;%a|G 2$6zTw׻ Wb2`>дaB *SJ[Dׅ,!>*$:0ÿyUNٶ͞{itɿ2 ###j/]wN*@?owtNR(owl3}&).>SF7SFs  )^[GwI`eݍ=kop ))&$3D2vou5r)jjct' g&sK7#]=(-^#׾f/ m'sz [0V d0f[)~_cLbtrܵxE2A. mT={xj`W*r0Zl:|?ϓ}= ԎQι%ۈ躴e%ݲqńnn24ԎRCKb*Es v{ @lY;CEMA;MU5&T[Z&rڳ:"~4eDeR¹q3 J.N6JúWtM+dCCD{H\(/352c`SZK+w_.a^A_nĚr_e1ُ4l sMJwQl ].)H$HN]\&p&N bX-K{녯!*`ʩ%NOOwm؄^<(iW;cmӔ^sf?چMM ĈQ |{hhV@՜M 1RQ$0tcPDEx?ȉI̺$B&!,D zd#J z:מ-#&H'A֐@q˳3 9}f_2&|Q3t p?Jˁw,ݦ:^eKMdŘF<3t#795QNE{BG7qoC˂d^LpvmZ ],!@R1\Qw񩽣!Ujim?h`A"ȜU٤o F!~Nez?u6ͦ\xi&|)c^cAڐFI"owmRGI}hUcXW= K4ߨ(mC.V7?wOdKe@e53nт,Hڈn/bc7ZrC2.1*`Du&2(^k>>%/S9V(~%%1"Qֈ^1EA;cmZvI\Z)*&ΞY"B gKhkd~L:pAHG_g͎ !3hFD=e?.M숤;F``h^] ׆Ds;O@v D#d>x|w H;D*{̎)줘`,UCh+uҎs35ei &05/̉wОוa!MObBHBosgǔsճכLLSGe rDVFAq–$'$/oH- suo! $c!2NQa2ώL"`}v%kT`'phhfV҈f+#"7)zE4SAfU&?ת6!qX v v &O4;:V^B__-B-Z> dl2Kf87gU! }}}|păqC,[%ƘwYRUag5bIs~Pf1׫w{we!"}8'1jǯ-SZh @[Pj?k A-{dgH[bgۛ5m4b$1*Xhc6p%KKe&@ i^V cBlROIb7 ^:'hu4'*3k;97g1&8+ńV@wC \Go>MYw۩$(N/'e,Y?!zf[3XPF4_/^-nvN$xw==|צO$k~a$ IDATHzA[1܆r?(`{X{XeYVM]@Q 0O@rZltL'4&>$ܘMx93B31umޑǶP! 7g [11R5qjLv}sxrͫ5ǹ-V S{{ElTz8WeH<DMXžo};oݮ3wK-'X^Az|ڟ=m&:낢GNIhk]763%h3ZTסfDxK:'=JaXCRKGGC`Bz|ͧEJ:Xܞi]CҐTOT7w mJ6=֟@|=ڴMT/VԎv9]]hĻpno7;ͤоЇ6~b:`vw8MN0l+'mnAjoodnhw^[mb=v[jDu4'ۯ=ޙ~̶\i3%=)LRJͺwߝ>&{9:{/y S«))La x'_ymMF.MJr:0Lm0)bddUV*c`Wem!;wfsUbf S¿ Pܣ{Jxop1:@c(gG9I BU.jJpQWZ-Ն2_̣7LSeJ0V TY:)[,{K-ϭCuécߒ!e1%!mI*[hLg!v?s4oʓU%3%}\̧vGKrJ G!#_QĹ%*-H2 反.ȴbJK?ʌĹ$~_l]$9 |Hu@-oz>ՇTR *$nWMݷVQE+ zOPe) !3 /tӒx0Vm"Ҕ{xTNm#_YT@4E=u~FOJCP>c$+}@JT Y&АOX/Nq$^.I2iEL-v Yyd{_Q$⋀8wڀ"' j%,%I]ekkL?n`GMW1ۘ2'p6MCj}~wiEŒ3xqvˁ+W7_ ?IfSCjŌFlFX:֕ҒxKjTYF}'NZDCiV7; ~?5[R=w^c Ki(uGS&<6}I!^=@ *E$opfI&AG0txF#^&iv]Ћ.&*"|vSc}kl/\O]QLi )yB$#;iQ  ⁘/+uQ/=i&=045z?sC?WC|cU~y) a/?|sI‰N #˛!v_SL鰇*b _3C$ M)Jrbѯݥ=)^æmShknhVع#lBϧӜ ^2Dմ^;c8:hk D87w w\~'.- yN$_31pE`X/>y{Lv1>ZUG{\G.HU#Ƶ #&>,uQEfr(.ޥqOڂy;2$%'SKt%OvRG*aUN${m.kDs"JrLRWöX9LMHϙj,"k`}bc ${~c, y-b_-\-ƽiE#Ot]C6"۫M}ĦM]2hڭڈ kە9!Ow ޡ56!hǰs02 bP&ZsFݦ:bk$n0h?pnɢ 鄋|*-S_\.bⅽԎ+u>Y?\ۦU U%{M֎ *Q%vsG=~,6aN7F0dJ6#&>Փ+X 7ը[<]][v+l !QhMv\`ޏ 4m:LЯXK}N(ٟr+֯`|ͫs뮻!6kwJŒ3~>}~/s؄;GWцu%藆8vK気وD{w`͵_;qX%s:N)ʏw: Xj1WZXڄ;VGWs=I_&xF}{tx:S?gџ7FJdkn=\%.ݟzR T\<1qA+鄻Noġg*>uM}{dc>Q/$ϊZ[Q藇s(^Cw9lT`BwHHhb0L6Ydx$Vi:wdb-)hqa-ۊ6^ z{p=eIUlY\J16 i$yx@L %8t1nrnYH㙶gǖAssq]p4g=kr;,T1= [M)8ǽFM*|6d0xWˇG>*pU@of>K,dz qF-)ewZǾ&-o.]t5ZT~4Cgf}9ONBL+1l1hDKB7໧^`?ZNg\W:ڂI_¡og}SLNf~LJ11FҡW%;\@~7_:"‡! .ri녮6|jo?bt1L WClMv_wuBKW(|qxf;CRNNs]_!gd]Bާil~tjwK7+ {ʁ'8~c ˩(~ uFxV{Dj ֮#E{'\^fdaQq8!dBhlUpRDcsfj"GDU{|Ѓ*Y͉(}kbG/F}^[!]- q/IJ7fqiǢE>_̛7]q{0Q$I®]-*??þJOW٨U _(!wޣ:o\EGf:l}?:!S֩VY!š耟 ^(svu"\!gm\`%I3A0 {Zz<"s]쫪 {ZYk@90hN{w+A5nÉhFBK!֩~A/zd{:.}C'=}m0RjKǹFR)c G >jhuN4;;?cӺg5o:]^- ?21Cm+QރO@5G˲-/?SҐhNzM%:5 oDZ ׋(#A ɀhGo*#&KE<[D9?e,7. |4!*hUkH }{#Kq/uX7PxkEB}d<{CZH5CFO"½!BݪMO0XѼ[Խ*: s"1'z;I_{C02LЃlt"gĸ ʈ_˄RuL-zSO=bcŊ_G(… y駏={08I=!sS;Ex$$źYR P|ڮߺ,55!QT?+-?%UR j»!37҉hh,?%1u:A}rȔ OB //ޙ.,>b] ? }AmD|r}m89'O[>)8vI>s~Kw=lӰ*wKZJT`M"%K|wKtB@<+& hOj%!kmA* 0fP^PE=+c2k4;BDs(H"=RG٭>! saD8+$X(9ڀh Ê'о!8C(;%\ I BGEvdAZLp W|PB av/O)X QjB` wОқzGd&s\]?#<'O E@p[kRW՛!TJ^: gń'H"X+HKCJKӺgV`_WAۢ-Db:.j6?!:m+Z>!JubN˖н!]IN,!)ɑS#)lscq#/CH$C >Jxg$ ۦӇzmqy . II+H8bt\銰N_(y]_ITdOƹ4&tx>҅^Ve_d't%TmG|&&!%E9tP&bAWL{\o\{Ugo۠`%oӑMͰÃxh'm;ʾhODcU!f:y,}$۷o?f4MgJ{PE W:|ivCV3)qtMrU 1 $Z)[]~FA xtQ=|hYV4p|f"N %_di/ZuC5cz+Zƽ{"U3g֭;rr;4M̙sLe˴$&1_o wӘ YagQ<#5i5zLdZ-k"tk PVwU꺯|JO DJڟ5RMʤG_L B !;dU ^SV9#E:Hr ;fR~-92RB:uIR[qR!EҨTlwmiBWDu & i4?Ch}ɗ20N; Ig +eo yV"XԜۙw5%Ѓ-g8_HJ'Wr!=g~ jأ$I_U==lxbsx=com/jM?m{K**T"Zָ&!;]JK7c`,LAržes[ڹS Yu4^m)mhv~vjHgJFgNe!4}!:փ'eSٿ6tw!zw=tλ tu5NpUI}x D{%} (]0𞩙ZTMAI_J54'DB^5*Ypi}MީLMdPixfwK<'be33?<-%79dzbt_.d(y.Y.0x]4n[kmfGgzPƼ+qUmՂ( BW;gVR-%ZbJ,$"w4#7a;}<4I.!^Q{[hYH]j&oT(}2J$b}s۞ϢN pT#DMNRI5(u&eꓢ!/1~C9x;+ (7!\[u᪀'4dQ"~SagDhu_*fjd9f )$Lys$OD学xn~ALb ;?Cvv&b\v}c/_?o( ̘1}3m )|q<"Z q![uQۚ@1.ޫ)1SߐI9!ZCyivʞe֗R[2BSq99ӳc"j+baa}͞.ECFeBߟd1mk~`;qh{nx8Søh:6 q OYhawKne!D_5l!Zb_[%\P|,g,%Su[kgo-)} &?C}}N"8e Ueq| Lf^5k%S~G LRLo͇m kϡCSP֚/̩.Z_wՠ+̀@O逐UwrBx/U=5[X `JZwrӾT'P)~~7ѷ"%.[$ђ'R'l#N?;Aml of5ɔWJ8#; ^1 ^; IDAT^N [m4)0lvA'yn2ީnPpq@pRZLCP7D8xr)n" em_PTQn!rMtxVڹ*EO't=cȱDc?!y2'] r#\ldvvTdiҧ05mEt^oT_i!yk_(۟$\%v/u3PwDKn" 'uzRM&ǙFx`͚5}TXݻh4bC(*E$_»F>(twLkk\gGxr3D.rUƼ;"Np\A k ;ĬZ+ ̽FJ]9œ-2K]>3 ֩-!; >:tWw*5iǚ[xJLߟ`8hG/m5 pU`T11'1!"] nT k?G(DUF?Dy _,o6P$j7T0nAdzb]">?^!T!~C,V5CES>To!r_.F.K8b‡nUlxzw$'u7NPCݦN򨾳{чCW/%G IWz<]PƃDЇxzm." MrF$Z(}-5qA??`w9j0"yt%jO81DTyBߺeXߜAwCpUvw-uO.j^!U\TKqX#:.ĺ-ߓon^HJ0zF3}㒱#N*SeWvQIp{;- .u;P\)}gtս*XD'8;ǁ"7n.?wL"(4yXRxZ€ʨ;Ԟ }m*^т2hrG꼦FGAL~_=0$ iAp(l&leg5yPwB4֯_?u)S&F,;Ql&ep. OcޚǼc|,!7HëķƱ͋u47?ql[>y{ pwH uv= <]ըmѻx: )U|GnSSz3rP}GuqS"(R%1=*[M!k!S[#\ڊ"\`_]EݫQybx)ey#:%QӻR/E6S:V¹F{NCl=滝pHW{UYDJ%RR6˖"}N ^mN4?8v))Zba7o}FPwP=)m3XFB Pn3D8]cx{zh^D  ;jE2/W6ohN G';E>Mhe]nжm\qD ]m><>|у;%ўհ6Ӗ}CK˽F۪=w߽TWUQ[QA^A٩acnךD9zvZ1$[nF$Oq`ǽB<'tdWF$cK _SQ] JUȒGe;smobY.. ub-y A|؍菷H) /lRpO5T 'Z>?h4 E87WV yn^xzK íͧ>ofxwj#Oڍh2B142uZSQۮX}VX=gZǸD{hRJ##m?Оא$JF}\8Dk:?ˉTA=50*"@VP(({T o >UrBKy,EnutA (ÓI>wA$ )© %,R o=}L@ W*a\) Ckbӭcg?lSHr ꀱ 7&N% "*<Լ?77ȇe uGGqrcQ:)TIgzndn ~3S#bB4?"Oѷ<ώqϳq_o7K-<|!61/` seڵ\$y睌9gK021QgF6ȡX^\&&Xcaf\D}V#LN >Z??DVi̬۬{8C5<ݱi YFI'3#bҴCy"4`6ZR3%JD#žFKB5Z=ҍ UOi6/ \u|H Wx^wOrha<%;b+}!eHAٮRڀ+V6 hj׉:`CC(z4Q1$Cc+Ck+jCT>4(}|J}pEsupA6ߑ|#?٥cHI63)TK7.;yQw(= 6T3KLQ a('jY`{U_.J6f)zGJDą^Ƴ"RC)P:ri!%ǔL`笳Κ} ٛtsfOdp9\J;i~c:roܹ\ɛ$&I"ILbG زe+"{ҥ!I/>2KILbz4ѢxJRQf6Z h= 6ÀK1!%S@<+"jO= o>i.E٫vȧwGlxP0}SxvIb5Pݣ씋ScE".M٥.I1a% ה53&H[lCuGL`SԘpaJڋN'JKJ{6ώfHEm";2] D Bi1rMB5e ]Ź'Ax, K]GAtUG:d=w ]<& vY"TѓL9X)nk.4NRR!3S8SUk WR"liCMZ:zWI(,]s3y/I{n[oڀDOPWRt c!R,(>!K vgw|o@D*RL\ @۪wךZk^DM[w_;紞{H9dkhؿ= Isk bS޴hA4NSSgdWe茼zAG .x0&WmӻҨ ׀l]sMLa6R zU(U ڿ5λjW*CC S#EROƆ4SI 3)zّўQ,Ya,p] W?nofGx+Cj`WgwC:!uѪ}}%xN*F %km$-˚Q{g)PTO5#;%Z6C&@ N'8?ûgyr_k-+2,Lp*AI;-oF25FU2F;%:ǽ!uk}6^'4NR"HDt\)S$ O 6x߻[\Bs=({9XvYE#\|6ђ*gkt`jW/uC=mX# MAb 9D#̻:t'5U|2b{CxO<'"xr-kLtjҥF*( V8WVl v zDhqH #%W:u/s5ih m!E\QPW{:c6s߾}l۶-dYSN4$I"McԘqv(?US]Awq xDcx8WԺxAutOL31BX;c<7,T\BbWBjU7/ VžWTuHM-k#o'(#JgͿ%Z_LMa2UPݘLs=<$G8j_5i<3?WͽC:DMʚfyLcJ<ZԾEzt/ VMk|q&2N)WzξF2%FNjxuϭa87d_pFK?j6BL'Ksuz'lϻӍ>[| VwK:2 '^`}[P?:}u^'m 9S)[/;Yަysw"R>ȳg X)=#=o8o*S-a}KK W]aY\@X}MHxb1AxSMe~I͎a>h5d),|*~W 9t;=QS\S ^$zd+` ?nw9B^zHT;ڼeўՅ~=-Eۦg$F>WB7>`j$"v[:)Y6o]9GDK¦,"a}@4G8Hr [-{y9$D 'ͷd0QJO~GMMt*omʲ_[C%J7S-SbZ@krOXշn(G /^3FT/Vx縭u\Zo*cnV($# HBmAu֞yXr%8&ł д (JȞڞmR!ӈ։TOOw벊)ÐD҃噙O3phX=sz|SH:{sɢ8w[Me2QiXo9}?#YPFeo NɞQ%a~'L5݋=TDe _){?SҲ[˻4h(ܸ|vKԨySҥ`N񦲘_ؑ{+iJJ*E{g#Kؑvg ֩3=yϔ=vhp@4T7JE|~io103h暩W)|ڊj:&6ڎNOQѰ@yLqλ5>)>ď,\ևw9;r(;UouLmRӛ Be.FhȪCQwnNE,s uBw.ByWжk|[bJ5~aL+v"5S*A1oz~lMyJLC#(;UX(=~~j;!TPwhS@>[w;*$Sc{Mfi_s =g ø'3|߉T&;VϒN$B-VmkSu.h~D䎔lcծO03{|jo\l7e"Ou/=-ds6 QaCHD'{`Q*=-[")Spc. TUeT?ۜ7,5`<`$ o\<{))~͡٨T?{fcI9{R{M6@{DA&!j0C'<9;&!zcA[yCf[j;kC*Kȑ: T`R 9ͽ;.ޫ\t1ѤcrPbRA?j`i{)5Ѣ64vCd Ƹʞ/'Ě5uHO=SA]/Z1v0/ff}Rm ; -#;%.&ǩGhF>uy\߸sˆяRsEƧ ַ™@xWrI -qbF>yH{\G{A;0n-|)Vahn_4-T?\Ԙ)}8pSh6w pBoN 1mڴD~xzVf)"5ma}I?&kv}a|ʓRU"cɄiKEzq^wT1~fbfMhɑPQr[Y(-)xލ~0- 2e*['D=|'կѢj]~la& ؟ %rXiA_x_"dCFII[ӛMw rdj,8f"63L4DRH BRUF$#-%8Wp.3#lJЇ:.'IHrPأ/vC4-RX~R BF>yH7n?;=#%'͂߾?@ݮR<*<Ҝ`ݚ'wi04R:<*RWndz_+bޕp4$RrTNuR[:~]-S[#pxUm<gr~kBOftV[W<3w(Kws?Y0 IDAThʪh<1RBҬO3BziG3Ec6,WBd З:UV)S_')'T;4:zY%@\W鿱b~{طNN$|P1㼈#JhxNu[SH$Sbg4˛Rr$[9ss㏳sc&O>4rmN R(2^ 6?_w ZJRNfq|4[} gd~VWN<81j*J.`&lxl#"MVT>(i:%(t9qF%bFI1m;yTHC'-^Hk_Ρܣe?{JI &H ^/lsRJT%4DS>& q \ᨤ3WK ~LRuW7FJx\gj]9,4ϻd0aM\?"[;\0wba n?rdj_&OA\!YLc*x;T˞w_-`m , 2CHkmyue6U5SD _)b. 8 LR*y] !ɴOϻD3:叶X ;!><~ޝ3V |Jƌ~tX45tjK8(WS=md$r?H*S?=$pҧ&y'1I4<߿˝1c6l8rq~ӟv7nobsL'1IzcpĄ1448af͚c.3,b֬YT汌(Nbb$&1!DQ>Y]?:ItcѢETq]ʜ$&A2-+=i6,~ѳ;%P5cW'd͟zz :m4޼}-y hcPA{I-uPQhHE'a{C:ETI)H[]ZAt6gGv<#?[]sD'P;Ԯn`O˝188Ç4Mٹ{'oKŸ/ =}?ߓܹF%#ݛc8̘6&i>]'n1 mҾ-amvR& cCzӔ{zWS=WEw(I . IQK٣tK"0jѓ=aagZOq_W?4{=OѰ Hr7C\qU1~]him[r=}.CjOnuRUl">|PYoXfdόQwiMJeɒ%vcn"h p<&aP}ChY(HȔk切,qv/DՖ, HBR1$[ i7sE>.+|OQME$;ǐ)IkT "9(k$1΁ώ\E9⼥NAU1@Bxj&dJ"]?2Jc\y!%SAH- q.IG:X"*X#y-O]7&BV:j_ԭzxFiwVC_EVopapw+T:qrC)kEKKCx8uP=_!IڳBV=F)H#7WIc @?!ԈVICc_W%tdNA+ Y`J DS2FQ$_j~ng?,jJm ^LO{yTŽF(=) m!@sM[f<-¹<`_UAۦ䝲MG<f Kms_?Ϧiai&i/1Wu:y (X&!@lRB2%F٫5Ւ;(C*Ջ~Sƞ6j34 s_cy#Zk_d ŒhRj r6όp GECIxO.DHÔ4JdilϷjcH)$'Hޗݯ*Cݾ׹$sb2AtfB6thӵb@ZJ3$sŰJp&HEs|H}_͐(C5ϻ|KжkƐhNsu 吊 Fω\5hYDzC2 5 EݣSjﮠ*HA[]OŔ>H4LCڼ^xwKR<.&CR"LM!v= wHemC'hD #i1=rvY[k(Cu)=C5uZC\Oq!n :xfk b uKo6XOm0olhas'6Yw=J#/lI~A6 iғ;^& 1OAfISwT7jeJnC%Qq|yRꎰ^l?nd\Jop/daM+eݸ~$["ѷ}*f).I9ƹ&)OnEKPv)n7]E+ >b$t oɌT&&xz$6gs]:}N%\PqUE B&ߔ'Z`ݞG{A?\d=cmʣ )Ŕq.E5.ظ/DE(%<>NGtA~EY& fx go`Bag_9̻r=@9[Hq?=cj^m>Spm9*fh[trwX=De>mB:/W>œƌ3(&6$~¡9LnEm16ڢoޙ.ʰ#ړP~hXrea73 tG?xz 9Ǩ,kx_tkd aq=iL,x?@db"c~@`x˳,!G/0щ!Hm2Pֈ7͉,K2`=)hj+wn6P1~ø_NϻޟsPvm.x8+r+sTd{?~XÇ.\4}-VˇiEKTw?誶Vk @Ew}Oό&I{\i>Aݡa$s4o297p83Zv^%U[sYN g4̟0΍K]#Uej/:OrGi![uDƅ&xnPx *I頉T}(c u:;R*ERz>;;ǥ.}B/LE{M8JQ餍 UI9A^}M"p2ۣT.df6eS)$荇1a$yUU}{mj&$FB/"E H{iz > E"E GxD(B3T 3{Oν̝SgϞuYk-g>z_a5)VsQn/㘯rcV9MI-yM$~\n'lF<"*{a2pMoe1U~[M~^5K-ׄ^-~uh/f(ζoe#PjzhD-uaA0~xƌ3㎢SLW_V^M O1_QE2op)5!Hf%H630|zͿ>ٛ{IܚF_iNp%EK{[t:\+JĿD[봼IF <oW*#U&" eџ0 !+ 0 `>m*WwC+ƕTVr)%Krh;#VgM|7~1Ll85:J W|ChoH=ll5هx[}-% [= ,.P*@r ZG|U-JXBCʪP>9%qs""BQ@a3ЫUy"ODHޞFRP B3SA=Q<"K({(VmT*|<DHȱ,M}Nȶs t%VO7~  ڽB?Rˊ -$?Hz\.^z*R* -RUW 6߿Q TQQtc$~lȴ+;ɥ _%bX%oei&S1a`^vul]d֩!h,TK2;!vNC~L}Eo0!N~ȇTN}yScuyU\.",ePcQ 5Up3fǏgҥ#ֹŶm _.ik&P֨ZQ)\'XQ*YU]FT<*.h9PP3*] |̕R/r }!w{ߙ "3< ,S "}EW -oG;'ӗJg~%.=P5H%t N~aehQR_%@/(jI 7w!!P4ŇKxՇ|> }?DiWMWvN9N}ol QQ3LFJx5d27d2tvPFG&{s~ҫS_F{;KNhKy,:8QzoXq*(\d! 2_kog"sw &U0'%?/[; !5?ĉ>̟^я;IK -#{;,[}DžrbdZvf}?P*mW4w"C[~ʧ5IxޅssXDSa!wC*)SF:5w"*WQ7P ߊQUuBok*sZ}_Ě}SnzŰXz5K.qǍn^x߰,{lntvv(apE * oƈ F~ 7n܈9J =QŨ8QK,H fΜ9J_~D'o=(FeJo[69Lb<QSeuFV^'g{J1t+9kMkgrcXK{E282`(O`0B. ڿү#{oK`FȰD•#Kسl5FmǔhC!(XS\oF-1wCSR[ EA`>=˒Bޟ~ҧxds0or| g`Jtk!yr,OK!vv)̏Q:;A_bڳJcoCHW49JR+3UD E*([ęi{@2R:#hCOKfN-rwG|ͭcǎgŶ#ŦMFl7˙v.DYj!Oܖ`sٰh#> :|ĕ0bbVR[ oސE1_sẍ́yA]_M>G_| &ڔ:_OwG6"IObʗI[1Dԙk څnHN!(_^@0˭tRoHot?SX}qa(ϒ\P ۂvÙ.9CljECҤYV[HcJ çWV= I*ДO/;PKC ogރQ==B{['ai6ca˟,Nq0D ו6uԯ/@j;9-\rcK83F1cV Iۑf)4jTJ8ۄ* S:@x2ѝbqroK2N|BwA̴)'`>wgC$DlJ@-G}Ԝu`G*#DDz>R:'pI! u6+Gw!Qe(B K"yIq{ 1d Ճ*P83Gx{y\GqvvJ8;Lʟ*v8l>$i6+\BI8N#|cYtzg̐kp&9'v_IE%7n|1Rcb?ڭJJUZ{T|*/Sݯ K/E>~8FOƹx>OPK**c^mђ|:|g T~]ή6N*->Ll~ sIxGۤzNTߠS833xDаR9(k~_u^߇jŚL*c,q)^qdi^{[J0k֬*oWIC*=-%9Iwġ1(][+ 㑻ؽIV+˓^"OA°MV{}ojGli}SzibPA|U c*<*T>Y"dU/&{w\ {*8 ƛ~'{}0]ڧz\kN5@\=Lu*”N+D`Mr\Q¼/\nG%NrQ/0іhus*x5Rߋ.µPx }m+t:J=hNXd 7 .N"˭D^|&SPdQtAVSž)`اCvm?RoZtJ;}hҩmԜ^ >Gpf(BR\U=-Wcj16i҇8{Ƈy 5{קQ:=Xb4TYN:ݿuPH}%S3n!Už3ocYL֏Y~$ߕ$HeW?a,2 :^񆁱Ğc|7d i Dd"}A QV IDAT jV#vg"3}Z7a[}IwXdR>IQCGaw^p^cυp'NJc=_C E!Y6^G:I GC+{DD`OXA)~5J O8XKLUkDO8[^kP_v[Kʰęfm0zXTL7taA E!E!r #|>=:lAw0"NԼZ!gXޯ 3;5ּ;EqE!ԂJ~NA~y׬E%z;{RP1xj{p&^qr(Q$('v9hD UChfU)ӨO)SwN!{x^^P=; (-#C|+4ߖ"H}5bfmQ$Kio`=Jd =&DK8$G.ܕD=N0־j֢mkirzIІZ0v.ovqj7OoI!="EӻA@c$'$8YUT!R_ 8g-ǒ N497H~;-wG?[G梎*PeƈpaV:J_m|GC!]OY(u@0kӂ_)y:Oh9NeD~:{TYC%Wc U>>/)\qizl#t7e^ڮD]QV]?ɣ KA8ۋF*"NN Brc~6mƍ[G UUfܸqtAJGGxMp]q*My R~z+VbŊ@q,TOmuC7eSMi v͊$0cPL%TPz ;Q *tN]W#B}NL8x",w(|6WwʱQ"O{-ņPjK~\HP78 x1#z IMwxG@NGt(h۠+O,&Եa)DX̶_Cj3~Qi}>$OljbC_!7IJ7 G~ңtZ?2N>o?$ӪzK!3&ww%@Vd#,c<_.LpAߠS޻J_$oT5H/$HqzF-h5 l$ycʱ~Kn#$yA-kNj;"$W=koܝz\0E},}RQuN{y_ieΧ 6̐ {*˲nsfwݒ3ݖMxޕWP7}JE\29Z4/2ݫH2}GGͫ7qʦ{֓rE/5E 7 gdۛ?G2Xj"\~o47Onz4\lR"K(T|QM:UrW w'i̔0[ZE)6߫jYU-|RdPxƚW"* 9ftȗ 1>t n7CO+{!Nh~>矧o aTcf}0!o/n "$p: MYf 3Q1Wz>Ͳ3nx RQPh{GE2ˋv%b+kkk^H'wM?2 8]F΍Ef=OK]aY0%GtMR(hSh4.RF4iK޳7e.3Oyu^Oה+Y!| oRϋAa uBxc]6JIsLJ =!Xad"&.p'%%֏E&#WLEDFjh5+/rHޔCNܝ&?"2h{ĿkkߤvU$I98 zL_S9 z_Ct|M4Ԝ*}/#}pЇ0-Z!&.1擩}"%v1.WZ})""Ǹ5/ ucFG*Sg't>2w\Q<;8ĭz,. h"E$zxhd!C"ӌ݉z?>E*Lx菛- 3=ȴ) z[b[ziJ"ꡕa5'AIBKH(qǹO)2"Adm$=ܳoxby4(1k,c(K,ᥗ^bɒ%ߗ7ndڵ~>/tRSY@ #Q%J44>còMxT!V3śRn~+)O)'BGj*x 6n8bb9:RDQvm7^x~a|MTUEAQ(Zw.H$1 =ܓ<UUyGYbň&OayQܕS\"+ zH a}Γƀ_#Ud/'ADiS=BEs6DC(mE!'#s, vQW]OVx LcHv{ C#c!wgka,MZMRx&DGSN%>EQZn8SYjPOǔ)S8餓xyg~}V̡i:fE>ID;;K[[#)\M`ӄMUۘyN}׏z4>t Lgk (\ŝ[8Px\]l_yOQ1xc=bʼn&SIؼZ+m+K?_™b} N<'pwv^Oyw?$iIǖkdj}{ 5{ ?s Gg;GAM|%v.kjv9m ͑B}wn"TöwIiYKj.Ԝ* }?i4.c~߾I*4֯݇ߙ 2?;!!eǕ6i]pJg!But=]xV|tJA%F`.ɡP48+NG~B&{wa{M0[{R[!;־U*ǕH]Fl sqGn&$~#y\`ɉT(cϱh?COIN51 ԪZ*e<2W jy(F[i-T8[:[",>U̲}qƠDfInYZ#NJ pN_t0 m!2x.+0~&vᩋb+aae}-ɴzkAU[~J*?S>푹\r(*;!{mw/QX~G))jY%yc:@ eU3tr,ݥpA}I$~!`?"P<a{W)2UEL g<#@ +uM?dž8:`˱r_'Ô6BRU?QE .K:Q2Ov^G%"| ~ʣtJ@\O  Tn{6uTti(aaq?)^y71)gʣ>͌G=V )cA1،m9^|X~=]t]]]X,7p~aAUU:(;0n{? >!VFdeeB̢vp`+9Y2=;L7^etǺپ=@Pxx_jg&>棹pYHWq B]v>R;sd}Z7(JRQI}R );/@-:ܵN*)xY>w}(nM 5{WTf*bKWA#UJ'DCt|f3aWoهE~Ț<3lcAiX8-„ K='T#eOۿn [!նa {*#*$ˠUZ?6~F'vK(c̰1 KqRƙvSQdۙȟAoBk/)IDT[Hy}}$B4ꀆ3u< :ypf۸;诘n\`ϲ(S=V5 2˸c-edzxد+ 'SoGk}" DC<5鿚1a„wp+xYt)w ,qggKΣ1cư`?םs'cYT 2}^Ps=W^y=5GJTWZfa;eq~r>?`@_.EXX%jGl6ldu莎w=VwzT`W[gw3B8;[, "u!"0-D fq?ᓹxJE!u™ai!!]R=Un *;[K(f*}"kiT?^&$DeeRgPs*j̻{!:[҈Cl ڔ=|rl }rwt&htB/^;:RP&x(* i&ʱz\rAS,jNŝR# Z_g(xѳHXU:=CrATWBVέ`VEOKRAE?aZE(}O\hTDH(T"Po;wGGJc%m~ƧzL _CtӐiaXګ*e:Mj2ZO)HG[硭x?*wLG:($I!5*z&Iwxk@Dw5*ώJAA-] O1_!飭20kܐ"S9b!SO#b)ƹ?]$8DO+gW{;έ1]ӈ\[Տ) e̍P$)hJ&.F<} IDAT}' bSBjyn`Hzvp;^ RJ 櫲g0Qs Dor^)cQ;I@g/1䫗 SъW˹3ed$Hyeܸq$4iFTgCaҤI#z<9E/3>wu{xW0ag}6gy&;9S}CYLR\p̟? /o[a&lvĎޅ+]F̊ )y\}w,h{JB<'׈$Ln,*bOa OnLH_)uY3mƯH1Qo+C Lg?H=RGe$"ƻ]FcB+u{7DZ up-ԜSyYjMڧAS?.S_D5: I 9ޚZܔrhEHrXUB u_XFQEuT.j-G2f u@;CHmV%Ё(K r24{%Q7թqXlB4`K7mL)z:4J83ONwQfyUVc5{W{ {7gREۤT"fo "S=Zj͇p!ƺuj!tzhg3oAPΝ+swT"I.ޭ޳|!q%h eG .6άbaUZAL6ػ[C,#d _Qô~۬[cnGx/86;=pW"E[}0aԝ`-2/7ƫ[ &-uz(2(Smޒ l϶]4-׉6EԧrL0:T| A])~ﺺ~7̩s9ロUV}"m^oo&o1N u&||cp1G/ I'ndɠPtXk,_$+lP?4tATP*>^T3@ j%d{=-}+Ǘ^ jRtL=0¨uA{XwMd NcM7ٻxcػ{っ^:xJ؃,B3݇0 -_G#а`8"H0>V'{5lpp>Rd_ՇV Z }(>_]>,ԩS(f?ҕ 裏f|~sg}믿~H$ 뮻.c[y^|ֆ<}هK/#M9Q"mK(F_cr9p饗ko|EE&!oC)<,_O>C߲ V\IOOV{[+yD"̛7_׬[nTUm)TZ[~–sy:?-L(F /!ooz\)'G"I0ivF{{;r \pׯ8BmvD2ZA*===~! :NB}Q?x:(~<ϭa̝;_mۮGbvP(O=Tz!V\Ul;mmmXZkeP+\{|K_?!_bUb >L(BwYg~(.eY\ZuE(zch8;/wz-Wg"ciQo[x.$Ywe,kwX]/c)gkqlCh'%UT:N"iQAH%@)\:(bk*Q.. eGiL3ARYY\r%[N*AU}6I34iޕyP| GHm̘18x7oӦMc[ի93HBd2I6e̘1-߹ xꩧFl ׭[7{i;~BduC*Mpr,_ ,=O~qg~C6\fNf‹axMdEevTPTC~#4G$F |[h4_k7"뿹<# aI# 1ػ&$~:TM7_"$p:jlN[{"g!п)cƓq͢'4$~'`wp>' )H_ގhPi } 7d-?IC0B-}疱fTq@nԍjfWgzF|ňA~G,_&tA]Z.Ad`]9,Q \^}=uM񻒄mǹc΍̶T!sUSd5`H_nc^7Oz&گnR<#O [Vq8$oMS>Dlot~b $RRqT*}qT9:3@ImGeDތSe`ϱyb>]@ߤz"EQa cJ_E8($%D#i0/Ga7 .?KR!sILDD`\AE@%,c(e &[5!v16ʑ%TGXlNt&:O!zԢM,v;>_$UnbfvK9p .x\{ K_\ZωiSP"!~cBߠTJʸa&L͹~~ D.]g믿>,3{lxgsaL0y2ꫯ{O08{}E1SU\..x(=W[jU8m_Yj$S>:y.'W̱|RƬx{j6623IVd>6P2tmll<_Brmb !z^KHRP=0+!N{6qv7 ?USJF+9ۈ1[Fj ]U:3lGT6u*+PES=B߇/~VM`i}ɠeI{4ZUI_F1̥&^G2JQXVcL17E-h $BnLz$$6NdmNMPb%i\ԕ;^Eߚ$X}Q<'OjF2hL U`_ř١M'}S;z< Ϗ9%Y6չ2wP>H(j_+Wf,{( BOԏ]lp IW{T7k$;A_#@ݓػK ?KR xG:*Lsa:ÃRenhPhgWK.;) WZ)2E|5ڔVލX{HķB[#Ev@Kp`ϲ9]ow//"ZSF}]w~c:TWl/ȼm9mvbW.կ~)߾,X׾5z|_nwaz{{1cFycĎIENy\U>wtBHJB!a" .8QQ2 2:A!d@X d_:~TWt:I2"]Uܳ|spm~;R1LDF`nPx! r wūFۍu"{aDIi]tOfSn*fmVBgb48 8G$"Αk H5m>Qj^lr }jyd< uC3 i.0C}Zc̺BBc^Y5λ}/O;jN6mb̙3k׎Ho2srٳypD"QJ>3я~4" p1ǰ~z~>bdss3;wPHKM!pDB٫>s^pH|wWMĵ 4}hMxWf:L撶̰HP֛R<+O`CsH]Y|^KܙwĞ2(w16u! ,A<  [Μs9oW)<k #96A*@Iа-= K ; _ 4"r{I e9DFsD@!hYoxf  >QY ^7~i xC,"0JW .2[q+J)gDd5oHUĝ,Aɨ\.!,)jǪ6ff!q'} 08G8l]g"!a%xsjc3JZ5 GY2ly$2 Nz }8^Χ \.&yͪ>Mi^N0㥸p3J]JEiqRi|>T4McҤI躾-ގ}7YfMb/R8|l6˔)S8餓[oW\q7x#SNee%KFYv-7n\.GXq@`&\pv[]>ЭH$B__8*q|[.omt4||z#&lfM\ y | }n I4G#fIx~THaV, $:0+}.G-rȉj޳^S'wz6k л ʠ41nO8z{ZNÝee*BD A{d>ީDEDalOO*PxW^Nlީjtg7x\Ce xҤE̘~<׷ ۷K/WdZciL+4C\ܣm:}z>R!!~L;Tֲ\G:"OG^ƃT@ci %P"'HJ:c/-QV0_E54K~_ms:F|gNCH`3!`sUNNs_x{C p:`2wf?^~7^TĈݫkz#jʵ:T;}QMo7F0fNշWʊ<\e4ezHMDAo# >6<^:`(G. A3 Lb cYLW-A `\gnz",ieBQSViiAeM [uZصF8.-$fJƁH0ޭ['[[x!7~Fr}YT.""."MDQ0YM}4:::0@P੧bҥ<{=޶mrLl6m477sWƣ>ʇ?J,X,af̘s=e]_g?Y:::۹{k~0 Dd2I"!/'tB8y)1-Ј#UZ6jrGX]Ħ Rܦ%D6EhEj+ @Co6 7t # 퀱|Bq47 GߡkY<NoܠqUKe^oh;GX?hy(}_] =@gd}_4c=[$J0_2Upq: D}.z1:NTY%ߋ>,- ʨP 56迈c`,qV̥+ D/֟mԽGcgx|AZf3@e]n=ո' eml=)ʩΆd_h֧M ղ{/ {,G'iJk~Cu=@߼6 >bDUEkgca#DhnrI?c~?5*u#͒f˒.XV<˗/'n(Ǚ0a혦ɲeˈbwq<3l߾L&77L<穧b̛7۷yf|<Bb 455L&9cַw}=yD qIʇ<Zέ/^~[֯=~m4qdNd01mn|ѬYd(N'0BCbȲaѰ{l8[#ҽlAt7>+w""ܣ`C^Jy3k,^z%;xϲexٰaCwÆ B/~|3vm7Bp6_& 'ptpT{w:'FOк5ƀ= u4,`$- IXDZS,lQF>;-2py/O}v9r{Ȳ`N>dy۶mcl۶.n&Nʅ^c=FGGGYG}nm۶_~=ׯۮկ~QF"*˒N+o7(G]IgQ-)r~GXFhR 2P_~'xUV .+Y>VZŊ+xwS. _G&s5?=ܳUV1i$.r,Y?>͒d0MZZZQ o1ljjs䄄m0 cmR[~2"@h=UC*K7x;|CT yMidI}bpfD#z59Ķxf *$3 h[uTMEw$ͅv0ѠK-"N9DQ<ҟ-gpq;32W˛IޔZ gC -α#Cȟ[K/SթJ h;t%AgC7jl.sh$=s17K Ke<dDEL߿p\GGD).*Q_v"D6\BvבJ: Sl͞>ЁޭS<-O02Vз8䁆T |o*½ʲ]C1e!54@{^` #ÐB>4|V.¶{#!d }AJz ϞWě"5RmpJX/Pߡ*KMwjh9[S>( >sD^576Z{ûvP&ٕIuseTQ@H12$dւu]-~rرL&GᤓNbժU{,+sWpGs]wq饗dWoo}+MӘ:uj3gr's%{/a`.r~ʕ+>W_}5]t?8F.k.vM{{;{38s)p.2%Qw}/9 f-6.y٧RS@r.,|$tHl +sz*D-E'١MtOV}?sɂ%`lVd(!Gy`x.4<؜i oʠ:bS@i4|1dUڰr۸.uNAvOO J "}A񚲠|wW U˫7e('KxZG{ _ޡ.NgM 0b`kmDeyThrIcy8Z ~"Dbl6o mXkJpa\@6/FB;f.߾ۊ%IzP񜟭5ٗH$œ9s=z4 hF:h4GUW]W_2L䮻;Dc~a.P3i$f̘7X>kk "Xf k̳֬|~8z++W[}v477c&L7x;wE[[MM[p4MaIe5MZF :XRq}^y&L-[ʟS\r!|hٿvFg5FA,PcSɘ4%b2n]~ A{->z:J([d*r (j^ Kd(˪r +<\g?Ve=ҟvp~(Rñ}W$ӟC yO_6zy9&wrg:L( ňW ₩ @/9*~e g䐦Jdlw'˾_Ґ}CZ #v=:)s7%, Wlc}ހI3(\jS˒L7YDm\KЫ?K3 .Msvhi sUnqg8d>Տ4dّW֠+F_ EFa=U.5rZ(j}:_'ʝ?;KqY>${ l$ͮ9Yh42{<epUC񻓘/Y$$~B.BJEu'@B d DU@ldF6iL<M;å^wڵk-[}y99a y ,`Ŋ?ضm^z)w&M>dž,Kup;/R~zlڴy$D"{{=f8ٶ͛}i$qAϩ `RH)#>I)8Xrݱ3r΅ܣD3֍J1>\hJAOȽ'sl1/֫.څF%0h]j,˽'Cqqo.uSAY`-VW>\_rL(N?/s|J%2KAx{ZFJcsзWkWIWrlB)I$i|(y\20lC\2X5P% ?Si̕}H!+(H\sMMH$|7.8 4_|1Fs_: =АL:N;/~uկ~{^駟Nkk+>`J5jPJɶmFTZ(H:@P](}W-7R6R`ɲ|Q^h hANk]Pb[q|'zp(#(kok4Kfr_|1cǎ0 ?;w2jԨ4o~y{%1)ŋp\r)tV^mcL7[,9as:Kʚņwя~h4֭[֬YS>wɒ%w}5O.c4RTjD 8er'uUq;w)T!&a#lC3n2g3aw ǚUL7CfYÙܿ|'^vP ⯌/SFZ}_b۱m|ylذ}sO|+7˗seo0j(֮]˛ozIRE`0Fl5^JA_C3>z4YA2Yc"DZ4EdRKk5rI!aOrj8qѨ;Msyx A`WD&3&6QGo%<okswzhZ]`33\UW>̊)݂g^?(Xƫ!G3JB+},H{21P= KS"K~h R`ۈvyov8f̘A__\sMYX8|_n㪫²,|I~ʋ/H49),2DZ+KKȀD=Cjңbuwhh%Kz^ͱ͑sZZ8琉f'?cy9Ø6myg}6wqH)ٺu+7t1vr֒ᠿ|>Ϙ1c644ۻ:)Cߢ,@J<@ Jꢡ*YKR\R$mc>a 8#Xu@Α6AS(K}TÛDZEB-P<>l:uWj;Ze<^TY]uBD7 *Gpvb,UՈ< ɘ>&6&xS :ma;sP׬)L6AKK/kyw^h9ٛ,.*leLR8;+ @ 4;AWJ^\; i&['v⼯AYd,^Z$XO.dp/ 3>@W 5"惡md< V%лuCmd^RDԉߓ D%xJo;ÁXmiw3FV`Ocf7!ZUwɲ7^PD6IDvoP<=OЛ{$ʶ;!8GpOix;O!@Ukh9}7HKwI&W_}'o[rsA`:n8LN>d*wI:GJ :&h}ع ֆih`ƍNܺ1,ZJA]Ԗ2 `CG=D[:/j'Ptp.[lnj |^CC}}}#fYt<S s=t%|+` I$].cehNJxgȄd]5hbPnEjlw$ߝG̳qg:beQQcSeJ},J%YHRd/I'NZ-m6L$ֹt|"d }Iޚ*AU h9brqcƢHx%Y3B ZV#򭱱]{e 3qKߩ+W- X "VHHFDV#__@a_I0 qOSsm#‹2xkBS"2QI(/9&9GKkD) [#&Jtu>`/>t_U чcX{r+qI~OpY"[|) # eG0*0I*׀.D#(M@,^`T b;uD㞉()Oy:7o/ֽ}rg P_xEa2be['1vD؃b,|bHvIƐ{h6H:۶͎;m=!3&NjM :^TEK]EJ[G>.;4/^L&ᥗ^]g|bvi\%NN`͚5eӦM{NDur!R2)3Cm;c6#BmW/w8R0u]ܘhOmېT@4(J"0KdLy$֣v!>b+X/Ej or;JPsњ~Ocl0OB@B:}_&wC\Cwj4h H]^-"G=ǁ$6ڈ&w IDAT. SY[0|\O8ˑ:F7ET7-|1 9i.f$vCT$z7_&*:BE;IܜzU{9 luY7{lojjvڱA&s"Ce#Hf#=bY .ի L,H)y'ʇ=)O׹_O.cʕ5'IDR!~Ch7 ;a,C:ћo)Ѷr:G>ຮF44!5 誽V!jVp!No~Yߗ{HwȪZTYRD4gLXqw΍)߂t"tl ,Ƕ?!tO0$䨽<(ot]!h/j| z~IfMF|\o\z"M[r${Pgѳ7 ɀOS!B,_\}[YI (aIC˺4"KxLH@7r! qIw#Uɠ_yUs{Nt#<IG3]hyW3A7PEWzp56niJEU1Jۻ bl7h%iYX#wq! K@JH2I쾄!C;ъd(4_hƐB yALᇍ|zg8e^ӕ?-Wd@]י0aB3(23@ Eo,\.Ǯ]\σII=khZq &OD5E'z1/d=1x<}F"f_`f/+?]c1;k %uϯqQϋKO@nXOa=>zn~q3;{vߖe1z!U;p HAD!кutW\%!h } ¾yWرMýD7pmq;=3^ދ <ޫVDdT~%IJTiS#TMOߏUߛ%v.4o|R[M>/NxΛ}'`'8>p1Xp3PSK?/Wi71^6Ҕt߬mʈD+w4_eT'BM_jUne>7c642Ǟ_ThK硦i`]9܃Wo~h9WNZ.oG|wB]BJWB OKN_ya8%?!e1,8pP/ٺu !t=voQtevs,3712>{>ȉ(3 `Zo|'P[R>3 d~ˁ"ɔg_5!xy|%ĭ+tM~ȁ`2T[Hk+m;pF)}=ORR#Z@Y>dه3c.*bWjn؁={*hd\ bh|`Xml䆷6n_8ܹLjF<1_@)%SQ[[3>pJGo Vc7m:;=r3m:`ȹbΝvaI477אoǞ,T|),2QMАmB  D@ 9` T/C}BF pMo-zu_g2DD>F!|:!i]tvv]v1z!nJ&# /0x替5\i5ݹqmc9t!\. ג=AmTE^#Zk@ZqNM!(ҥѻZ5#3uiVLTSFO&Q/2ԄӛY!͝8my6ZZ#:GWTg#XkkC㊸9*:tem4wK5{G_qNP+)PXal06ǝ`/,`?Jx{`OF~K;!ɟ v zgAcPߓ- 2QNtbSwTgl]X !eӵ^XĝNjtvv2}t^~e%v)?V0)X55o_?Θ5NYJ*S>Vv ƀYJ)&+D0Ȝ xr&yr /LT SW`i%2[K VŸn&ձE[[۰"ȈO @Y f}q8r5RG0_ rMUHN#dG%L󥒎0696Qte",Carjl6FĺK JZAW(.MBWw51sEiNwT!H c}m;-. Zy-!F!CwC F5Id@軍rY2!}0C{z;!tL\߯d@??B#P8vU,ɟ|*#mOS8;@NZ 'ՖUsR8ROhr;u T; {]rQ;~Po岈@iK&ŠQP)'O8Has3#krMuCeߗ%seL7|',Ej%T0Q 5f: mB,|Y#gWTʑ$H/t?'wI?Ia<-(i.!r RAN{uU/ծ6:!z-~ '(Pxr!T}|')Od Њ'0_ŧO, NV8чȈxbȚ1FK \1s"kb5u(1^{wEb+!J?},F8f&MZrY34ւˊ= Į#$1"EpЈzxG"zQ鐚ҪiB::bT|NS z@)XPQFKuLY9 jtc&>GY#a3)mx\m۶1}t4CSDkAYjB~R#h4OX صk~ T7D|3x׼ \ 4$"=;`"Xs^b By0 /uOG@{eW^2-K[u?  ol} ]G,Yn=ԖwqiD{]O': '0^)wB4kRH)wKF4OX'*8t@@tu kmY9BW=pѶDW4ܙ͟nS/JY ޤ16j+QKuzm AC7$O0~dD!xԢ~=ҶY֟j9Daq}Ntu[ǙkVKe-YR!'06닌{mUbE-)DV({|0NlU}sKqIܔ\Wۘ> c $I̷2D:<%v_ޏ٨]82!.w$!U{p0q]?Ef-`Y,'8mM$s^ƙI#mKKkutAJ=Qgo]4~ȳQ\35_BR{@>ѬΩ0sgU֖D%ͳ*vR3 ,BRJ:;;CDu_yRK3~Y7VY( 0`ChK`ӊia+_]2(c gk$tLJ}0ы<cKɢߡl%ZZ$`===I^; 2RY8_MC"5fDx1$?{oGU'IrRH@HB U\{/`AT)bzQA xEDzr9i6{B{|>'3f̚y>/vf)ȣg, F>Oݹ{KV{\r SeEϋ^GruҸ};ܿg('|Ma ;i0(y6H;NЦe$EW >@~b{CxrR}v(fȿ/G'1's\J}`nbšf9<!eG~:L(}XayXq']PeY )+#e%$[BV 8GvFS^,`>ZD6$DF( '܍nGl DTBLL 9/cM7msKh I+hakv.yDL,<֋jE[mc N !@)U9%;66 VRzN9YmSeX&q( .=$^a8m/dL9m1/z+϶rJǕ^WVQ p,x&WF8BaOx/=%Z!dԐB 1}.-Ty}˾e?2$65UWKˮ71[7#zD7X !r 3+adaI !k`0sA,USMi 8Y U]N(Nʠ-^ﰐ32Rm7-Qɇ+ $' _Au$eڦd5ym*,we$ƻ3h=E+ګRBf>b !ɣfO8إ ^qv'pT7PFTR_smjH)'a >H9URuJ @8DV&4t [m%LsZQ(5})o~VîTh:'Wx!?sC7y xC*A VEnt>pd>rL5?n|9s ҟIV$$gjAWp8FOst+"'M{J.1ptZEUGy%FdH NC IRsrOʤ>!$b_ks)=9>'ǚqhWU"W%r?4O/#(j1~UR~O68F~rZ 8çʤ"ж ٶ `GG^z:7+S(\AvΥx/EUPUEU?UAUUEAԽW[B !J2 ,_s9bε~yYy{)v(Ylh[uͶ,Vf\78>`rP(D$`жw'ITR9&qwMϥ.r'uTe.I`uU%9)_ w?x[Q?q٨+1Zѿ|~u(4 <ϗ.y=gx˯ػ!71h!黉|GJOFQTUq )TNeՀZ+]ɗX&bM1!@ [՜H;ObƉ!}]]oWEmƴ2~6`|PQ?b.XB{wZ_&z>\dJԔw5!{:k2|O?_NqI,LV,iJvH һ3ɛWUл *@$vw0fZnX~&28~5A\݀jyڇύk#SϖfI,?9IW>$9?5̤.#PW_u)Ru S%b}"F/#X=ur!wg?Ɯo!A@$xk!818ha3襍;l+יYߴǹ;SͶm |WHX93USڞmB6FWK(OyTU(X>xrmІl]yѫWz*[ԭ*YhKRxPVحI+FzԒl6K0aq@ @>ߨWmx}eڒipuFɜkw_A7vWBR"reyEiyqK}-, ~xP<@$_)WpD"~: )-a~%ER_LT/xSrُH^'ٞ@<{4U8"OiW]#-*4 IDATH${vƯF[UӦQ84iMjq% L!~J],93Q)}?M.#d'<>XrX8:?VEDK(J<`mt~,HweI^:F})Y]?DjuJ +ǹP8$`ln쩓u*cZza[ֿRI[(% ;qY[Qg)& !RJC-;2}[ذb72%3ՎiN5D~ڭ#}@_?hd\ u^^Exh h}'Q7@~?*ʑ;5CpmM$3ocNC~yDN=Y4d=H^Gx"߭a%URh[^ȶm+sSLK{T,uSjM 1T;E]ݔ-5%X#(\ DNjR)mY^ r 5[_9/ Tk|/A*H")hRcG#ga,Љ_5LxU4~jaM5|2 iiִ%,q&/$PU?6VĦp#4lf5J cs>ى>r^f{i""('O8rr7ǜiJ>BFwoQ41 fNQG}&`Q!Dglf$'gm-!'r\~ Ev/7;Hj6ɶY}'8J4Ͳ]@8jON|_۶=&]tx/F2=5-|nj%mx"(XLGĖ|*7˅ aI\8o*%6c6#sdN.Ѫ栫&; "!'Me;=:S :cW z} HTlj6qJM3Bn%,gv7GRv-K㜙 6!&ǻ>;h/+2zr'1ީg}/N/ExuœcTSecuX(}*ꖪYK_ZDD HB쮝Qw)*cn Va!y}N"fĘ#ιx^"e%Aɒ[1(H W7>R'ŏiS;69 xK]P+Rι?c.gn~&fY- zGe'z뇉 |_K8+W|M:_y@Lid/ۧ"S&y'TD[w0-k_DLDz7ʂς 5s3KX%;Nyt=w9a7` Bќ5A^+UX,zE& 2^wRFç?i0w}7f ?+W*9/;h99YK)քk/:bJBf4]0g8׭/.bth[5<[HEsį!|K%v`iz5s U[ݬ!<2δn A{CiKD%cb6 `6fԜbbM7Q }i)'nTQK[9ϡjc #EyK<&G{ÃRcy^O9өKN*V1WTm׼H ePEd?%= [jHk;ǚa`kƻHY@q״TUI) %ؑj]ʞ ɂaXX="lQMg4-v)/dԚrX&R^FNNSLg`kGvR?y$^P;}4a1U1Ȝn8 /( 4JXX.Ir^nUkHT}[Nak.H3O'pXsI {!57= #~Ov a6rB zL(Gշ}}}/+wW[ePa)c[1{Jl%()X=&r)cbXx_tmJʘζ_˰%6X=& s? ,(/%f[Px^4<cבeH%8q2Y=vy{ȏ):ǘ!du-Q(xG+SLok_/*Y?`H_y?˲Q\=5ƅ9U) :+U೪(ȥߡsg?bol[ΔnEQ+r2kynlQ|n f]&V Did?!osbĠ˿M¶Eu]N%yIwRO:::ꬵg_7<<ܐw"$ /iӦq%`&p:huatw vnaƍ:o|t}NHo$I Uk+$YmZk"՝-r5+$>]%5uǯ[sMe+tYlgrAJϙrt~:(JPhO !v)۬L)lհo]=to~z _*o֭ u Qosַ K=^)Nr94 |"g?!~;wuW>9V atё;lv#TpZ3jceZ?P؜iG߬m(φCU녶^#v5n'Ǝ-uA}) Y"(~pB '31&eZ?nӂ^S> <4Ԛa$ $;jc:^/ŀe\C3˅cshEȜpd-OCwj+"GcVSuJPz~{Ӑ]\=Dp[tCؗSl2 G.㓟$wr _|vE}XB$I"W!$qGXZ.{D׼NL ۑUFzqNJjI]h0qIa-9֫kLƷl|O>[ye6ˮ^?+⸹a6XUU4M0N__袋ذaC2[[nU6ڐi?U&80EJ4_Zq,K&IqEE)W4l/cYH?O,ɯa=h*N?rxj%bmE}&+۪z۩Mbg!}Qx.GmWF! 'di.kE]]Ӧ`!WJ/E G|"dH}j\3nuJaM9Dfg ) VkD/kQJr27D u.I/.yGgNCznz oc,-Lh?9)?&KǺ;bWt{7RJFCd>b}t?SЗ VIQ$~(mua"W9 8r~΃e.0H}!AG}mk>ZЁVM|.釻+:v/_alyl 8v!7ߴd|^&0)Y%߭7ONN홏{,? vE ~ی9@GPFDPP<@3yJ|,ƾ:`w.]L`a:?#]8&G2fӻGt9/rJibCa_eKr7s7r=[-_=&mt5bxb-:"(EJR=YFlɺ2WGUKǯiih`8n )m]s A26w ?̸|AYbOË#`-|$-_:VOfY3nrB= җ8i94bd!枱[96|M̹f^F-,m8V1d3Z`>_#kaU1GÍv#_`3 nQ?!t}{$.ſlr=o$(bkrhlW6dNc7|*$GP*i;c8Baw0$oЎˑ;9CQ%yُ% AyҟLUۡ&wj 9uZ.j`ABG _u8l 95/pF9Lj{#vi^iM睄2TzXfcauZޗ'wRv50wuF9_`M(C_Z}AoaۭNv <Xe3tw|9`%4}_3s I#Ҹ)ȓR3oR89+WNc׿5 .l(#2>oSŵhfƤD%I5B9x ]Wze$j>-[OjK~ uˮ2M EnwToZXW8NG]3˾=bTWtL? L3躾C8>$~^WU Y na!ʠ:VI;t>ڍ_u/}ʱ dXd$~Afa}OCyqƫӝhoOM@qėh|W9`e@qNrOpƻӦ/ 1]GT;dS\^ 8-0dH!'e?jlaBl6P'$K4۵;- G}9h=go6muƻ℮knk|ˍϩ WDDl߆[w\3xHc罘b#@ Z(i׍Q7aG'dL+6Ud!9Vm Y 9S0aR8*Oљm( s_~*Jqc,=G{ՃqP1E勱6MN1re˥^J*ke͚59o ۣ%FχqT!ЂAz=9sy7Tc.=ROHsy+U-“ MMaʼnN#K ;ȟ~:wGe MWdA{b}2 <4,x*wypcM.ô[]TgӦ=2$Q伌p/<~kY4EQ]$2,lSV{Da }tc

'#$Ab1A8`{|,ςFYAL[6$A~TA{~/? ed2iZlKB4Y~m*bKwlV8b1#T5lժmZn*5G,Gӑb@X@M%24`ٮY3AL @ (ʌy4K'SL<'/auE&%̄AJc#A@i[6?'*$+j*߮O2(,mBBIH2z@+h<ۆp]%Y }+I5PMNmsJ XkrG  S7! ̴x3-'yX_B+T|v"w+`=C%6 ٓWJk!&LW;Wr\Et^c[̒o#.P+n!J ~mJBT'paknh}RRz0M+e5BVŠX$ƓȞLt.Jl6ylXa;imǻgY?j[-Ԣ[ Z԰<d|Yq|Rr@–-1,K\ ꈆ0Z؛,AooE+UManMc%D[ U nt 4鷗g >.*X2,I%YhP!ɴN!d1 X(*/WNeXOAi,"h7P/ FC+ y^WK$_0c22:db["fYQeEQ@R%=Esmj)wvl}yJϫHWnSoDݢsSdJ^"bk F׼]a5qzk}l/UCe>op}199y^?ίUINС,dX,.,"#fPT(Er)I(B1uW*jq?UsI RHBJHhF TK>a6eL)RN'l)o;΍=Uػw׹?YS.m% du=D˭u!#kH֮ڤo}2Wgk$[VWe,  7p/p-p]wK/N~EEm̙?  ;2ZY# HQxy`aᨎ5_}_ņU*qnDE_Gn:4!5D #T ChlbLoy~PŸ7XWӮsXdLrBVlh+ٽkaP!{5눛[!~nM]zbn3fדZ!gR{ꫯްH$x{߻!O$$$GB+k!RF@[ظPe%*‚̇(w!2ͿҊT <ϾiTW傿DMƌ䫓?6ld (j*KdzE.'>IZQLT0}aCq5WAB*w>K]aprhvꏁI( S2L8_ 3 (N Jabci79=]{#H=r#V {VSemG0p&U"'/̦{Qj/'Qr/qvYU8_p _i׹w~uw+R+[W LP'.5 DkH8Eֺחi`[M `/!=yφft­G?ޏM~B >->VZAe]YZ}6'{,lp҃HM 26Yw9v7ϒLr՞1}o:@COi=KT,ei}ra1jm7m IDATu$޷7N3<..vGU3[ dkW4 6nQzciN&M.rpoETޑ~zNlB}9 ?G1⟜p.Cuj,ŗyp4b8S7<dܷ.lK"q Ix~ ݗvқB{)gRys`ܾJKa}CI2Hw+ =ŭ{g ^f "d)(zO ihPi3dԍ#.elz̨eEZUK|:1fE:DMOtBq<գig>3O2-x]ZW59%I^D+hlgqmwÒ@p $Ivݱ=DR,fL8GcT-Ldw/=\ly+`1=E!_} :NBE:BM(=3mi8% 8FX[6y} m: bo9`_D>^gYjZb|pRg>(l+K=ܛ{I,zϏse?A'@0ZXe4]Gaz%w # bȏ*hY7%&ey$5O=cEI}BGȣ}KG.|)-GĹ8]f7ORNƉ۴_;E 2MBB,lĎN^ؒg,&mN=h h 4P! Nv۠XgNĥ}/zl˾p##f+yO!"S(zi.+P T!?I`/wuZo]QoQ:E*mT !qW(=*q@iU0 avb;*R fl&}Emvd6J"D:5wPAفE.:fjlN)4żDw*15!}gIɶ8=u&tvvr5ל^GkEl3ؿ>˭{J̹.^FyMl[ X;:WoKsguEwD넝$Io>CAv<ҔّOP|j%xGzpKTv,4OQBy_sm擩5w ٵA9_dB`ϊ`MQ `yo&lM03c 0+X:m?yNl9N&:'V3*m읠ͳ'5'koiL`m1\Ga11Ǹ.s<~zuYll]ss AI:H"~Ez (gq(x"lFr 鷱n*mq f.EWx3&k(,_%h7U*ȜF`EaIL=np5w^Bx*X=0qstv1黂k!]!e`S65!.9 "m¯Kr_1qvn@0RJg? v2amE!K.kb=FC HV0R@>⪨0SdIv}uɾ[TzO]le[z$|2ktLMgw/ :1@ 5(6=A/͞#\!Os4ztܮPy\ Joca/v| YQ=%PkWȚ`y/$G+asp.y%LwO1iᾆ.Ǘ$˱}uf|k2QQ&ys)!C7ڈ\/џ0pmno߆wZ ɒP?nw=Xnd/_LAs`Y,;fg ?%w]$fn$r4Jt4N`)D(4ef@$COo2 rC"H~ߗݱH"9ok^qVj2`8]R7z֠{ʷ-^0Cj43 j'8vf`)'c3Ak5,ޖK Bm3wb rL\0̶)z~ 2 !֎1̦2)О&g/A ,\Ɯi>Ads,`S󕁑]Wb!k*. őAHnp5w1̒ÒKL W&%H;A׌e}1J2}U[pҥEZlی!20?I)bq,"}31I 2NUD"?$\Bw=JRgɞblMy  |I8pnN;Eg[Bn9֋H;~3X0wq;XEKKvvoIf]L<)N5"Nlpt';M%&3u7(Bt*FxU~Q̀rh#Ql {Yb=,&UW鉫DM gvRKEgcd`l@P)$;0d2rD5b&„_+\]fQ_`'Z'z12qf"d7UB̍ZcDb6P\Տv}rc]ed2>0ȖaZfZ RwsLS iG%5"a*&S-z%wZPY^i{7-S_,Bʂ VM~7<E4I|w}6KMO$!!el,_`^#0 $?Qu?7䰸}QTdPw7P^g6GnFd2T蒁#l%8(B!,ETݾdS&~4H5cп0@-T) 3Շ/fj"l̶ʮLowY)R `pߏBJft$fM>D fB"hDƣ`])&׸/Nb 5 0d(V눃X}N]gY$!I2*H C `mJ`h\K`.7PK1U:nt>M\I`&fƂh($@F**HH yFA$ {*./t 3M`b31B`1ӉGW)&m4l&"QqV/1\t (IWZgO896[oStSQ\\Q:FHGF<^_:fcO8Dr1cnb i*$(@Ԍmwl[!13:[Tew=Pa(' 5O_%l-_m#0čU ɖ?\Ch,L0K((f;vyN}9DPɫB?n!Ss*?,KY $nSU `{eE),!wwIv%IY)bzAO+]1#&TyyH%9d;=3~an3d@y_-!3XNj+#%VJĭ$RxW>F|,~뮻=_|K_:? t}_{&>B<Ô AK4B[B4'i7Y:ϫxNW2pDpY%훶~pBdL0bf"IN7[+W$jI _7ic+IT'֫ohDŽU\Oc#OU9.4)!5L ّO*^E-VuqTSPgE`7H.'87 N ;Ȳ ЖtWNJ+V+]? IЗ3 YzݾXuA[H,u6Q7Z gZфW|ᆻ ^iuP2J6SrAFtS'`D "'tgk+{xARP_\ŎHReo͘p !ǶTlnEDDC^Pq,pR o=<٫$Ω/@ww7\rɹmG>7|XLM-`0hQcv_5h FFou:YÚu!~WXg5Wb͍C"ફ8}kc8Ȯ@_ePCoЧ":^W7LDj@&q\#{iE ZVS=S{ `6B@s rl2BJ~]r$"oЮaeam*4עENZ3~IrvFX(3.3h4S[/$G(fQ^[L/7ޢI DD BĔ֍WBXw  b)h=J kIO伂Rj- kIy-HN{>\Fe+ʓ0ʆO}9̑ٚF)õqBv&k›,\ Vn'/Ρ.3[ؤ53Ə<_"h踽BӢ^o̙.L8Ȯo!>X!yVbqp7Ԗ?܏SٍbdfLoC ]o:=6:M$-`Z"5q 0[>]v( ,11e ۵a/K:T$f~qKTV+(\c{ MNeVG/116f+={-Q 'pՁ/m&v8у1ZWr Iy@L4sCOl>a&EO;h.4T2\Ŵ|)k']$t(B7ic,_l+3=:C^kQYb,V$vgW[YGW~ŸhuY}DeT1vCR`ìU('Yp]]V-7(~[O~3l:FmHTW i'd =쓈@8OB"11\%- [ [;l(rfkx/'3aRp@$s< T DF \d (m RXMgӜJ:QC5sQGX]MdEs)]EkI`gm^GϢ)r-|<0\8yJvS o)軺6&Ͼ) )\/k Yb-W)k =ŗ+Cc]82$@d$C"T 4M Lw{c[$8d߻[p 1:OHf ; X&:o閏(VԤHm&Lm P 0tЯ+e:Ε&GU*}ٕ9K|1a;&ӿ4RPhXo4X"\kiΙ!(bl7hwi џ;n#CMa v|n7joYn% qr z&z-i.2cĂq=-4hByLCA…F?4%4-4}$o6n=Hn2Qiyy'T^KחB`m7QT9uT%YMg1ɽ{\$B Կhy}.{}KFU + Ԉl/e! ~?f p^ocDyIAH~gy7:@mpi{M+xnqp_$|,Ap9HhEج}Mhoiy?G;l :!9kw7-GK=hk!A?bO8u.̠I<:Z_zDCe9g"հ~hߕT)@nUW`v #mI994٤*h3;XY}Y2#\f[1$ЄG:bh9u/TV>t9}nms8U?3=́mTA r28shG@LmFi X ۧpm_'BB`-ďM"=X muOGXE!a" ih*m#&zũC\8܊6 :|B#JX#IJDW-౻wQ(-_|`1e͆U$Ɇ +K*zʩzh*! 2R*R{Fx$D> P[:$--dDx.Q3oBO4!n:UJ6j`4 d ƛ|U$hUU휈(Jx)8Xy\9՘RS 5z{3vhU-1R]qn&Ɍ"z+ 7y7@CYQwu>$׃ ɱ}{hͬ(zs}N'ss ]y;+g?9E8{?+4+Oɿ 875 4BS*PuX | a~O՟>A, r"%rzp-E<˛@}_GS(^Th=ȮSlȒ-Ӎ * *0 6zi&)l#0 cXq)Jh #MKG\F.f`閚Ql>~p1]ͳ͋Ftf.p1z5~ Q8Kg^ݳ 0w[Aw;K//Tx}\۔C T`TE5r=9d %\L (AeCY%>;|Fh%D/ >i` /ѯV=UH-mDnDh;[תD A, wIRXzJ0 O.owg?'l߾o|gvvyoIw[pmD7Ǔ$3.dQ2pT_Ee}Lc_TM7?s0P$0CjvuŰ&&~Ƈ :7& bg,02iaS9Xu('4,#(X5y+HSBʩZJ:.g$gW*UȠx"~:Ȉ[뫄Fj5mVGϫ.uȠB PaMmM\h;r`ADftm57|VEˑ֦bI+Zl#KmQx{IZs)="AǁΦ, IUj4~8A QruRO6G!E\PD $Wee4[U3 ̩&Vxz"fM_nyJ" 8rl *2#&֏S(+)tW HވȣWu qYY|h *hԁX]B° .Ўiߵ1v/`GU)ۿYD\"U jj" )IV5E%^2F~[?p5v}`܎|qj؅V]F|eRV/HRd慺*%7W)r&}OхڻF4G{ jlq35<ߣ25škjDLd$R+!\/;u#>ŋWUq*:>,q։qgpg͆RE N!?!?!)rjy`۶m111qW!G3>2Fh.]7͉Tzʄ-!DfVL){վ*YdIHtnOػeIpHbo->5yAc6H~됅Kvநv2 %f Uc3k5}@َs?sNLL ,m1~JwEž*ppo%]*]:)%[f(*PZ.W|*"3"#B;Q]WM|zhm:ְ5ѩ%k(BO1hMCf?tfWRm0z|s hdDL[V}U]Ԃ^)Mms~ zqQ^}mh\R pNS/+ M2j57OFhLMPɔd mMP^]BkpqzG{kx3x=殛fl-ר`2}no(9*5ϊntW}WߣހKsF!ha ;xi!> ؇鼷@}3< RMi Ĝ{LKx,B`ܰ5c9EOC^o6eQl{ n߀{#-T^SE%}'uU]ms38 3kLZ-f|zõ>۪>BI~ ʂ;}#INqwǥİ?C1o MYtpgdUb}9D /9̽w%zW ٞ= S!wFdN(t!@Q0 W#$#METhϴ(ٍQ];MqscҪpqnS(ŷ GwqnfI*/QjZ±Yvx)U CCC'_#ysT1ύ38=YT*xF9m6`b+aca_a_k%O@-7,= >!G!p bK'r=lvu-\;FZ!`{+Jҹ4;8bg=O4_ה%}] eH}hS@xUݙ!-!Z m'<škR}v٬{` p˰vD~e׎Mls3bc1O*{IC ,Ai9JבdJRiP(o˓deV0aHyW.GNȍM-"KcN٘m88"/}KGb%M6޻ثmLc$.4Cײ-zi/w5́ m&H YG!ect5ꉨ"ϴL ~p߽,tbqͦ',b݁ 9IeQ!a&/DF[r_@=rq~}Ͼ`CaD"Vi-!01ny+ki}IWc E!K[PnZ`Ct}οVZi:kEJMTS:tvmHZMH|Pl",aY ~&" $(Cavг9d9 "O=I*N>魍U%j tk㐅s_ Y5w\XZ9$$B[mblZtӖJalDȭ ,eU-TU4OG7 &T΋P ʒQͻIO4fQIMGT{薎u$qD'D)` |?y/Z1g-O堠BD˿剹,V͢}e EegiJ۩aS|EL:F(ܦ }NaХ$5hth$5R0)"b\ kV:_%D_xWs߰ t,J#!551 [:8 44Ѿx:w} J~"1 R)MLi"SwjL * QLus,a>Dd*%q~hc1AH![FFg4"%&6$UjY| Fؑ=Q*+3qڳ&c%FRď"#"$ BcQl!d\ =4-@iJBjIc(!aRt;еyN8=t>Ѕ,JFHCVWr!JH30,Kit`EIueu^W46ɪgWoЌQ ( i;N.n:P+Xk%rFVjn']plцD|4A|T #(Bӿ4Mk,)G6z'6Ky2R;m8AAߍIbt*kxED+/ J ǐYɑ'~QVx5ə4gQl)KkϤ;+O@GHfHsYKYo?{.k]OD$%yF :- YGCYjp>h_@"3yp#qɎa㻗xԠey6ffr+Q|lASOk=hcJNc ~"^~f㜶$p;)9GO=&87i hPI_Sj+!]۲)[eے㚌`Ϛl0ד7>gPL$K mkdӮ$2_8օYzj'aM($b =KO=$iGQUK9f.}GWv"sr7#!+݃k; 7F9S»;@={1 oo9y+xvAoMi t+Z%AkC)u 6y 8yqF*[Mi6޷)#Mߏ=(7V dCs `}E7V &fTz[qvŐ$qDfZ%Qh =dRg@qc%Ek b3=mQBONqy$Z&$`}j4|S]BO`* xƀKZ_>QZvټe#]y/F'a$Q:DF*XbA&qMӁQ- &IG!] ȅ9]>r 88 nmRnOӌ؇sޱqF%_s6lذÇv&(Uޥc^joH 0CX ")ApUp?lŮCd_[}>6GQH 3_#~|?+~s. ]>jO6r=@hk4 :FN2 v&ҧ!=d}Ks>}<#:LLQkH,\Ht>,qbaG;SC:_'Ҕ5AIk^ϸu7be $}ʧ1Ds7kƐF`ɵ̋~{1}RP&L?pZl^WV+ocRkO)+C7'lWD`m fz98nr+y k]$^_}~TGB-_D A ,j#cGU FB U 5y&W2 5;6/25,+ɞ/ `:Rk!7Ry jjD㑊u˳{x_% s~bhhr_m>ϝv ][=t_Vr B˵Ђ[r:ɡJ}ۻtY9$[򢽓8pq|͑#%rA?)56)ڜ6ڊm$ILZs^az$2l_m75'}J%,q"R1h}k/ V#rؾeZ5\Qي/QIU([e* ބG4""ZR[e&aǎ_DX{^}k9r>s͛>;Η7^,(fJp6w|*vH*PhlXB\..l_^N^D^DoE/1x^iBB%S9y.(@O._Q ,jxGPf#(KI%MY / <%l D@-])UJ&k!V9Ν;_/d|o&<_׸;⯗ l[n}{_~}8?\hvJ*0&QG,?MExGũ`)E5^I(H JT~'f(;ebAc}K6>[ǠG+7-' bZV`oSZ[S8ɲKɷ>ĭ4]WyC%%ʋS͍yua %B/ .J:;`cIwH$8b60 YfM%KRkh' 0d/fbIܔKL0]ڛT'K yA֦.ҵt s{KC)mPDhF<3hQ2q S+ߡ\.SPd h}Ӎx9ɿ,qWsWS,{{fH9ò,jnFַ~^eÆ p]cATSJ) qm[t.61yZD"6|!#^#5 )]Z$(1zL:v50Ͼm[Y6S:L ^17d旦rA)@ ITtiWY4McUgn'מ@҅L}STSU6PGV5 Aמ !Ql1`Ł"W?3>4@H?Nn<fBAqU5ǐ"YsnIo3}$rC=@tW%QI ]309w}96޵y\,-YL 6@em#ĎǰL /SJǺ._Q:wD/xCR`pU@_S,µ mU~ 񝅜.*M";MޒPakIX/Y\%]x{2VۇiɷbV-0IxPGg ;g%TԨ}FJ&hɷ}u'+>zb-\gb8ӿ5{6@ۏNcbbywLrj2ԬVB%s6rh:Rb лud\,Λp^d2r-r-DQݻy衇سg{ajs+dΝ\/ 󉫯zYPg&K!@d 4|8NvpSCr4u]{k^NJ^~P6Ȥ$l jF;Lx1 ztG2dOc;Zʪl(S~G*04Qs̒ߒ0'01IS- E~M7a8$'qC71r0vwW߇*Cef/!60t~gdFNw"3nA@!Y`Qm9e:F~2ٖ3PO a_c9ER‡rt?CCĬi}W_@/UXXoP!(\dGCt݋'C톅Hw"uw$y|2xߑ ~G/y,ƻ:l圙O5G-;>ÚE8~Bڜq~!8&@{;N.rY寛!4|H))[e*؏ňݙO"\I8zL~zBfnaGC 31GL`꺺<W1AݘGMHG+ޘ?#:hv6/90G>E|IvE>;vj*,kyNx 6p|/}K޽{o}/E`4 pZ^Ը:- FcU" 3! lֆb~.$zA';  5fWv $\bYf`Ri =C ")ڇSȮT"&ovB葎Y2F/77D&t?K>MFDLpmo{8VZƙuwc3HATab`d%!4.{`;XHS1K:`L.)21B[ާ0-4Og6UT?+^?oׄ-];KtՍq6>[*:zȘ#Ɛ$cgJ!MBr ϐ<^,FHKTP7rGUOI:wun܍576-wG :2'MJn @ogI|3Fq_ƳjE<\+(J.u]\3hYT4R:\`Jj*v;N4R!U&Rnsh'Eh)t c-ri/gcyUR 3WN3f0"ޢcv:c,.yg]R|^nƆׅR*(E"r H&Xi/.\sͲ$nHPQ pU"PEݕF؞uE>V9Q!lkO+RᵻASÈ:LvbK877޳ X0Jgo A:Ճ\6LyM%1Zc^2HVDaJrA3#DCV`&pƜ1"RSRs m\a>#1.D*sebZ̯CK fhԕD2/YVG:z#!a% uzZ 3obPe>ށVՉ/rpjȴD%fnlVV%QJ`Wmg{O. ڼ$" I5) @#B{|帱Bv8D%&!a `e)c|D_ KTV>'RLmЉk0.S됺$U\)Ki6h[Yu WK@ᓋ@:)ta$y>"V2iR㠅vXU[>}*=N+\Ef6Ls*ʕd]} &q@T>%O4.]2pI6B]t(TB2mVV{mG LXtE?9ƹ/A򛍚n.UIS'![De wjOH~~!' JRhWPU*g`dz5ZMPT)'˔29#K8!~W ַ/ǮI%II#O9RN>Ɔ6aDuP#wU/e rUٶK"AO|.ёHRӬm#'|jӗLQ g$ Y删5{Os{M&dWdqM7qK<7iJʔ="NHdI-j _Xo`oHnLXl5iV+F`:Iy5j>ѣ} Kw??5d@kt,.&Va,@=ϡ[N14l̥GO&tt8 fn 3o\*E{XCCA12چ}O(% Í{7>OܘR9 Cᢧ.׳dn\=/ ஫qxHŻN߿q?E+U&>˿ueñ{WCMB"L $ߣvٿAB&C֘E r))0&\j5FMo) pܤc X[&n*[#201:HCzTxfoKETV.O4)@ԱVY$V'hmi%<#s7twuy<{p''?y ~ ZpʍIBCk0{M^XGx"NLand ]]jG'ݫ$/uG 8nhNNn}/\?. :VRϾE,#a'M)rh8RT=;:zLp UX똶"10t]lqdF0h\O!bV u$(ز x1mςJB'4CX@"\A*&un( |a-,4U^7mjPΐ/3Ch-Lߜ6|5y֢8y$q!QDt,±p͐ g%O5[Sˎ1 =5[È薎agԕ$zf!?ԳQ!a5$,s!bNZ*6mm R`/ x1qe-kRY5I B#$̄D}ғa ѩk'r#DAD= S(**/];ҕ4VU Pr[{8Y\EKX5oJdu6cMmoG *OY63=LM5*&7L7W"}HY^Y5l|bbn#-Iw.kjhY?,F$-NҒU_o>WM d́Ky6ƺ{7 L(u|=ҩe\X\.HO9mH]*35EʬEb  303T=B-5F IDATl*'ݺM_T$]jI:ѓ eHRdO,Oȸ[t,Z6rsK_7Dki;Aj*E"|o.R&< $ O[ͺ)i1 ]q Mj^?G'KK["E>[ '3cd[hIcͺ5k]4#oc.ڗ [@Nw+smYFӣh ^<{A5~g:hhwr J*Lhd(";{bwm>_G;L-Q*"%-mH ɯ1'ze4i[0QV]Eg ͂bkÛQL0ch 1V^@}MY;CYן$'\ m<0x|H%[z'$[,zyH1HSpz[;#OP]Yi"cLoXY?`OPHs/Kh1l⾱Ԅpm@7$%?nnWh븅VЈC7Xnv-OZ ߀,dSstsT@4&OퟣwoL7,Trz&e`0!&uG(SZ4%Id2Gu0B18&-RzƖ6S*'ny uVs6yG\iHO"D@ kZ̙_up~CTo$|&8io5R! @|;N4cL3W2;1K/p㤝4SN~86š6.10H殟aMdz[YO E%S\mx;s\{W$ =ɑu)Jl<(t0xB V{+]DjNVVyZwv4];gn'҃rġt|E/ͣ2Kgpi "1 ڿE}ykV0 yJiFWKxt}ud۷Յu40<,#x%?=]%]!^HVhËy<8jF] UZY9g1ql W',}6­9:C/g酥P#om3Z̚PoB^坱1lt+ƬA53ĸ_N47yf}i 1~@Pԁ4p|1{.c5vxB[ cb~ե<Sbک) 1ONqк4 qI8G$ șspe-L#WJ#.e/v*\g$K:OgUfmwu-]M"2"(W{QQet@p@ nz׬׈XQYES-C~'+39~h.`G?=I&'܇[/=i:x]Ȥ} ,,R{crLrq1ѺuDaXJ1'SMwp'pQ?Br6~z/9w.#CIժ5$<ã]H=||T%y8mW_Gc{-WATك]Qs3n'5fj$ wMqhM\)h5S4![؇ZШh_<.lӛObWb;~;B0䚜GX''^fme*"3ӌomRȃkuIIoTS/uzWcUhU|eEbz>bNU"O\5Ʉs9{il>j8Vw2M ˵)_Ry/۠#gEj:{wٷzNJƱQ7kq@ggI9%ԡW*ʻ*C/pnױQRH#qvRHsYzo_H>fLY:TTh0 w]dg a;DLzg~[Y9V]ɂs @~=n#f|j_2z/ ;3e4?E7q 10L?q?毩P\eh*dSsEc1?jGE={%C0tqB]Dh!RM3=ڊl&ֈ*J'xO )fxmF('l;6BюءdV]"n:/P8cLw+F?0)񇣕 DC)- KF$ ȸX "uI8=%}uVi ". wGg5yDlh "f@xE`Wm)eyo2|j;jRGI4-2A7o/'&I)ZM:~ }gXN]=BZ$|Ncdd7ʲL2CȌdQ=x!PŮ>Pt?@H$z9bBtv+j9K%|4fP~],tBs=k)P.P(B\I_MƈCMB42UEoJ7KXvT#7\t!#o9w|Fh`TL̋&~z!d!-- {3 ΢Hm"@Zoc$Qs yJ[!0O("#7xDH٣1&B 5P8gC*~E%" ;d̃6qQdbWY*t;݊џubsbl[wޙ/ W%CDN9TME m[RG[N ?i: o u9':6pc@mkۂLbo{(#6@5⽾4Pph/Kg"Ѓ2a6۾nh63uJ|g]$ZZ>PBW 4]ࠖT .\Xɋ TJE!(,wMdqLDEA%Ud-)vL) ڈ 1d&2PjrQks?s ﷎uؘomA}O$$Nҡ292AmNx* 8"g%Ti٠뽗JaPo}D_b(+4O֛\7j 9VU 6l&VQYĉ5PRD%:mr fM#+1@n!O!K`y;5U;݅Fsg1Ydw<oavl>$;l}=϶˵UjwVP E-g/"w:T~@Ns竼Lcwf" ϤD9O8}&Ѐ5πwU(r5Svҍx!%a&Y6<9]G zy_gac򨿡@HXh,we_$HhHOn]^!Rnmd 紃 nЙV#Sr jVEMWȨБ$'fհ-!O KКYOH&Ii2 r loO͏:""P.x?WhZ+%TAh8)J’DeBc?GfaȲZv]ULb j\EG*MPCC՛)Y'3NGgzd!~wu`]pŊ]|V.`;M:f7T U "nH@Ԣ/hj5+Z 'd>$ѕ I7*ϟBZ)p}Y:^L sȑ*uRfDF*hH@D6r~+ѬBFvj)f`2"|zRG5t[G7@ .Ґu? ?H Ji_FMZRz,_Z : %Wz:00@Mmd(ڡb$z$H`yj$ BD/}{sX !!uBۈ BK15w&l"HG &jtiEuc۶m7E]m@=C]=/_MEzŚVE$?Vw?54[È 4_CvrRf@=YG&$A& LHS"D?n5(uIP p;\Yhٟխ-=x̩sdg5Vlc¾'lt'vi_6 WZ%k%V >C=_gRZq$.?J]RŶI|*hF5y̯ ELڱh;D;c}h߃O9ĈIOEr2 $HԎIl 0m$/>ː˝cÚ_fz` I`Dxp]r܊Yէ,P*f$߬fr۬FcB=~̧Nq_ HKTBB'$lQKDDRֱ2XH mEtmJR>onzS]|6[6y8HTqkL&c!7Hj+1Gb;| [o0OXލw>kzl1xG`p~~tMg\fw'~2pr={}4z |tnJNlaHM<{Qb q]!_$QH25C)#Fxxk4=oDFnz xWݟ#ɯ1jx]1<   Y|<=u+eafM~Ĝf01pH`3;H)R~ ^cNJ3gחtu_OK_vDS/GB#uGs&=_gÿ ;Kb!܎=]d 3nt)/wB#I)Z$I"r!DD)*K侒CoCH=8cRf_bYP%$[.CFQLBur3`[8x1dz&z)S%*2Ob&y1&&r h"r%gqӇ88@Cx˓z]MW{[6Xx{,Xyjcuvf`z=^_9qAz&*jџ}]Cukt\l)?tfQ&O/&X2ם 6LS.7Kq1/>cFfR;3؁Qt7g?`*9KpX}EN9b!b($P\=Y.쎸74ᾮS08"OUH?DCԇt}rBbK::see<;V8peT6>=/!y,-F*{~j?$G\WӖH6zS/ʒQj*Aާ %ɿJ!4,O'WZ9!f>vTS璤FZ̯HO]CYoKaqmCPЄo  6 °F(T*{}NSUʖP/Ĩu곫! I;st%`ֹŊ?"Aݨ( et91ZPyu9 x_J Lj-Y)Oϐ۔AgNN:$OO H<'9NwgC˓+H?EOk:YrmDq=aױ7_uGO(Ҋؤ2 RR\ L=C@y&+GȑX WE$]/ƱB Ґ`{@{HaI|GqACjPsy,#5ibm0whiTlSeSw $hL4(&G @%RL՞j8',Cfδ4>@ 5%`*5bb{оppP)ТUPS[ Y8@+EA8(P-s-G8K!U"TLdԙjzv_cMKhQ)O=csk>CCImVZPhmpϱV۶{|YL?>X16l.%1$JOɿ ] (uHz2suRSTs%5!i+68:"#xO5]F Շ6IB=4Hf1!Pd"K}y?lbMbWRH\dhf3{ %Е´=jcZz!ըW$%#UJԑ4չ*rZ"$LUVնqe5-6 r$]JwBիP^R#q%jd.GtK#^&KzRʦg;IdX3 i4_BK(,0O/͆{zz~ ױ nX7錵l9E!,` >4ܷT#\IEF"쌎Ѱ iE!DDnCqW4Y.?)}1$^CUkŽ"IQb'yA̋/s!RBH7*('|G显}>a0<(BSSlٌwQ_Wj*׷cG .~Ɨ-H#QK9x=$] QΕ'fct4T(f,U`bB[ @u*Tg PT%V%< Ox\Py UXV,ym~@d&"1=`TrQ&W褓# Y󉨱B9v\"UJ!74WØ11̄qt\!03Iт~;|;Get`Mfկ-oyˋ>X  v$~iH3k L+%Iq@0䓫t`?G DL=5ǨUh $3&Vmbz 6D ~UKGFQa25]ܔK`Joa:5ӃTe!HMI*ŷ;@2"tG+~'CCPe*2i %߰?@1xH=Jca}oV/H3#:QkM&ɩBs Q6ul>JNӤ)4WG&Ҍ]tc^j\(Z1 >baujJ'ܳyp4DH*[Y):14v$IH$\ΐ;#^cWmɤ1-(ߦ[HU;Ô&s:jU%}41Ql(yc|P7zI\ DBo=6ݸĻ.oLױe~:'~&AVB|L ,Buz#-QEBx  * Ԟ4!Ke31RȠѻpjG I}f0\˳+v WA%BiB5h\& up8nj!a:tMT+N01D {_\9l.rZS7PJ(TҟȮ?}0W [u ?ZyIKBCt׉~@o2qhז5T?ٽfiHo L?X]}:/(J %ȿ|K$rQ#*7!FzbgwU]^"-czї?hDmuBE΄5* "nC/YWEۢaI|zƱ]{ž?;q)IpjshFO"(sco컦7p/m~UaGל%_>I' )¡9*V,Dú:nek!4B_%v~tr_+crT^W-iDwyT&ZP(1Zi鲈\HEяV{W _D=t돣v*?"ZM>?\zN'ovH'tjVzE"!i_l߿ld٬D!曱ٲH-e]6Bm.SwOmUQS*3M =Z\H'*85/~ĹA*m$?&/ jk~Hp'X ˴:Ԑ)Vđ$5+HA" gXK7|fu8 I"sg*6qqI'_}n?z8 ιγl, : p\:t UBКCv/Ƿ=ۃiXTV8|-''ЗtO` r{(FmeRKKzou1bۍJ1gL[t񈘅!^}O?_20Ι\*c6bF,P2tRu )ubmٵcpckqA:JK BP-5ӃTR.8ǖV뛥+^Q e1W-K1ļ .!,"7)LqV,TPl%J,Vк4~k")FlZܑ Ĭ72?4Ǿ3)zwI}#Xh :;K$?^NGx6CUgg/# Mt"QK2{v+H|C2-] ghu~ҟU|| Xǿ6>177wCP}cm"?= g wĞMtfgP,]!u+k$h9+)a2G~I^6y+Gzb}Ii*[JS'qu}ie~ L@ P/2aUU50-gaKP Te'֢Mi' W4p^Y'NvNmQcgU$] ɿLcs y _Gڀ0om˖"$|I5N1|r5_Ccu eFyZ _[U (0+gj,vSUmPC0w{m{$Lڞ~fa TO%q&G/rB fJHER):R`=#9rս_-0|ZeX!kLd'84q㒁W_EQ8x ﷎u|p3!`U..WQ+!"SF-҇ÄgxLf~3k[d.CUT3;h8Ju">AXs!~sfQE [w^#3AlW kW$"m1`7hJ-#(k.0A+]*6WܼjXY8'h5(韡l]GrG||)H},8*)oA~ua:Gݗ7p_ߠ.Bᨒ| ۞i%xj|^=M3LvN9]>f*㼢NuwPݥ_sqt(,150np:&ib]<(לTt#%?C]^$gW;qdV"rXlq,Ӣ.2Y*٫r2PPQE3κ@_|wAfFC XbBE@6A68cB|l!G5]qP}V6;|A#N㢃v.jBCM( "+`qݔڥlQmMjQ=9| ưLx!N44 mӱ Zl?JZBvAB`\ye|g㛱6Wp_@Ѧt:?6l9c:fvG逩&1& ғ:Ou|@\oЯ3Cc(B#ys9*gre[vK8 ?U9 Re[ʗ&_6Nυ^ s݄Z|HORx,J4W#^I00I ]QW"-(!_!g>󙛻_7\x/2::ʕ+Wھhto|?N2|C8x GvNiF/E$GjEc&2=;OEHՑo _5qqRt̓wj1:t{(/ϝ(0vTOjN0AO!nM<KhC`-٘gۏ{x.0 o|~ 8y$NZ~ԩFv}C*Ǿݻ'N~?pб;n"Af$**Jvw/"QgmY[ESq r:Kv"faLR@(B@MؚM|.~oEUMA T>AA!4B3Tj44N6nW[c2N4&xc^D˫ͦͅ}M;EwXcg{$ڲ!qmBrY뺎Ğjkllf[T$>*F-W% , EQI'03yWy}oURidɋBwȄLB $3I$$d%l&6-Yﭻg8WUuU%V}dy=cVä"( Uf̨_i4vJY }x{\s=/@uU%d}ot _$>;׎ܴK%^^W[ԎVk:Nf)+YMe fb&@TyB6AkPEQ5<δBתDvF IDATH*Y$fD79Z^?=+~׿u_<>O͇Ι3g?۵kSSS/EQ뮻~H=z ZD[n¥.SP ϕل-鍸J%s7MBM͠IV1=,sݭ6%ӛ$¬kZO&$ 6@h w>a5_KET4"I-մouI>J넩UYDєM"ěyW ky^>}G}'xCao!]"8j[ j y >W!_/tSh/"p<#O<6Zs \uB-JrV2豱@~vY<ַŹs^.]~v%M1TǬXv߄[(*~BkP`(@ͿWD hFt0҈7 T0zcĎQڼxi7堪$؂h>hE'Fl } D ̦!$Q)#!`~KH]@Wuq~oKl@A;>/kxnpzYj$^I٨BE4TUE_4P-DB ~5$M!V-TC:b M,oss <}nZ@Ck ZZC3$nvOA$i6d[QYqH6e=0tu`嫖(ܚ'Rp\_Nw_ |  y-PܶJAD[].-6v{C򿦱Ul@u_a o%:#$c>jհ&c>&ŽV_ eLdz !!~Sder[KĽ8;oO[[:Wi:MTMt1B5_dibАf0M@ϭPtzV"<<|'C{ ɵ*yuIl)J)L$%Iӭx(JK*b,HWil9%O4vQ w5xׯo>.7bD@km75!*;dH#i'9nKcʁ2';NpHyOs(a:GH?l7P#'2vk/?\[YY&dffҋ kV]ZR0@:+%.ը*5;Yc۟Nb=As$!4׉_I҇7_MxeqA{g 44>KpH*&}):{VYLŶh3 ԷW/8H|V= ]jnÕk-|)oGl !`&CܴC !yKqt6hNFD tܑCS4d@0E@wK G庯@C(!H`qs ͼM=d278?ݩ=-e "wc@ߩ~&VO)Rbo5)xouqx'X 7Ლ+8h(Գubo#ϱڶ*5TH:ۮDuzT1&_K8d:]byE:v}B!?̊"qJ" /ovX[Е'g5.Hg-灓qL?y o:C?4w1ME 3 TB*^UKD HE0R@_NwͣtuD2w@ȩ׌59)nvPY4Q~ԓu}p@T ;5eleN&j]%4ε[srvPpG^WDoʓV*KÜ}itEgA"E2Z'+EhِT娤ZȰuT$cQ0 %uy4WgIN-N@a@脲(K)hI7J$Po:!>2 \x'_ 8771LzG{Tqs&<-Z(c+ ͓H&Iߗŋy,זIA29hYCQOEphğK\ 漕VJTQƵl:P.#RxR9Uoa7 /կ~*tznֽ|9l^PKhX]\tpwyK*'s2֞dΪ-ihbfzgD-A>n_RUYs&J>TNS!5yӨUk87.u?O[@1_Q}P([sPHoԪ )+Xj=IizrQE{h@/&$Z>h uAEt " ?L(97wI8)=sލ LO褏e8ICw|SXo}!ćYQP /[eVofJ\/n&d}pgP}ԓi6;>( ڷ_EFt;p?9s6<2zZbz_PAëdMMgFqjI/EeTȋ a! x~Qr9z8xԆrXWrs9ru]y[` 0[Op+\\,ԝ:K[E^2U55,n:W'l']uK=~. u` =HCq-p9MTiʯPi|ͱ%{V: ɞu{=L%$ C}? 5x5+rO굈uHF$$ZGHW PIvB /L84hҮ",\ m%lRa (<)zW{Ϣ#%ēw^ɻ,d~sѤ4\KP@qK_Ey- }z!>Obrwf=rwcͪ+87CwikǓS([ܲ#3l/ܴʜX?ȇ#%Fc8ϓy:KpFդLr`kK%h)ZWs󣨚JxBP^>mRY|cs}\.>/ڇ[>ϑo l6K__{y[rwN>/2K3gl\&~8B:+ Mb9d3c6Su=פ7ȑPU[ 2R(kRz*ij>:C,:~įcD+c3}Y򻗉>YPTäOf%g:ڻʡt,!@@I+L0cb}@P[軞%9ZyJwii* 5 ɽ tT{*9(@|otW'$qs \gSz* WX-38#1D9UmۈQEAkľx%<5٨3IFUB y辺G==Yܿ~T :$A pqH|6Mhf'|jTY9CFug;O|vo_ QN(TD =zl_.Y/<^b}}Y ˈ\Hsg b.J3dޜci^RΓUYM8/=4IRAtU@'HӅ`55$/[tOGweˉW ؎^XkRTQ-W5䵶6@+\QL+?X>UXQ,YmٚBx9Wv8aI6eI@ B `] &ܶә׀s"1m 3*+Sj!O"- 淏WBW.zv\J%V'˸S.R54d,ϦDqjD3AAt)eZlqMϊ^ ƤA4fªXv{VT ؼBym #P-MQQ*0'2s19y! }0֜UGżȻ,09>˧9vئq9y^}6;{,{/'~b»i2<< Igg'===tuuap:5\a{u)_I0AGUZk5!u~c(5VA ŐR-+U))uFTTS*9G`ן[>ᯃ,LDBx1BO*#6%Z<!՛&`M2`ALáf֨5βAu< jXWYdtݤ )Z7QB.RHɅ: $lԴʎxʦYBDB>sբCltSGQex!cP Z;AGB6Q%X WuCxIO@z==(?"UUTm~(~\;C!#mmU `^EB2l5._YMneu:'-Lmki/t]xzC25)wԫ:eU_%R|,Se5.Yzi+O]B9PV4 5D_*("ƌ֥aD;cqax5NŰ Ԓ*ftH 9n+B3\6U j*־w:ab86_Ĝ[|t4tKعs/zmw} Lڵݻw}vFGGbt4ֿ7*-VAU@59:2Jm鎾,K *6aY -!J T P%'R@` ѻtN[ a%Y[$R1&0|Mʤh6XS[A;y.F`@i(/E~O sV9BjED8|*VEqYfMhhQƪ]6W_~%[('Q=e=š<q93"&UԬޡcuZDSQZ VaMt[G dEJW|_f_RZ}A cye샗+ɐV655G?QRrw˲ػw/{a׮]\uU޽ѶPTRiWn?^K?\ ^84A@< E **iIi+aш4h.~',a `I+kWyZM(ex@HнԳb(HMRצ'rzPYS)bST\oإ.o$ G2hW3>ϫCup%Q x ^_L3`%, '"kJty qY~補T{^sW5RG6g4SM}+^HY,s rꒊӃgS !d ::Pm5F`@*ZdTdUѺ5NuM^!D*4WC-ĻAs 7 IDATUZb |@6]И ~rejdWċc^H+k7Z$1dOP*Jn-.S"57>oС]sy6;։y*$xJr O/1 /l@iar}|IMUUvɁ8p7p4/`*+Z 9qsFZQt>ڞ]}:7tNFη<{V;| kfI9Uٹu zMf .ZlzhU =ױ^MC4YVS]wA5̊j]E+5a Ctd΍rD3*YdM]z]2QmTCҫ5szRd~fhbnH <mQGC#huy?@UTt]SGOϭx74P0$5Hʾ2Ko $i605fIuG4PTıf,)zE"gd<9Y՗'E+4N6:;Gj,.>ӓg雷(@87>+xBnT M:IsrStnqlnh$8~&Ow~\b坻eF&v|" ~S_&63J; T2tf}ZfTmRF>Af#`%1D3/2GIyDX mmV)抄n =^qXxsL<:I{K (_]Y~~[[[6v]ө֊Sputs"fc>.OQ*caFuPTDD7U! Ag!t%YNpG}7h~$W|GVX)0\&\H*o/d5?~`$OR !APHi5U$E̿zZu0]k*ui"3QP(0=y@ZmF.*U;K]8  ._=3K!'9dEpyFH П`!IIQCeW< yuNBKȲox<ͳ1T_E* 8qhJ0誑~>Cl)𯩡 YkqJh Ѥ*ϴ@w!BTjjΒZ"~ U>mM|j o4 :K7ɅNaytl!_}sս Mp(ݶJ pK.^ŧJj0Id7iUY3~-e#Eڶ*k+(IL`Wet_?̇4ER7mUVz !ZFŊDPO+IC뎢ū14C,#]'ebj" Ǝm m%P7p8u ό2$ EROx2=v&"nOw-̲٠C@gǘHo#jT鶻 |{a |t@?⳸ol?=0*wiD87:^Ak}춱mxxOqR$ͻIڢN#}t~kh;OC3qlcfWhzM״6/kTZzj3Y Ao;{`:|bmE*e ېS;E*;M:-m2C]Y:f'~j2 XXp# :c&,(8740 B 㘉h@^V]%/k&ɏjjQ#yo{+@l?1l3BC <5qDՉ~5NsTAI_pw8{^JP5+w%rL<>_2̏ۏVVV{u]C=75-..^-̌TI#kL.i@{WOB7 ?Bz z1$rd&rz Bsg3161m=!:YW0qdC%A![F.lW"'Z] l&$TڎyZ/'rL~'(sTn)ԒtZ&È=ʶ6"NzJFaw:g)7Ci|gl~vI^kෆBagķ&yt 3IUJR)'$ڬts V>?UG%v8AK[ج8}$}d䟶a 3Gn{ron> `gsui$a1~E( <=jFoGW6T3vF?5NMlZWv։#A6̶4ѽC;N/q*1`KNqAJ/yqƙ,svʙ234};Y^$]GJDev]l66kxܳxצVsOv=#[ah  2a!!uQ'1Gẕ,5Q/ gd0~pHЌ69CdRJ)V" %|v u>0£?ְ|& 4Oê[}rV1j]3]KnHA_&Ҵp26KcP7 zu\aarX(Pw Bd[շj7VoLK4Rw#TC}Mk.g;vx74'½+O?"o}+o{۸;D./´XؼX4h 7!u:;Y/@Wk7T%8̈́A^s9m<Z@tkMZfs*i/ۖ^U>R if3$j {$+INMorGfN[z4omaa {}< Py}N[Gߴӆhp6gzԧDQ*}ef~:;YڹPbC(9vA} ooWȜڎ*akgzNPPWOӅ8C";'y5OzO㤾%;iڿk"/[S/a7r/qzHaGZH{a 2Og+:AgԏolIT 17*VgbF`PH+E8y])boQ(4 d͜řEwAw8tTu-( Blѻ˞+eX; ~n@L7682򻖙 ɇ+K$[cV2+OtE~yk͈AX#4j\+m&eH򯣡 #E&A P TԸ :öƉ8r9BdQ.P'FxOk/Ͷ&'D1 π@t^&4/ :".OHer }~%ZVGUu ?ՠQ0ߤzķ y[TRtοca'?yE%4M;B~ JC2f~Jiw.20'RWKSAy X0nh kB >Ey1g+u|f$l^-4v~*s3Z ZފDXT8ffu뀵(PZ J(/Vߗ-&Br u .0=@}ǰR\[ #&rcƤq"6ŎCHIY׷\2` ҍEDHXP4kM`8qOkn pxOc'w54n*zDG<{g_"b )*c >9LX/Xik$XV19Y+9bG.qU2htV9wOi-[_"CR.4w8)i6씉IXaj 7}&`0ύmߓy!K}kp3̎O39L"̐`r9݂D, yxL+dsدo]xur %D'8cD"s l{jYco8JU! Q$$;?ArE_wGG=:FzdrY}Eah"B$ba1c%P YJ/roJF39ыꭃ(*₣Pu3" iL;R-qؔc6M o{y*+x=%4/Ll6nkQN?} Pԇ8EN.k_;N xNI_ <% (:?ȡ[TϞ1;21Ny&ķ w0 <$ vzkZ]꥓?/$OD?z3 ^vΝ;ٽ{7;v`׮]mMyپ}xg_}b1Ä"Dxs X !U(@7onGi W JUkz8.?Eד=k\vyj8:qڃK-:!#JfS!UL!dmm~-%qWloM)͔(,q_.ܶ4[C,8{j{D*O0qD /@ie*  j',jT8hdHcMUsSվst"J.]-k{uF yʈ ~tSS5ZN#ףt)(~,4W]L`kt<Չ⮏c"NA;Bv0Q8w 7Lj22UݟK\5TeG~\UUH2#mI~~-Jx%u2dAC.>8Et+sm'ׄϱy@=e鱈ZQ&~}'ZU&ZHiȉMIE]_!ijZaP1:@e<R޳g~;~;w}7fWApE20Zٜ k'Tl\n lIW" BV^٫^Ą^>D(3$0-1SW"v%PQmv B}/{^ǩH>|Ç|˲x;Nw}F~,_Q޲._k-(2c[o bx=Cy}4t( )u~sV' N0 {uۧםٮ].[{ b0~MH&B J!!$Bsh(0Ƹb[.ؒU]iN3+Fđ(6_z/9{<9k"5&z Kd' n&q]3Ү`PG4D_m&S/@UUˢZu>g^3E(]Lf /:D"O^ȋG1"Ɂ=AmsV5Ҡ1X~~bfäCm+8u!޳qpd#SHJ՚SD ~ 0.9j=z@@k?Ԯqzmad*s%:AnBlW%jC]-+sfߚ?!7|3o: ஻⮻o .Uzoxhoo%MsZn+ v >:/ͯḦu,\V gHn^3&'Eo*P V%} Guz5䈂8*mu ϭrf;=<ܸ''zzF "#A o=XD+|bEԲ5毜! ~BdeOfϞ^gIk胹 zIC75N,ĸ&Uc1ڟ]3q8ne+Tؕ#uhqUE#}:T\PMy9(ԥ_`%S 2VĢ2er,X_WxM(o `Ie y) !,8yEbI*+,Q !D\ reȞp\fH 7 0f$<F-i(e&j!rro hE (oթR+0M9=7BP@L[yRx!T#\_‹x6y9u! !$jV17P.X@0nv~Mg6D,H5(-UBa:RI%Q'5TCrY .ؓS̵R|칰aַtxҽ~<1n>Os,}}}]']/dfp6Cy>jB"4/m9+54Uc%6i8qIYiAĂ4}2$ K-[ {db02E>N9Uޫ]e|R{U_ 4A.R-%&҂4#*.FĠp^s΅E;BaBV%(S_C< 63& yDsئ IDAT-!|@T"֕ё8Z!ϧW z( #pc%Td&>C׾Mobn,B@.(y-5K!0ژl@=@uNf_: 8H]}-'z۾Б0s RŇ1u (#efdjgy\A+vfGQ{3Cz rOWjK[lF=!eԲӘ@` e5OM :c}n:O4C5r+ɯ!"#8PrSu}*N'Htv `)\ -}|9w{8BK274ҏg i ċEHܟ50 /Iw7MM`; z]T ytю9lSw^c=#z ( t~ݛvb{eR% #$IfdMVE惍vmhOiT_VagB* .=0Nǎ.Вﰰq.Ѿ$3WLqPIEk}j[5O8,}*E߷|4maeM2(x3c Q*NrdC2yȗEq pOit]ce]'|خg7ӑ[k[yN:gAwW!_||S;}B/P>lw|7 4׾%\–-[>wz7zn$$ڕ k LfYpA:Bޭ,TL:՝^ ?^Ah0DgQ#ZA_ZWpz?Ag 1%(nSz4z6A]^c`a}3U z%i`ʩFɠ}GSU?5ɕ=A'IA;BtMRM@T/㝨? ^''#MɤjC92}$+n^hbU$X% QZ8eI\JRB;;iI]8Kn:B=Qu>01 ::>!{#I!I5,Q}iB(|DzyJ̪Džrڐ~38BxG 4 *@Z EW94(!8‹4`!DpΗ֐FO{n7;9gJ7M}Gm*ؚ>Ϭ"t; q,/XG`Қ˿ xZ# N s9gxA^Elq DWBlH5 uVbeUe$?FݧEu9rNn1CAB<'yvkwPVxbӛ"_/8Ⱥ7F2.gJ~yM!1Dr$U qkB= +i5}*XV"x z<  :4 a?O?D7CҼ C i.(6B;IGn|H9ּ}JNvdq%+hR-!.I]#2ոmm0uDQjms6Vks aBM ĢIݒADp4=\evS@*4gEM\w=߭SHnAЅ"q EVͤvƕ]̠xXYPr/P]8OqY;3lxp35;m~|=nPk*[y:年W3ExCq/GF5Ί&hdz릨]_!őW# }̓=&5 `ZԮG];L3`k& ?O O(>U dC͞Cȯȣ$VF4ߵ/28D/O4?t=]Ůha#64l+CFABw6ӹ.!" +hKshxxt{FL'z<(Bi3⢡g+7{w!t-!!S K1XX/ ΜsZLߣ^d?EL(iyVP={ S1<MА=ڐGX\SYp ڗz1~aHmN HvcP(Oyʅ2֘"yp>9H}y.\A_"7pIu<<h!xݳU2]ԨuYVNoiy? %lU =EA1 4Ǚԩ $BƜؼO޼yT!椓UcDQ|bz١3qʣ5}&⼩σ" eEe2JDX!FA+kI @J9lLEg< nmrJ<{,z=UhNhT(}.JIe"wFr#`aa\U6cZr C0ZׁʛJXk7c2 "n2Sn[F,GYy`u+G @`?SFݥNkάfj$QO!\j$g^O[tLqV\rܲy= q֖ߍ+QFT S.6R~8qpzDxn֍ߞ#DY%tcG1>Gj4Rv䵇6wG~۟+|gg/CfH! 3vg;}`gXj/?H&"h3bcCI϶k_ri,Q'|G%9m(L1L+O"#h$|Ժ{\M% uJ/-`,O7S ^lA_>F9z#m/$01\Rb\ 1<,jj$Qqi&:5Os膃޻ Io럤>P09Hy:>f=m$I|}Axx?~M7Dtֿjaaet|&i{Lw~]sB=! iM*E}B$ 4lRd>b ݴ^nR-{K_gq+W.n.|g']{MԮˆHpc랤^RhC̈3 m( gٷK0b &p6yn!]N fgP]U!N+/!8BFf <qǚ^z[;";5ԝʨ"W>@t1t}L5:^K`w+Dk9-qm>9GsqjW( ܩ ;[#pou<%Syc :n(3d<ɊJR3iRiV2q 0I4ݯEF EƚxFWPHXNj.Mj.ytM)7 ~fk}t4#iVfn,YRUyӬ{x=)~pGȷo8>Q*lclw-0xAVݿƿ_.MiusƩ+d>Eb"A.R#i¹Cbv #IrO=x5cRsiBS^oz@!芔|Od<,'5&5frD{$/M%Pyr"W ! 8 wUƄ6ag-PWp*jaBziA}inpCy. 2{bn\w9ȹ$SH3򒞓`_D,Ah!p; (+doMqI7U 2 zWrnN5 r5 17\_^Cȓ2f38S!:>փ2L %!BsaʗMyV4: #sZ}%!w>cV.X C85"Q9 e ]F_,x$bbSTT]E+A!tOyw74q> ҲhRD=@x&BP1#D?}b?ȠGVpzmaMY>D='rM&H!Jbo m;[f*x8EwA.n ēq~w_}9]t[l8VyDA ʨB8Nw!jWcȳ JF]6yD!T |D/ǚְ?pWpdf^龹9>kʴgUuAԊX=J3ƻ6LKXQ!\ulf @s#<# %b+z4+뼧9 Vʏv`H)JȆ1dSa ˛cYc#,Nd.JsZK IDATКA߻Z*r]!w m;̥t67Ҙ/#Q~,"sQjKgzi1BV/vp6BfIIdsnVga_v`)Ħ;-:{Gh0#;[;KMOzv.Dw\7^8C^tsDqW1VԸ#PM E~7F[IEIՙ| We;_$]qY;/8.ޱ p5|QT2lɳ&H |jWg.A(\clwaڲP`yB*S\OTL3ICʛ)&BbZjPɗ8TgRRh+aVl=ʼBrLk-i,A11YyVZ!xe5{iEA$9("(v_gNS=)UE)sw 9%G&HCd쓏Z55'zA2 tT*, UG?-7(&f;W~zuoQ]u iJF$XmDvMu@|[*_8|'_*Hٯ rg}\ݪ:AqlJb } ֔YgIq'uF;f鼥"Rܞ.4+G瑟g&sKPzc<AuTD8%d鹾 ""VEE_ x&H1"c3{3w]TKn{`9t( 8п_>Rؚfg'B˳sh RK~\$jÚ"ge*畘9gٿ%H]Iv=Iͤ[1B,ڋ+ L/Xٷu/wm"Rrd'&<8-I}Cpm^]9Hb"bZ$9 Cl}GZR%{ṘJ+:ct :~ԃu yja=0֩jxefgk @z{{馛N/ oS)'fd/ʐy_d202k9FT0L H5 7R+1Ǔ؆me=D]D{1:kӃ7qܴ ^?[e_#MIDF"7辠aUSb``ロkht/;v _I .K _8_BqUckyF-߆:bjGLŤgRM;S сFJ\w6{X&μޥjn1eڹ:q<+x1,tG=t];wrWw2N倊<@^ۊK.!xcn11WϨ! cZ 6an3M AIqҼ.{Ey N?WzamG"e] ~af@1`)Db.+* 5v=Ѓ@=Rg!cn,k^#:f$\-5B&GU2#Ыnst:Xx]Y~l:*Fo^3 eKfϚ S퓾e}ԂAzT31YL߿Z3r;T:nݣorƑ'zp#XGpDDG@t p6K~S `Id%X1AqDTƵϙD>[B5bM6IW- ]fxA{E4ʯ+bglt[\.#Hڦ~0ѯ nD?Ҙ_ud,Yt-}[yUkE8&sG;xcwzܠG0@+t} FM(ewރ%2 [dЗI:6'nL;N)QBĒ1$U2Y!{Om5&G4DQU"$v7'V+t6(0I} (`H 7yU}K7#zr6/ƙ5UpL\~1$$ ~Q /298xs_cǎ']EV8z"&$/qU^K`"Hĭ5*cR'?",>%tO@Bm] eQLLۤYqm&F11Q <jQs@Ų .2;2k{{{я~Ć /[o']/ۛ%6b(B-_!`c#ydH`:şT#9e ӄSdA¤dm/ WXx< CGqC=@;d[8Ϯ2m2DUT^7tcN?FBBTC2Ớҋ 7PP-!0?tY8؄0ҔPM@_M1LLAhp;x᳇0:d3 v9Vj+2=w5M? v_tBDaA%F9c{ͺYDC9OM@{W̚im ~9?=D~!! Ҥi&;B9S+kw?& PKꌞ;ŶRRdhxzgmQϿ^6!cʪ23OaUma`/DӲ`7|4NׁZC=t.S;|)뺞*dY.֌6ccc&\f{lڴWr}֮]K/Aq8xk&M|'wy9.[$5|r8rCCCY.r~򓟰e˖_굏u]xSBq=]i4N׀S5EyB [o8Z+ ",[x<~!"}}}tww3::}?sss\q<]v˝<ę8x~p,3$$\zXOm;"\iyG,Ķrugùqa#+f~ $QUXf2 +ǩNVIӬuH7YĎ#?sMzfUtn}--G"$Vz5NwMך4U zL@9OrO]yzaO$:Ӻ,nhP4!Vkf(V{BAg:ɖohixsWܹs'o~ٳgϯ>G!"":@1ƽINQk\tU#~H_W2&'O\O2xG^%Y@X.o~M닔eVY+gSh~73cmhx[ ]O11~<|@aʕ200K%ٞ%/y >(===]$pRp8|l|?rCxkih%޻m7h"ߣ^h 4 zo% 0+el t{h=N,BZ^Id.?Á?-c3F]}ėS%4&p*4 7/6*(ALc'&:;ijo(oؔ> CG{'p͝A$ a6m+ڀhdקq)v|nurٽӌ0JM9rO,,)GI4&yOh~sl)UsxTp%L2N$x.oX[ Vm!6sG d#*_j_̥졃L}p C }=M03޳L7Z`% Z{qN6a$~g Q dҮt "201zNO~ơXJݼ-oy .Fӻ?y)=˽T[YxKC":Ǔ| "6T? _9NI':)>wsg3ѿ QmjJ{wopx}gK'8{ ~hoǮ|+~t533$$Jk̦7mtwRS,v,03-cuͲKG~ikFQs*FFE\кUR?jt,YCݴic!7+ CDpm}[Zߞ$@3"1sp-Eߨ+`Nv3rzC|#x>vo7/L,x|^0QNFJ8+.<%ᰶZn)zob )bs!{+H" qZWizgD:µ̼,%7g}KKǎ5yͯ\.l6KLq叮 eJ%4f^cQ9ywZX~GRu'ןV-E<:Z`;=EcHZaN41"zGeE:NGQXaZO0ąF!4ٽ׶̾k Zֺ_nnT+l~v5vwУ9k&V;AiS}}Y3@v@1al1682}z/x#cD'yQ:>ϿZa3A?3GPk HPqe[W0s 9_:Ph=ama=!:CZ鸻)ШG6z8'O˃ݐKiK~-_ݶo:F$(תx#J(Zv d]^E>#0v.EMJjS@~[!Jn'瓾1 Up%( 1;ϋ"fʠv]{EK"]L.ARr uLF鸭7""B>Oi)xQ-4+ c9ҏg0uZ4o,P,e]Ky|(`̓9'ZH$7,_.G>_[9D"tdSt^7)"Լܥ3TUvTqi@ 7Ɏ( IDATl(ENLJTTB1 R[!sGy4!9r9u.&O"-K,Z 6c6@0dIl\>m(cpIbNlbǓ"||_u]/ʘlކ в+ 1td*C4ΰZ梑]Ȏ̜69Ukz1ZjBG|ϓ[ulW!qEgM'C icElsn @cYt($- ~//Ekh~y6@e֤% `]db JRB57]U-5@8GAX_NZ=ߏ3|9!X/. *MTuM ΑQY$\ :&unsAyO MҨ5 qo4 H2 gMLjiY~vwy3e5_5ڦ@tufw[/0vh[\乀&|ȐCCiQpX,*T.*΢TvD(+R񃣔J{ X&^CJbda,X@IH%)(w,DɌV0dqJ[J 8q;oa4V5W!d="-EJgd`L7DԶU0Z$KX!FE5^rӡ:ꐂA%O`9%iB"!KO}޲A/DP(?422M7ė_i$'e_F;[DFb!2SɕQkcI M|uG8)rkBPkk,:H DK MN ߀Ku+4 &qvK2"jBe'o0Pӆ+PĪ'PZ%`ScHID}>u.RHD*ȸ;VO !j>{-.M5"G6k,!]wV'2}h@$ʛKT3)gs$)m)Rl/ VEU<#R^I`tYbJW(K"T!m&搁-S@,H8,lR0dT5MP:+P )eK Aŷ>C˩,&1 xt=>ҞE*Ce=J1vȳ2&NC%"F1t[ $OU\RL!)HL$m1,uL]6I; JHDLh΃']Qen,ڨ)eJ>|VoTBiA@oÓL\/Loa1KA]xJ! XUKTtpX2ؒM'T2XFmVU 1*@2FbLDEiT"_i tk[vƢzB@ !cJȳ#wEkxcBs7[Lԭ5H $x"okBR@lE6IGVwGD#/% 07 oQe['0SyK Wũ9-ېX0i! H E r#u~( -Aie, ;#r1O|y 2ea+@#90j\8L  9iT&t^_ËC *z1lYXCo^҉u\|2-TJFʣxʲ/;!/6V~G,VQͫV?`gu[h.†s",ܠ(MGaq?nַBwG/~A Clݶթnœ<GXw ~m%',qB!Z"Y2y>|znMzVu<Q}ZsYd"r&xqc x04JK G[ԲY#NvXVC(dӘ\K 21pqZwƉTǡ"wa =.d<.bu|b'~IeNڶ޽{_4xS&9~9RnE\Dت"vdj‰GM(9r1م j\eŶ${mF`}ϴʦil'hьrbsf?DؔMrmEI(\{u9 x!hU 0L]ed flTLrb/&R|?Ph6nЈ·4F(@yS۳Pj1i^HX%k9X.-Ƹ4i`&{=ϵRBxqa\pJ6rHf}V%LDf ݊Xd^?q\Ū$.!V $Ԟ K!s{w;X'WrL?Q[=xQb G)z/ C$EӑXY#}oo!֥rh-(DY-Wf;jS+qS(rl<.73g2/oG*}cۿMgȝM% uX,ԷȮatY:Ǻ1cNoj.͓ɛycm;h?IԎ]#SSAfS?@X7i-M e_zH|7Ieeh,\dxI| w&"":j{%"sOfP7 fX>ĮO^LfAE`R@iNx6ǎFhzR]P9qN6'<*Yr5k$*.!H|?sl`D0EyO'hm!^tv4mzTmXad#"E@ExPzl|/mzS[ȏ716>LBS]SZ-F;uݠkghԷ>Ŷ]D!=lze&y0&x&"xWc 4IBav {ey#eAB?YakH5 ,n%;Ì<򶇸}hB5n;Mz3 }oR3_HE~ gD(JGq\jwҶNCjPY; $-Dm{#6Ҩݧ{6.D<3:Fp9^1=@EBnwK'ha>o?LKMyX(04]GxPuMヸőy=OONAb[EClR`v c3NX ͱ~.عs"F\6(Ts=K:Lm;'Ibؾdw9# 8P;qߎP6L+)79҅p^?rD8ffEAF=`0H>NvӪ+]`9$O\(v.՗%8ÈF<݋Ed߱p6 F-j|U$f|QEe*,0Om_o!V. !-a.W>POPJ T;+lFyd:JߧgjhXEHeH@ϧַKOev|J:Tר D( sP_`^jrk3hiN@2 m~n8CꪫO|h)"&_w &A4~"c7$:a.iXڼȠD zg .F T0 J/_L'zIqPH;V6 5떆n:+X[L2VIÔsYhjG3XC$}#gV1. [@YR=VCZ+z"̥d׷|yG9tІًٟ_/"Jw ,U(HPn+13}^]rCLd5g^? LNԺ- !hU7@_Csm]1^ΡlIⷹ99$HɍwRd!E;(z}\f t\Bo=zT[.K$Ny$$4mdF WX?"X!, ,dƳ")]Q  gWǞLz n-1eDw!ⷨOl,k~Gw% !#mz$HBܾv] OuP6+L_2B<6։nԣC)((U\_ċx5ȰXo.ٚX*D"$cIڊ(ֈ8 ,FذK*0M}RpyCbޕ^2+$ IDATBxmHLpr ƖeZ( `.*`^drf0^BվQ1]P D<7WT?5C 86ٛl+cVLzOCJڂoN*i>BtGШ@P4 =+xxx6N-&X0{y !+[۵:sqy 8GV<ց*N? "J7&,fyDingCX%],TPB(D<Pz9d "BI skn ~FGu_3j=avagBdGyQfnPL [V#ʒnPB ]9ap܍=kJ..勋!b`g Zퟦdз|X3k4v!c%' A rk6L7Tbl6U*T͒^ʠ4ԧu(k.~wvя~yG2d||ÇcY/`ظÔSĪq$M>Fn Jm6vdĠ0iY 0xtM _O[d5{wF Kt$tmSV] ddreL' Ԃ]xz׏5dZ A_M#h堷PUC ޳uǂ+eg[ht^ߞ|ZSY/s]\jb Mg`Db[ } Id*JomJ `!s:ۀ7qpJp¶,Zف<+cw׫~B-Xڴ;+WQP "- m_D ؛,W)>a&(|:El"+,g2׆ca+L4סt$R\fjMGZfDvrg81K4 K2 $I?& [dd&r^v릑Ru Kx:TpZm+,BP!:%ה=J+cڸ'|),bTW) #c2CHd<ýf3,qQ -Kn!O7Y~dcE'Lb}w ,di@6)e5#4? 5Fa_\f-S!)0ܱn0DAd{# yڞnǕ].0zY!bsq6-s!` ~UIea5޸<}ˈVw#2q6S \weq=! * ѥ+U1bH+\~ T*~\)a"\[LL< İ #J>7ZϮb&0!1M %#K2`{637bvخz88Db+jo$GpA$:ڸz@G5ĿY9sc ΢":ҳz^G$DA@$B*2/Q/j7 U%3u+X A۶MlRԚĕJ3A P ^ݧcH 'dDG$|q͐T"+sB05 j 6qpmlc#*sS\ YpmЩzhֱ||G@5tm?-JjUP҅H- d@^ 󈝨,XOpk.ΜK܊vqp]Qq\0a6 z;@5=C uqNg#p*b.ZSqptO{bGFzMK" T sҬPh-c>=_1k;5H```//A`.Ikntdj#? A [_3"e֣?V H5Ui RU ieu>!ZeD9[ZH軈%dgz!@IHP̞*l2\a%_J A;C{\e˲DƱn3gdu{64廫]uut70C CЯHP H6VDjڍ]\,AEK+Cx 80`LLwO{[W//2!Fzu}{ςl9eZHRn 7: dAؚEh5Wu醻>^n?OGn3 J[AZ}9dב8mlx*Wtɝ{'*RifL4ղ7RiڲMzDu,wG:]8LI}榦>LԱ|^KD vFZXx^}]EF*8rX<~@M;;;\t;=CqqrΊK)077z@h=~@LTXoE@'xO(r̙VzI$G~:@{~FQ K0n>Ľ>"Vöu@ (WC Ja!!x %awjjӊ54n?¼-T ۉ4y뽹5P Im68~ZzSoz0h)-]%۟k P7{q9ۈNXZHCj==:tB ! oˋ0ZȍŮ8M%]A=S+.#zJGvd|wn~! 3dxԥZ=L4܂W DA;"߾Y~ sMULB`QqΝ;wboeYFBޮ\B&L8US="$ '8p-EsղE׎Ub @@jH}=wMhi[;hkrwOe y_X +m!GK8%4JMI'`G@rqyKA,h)mK!8Ԉ4 z,I`nn˗/Gl1S ˸!1VuעӐ,P_7I=TeOWh:FλѕX}l}B?rG7ts%Zx[>Jf"T8D*Pec;KT_N:d/={ZǛ+ b]l g!M M$SѢh8Hc .4uP!>ORSn!6M nVv#z[3@=L~24!PCsZnʃBm -Hz?O N~/I() ɣWyH@PRĶœS=UV]tnk=%'C/ɧld֑-p`Wa:E! `(d|&(؋6cU<-_;D|ʱ2cڏi4 dKN)?\xߛ#Z?k:d}Mh$H>V8Y&{Y/Ż C J3Eʧl_z{%ĔMCyİ[e)?qUdEjg+D?c15jg*$78#D>mjU!L9{,I.#?#8~3eSAlcx^"4 lXzF /owx{m>}*٢[tC(D^I⡇+)8`7v^ VE!,.EgCmO鄯uRH)R,`&OtXPB2O5ީRH|)R'(=y5x۩=CiQH)ŋl OXE.Ka8w@fi~U+QLi%6S7tv۔npo,& v;Lnsqo;zI~:Ga4OޗcQF{ue.K1Rt0Ex I$SS(M9^!^g(JX~ǀڦ֪r ûx(%[<!kCXEp "`;ZS?~Wk RzH}-GQ}9dS~-FdI,IbB&(bNw-F\p*g! ;mVUNڥ/:Ls9f)n'QZzu yyv#~jdfm*'\b (ilo2FriQ:]tv|ږmvOP?6?!Aj1MbA(B\8S06&?űqNϑe.c+8?B$}&7^ԅ9N|z7Bތy[=ljCoL ح*,'?;_"}3g[1-Xn@~(jz">'~uKFNj=qr?\ִS8A-1c%y2zn~'b''.$ 1:c[sh÷lETGesp -H]@Z#w8sw$6]\VEb9A#p|a` 9+'~5<ch6o蟜@l\vͽwm:7دMa"\nK!#ocHHz=Sh?sa|_ m[?u٘)QL&''LvP<穧" 4!!dghi9N~zh=F(l{^{ S_8D7 }!L\{ bt+i\X~_>ɉfI^J~-B:?? KKoHlj|,U/ޗ|uO&I|_{p{:4ՙONoD^X\t/Uq:4 ,Fc1*/iOOZ 7R(|vqSsχ)>Zwpxxg"y`Qy B`ޣJUfߜcsy-F)Bϳ-Onѥp B*9f0r} &89F_'v#NBkg쵿0eq8y!k±k'[#$גDQ!euzp03!&LsPG m]4.>sL~P%)9O=,%zWg{ S7I:ǡDQLqWiNdR[I ֯ïcN'Sr5<vfv3gI#D>ML{ą) rWѸS9{8N1rnԅ Rp6+1G VY"Y ",Zaյ!-bZW|0L)n^\ AόO1ujXyisNekin~U oמ#RT L߱|?yC3)|_#/:Ҋ~N.ulcn[w`?mkEp`^l O9 ] XGN:LDl= 3Su/e{0Y&9NhP:6НILCos2ƻhHCc+k߿VHC89yej`cٿiA@3ZQ8Av+7PF܃ۿPzw2ovމGݪ+/ x>]cx vLy-T7R$J!|%V⪃j?wo@`}PQ+$>}eto7n`o:d8gO? P@\0C&zЗ88lW=eKi7\"$^ A;"+$>(>#ܰm2-LmG`D{(a  g9#[QF;^BPd~pX)tw~! в[4EAD\ˣSa k>|/*7/? P ,oH~*Б-m-we$6V|_#ߔi۽Tf۶p1|^&f YOFnD H$<У9f_A`Ŏh@ZD'\/_)ͳ IDATg h54 Oko&N@K7;nQLGD Anb |Mӷ"4o9h;kn*&|Af|!|h /R|ӷ_WC.df]$-D;2q6&>@A7D?:(Y~6I. ց:w׎"T{B+?F1rߙl$740giF ϻ0|@!V?T1[G&Lʷ{]@ƴm[5X%űYbK2 5Bu ?BCd$7PW뼱꤂0c1|Ϫ5Ucyjx<^gp<^X `4xO)G2Eؽ.,=h:-}ǗN+1%XHt+5lտLa*GcI;Ǐp Ni F ?ˍw\#@,Ɛr]zu̸:>DJMREˆ<;1 aq8j+~JJAjr>ʋ]'>Sl>s=uLq!F?z54ek*qg?}j KPUX8rKTLjS~cL&b%{s_%-jG+`붍iBHk ny4Gx|oşuěFU ^ؿ>Ald{kaͽH$o$I,$Q*Xߖ~즍F %ÑBF}5,bӻׂ7g^a{-֥u\!B}DiH8&Qw?S/#%D`&eO5)Oo;;2/2tnOՃ#84u."X'b,ҿ?=wȳ5 mKؒM3t7RkDw$tu;('!VQq9סuJ`-j^ti_F _ O҇P3qw\-k4nОm"%zq/,j%xiCF0p-̟iú 8H_Q}͏Z\zEZc[6x',5V_ƳRQ ݈] :c<mMk7!ǏgbbBy Pd;3Ě1,E}\A!!(Q?O^śS$ݧSPӯ:npdV31C5<~ [ ҠUz@Wa}6M柺Cyq[ ὂVť`6B1La,hNSl쪮S/Ch )/Dnnl#R#DkEP8]39uou?%26P9 GA Kbjn*uk]~NW*-0qS؊E#j5$6H+=̽nXq:@֝WN1vg<љmvetc nc:`%^r˽_b[bGtF`Z8( k;u?O={v+EuY>'<- zБlc- B[)6|k5Q{*#Gu'8dJݱ0˛-Ѩ.x^ҙ4,P:̂!yXŭ({myv6Ng_M:{r*;hgz][G^YvGu:@ؿނ +ȼ7t(.ܪYQ'+[|Y::7qAhp!I2T'/}WJWifwl.or]7'o$Ibffl تtm2Ч^~lI tGwFu2*d-f;;? gYGXL6W{ʽ;5N7qʶ>>rλ{^Χ  y*[}^A>SZ9ڽ{TLv\%XG#މѻ-F,lpLǝ ڿ>x*:8>۝eR뀋O-fܓ!>gzͽȢ8,h kjJ.RP0 WTUEUU~?Xh4J8~H4ADQN:?=_=0o4MlFU[F*[[[ƷOPÇ3<<@o6c%xJDfBtvGwHmfx;j@i"/h]\Aʐ- :o8lC"#:7V=V_35l^V^ Lgېml8yOh 7݄`P!Ѿ0#:l> $=p^78MC` _t{j !) "a,"/Ckq@D@GG@ z9+7ަuS#<"|U:i5H{u]tik@x7Bjl&ԏX/ ]֩(6(\iiOjH%+%[\Ȋ{ʙ2ֈۓ3tލ:c% i@jwt +b].XcZG\iKQjSx6G@ EuRu }xT:w +aK5͓ r*^!~m4*zsC#g1Tis|$ j$O؏T>7׻6ϬaPKoloRSD/X u<^^G'mP1LpIAT'7؝vS"M*!UBe -Wd'-ڏtZFjPܼ, 66ݢ}4(ϔ\pm ;>_QUd+nчx >/'01 oSE w @Jv`Su!V( 8SrhMjhi/DkK66DH,L91܂Mʬj3°6=}hslMZU.ӆ4tdQe_OܰmUZ**(mIs >}iߦl-TM %xW sy> /bْ! vw#:\'y6Fp kzP|X6BEDH|זnpoӈY>%D=ìi9McvTLbZ7;hv{qJ"]]&'{Xj,O.!)Lnam \lR@Km[l<}nm9hAd[MjGԧlb쭉*2ۏm!`6mqdRX j]Eu1a#[d^] 凋X+m3KԦjȺӰzEB܉]Za6ɒ[=-,Vikh,wċE*ezM ?Cn){7oшp4}fRV`i%E`C "}ҏcMDS`Ħc%4o ߼A(A#٬=L-SEl87eX%Zghj 5\! xNT]1DCu쌅Tvehǫ):tƏBkޓCn,Цm-qJ{D (6GL#xdOOG;jv1 0C:TaĞ|hP-[U6hOj4g^zB2*"fyvSEe宜o 3e?ކIKk{dDt-۲Yzt yWlǖ6;On!DBopfC5UG*XQ 1/"̬AC5dQF􈠂T i"dzōԻk*;l&TĘ/esfW!g7ٙ&JER?F[[1=c]bTE@;2-|2O5^%;V.K!P]54 ]Wc W/B~dX~آkmj<yOe8?,G8&0GXInl.`YnG8[X#}Wkl>[M 0 \c32_=ڧ4jo2TjXm N_=B3Ф,PTl*aDٮ;ز"͡JCWmMrw/n{({ˠvq;6OS%.,^^dmf '9柾%[i,?eD ѽ[C*]^54dT7/3#"U$ ȢdܓOHHx5ưy%*?YBZxHNHʓ8 Dzy]uקlE,"˙GklZHyϩ,?ZHQ])/Y^_ %C$;SsAĒգ,YFkƘM^S4Y-1Zcrg RƏzU*cM_Yi&/L1)TE%k0-g=V,N/v;m&4ė<Ʌ$rfϳ>Gj)|cU23%&^bry M1y6 2tn({uLsNf(+L^:&{_0rkH-zP.871j85j.@p)qVp JKf)Roi$̾=pK,;s їF4L\,\"e$y"8ڏ L2IkIp.㌈NG"H1ĖH }5N]9?Cn$KF igճ:wzn_1lȥ!JORN"y1й@؆Pb&D1.}ioG^GϦ7x^X/:)->d'&~C< |!sra.1xqs.(nКz_.~܃;4sQ_ R8 }ʕ-+eEQq-\o'J JZLcnϽ%,Ј7H9o#ꚇaWSާL}MOiLN^b'֙\b|yZ:BkE jKg)eFX}&f3$闳'kY2Z Z]fIƿ2Ct)Nh%^nqSB)կPur2y~ɛSX T5gg7ByӃ&Uu&ϩ)עD#}_Y]L,SL9I_׏)ez#X1rg`B&nP8q$ljn.;?o?>ekt&oM\i29vS;#Z"B([j6+H:a&VX}2_ٖ Lb `%&|c`%hyˇ-YWcLO [b|dQ׆=>,-<+%b$8DY_6k\!(ύ*[aBEh~ PXxvj.^1ȯh?q5z|s"МsEwuA`ZOD($3a;?RUbL2Ⱦ3oCL|}P>`  VYr [LޘГ#nTin0f<]obf փfS IDAT*M,Ce ?tѵ12+Y|y ~ @lf+\}eFEYR=r Vƭ1;SE|s3:O %.f!ϻ/}b뤟a4\zNՆJ}>K(rӻĞO{=VoX*zϽN׳x?yZ'ZU }/-Oa_"}X0zGvyc-G*糤H~gٝ{AbDb}:|7{={[#;CDŷ>F`m~<*QttnU/_#zx|" a/s70AJOу7uAdtQlk:3vzJkKZ)r$yދQM9 /D±׹E͎#8Q/{Ḣ^O hѢ1o"% 8Q6t{v'p\hkL1GvIuI(}x"2En} Fj {$ OWrY&hߡ `Z&u*0q ?jF@&iMFG"(8ap^P"KHBl &>8 E xyFW_N1P!@Rč댭c;J @NEQ0Iyv"H blȺLFƭ23T Yc{tѥ1wN`>qV5ca8)U8@%^ftcڰ}2kHt" 2{t{mO(P߹'*xPEO__x}9rE^ LLZO4*/~ݏٰh[.$DLe՟D "RZ{XD 1~|z*LₛvNoI-U6JNm^uC>G00h#QBCG@F L;ӤȆL׆^ asSr|jYу= t2ݒW>"t`!dHTƛ 2|/Z+}(uIt T7:g0-θȪZ(ځwTlYp sl:;AG`&ڛ/txL@^+R/s+'Ɛ uAXY%49 G5Lp<oꫜYۮLwpА/_?usE\gҚ|Q&{~[:Su_/ dbܷ;[h6C3=՛v a Whؘut6V/w,g91Ԧ;!-um->y ݞ?=:~OJ%Rӓf0pc s`llᚌL&tVnV*TO?Kڵk^{}xs. ?;@T#TK_LwՀ3b!d]&ono'&IB]uk%Z7LN#f,Uʭ(PSjy QliAFM"dq}Ӎs]h޷IlWbt~"W#lټǐN7i}Eh4\)x '6@FzS B(8:Zm_ab=`нtf='Q*½ 석a #2moyu t]cʕ3DS} ,]/jku7Y~`DPU\qC?LЬ*5:VN/۾uB{?j9ĺb9G׏/: (D>؃ۀkw2~d 8`hdسo=[@?fy4mM oğh䘂ɿjHMZCJK{:s'tx(*:Gugᕪ*#;c9։b>vc/Ag !f/^'vk-&]=r9gl)7YZ&s͗§v1 +uaF#:Mݨm:4W͑5kcCz S$PSk %1KI-1)WJtvXXB/5Ik̽֫ 1ͼGBE_]{Qu`mrhOa;ݏ#e-w(KW76R =}}:J$H0 {7c,iZ(Ih9B󈊀E6%dI^yW96K653H~zݫK m }ُLyc+̞/7iV:ʌ9(& d7mƯ\#r" FjG & {s\y2fDc9Wx92 ~΃H5k%-s A>ךuzw68lvCMm[ 9d~*չ:F4N25^h0G=]1ﻛu8 ;U7o(K\Vwtw9ډ+dCh\>!8cVȳHwȷ-^sd c_q\֩}?ҕ;ƭuk?Cd`4u4hM"<,B's &+6\]9`9Fʫ#lw˖dkrs'pAj%Pf , (#2n~ oC}Ydܸ~3jzȂ^kMο'KH 0QѨScwn?uN,mbZoL36k}~`.!|4^;*E.?n=FLrB7Z'm ̶l˶l˶l˶l7ˋmٖmٖmٖmٖml۲-۲-۲-۲-d3YbsbgI-WUfAީMyKb S0=)ֺV!,b&;zp[3_wD h [!d3ۆ,Kt| Hּ*׭#JuNV}KdkzoYB070*1©ֱ-߷D={M־K}8 AX` J"D+ 2[g}Zռ;zp/`2Q+k"Iz:\0Kjߪ,Ғ1̨AbL+ u`kIfWft%rК2yYg;^&(xs>:gZ嗖eFI~7D~56cs 7a EI2+zލR+ 4%}_FҌTXcR _hkWWܨ4vל4ޒՉ}%ޔz>Dc^+MQcEj;uu Ab7RaMIp6VN{4Nh>8\o# "O*ܝc Q306g[+J+xZPxtcɴT įw<DЬ`p`q\HDZ8DkH%zzVfeXX,3هjSOUcZ@-@qMbh~d&s[ Fъ Ni|t#=R)3OQn:@+lKPKZE].gwYƟ>ٺޥj$ir Oxm]T;QvO1'|kCU%XSNlו׮Q 9,P~56C:7R!j?Ϳ-{[qɋ400cHDQ%>j:sژx8ʭje7oT#ּ,jHp&+ƙv1=pl<]Y֔L.?z`-ud޷BaY?ތ/Qw$B.es/D)x%j5ՆנGiqs>"72n\i7I3I*zX'XX6]hc=lTKI^ʺq,gh &M4 WpN;TCUb:;8K9Z"ʸyd+MθD*x*m#ݸV" #Ebs> z2؋fzk :W\&V%GS-}tjI,߿Ď{5Y >ގ{хs_A&:||&ZH#'M q$\+n\ nHySNoO\^sPȕ蟣+N{PlLI~gPp-c[eцXyz礕8sˈnߘw.P/$k׽ߛwOiĬs9К|N͉{܃;QvT˸fL[. ypf\~f "OMƾhǾ %VVP wg?pE"_1[6ٟIS\\qʸ$GYЩ]]"4a h%Q.bXxYgw8+\;tv{EjY'?U7BYdfHj`FB4K'ƙ"i6\yEAFWt!g.M] d}7ݸ*n#=FB»(e@ը]j.fT*Ƴ^w3&H{ݎ$=rxWjYr/Rn/]n9 A!ǖ7GhNfvOӶ؆fzxxFhZ﮵_uǏKs[4T`*+01ڮǙvNpK1w͋+Fϐ 䈬D 24 nI_G,xK>TE=.pN̓XkMzHNd1J$%ᙣҍ.H1ojNB?]JmYYrIG9( ͬkis*Kغ"({'8# SMRYM`W]'lF(S=xǽw ^nY7z܅A3;L yJHBеC :Ep$L.tq Ϫa炻ԉ\e?=iIxμsK|EV$5~ڌFn:5psc?@S@QrU]A4%#A+Nޱl3Ѣ ʼnexO)V[]lys~B0^mUycu~0s_"~h ,y{^̑4{ 9 GZpk.q\֜qIx/[KwiJe3(LF'&<=nxcbU˃DGڰlܭfx!+>A_֙?;8~eb;K=_QL;s!]w]36jfGs}"84Oĵ&n D~H  2止}u :~(Af*,ޙ~~R&+"SQ q'RG5U(uGRW]Ð,}ETY%%p>P4gii[|aW۰BS27GtAKqZ'Yn_UpѻGf<{@&%fQ剬E$; F9̦4NLAXٝħ'XGw5t̸ uS6չwKFեR9|(kr[ 5YY2yhQvP[:5TlzH.JQ*gu/_ \Q2-Qͮ\O&4<\" zPAaے_ $|;^uX-C'oy Te]Eg5Ev\4U<DZ$p11цTP;^TcTmx'p!(wdIxuN. \&P?^^ʷ.Z.\R˘ɖsW6K-C=!VdֆW}4?~Y ٸLH|PѼsMg,Z`s}y.%Z8Ѫ1g5ν4ߚ橆-,hL$eε*$qbtweW]Pʭƥk"GRE.8F{p&p([%lyơEI$;݅I| C(6?Bmo6.)i駮VtҜ! ;%.Y@Q[x/aLb9]sқd3gN k2R\Mgec+5#tq8E]ijf%eMTʁtzuHy5fkvܺ\{6Tkݻ՗`BY?z{>Ŗ<ٍӐ@/H1|)hcӥmhKoG}R=k6q,Zقm[\73b~cf_/:3q\=tJ7 }lW FQj ݋fw `&h/s# rtErRaJw@բD_tnG[U\Qͭጂ&\=Hw)VobْZWM=: !/+;nL5/aelfbIľaaW41ճQ CG7zil[F7tj:zQ^+|MѨ* HrZHle;R#*JA^.5Q%9KQnw!@_Ai!C4E% .fӄ <;h f9?"#"?~{5ra3pzB*Fݠ. ( Qv 'S`E{1 #G^TL0z-ÒoY9Db4hH f i6z9\^G4(N[:g-P]TbԃIxt?t?rUq;Ojr Y*cVYu".Eթ( % mzzhJQPM,t yEsq'~E<+2Ss ;5 dw9GjԽ5dia>զR@OZU';{s>j*RTa/X}IURėnL46,xbD'F!aYD,6)ڭE]G+jH#iٗcɍ^#BA%TFeYUȕ{Kv}CvgWXSl}ЍD_AMaքY?xZ|ŞHn݃myX2$a`sxf}9v%+):r Qnlk]Q`%Iƾhs/!؀sÈF0Y|8. A <$漯/,RRTв:B^d7)H `zzN|7 &~f YAD4M\*oYE5['.,IGʯ\Ĭֵ !o%)>T $E/| AV4-TGO|?U~mڡ:""eLv"vޮW010(GgHLc:5G"ѿc[w7)XB7 O7:>q-"E~οn.Zߑb&QF˝Ǜsu>0dPPP:"S5k[b#4<0tcqbN6^iu<n$*I#Gus 5z;g j0xƽMcӏݤ'ox`3 0;F7vTD@Bͧ$Z p@X_oze` (8irp ^i?@ڜWܕ'2v?5*Tebㄿ%cxtʔ2Ao˶l˶l˶l˶wml˶l˶l˶l3e[e[e[e[f=S k+\r=b­|~l+ulh¾DB -jssv1?W g4~l#Ot%Fd+xipž+Mܼ΅'cr϶W{V38c[ ›aH=gn?A&@)0 ?5(yz`w! `B1ct{7KT^agTS'xS~!$~o׃ Vu4kTʑKgQ'~iL5C畮wK OI=}NǟtotdȢ?vmk«>ݽezy }VsosϿXv, _  =S9/&v=NZ $/ GsI:׻pqtIc6P N m>δ26 o!<YWɹHU@١:ʘĮlj\1iL\_SҭdJ?9$I⪺un"1zR~f"!lE&,lj¯E:ߍ^p#DO-2m"X^s'2̽i" }FЦUn~z&L)K7ɗ-1wdX%j/AV޲L!jі%=pCUXXsJ8;giɽ+CU)By_+\CĮi[S,yr1+h9Y‹zѶ_ 0w%K,OMi'3*I+p;9K](8}|=5ģRzzm7$-g\i,IzOņo?t@۩vfO{)-bi >*eV^L`3sZH ׃_yj/ +k֜T{,~4AGI2rIԬ:ƹbf&|!R~kg'q໇>1El g!k}tuіf̰$t"h"ӯ-1a„΄? ^F3 LR!2޼)5!&&ژIr2vϢʮ27o?&x:dz[ʫ˔=m͋=}Ύk!/]t^yҩ֩Ibj2 /Oh6ܦ5žyev݃l>,2|MN9zIdDA y_"l QYxY=󻉝8S9/^4gv6;qh{ Ld`d8mqfbk۪oKޑ.!2%r1Jͨ1? 'Emᛇ'8*Y7A֍E^[Or;H>';{ib7q8UӞi?Hx:QTrKaRoX{k5o;@(DdF qm7m8mkqzXޱ"փmuN?}vV#fX-cZPu}.'ܔT bA>^3>\8_pa ?]z{o}0"KoY@i iVƞ*on2 VQ=Y0M\cnV~u&!ʯ+bbGoe=u@1)ܟtMssMvm7r'lg!eyk"Bɰu]5W혂'p ]ܜ{Ot)fio5[MB:9CVF}#Իp`N.Ye!|StpP7 EWdHȪL6ar8 ꦊ! zEQ&ڵ.=CT{V"eV/ȇ^BuGSԒn؋3D'chf.Ցx9dJЊDcLl ;p>|m>Xs_~gŐOkGKAb׎=5%o;u"0 bFB0z^{CS.^9ݭ'3bisv⯝g[]o  ꋓ DNnIav>YU xgbbv()QYzlRD7;.5$Gv8*>)XΚ`&wsCC"fD(*T*Nm;٩Pժ-JHl};D_Ĕzͅ9Y\:6(@5\A))$!*FeX7TL-kC43q7}߽xG|ZJ%בJQrgM[a~4mЉo4jo>jG:vvJ]1<ݶ6 ?6cg짶VkoCval}bહx3L#oug fӾuxTkM%@ {a! ȵtr{nr!O7bg8p0b٩)T*p{>g RV2Em]=hv7JXEJɠAy1#y4HwpW@Uه3`ֿ|_8]eF1U ]CLbpn'$p=oƑwE_ރN(JTo{Pv0PyeLwn9d2)W~l%a;2Kꮰcga.Pr(..S8 IDAT8{Vm=hb_oDuoǷ7_ 7ڰJ ʈOh;Uwn[, c˰0`0 Gzd(B.T_^AQ~[oK("0M1<6'eL%a {7ҫU9L hA(!B$ۗpmlk0/. 1" $@qF#MꞞαra}Njyz׻z׻w1_\{ |çUjrkchFg=`LTnk8^w|}=rWY:s~I_yP^Xlf=;H/4v%F mԻCB)rZQtl6Ӷ&Tg7I}h@ RӛҚo_3fĻ15pTMjo0"vm?:+On,4x72P;!^b龧MKhcOFq']ooCQQM-avD޿x.ͱ7EK- v5n(J`;CEC&ko\቟J Jn{/ Ҷ'-x4^W{ÑkI䒼NCv6;nϑT+~NzN#utG; @_2rM ;Nm<ē_i4 ޜؠT-3>N7$H뛄7-Y.$JXeD }ƺ6>vi  ^))Fpڦl?Jʎ 6һUrBC}!Eg%L5Y$ˣu(K[&C-[Et'( 7+>1=buZ]VVњ*8f$8# Ƕ;N١.hn?C%D@c./kHoÙpPL3CGœc='c7)<+"'|.^dʘ"1+ RƌN@%Jg+Hpil^g]%+>4PNk*=KҊ@C =[k\hКk16ȩHк]G/D2`OϵwYǺX["YR.|ŧ~>ݣqȚ[pP7OgO޾]mbY"v.G63]S;IX}hbt:zO@K{WS.^Rfp4jSØa c['As_X_6 ɼip%y r\kD.N殔{qG?C[aLHl 6~`_|y QWl>n٥tGq7ѧ{!dۃʔGd}GIْ>N!V滬n E/hF| i_Z+5NmO5Pqa"Dki E+zA36dƪchf4vՙ{icF+Q`sc $Kx h‚ :6Ngt)b]ʡU55p=|f`4UR/GWX6a&Df wڡ;ADD}YCn:%%'IËp8դ3ݦHx!"^ʣrz],П1w1qI&~c`WBw7]K\2)_ "l7΂CTE\{tޣPCjm,G6i֜$}RU qfmAVǛtId(TvE'ѡk7蒎+@N%'8]3Hʚϭ"nJd9 MGOHI/ovсZ2UԸ8Օ1W[nQPг: cC];[V;;!:Iu uj U3:fDFRBGMj>WŸ=C1u1)o ~# Dv $"}T'Mڙ6/{ػ-瓨Z0b?:Ҡoh  o aAshB8"5A6=oc6P Зh!O&.\]ꡬ)'"WĝwL9'%q\GpHHq2D΄s>Iam4ssb;n~ qp\B?z.˒\OB7:81GsL<6q*q!Rq o%7 8a+đc@^1kآMz*adPV|4ޗ:CF:C=UÉ9 Q7Ied'$ƮS\Wuf;oj*q)rpRzL'KBQp=M;ױ0FL\d@ա58{,4ECb@l#C5T3'@ SY:>=m>$[4b@|1GR.5rEůq YI.<Dr5q6Y$G"'s# ='9eʫc[9dB H^͹&#Mdr8-SY@gC!r%SѼ"sWc+;(/X) "Q rG&|'p2^y(+ O #豳(\ !?R{?;(7U+L`tji$S ܼK"ss6RGqvwEdkI@BCD)؞m[`S4? ؒ8ۗ 슍֌b>BV y>ĥ$NŽ~a;H Z]‹?s*˹ 3ӗ3K}Ѥ#HZ :D Heb~$#">:! $CtE'uHԒdoRvps)g#>ğI"z"t E<|rbEiҫ>$$*>V"b'[5:b]Dn+ؒ[Ʊq+A7r-;2 "* 8́0 ^G{.TsRI;ҏAq|SAڔpDZA}FB }Wo9NҦ1[!BAr$RݡkJّIԒ8谙kT#Mf58Y'tpT 7Xd]f|EA^;}䒂PF@4$*a @HUğ 签!dL8V'iYI,CvW7#HDy؏+ @yupn#M*%7ΩĮƢT6 3.Fqt[:9IUcy~!=CzH=|H!=C'FCzH!=b$CT$qU6wn]qsG ק!Lb>VCdN~"qĺȡ_W}y "+߶*ڼZ80B흌}r2j3V̧u7(U^5w˕\:knC!$/'\A!o @BNl8=|0xHsǏtFynjj P8Fyrd+,YDT$A2`HLq_[r)AʧFyh. a&ɠWzD]0{k@t">xqo?Oo_R!ϽɒB@qݗ<93z__`iDwSOhS2Ξ_.7!O$}4'}9{ ?,rL"#d7-{o DY q5i([U<DzL8O9M Ԁ[޳h2/K쏼UU0VCT?Arwk{}4TI?}xژs%@3>gw1i$wGxڿ\f2siQ@X^֞H$";?"?Ϟm[PJC$-: $~äuͱϜ d^ q ~G%x-Q?+tN8RǷv<.b;_xB4#9 u-*3_AoǶx>y&BC BQf8i|uH!TҌbP7/-Fֹ&Ǟ; i5˨pCBa7&6)&?[rBZ`ZD bBo˨kB(7N}2yqCΓ<._=B( '>Zd[ު +e;z1 F=Sg駌Hĩ=67s\h=g}UǷ/!apU:c_݋`O]vh##!&D.SG7ɀ;\HQ<@ u\K!r쳏x*7m7CH_5?YKEc! 10448=(EqF@Y:rG8 {~mTJ]"oH|152Vx瘅\@K |04NL`L\v s)foQhOY=gE,+o"}IWw2v}yUmpUvkk*XqksG|>,O}aʵqDS~e|{1c+_52Z/^9kcy&5T}Xq5{rcFn%V<;(nWimnJ޼Ko06G]”#f/% 'e_ d> 7uh ^sŕK̼.%ԟ1x/s_`sHH4X%fnn HQ[M?0N)RqgyA֧:9:G|5J[Ƙ΅yIV\\$z_9ĞcvN}WXBnN{ϝfF sdm n١xcX%ʟC1WG;?"B Liԗ'| 59?g~ vt,#d;,6;]H,&9ߏ0IϧiQOfح(͸\sGzĪ1FpW^ ӿI\L|_1Jmo>ɜ4GZ\h>Ftycf_c)қi:\&D1n N~ _x5YdlQ4>Ηyqb-*_u^/トsyn져Z#vY:}/=vjz`qMd|m#Md؋qj{4S'QK݌?fs=|beDADi䧆r]MdG&5)h} m޿sQ>[!H^WE+с_`w&)F(!bGd]l=Ǝ9vbuŗ<(miE,??]y%͉Ϟd<]6ߺGrk_ݓL.O ZoX$"B]ds>U"wqc7b@kı?䧦)~rUu>x᧾AlI9jq;>A=@_([cg9[QZ'̒gۗ׻L^hpO:\~EJߨv5nSƱ(V˄w饻^*!so1(0F=b L>3\{ NSL8I\dƛj1HOU\jP[M?ԧvnZ`KL ]'я&gӄ>t輩Co"c]MDv~|ʕqGl]%q#{+iҞx',me4Fy2W0ysrv͵W,ŲWr'X޻DvDwy>{_$'h)-N_afs)*zJS5G^?gua)6NK3/kcAtً ~Ho2/h|wFYʕȉ{/V|ľzvJc-Pt•ro c&ݟhSc$>"\0^S?U%h$B媊A. 1ktܿYW0qjؑ8g~…SެHdo|_oG'$>B}9F7wuRK+fQ #=Fz$ju6o2g5 Sqm* _C]LqB0sw; DH (QlGHLs/QqE4Oc߇eњ'Ɨ&ĐDg}8ߟ6Ӝg_r6P[BqkW̶yP`0:KoaʝB#j q ;jXG܇ j5D3l%[,R'G#}w| G 9)]?!!y?&lO龥/?o"ۄ7LL_k@D"lM=BkOG?xfdeYFJiOg"t/X|A~14Km8wGɽaxB IDAT}#D{mԯ@SGw@WFg9іc[ы :m!{6He5bKnma)N4Xygz:|^#7b}&7dUA-J@}G3=;Am=Db.ҕṅ@)Br}I${1a"BG`ú6mZㇺ$-|11ր:Q2Eg *l>k9$]'S{ĉU}*#iwtQqlܞEj\F$ 8\HS=6߲GN8AxW=d <^q[$_w˘ߤ!vUɿM1D)Rd޵v7Y|oƚNXYw(8ns/ny'Q+:3mo=z)ln:'4N*G6:LHҠ9$ (`e,63mC;kû0KG9*:)Isi6["iDP 缾tbL|y/pmQKPOYM7 i0eQg]K6>kUJcYEMXˮmJ2FXar1|f;BFeL|aOmN7.cL.b8+.=ؿ NfP۬mVe3+񯜀lqt o!e$c/kx;]\o{`Q(=U@{]H$龵C'b3i!!!Ag x.SAPy_T^pӇx ylBCo3j{@fǥy >*ނǾD|Ad;R޼K~'[~7'Vv-c4 Ɨ'v4w7B@3#߽t-iPth i eowq\ibƢH I'y|Տ)֘ <f 0謇=cE|@eNU~v6FRhdj "I"9Ꙫ s6fB ]D^R辡)PCYtrSإ5L 3&%!H$"E(#r# 2Ҭ;|8JE6$#l'Ό̊őla0Ȅ+X9ry:6]}J\DQMEQ<յ|:.}:xצP27:Xd{ЂZ,)$z &~gpnx[~פvYzwF$#;61`|SpKw tXX,݈'oz/D_ԉX='jD ^*CY;b$So!CUUѧL}xzk}H6;m 먲Bg+^x"Gu;,6`B4s+Da #^cϜ[Z 进I{Y~(J{=惂.w h6(0?kςp ⺄6 K25뽼r4huْXXvzz&L /!oo#%AL`>HmJk_ K5qms +9PDD>kdYZ4"q3 Ȃ bnҡAhP^&6dFk43 z.*5i.) 2 cqڥ6}5܄CfXPYm*R>Eyfb^#{!O/ߥ9 JeRpبӜlbh:ќlȮ8mVNMkRR((4cN~sFY-RԤ{CuA`$ Y+q7hl`3o>t-u",pB@n)yH+O.>B(:ؠwG1Eէɭio@i+a,|Axa^@Wh:J iMA܊SX/bRY}"n `S;Pɐ7S$Ds]爍!aHJ;Gj˄ATI^Iћ"'e/&Fp_ߣw8f= UeDl|ɳ)}}Ǻ8m+` s9bGjK>O"|vEsWS(\.hy`*&vަE1zT$9ߠ9 VS]$֍#r㻮Ʊ36J2vsRjLwoo "+߱H('Kbbt(::F@ǰ4 O\F@8za9'u1s&a}ZbuơϖqurE]IwII !e'!&&IBpa`%VlkOtn )b 弊`Qp2I3Q~uDchkG8jע?f`A:CIM&%MxX'v6t@]=SaVA7;D8]u;}#T<8ofL:v\R:|? Yn B(QUz]t7#$d"Ě1rˣ^u/HQNkފD ɵcCHE -lT"ᱠ$P՛Ќs Od~'͟aWbUҗ3# `=n6\QQ=Y𑨯C(0Bҷ2$;z]Cb]D=?:xH`t$T/H4j!ՅM^Hj"֤;:> $#C fՇݜoQONI_^++>$(Ow;RbN;w[/ ض$ȺB2=›D)1WǷj\ѥޭG$H)htyq%oYyt+ԅ %nPgDoCb_Nl%\p"ybk: "0΍Wkc 3|f˹(QDIp} ĥ(ƊEghl @?}:Ĕ_JZ}"uľ<'E{_+1= Z{xY9DQHGEJUYnkrĺ<⻃q?NшIGw&i8M yWA8SGhVH٫c{Qd@LH=)r5FԢtP2@+-ۣT ό#)|RҘ>j(kQ+/$]qZ#_{EΨ~H>r{2$}yTN9Tw%XJe% !]Bso䚣蚎z(D{6e׬ZdSDQ P5B}ȳx;mGvQ0~yb$$Fs qz7VJE*룹{2!.Fy$>ANKZ3&vڠ .;hE[ ٓG`M%DssbVt-m+[c&Gr>sGQB$"9ْ#˹ HM! ~!gҶUnEEBBD|'@i=KaԶ:}A5 2 ֶNAʙbot,?B p宸s 8hqS J֍nغ^=A $D[XMw+ Cp=<<]<@jUZn2ʼ4/zo9/`VbWy!He xۦr4((#w ID@XPhQ=ZXw+~wTPf]G$d 'Gy2U|dCB2 Ǣ}(j8GuhܿT@\i9%4F{ܼC(GXa'קf#m]MVr2Hq ;W?&#= ;dlBHYKj6s 'e( dGFvOb'W#jH$QŲK7!QS pcaepKމ? m - ~HETyIyO臑m]Ҋ{9R07UqM&$*v[Zɨu??=5Gun(V3kD_D5G^R؂'DE@U}Qf'd ?R(BAȦlC=GE|GT6U82nn#6ed^KnZ,Z֢3Opb6Gql;Y١UisB82?:7 qrI"[ Xqt3.O}# 7vm3:{=c}jv  |#G)t[cb@'uz;,\ّ.+e 5*m nˣk'XY$_–m+ B ՠ1vHQbDtt!_L@A6=lg::Lh׆J!BwGW-ɴV#1\덁#$ke4K×}z.vQXP dG@q1 9[G˸tcˤ(x}r_r?{o%uw~bˌʪZvt7h(pA)Q(X,x43cь-GJ#6I)RhVWU׾eV[dFd,CTWVvV0.7޻{{]|x!s%\ f̤-Sv(?h1J1]w ޜꢿlb3?תY) wDj#_L3 Grh7t/`E4l"' v0)SjVOohL|r1ci[ž 8Xn. VIC*eTO^ `0$\uAfd`]wָ1cZg8#C6Gt?Y\2If)#*BwѠ VCwwG@OG =[uygHiTT*/nS],3ruO=ըP ׷DJzlLelqA'c?O=gxoʌ[=΢!DIb҇ xQ,7,)o7O! yw__GP9xjݘZZl20w~Ԥcy16͞mF gtgzbS=lF3,|fkd>oOGәo#ZB]~<~䖂7f*lmwh6LNljZ&CrEg?\Dm{Hʃ}?yk }:)& 徱Aaҗ+M0$o᭩8CsAt6/6'm^rZƬP^(xH#[Bl &u5 iṱsj 8?8Ah4\Kqc(Kh?qz2s>Rcb݄{OչD^^cRKWXZ1T7/鯻ūlOmt6G>FӶM1U 9t[ s &V&Ioduzuh]cDD0#W.ϭx|LBT1gT}oۋ屨j # yYH/b;(0tdw{ߤ:Rf]!σ8+وk_Ow(>St/|XݠT5r9Ü,1=עdFAbCL'c(+=k+bFw ?$:.j?%{zr^{i!#֞^eDRUvͱucxލ̈ɫ;%S5YtzJ9,;LAkd֘H+/`[DK1oMf9 e*Rf됬b!qeRKA@<).5g `v|>T~o !o׏ ~2ߺ"d>@zKۆgot=mƋUDC"p'H+G}%Q: `݋L,A\Ϝ'2Q,KA!*s-3,rbTȖ]7g6F_^ YB*H5{'#vQyL{ñb8Zw\ͻL-cqQ|?_i~͵&Mg:͟zT զ'} IDAT#kg( BK4^BGЫ:( ("v}g9:Iw)߉#$0e}YM7  Ԣ?7m8JJZ`D)omB_ U$MyDsA튻x*P+ax˯hߧqN=^cӏ}su <3ٛcK!|ҝ[ZQ "h"z`B xlv?|P [IxQBs.OdeQ*PWkT >\p-B5VelpSR5+ĖK 4 a6;#ȶB'!}uq1H.I и\ݎa&M`_"zE.#Y6{ve/VԹ,ZETh ޚpmɯLGdM+Q;j'7xˏq:"\S|5&L2:Gp;A+ A3< |_ =g`>%qoRLcg>pFK@ rHŚ1-Gmg7C Gj>f^ʥ7p"h@_W?x_ўиs.\t>4 SU8cJ&/"z٢SS52wj8ͧZWmT;ꈦ@dTn{0-yAkp;ʴk$#3MEȸ)NCG4'D 4glNg=B^l껯ߓK>ʶE*ob/H&'>ZPNc0_& " w}Bg^E$&9A||6|tߛYmaڮvm % 74tsD+IСq5xC1#C5oL54Z8]…#r&0gSgcLc}&$'"@ _c"fnKO=S*q?ßN%f!Sκ:<랝^c'Dį$}6ikcF aeg6 UȬeH|3E9F 2z*D>6hwTBfa$@M١[2m HDa"SgۮIZ]X! :&шN, =*lʅ{gKzߢCDQ(VbB! cnB>#\nq~0[q^E9_dUFVBCq-N9# rX&vI]U]}]8oԞu gm=doe k{r)VxpOL+2r$3ie >WPi6ʦ$']s`;HC-VGНDF$sAf]IY[XRLԜ=I+meU]&qIevtDHwh^iEbm4$C"$kO $o5_ b6Gj#M&}Y{8wMCO0Cx=Oa&Ց ~:oÞo¢hvE Zg bGm';4~^Q-yE=i9c\.w(禊Ԕ&YUWH%ƀ w#6 {ԱLI3BZ%IL7ӥbX 4%ԽaW¨;>١Xy oNEmפ C@gMjUQ0z3Kj5TM ׂWތ!e[H:&^rj=]cMT Imx1"w>r-usVPzTnzN ]O-PtךƯHAڗ2^}گ4fxGǻ x{Oź͟mv4u«Q5/APA,'*z@3dolP=JjI$~k8CsIS;,լO-mel6H6Bvo9?@k"$]sv|lKul6~9MN`u.&7ptvYZ6:t"m"18y4r&+ [|R~`%#8fx^TUAܖYMM"{vp,bHl;^N3r3屨L#oʅQ< 2I:1Y:ixUDSA @R-nu~\WUTbtA`i5h5F&@+q3ezv(L|9yj CzgX>rnw DJP2[9Re^\.!=F;4# -P*DuDvVI/O v)f %_8py%Zn.}pQm=t}(V#pA10J 4G<[KٕHC٢lgaqkcɦ{2k[,ѢNս"5Kwdh-~B++N_-9fH! E7n !R=Hzj$QA@Dr`}#9؎c;ȶwsSq, _?lMB z<jѡn}T븍%U?wf䗐Dw>:}=ΊH8nDWzŰy_WLM̬y.bWNp*3}v=[#M>3u4`Le ˲}AwCR;$%iZ@bVl(cUMm= oc.Ѱ cB]2%v& *5pEna;6GtKDtmsW:]8]J6q=ܾjn1E|ssC;CkdI}N6zf6Zu58Oہ6hQx3_ɏtL;aIWs5#|fIFDw FР>\s @vVh:޺C_6}p~PՅ2vn9.aKhsp<=voYtx hP[:6͡&#_jS;W]yt5R w?8~z2&,a6f(Ҩ͉XoKh :4 wvW9PŽوA)9xrTC[>}"vg5չ2R8Mصv};Цl"t3;ɢ,5 |/ PO0S>Aӥ:A|yZM""3|٤pym3I#%ރ Kr_OxpSZބH!ʾ2v§ TXjN݇t(5X:t Q!JZ/J*]pL/:"3|ŢrE:EbgԇjUգjkURDV(ٞB*8JF>B0Tے,RTl ͫQH_Crd"eTRe-21ѧlkp8v@-QEjh!p<) M%^8_PdLVU4F{D} rQ9Sȝ?/ 5тh{Kkx|tYpZ="@*d-QIW:b(jVv͡tHMXaXCڊEuПp{|OL;pɡ ?U3]&&r\:\׏gw#3:ƓZS G 쬍5鼯w{иcVAkt qEV rW8ۧҭ]BebVf_Q9Wnв5VUdYF: -բ&LiJ BC0wDbdF3SSZ@c{f -=}C*eX~ȼKI -Ӣ5b=t.޶o݋59|t ZC-ZN#?V$ ]1VxDR/H|:pNUPV%[q@rU9!,.U(?Vh>λt߳wF:/P 4F5'UG9;;T돯x5HD#(щvl=I3@NKW8QbmI[ W)hdh^ mDcsv0G}IDAT^T]R,8PHDrD1[1RG u{6tamynKw(۽5V)S/P-9NN5F~8G5^E khƞGB}.PЌ5:\C{nQOВvFi(Ҍ{ TG*-F9]f-Byʇ8Ū #+i<|}* -fi !K2cD9Q.ZJk] oˋpt=Vm!;PeoFA; X{dQ۾ҔM Wzt%R췻sTebM9!kxVI>43;4j 3@@Caqu%Fdқ'׭?F(%c43s>rljr-ZBƯߝF,%2qk;5[Hbc 0=ᅪ/?]ϲe;שh#ʄ]?~0.+."qz(#VƯue]'3mhB>BZW~[EI~s͋ogp؏ZH1:p(s48mD }*s<͢4=d4N'ۡJnUod29's?`$REK:;ۄ?%(ҁGr~:x~Ç r6lОi1%#i ߧ|)j9:x ͽs72_2Tt?eD֢8$$x'ǑZ1c{AiXd4GQGzr/1}4oFe)oݱ u^1G3L ҎBjeTƳë#" \\X//pm %9FpZKH!y}褈WE(3ff'E &%w].+XfZx;Nn_삍? Q Z%jZ[)NXM^oO Hu&|/,(9H# ";'Kdv~f;;>S/R-3E?7sC`KA\}uKg +CgRS9~Wk&3~ozFA= 0s4µ%)ɝ!VOۅ«K̼6+ f!qtm.2iF?>窗(fOO(Bq3e~i<;K=_X#c%+c>fαz(ʔ,\]bTF7~ޜ;gX#;iF,NlXt|S5c1Đ0Ď1TI0@dt#s|3NG6h$ IF2c5z_2wĠ?=ӈ/OcfLcaK# !7b"y$B-:a_M~d'j vh ɓ@8^|2KaАܗ1HP9>q8;FI?#҈2yg-!8M=v"~6jF}F_/Z] oہ6n̅[\FvxrdiAkОjS"V;%c18qV.F[/k}C%Np~M4rXȏ8Hو2)};M7dvD}ή{ S%bdoh=!p׻4| $B9=2qAxa.8kS'-,XIٞ$T3:Om?!3r8~tÉ}()xϱ-at5C<P{n/b(J_Ç1\'o!G':S`O[H͎cp8}#90pW`D#Y?6m6omq/sP|yal[)Y esqDZ6ǥTa}-e's.coi5H \d,7NԌtA 5{H#s$n1ΐJݓnQgJʭA?cKSg \{ @_D<!APyT'ET$%`ǎ8q؎{mn>w׈DÁM&=/ Q߫ 3׶'k$×|6wo1"Rsj80)Rk^g=a=aBpMUi^ ˇ̗K#5=H=:(Xn+S PY4 hk:c]48ӧ!b%*o`{yr,0eԲvMmb6AqluE^E*@{3|Cy cA" XF%DOVvX &巋MFqGZ}ʵ2}_%z:Y68*3Y|ȃkvzTNG-_~r> f`^BtDf{MeP[Y՟|8ʱ|՚̛7D[Tm+XlNY1F^G3Q5"EDCDZEI `̲6"߀w:-k>GcUp -vDnnNpז|5AY/c6f4-&޹ S9~o7*BH+MrB׋I?>R%w=u(R`5PA|>x.sH T6#ӯ(/sy(׋G=Zg"ooANN>AuZѥUY{ޡOrX9_I.!wަǐ%(jMр0ZW\Ws8*s]?_ O;8΋ 4븣=P&O![2n%6넪|_Ft#{r%!\D{S~bkz2(9%hmg&)hC?af$tgGk}<<ܡX|A'RWsyت'` , ߧ֨6U4L_BGq( ,[B=z:6Ro4Ԉ}};j!Zl""c5)0u#`3)b3.BDgǚ r[d'e+AS3gHuݹxoěj#]@c꿗kV)\͡(98|90I PBHY4j|ej|OSh qeRw @uT˦! hڲ+s!?jMk!g%` B!mÇ-=-gKpSX*ahHIENDB`java-binary-block-parser-2.0.6/logo_sn.svg000066400000000000000000076047161445000440600205070ustar00rootroot00000000000000 image/svg+xml java-binary-block-parser-2.0.6/pom.xml000066400000000000000000000226171445000440600176260ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-pom 2.0.6 pom jbbp https://github.com/raydac/java-binary-block-parser UTF-8 UTF-8 ${maven.build.timestamp} yyyyMMddHHmm 3.0 1.1.2 2.0.6 ${jbbp.version} 1.8 1.8 5.9.2 1.36 GitHub Issues https://github.com/raydac/java-binary-block-parser/issues 2014 raydac Igor Maznitsa rrg4400@gmail.com http://www.igormaznitsa.com +3 developer 3.0 https://github.com/raydac/java-binary-block-parser scm:git:git://github.com/raydac/java-binary-block-parser.git scm:git:git@github.com:raydac/java-binary-block-parser.git Igor Maznitsa http://www.igormaznitsa.com The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo org.junit.jupiter junit-jupiter-engine ${junit5.version} org.junit.jupiter junit-jupiter-api ${junit5.version} org.junit.jupiter junit-jupiter-params ${junit5.version} commons-io commons-io 2.8.0 org.junit.jupiter junit-jupiter-api test org.junit.jupiter junit-jupiter-engine test org.junit.jupiter junit-jupiter-params test plugins jbbp-plugins bundle ${basedir}/src/assemble org.apache.maven.plugins maven-assembly-plugin make-bundle install single src/assemble/bundle.xml org.codehaus.mojo properties-maven-plugin 1.0.0 org.apache.maven maven-clean-plugin 3.1.0 org.codehaus.mojo animal-sniffer-maven-plugin 1.18 org.apache.maven.plugins maven-assembly-plugin 3.1.1 org.apache.maven.plugins maven-gpg-plugin 1.6 org.apache.maven.plugins maven-javadoc-plugin 3.1.1 org.apache.maven.plugins maven-source-plugin 3.1.0 com.igormaznitsa uber-pom 1.0.3 org.apache.maven.plugins maven-jar-plugin 3.1.2 org.apache.maven.plugins maven-compiler-plugin 3.8.1 org.apache.maven.plugins maven-surefire-plugin 3.0.0-M3 org.apache.maven.plugins maven-compiler-plugin -Xlint:all false true true org.apache.maven.plugins maven-surefire-plugin 1 false false false ${project.build.directory}${file.separator}tmpFolders ${project.version} org.junit.jupiter junit-jupiter-engine ${junit5.version}

xҧ&QPz( ߦxpkQ|U ;19nSjl109)TI|6XwgEWK'HTr#e% +sէC_RĜo`[,}xs.x;*nc,Ա,r:'5~>/6q,74!2S±y>KJ3/ׅN IDATȜ$pe }zJt+@IBqNʹkx*<*co>E}YPKsE>c>ԭ*V ~R_-\͢ x^8.`M7Q6xk(u2q癷mP$l 2ʰ  n0W}!s?&kkUVO}9sppBz!V݆a`Yք D^﹐ԽƗJY+ZH5ϖ$PTOՐ4{y֮]K<wz((4@ww7se…,[ UUYv-7o^5z"8Cwz1n؍w[li`ԩ~,bժUZ UU;w.'|2>maV^ڵkdlll Z+iL69s0{lf͚ŬYd2M4`G?qb1'xhI;!Ps.׬6Hb4-%PM?3t߲)#[@@KT]FV dKFuRKZJWjKm~۲R]*i dM{c՞wqԒ1O%ΫfhQc^~Kb<:'>8w_iC6=3 ~|boٟo}uN(ߊ(DȏSƍg@EQ*(z&Eb:?#trY6Xg;l0Su}@=H%kayg9?W'[ Q)a|vȦx7*7(>kuF_nqi (($?vF߷@)&2\} &0$<3J1 %C$)2dHQFyl )N:v[hkk#پeYlݺ5$C6ljОuDIpGٚUWG>,%Oljml$yfTW-I%tM\SRXzW[emٺ̗U"Dt~m388` ̛7鶉gL*෶~]VɐwuyܿWT.н!T[kqJ̊ #a=|z#NLH*`tMo1 1MX^ ?숉<~fv̦_'e aeX4\{WZ AЖ0P_,0lS wMI-W;zH[9O?LgY6쳎T(A6G""[2`mn!zXK7SӘ_~׹4=3>†=֑:Ow[E.}_`ï̄X"j}Ey"G~Xj%ycJ]zkVO#ygnoV$Kuex GY7omu, L4kg4ȱyFf X~6 uy&缺ВL]7 o fYﱖQZ9'7 9)EAbĹYR&}r w.Vbg}8u6rJƘ//R88ȵtߨN"0MU!ułoh6ty:D;F1F~<@Sp.$R[7W}Qihoy;1V<;7e`2|"$t}tۂ t8^PrAxtלF}#cnj2}gxޏh_|$Ow<}tݡS}ȴc<+>}\y}ZԾI$7 y 4)hb;/ݴ>(4z4P]ϠxP#t"|;`c쥣bJofک {@wG5"j,pĜ2-:˟QXC)ۖO`򱚈KWcL 6]?M~ד}E2HQyjNGOO+Ucȷ!19%ŊN=yd1y< # u OegtD˓?39pW|?:Rzty//Eo45V<|sc 1hGg}*eDH'~5^z Z xkd0C :w33c^E/E׵1εUa^5!Sr̭( R!VؕPj6x]7!Ѣ͵dԔ$aLי,U$=t]9 GŚ![%zE u׃w픰$$~o/!`d ´CgQ ;ka*]ca&ZǶE(9d &FZ?~Oߑ-;| C:P~i&cOnkk ݽ+c2P1j{"Wv#ۅ]](>4fTkv_4ݦMwSAʏy#IpȦ$V]x[gkъ3ś2"]H7g+|p-%r:FIPgw`) e]y oȃTTDBuIiO?'wh MDY&*3ěQw a*UY;g 1klT]/L0ͦ9PLO/j&2wo 9@R$OR'yH _n,#g<$N7ީ?Pލ>$K¿9P4Su"lA+V_W9\>2ğ</Շ7:OInVC2c0=@}$cQIS8e}>]4\}:Np|֟U6mWôh4Av OG2$'[KYADާ}(u>OS|WC;e.#YZRCt, {, ,^$U$SBTdH $Y=oV],v.`h)XuIC6d $'&X G晹oi].]y)oƋfit3Bj$,,#i=O8/_&'VbeheF'ƈ,EQ,MhK_ Eȟʱpq/%*#<ۅ;nL:=+H@:SI'˒P|^ SiNAדxkoABOBe|k>E)zMܖ"} j)fgQɵeYX& "{%8ea^B?|]\cyq RLw˴wX C2C5UVs,|"ɮŁ+ˎ0t;ϭW$wG7rEuQ"%Qv/uq 7( Q5"h)'ΔśZm:zdᯄ:,lƶldLJą= C $gF%@MՔ8Q]6P)&53ټT|IJ>xfQm52oN訽e و͖ޫ}DGp))״&ܚ9_+à?|X%wZk;:pɉϹU䁙 7%$YB}̉2fb(U*/rb]wFp) j:JHFs/xuڿjTgjnu#YF>G ۷/aVj-6y,XDq'uB^͋wŋy3JXwҤ\ϏW*I.4qZX[& 9/yehκ]MBli[-4z72S%z";U988c0 bݎfHJ4TzeК O^=-mp]ZתB$DD}/V<_I&ti28ܴW\݋)ޔoj!ƻf{jɴ6+ d*m3mBף{^3$wo= \.G2sD#Ƚmr֋JF Q,УUNf v_j 86ցDlC F53zY\\ܱޖ_xE)R^O`;!w]EQ;h;Z>و]5 aڮ^X5EiDߢ4ccc $[" 4Йh8#{+x v&@p}#FD"EqG$Ib`` oAVVV,Bn! Rye Q6ᨵm2YJ[!BcYsԠ QA2$Ϡd돐P xQGt^ɋvqܱ4Xڵϗ[i.'nmC9^% eOj`,"22F  HmXRJj0[V;SiDd,!dV׋%諟wal"6N.zURQU*?l4kS]rLS^'Pg%<7Ň򔎔g0,=%֎/o/b)<{n4~ #:ۋh=D?|32]c\`>T"+DW{ib k$p7nseݲu%,v.]_!ppH㬥P‰I7cvWchb FcW+ ƟCY#1)CIԾ9m"CK${^L()%RI?L՞*Kz1R$>#1gDj іmV/]Cy z['wԝ:~9VGWzq jz"ɱctap=q0NH eL!hYi-|>ߛBRj;*_ўmW@-ѿh,i\zW5ZTgrfM@!Bl`lr wc)hY+++cwX'tb2?ߙFУ,"n(/'t~gucP:cv&?*3YFn2puo]"#:S(W9[gVX,S)WKkc>I ddz(!ZC+#) wU(#1wvHJv}k 8}0I '] RG$ x8OJ;=#rͱ:4̮4j*8Ɂ&vsk>D[B.G/K$&?7N׌3ޥ$I')* b*ěs菓N0.6ޙ"{$ab&VB<`J:۳fqF!Uub#rH $ r (K=%'H=dրЄɎq_>L %EgtCJ:Y @ep$a$$%:n&}y&FsLHj(>A0bF-Dk?\}# e<=^V:IZ~ݟbxǗ WT7Lҝa;{}:G]1grKpo\s'B5tQV7wcqჯqy_GbmE.|5z/! fQuKcߊhXM08v_s4$={0E"VuA'M|l,A Ԝ$WOS$JQ{o)\ }P*r55 \O]el n>S`0H.3ߥ%fĿk9F}u->RDC;Ȑ@]$&]1]=md[pm^}K y"Gze2x W "PWj4=S9zIn=@Kaԫc{VX^; ]3X#k7z Qt:0>Qj`S(tYY|8E6E+.WWgߕx׿@\/W/s{ =eh6 yr/{3؇4CmsQEw2}z:@D9s(K E޵+HfE".e"DI 8aJ vb3 | 9PUtX5I~&^ ,kh?~랪` 7;L%uEL#Htވ؝D|t FWb -}}T'rw8 'RAhrId&BXcTGeWX٠TX[$J鸱i{h5Lh`̚;60ηs‘2Z 1.22{*j5`cۗeMu"2vڱv}:-e@'j G9ӾUҼ56V-WנkPMsׁkXul 6hUvl(;9ի;RW,4MysYUղ$@`Ǥ9+ ^Z̀{uU܉U/]P0JFvLy+]WBd:=K&ٝh,W?R?ZAcu|.ݺz_/69O=|P6C=2üQCbg g+VkFtYVVׇEH$X=B8f.lfj^4ȧXId<ѿm'j _A6nޡc \;;YRb5? bl<=HI;|* O>5]رf fbQL꼢]qbY[whƮ&bbo:&6ŧ:]D)7 [l; 樽pt2tuE7WD B[wŲ9ZfPB?SYMW0}rk= n%$̘,I<^ޝZE6euuDb#mT ZϡWՔ4hu@Ǻc>W Bqv=-_`l PnWvNn^hb6|iUʥ?ڼcep,#=>4]-Jw2 8+"j8by36X@K< 4/-rcgbܐu?)Ӓ4;Gu%8YDTh6CN1{S ǂNz e^8J I ZqQ}ĵ;L~2J2x6Ea@$ !ʲK Y2!R0zi73+.x5ֆcx2ν «j5ڡ3!kstg s,N1PApsjW+?@6d/ 0B:K\N侪.2wbc35?ӻSMOzW[sՋ9bGɏ\gKsfSNZB"\m{jHIr{۷ٓu>&~M5^{]@3Z+_ح?V뀰]tcUt[w 42v=ӤRfۓ^-~1sȾcׇwEtjpw0Hsد (gN*zXg~ӧn8߾ aו{j95)uOʋ9?ܑYfgA.#!2NBb gy  QF^#O`>\$05v`M# ~ \ABCOŸ݇+]"?3)Js9Ă>=\yEk*{$p%'xEg7Juy/7gnØZsYu;݂|{ $ƞMD?d1iwZ k k/X>`ʹOۮ̡Qa/Q?2VJPl+i*jV#poq:}x=x4C!D.)!_ZC #{*{UgrM ?n"Uo3k'x$6mUV@ -l;J[ݹ@ .^#u)C|) a[cSAV$0$W_/|!G;.ɷ82ԻYKlc+v-M }RUdI}&\_۾'ZX(dU XWW7id;./^zd>`@bX*.g""۶qS$9r*PuYHU%8U'r gUG󞺲8 ^T^禸P J55BVE p䙽RJHBurm`VJ m"|j`mLt8dHHYHeCnKp3qC4ɒP]Uc2-#뛿hfKr9c`xxxKϲ,2 mUejjj*&mBiǦ>E4@!U:uM`XZ7CS@WoSz氙Xw lfn$i^_fJ6UYJA.]bYK}T6pB?bnЏlOR($T} uʆaӺ2*Fe&;p,rƲͤKiIdIH(1͖-Zdc[՚ z^U$}e"*BA*TT]u8/A۲27`zE$f !fd2:tcǎqq=8?8'ܶiRp@Ov4.S\A rԧ~܋ \ /pޏ.Wk`ˀf7(Ho2m>v7_Tjcy,Lh6oXqiޖ/U=!ՑĻjhebZ3s066%Jث6wdKy4 V=^l$NN"gzkL~$o(.s+nk~ LkiY6BtTJ 稗UyPG?KI/ȂBN)+zzh\ 'gU?怌2atR' X~-hF] Y8h2۬ x3>y0XYٹ(ꮮ.R)4L1ul"===q8qcǎqȑ%ڐ)J"KTɟ%epϵP+f,`G U@S3 ܖ|BiM򭷌7LSSS,,,DKuZ1fͷ5\޷3r6 <%VA>wENղxl=wIj}݋=kS1$"Dу8xA{~)=sC>I`1am4FWG5" C ,잇vFN˔%%FbzMLZRMa "kw#sG 9?퓍Ĺ|>o5;`8@`jS>U?QZGV1B\.u o5,JD^cklfIStœo Ͽ33PQQ ѧp 1G 03f20q5m.hzMc{Ђ Xy2ކ:odYX#JdbXTaC?YK.P)Q؟k Z̜_*8޵^+.YEk`g-,A$%ft Wv |ǃ!3NtHY5pl=;~A^=Hwz魍{xC2HӔ=_uwk$(Bqϰ:+ލZ82UYّ kdzҌ?JO%vwM9`Shz;H%ved}n7[e[a2 Td2o>#ԩSyqo\yePi*ފ9Η "!$Юՠ &u"֭ 4o-Z-Y4? `s\c-Y]&'7t,#!I@H$B4%l'ϳ#f`"CSp|}~_w`5IudȇrZ&7e]'J20q+.h*&7'QA @ʛ"%%x2`ih?Wɦ]'L2|neٲ("=8;:ghJIbz;3@MjAzNn7DhV`}h)D=%Rw%||tȗNΗs|oWu T_d4mLAL/|"sGfP 1ɖ Ǿb,/wUʓavdM5@cDll+!3MB[(r81e3;l fAk!UfIL:l"d mI1s4]9F.`6/`gm.E"!& $ddgldoKcێwG}_h9m|o]p7~ _K]KUfčM% 9JۗQ2#WFZfNwV`fovebSEaeeWuvv~}s>}??Ƿ qߎ,+-nA%m+הk*Q.:6h6n_35uis ~!r,..reΝ;4TjG{{(ybҾ"CBH1LAt惨BeŽLJ 4b\mzC2wak^K`(5T{m[w4c , pa)m5>:IHavM IDATTq} EqkF)&2m(9/gh2J?~_WKݵN*(J|rRnG'[,P 0=&JIm}s@gf4g=7\DI}qWYmM|']43MuB_RY =? P-e(S*/ElmgCX#%<LJi]tZ5fb2#70D "_i#M#6^i'~h;'~^|I?1hOHp} L&O^ߴ7MB yX'wɤN$]EmG#|8*btMǜ3l D^8_ )*fH<gu6g(ژdJvyv`:WY{vi:q7ڢpbeɽ"ב7K!@w_EAaB\A %VK=Ox%Bycy- D״4SNp}q!S(T~!_U}pҾUՌ Z0-@{Wԭom['OSO111P0vPe"mmmmh* <Y]]e/A+ׇPJ*W^r*!4M t=؁#HgW(%ntL @dG#S\%ج+P}sTg*\EYUܓpwo ggߦW~يoCyh"x)Q`9!{ֹBL"bۼ9+^H~BӂѶN #G^PȡJf~ 4v<F-0k鎉8 }%Bߌ](o?%6h$b!Y M[`iXڶ.z%(+Jo( ?|_8HԢJf(Rx3D p4?{ϊْ1J`n2ڱ!SZ.a[eA *bS;ʠ(<U8y0(2acIr69d~.T]%׹}Q>'S BHH~! ";bުH0XS}vvvCw￟p388[4A_%'`jn[WIA n f[+ >ǾvPf'+{|W 0*r_,_vv rylYʌKЅ0f}09DZԭsw^DH5 sovq)c-R,2X3NAw;^t]F DV AVnC%KRHB%1g[Hx>N~PL{cp'70w]s=Z~~W*6Qp⻧e_ʻ.:@C%yJ!W W9n>љ F[yGQfzidsW)vpI#pر7섿'>O<߼*yDnU"7 +;UT}JvhfZ௲,z纲d 8V6Wmoshܯasc~wÈV`?i}.Fϟz~林k,;Ufypk@&I _q3@<a- ꚽs=SPڏ1k5ZKxg@H@VvU˭|rIB!JH.?Gk0ߛ 8z>"PoH8Op4Z ,,wwwo|RY x{ {2` &p]VbD?7s@6~}nh_3^p=krl}vv;h4ʹs<ٸx"==gϞmYNe$q…-տ ^ Cc?Z)ٚM)X"͑HSr^ o.Tze|za¥0t_އRT,mj!o3a @]i/vƶ<+jt _T:2:zP-P薎Q20K&$ަhƯX}ۑ{/s4wwt{Ie?@뀠n/^ڍn}cn !ʹ~-O`+bw}ȓ2i>tӟ4333|Fmx:Q1aq{Ǝu7v7M;cZEHH0 Җms>ι|`X4v?-L|3esVނ6Ocdz2LmTg0@ikyo3nzHKalP`pj1}os#ib-%tZ;%wÇ4 $QYΦ38~QKN^Bw$MM[i6{!f i7v*z׻\~#Sԧ~:_;'½TjEX|zœ#E9/7R,,@!BQsTK/\~9w(Kp&dnUW]4.B\9qI#G[c'F`ȏ7a=h:LjO;)7t1>هdͳ,>: T]7( y ΍Sv7︊cȂo3Q5{6‹rPQCyC L7>zdjFurjb3R^tB'k!{l)IxE]hFjOy Ϝ/5"H_9xxRHGKw.23߄6n*X|@(ʬH/!E%ĠP ]K]hE tOٴ͵H 2iq?W'v}#jAUj ~U‹aC{RtLu Kw,vKΩ.IX'Kg#X^s?*CRAJ'ILj'vgtJsdgŧ3ȋ ւ=g;0&!$J(// DUU{1T~TP~{nq}-^rQ_ T2VF)ؚy:֌Gboo\-S=8ߟɟrJGGw}7?3?]w݅$I̐LezAb6O'o܏tOA]]YĭD m䏀q(wD퐊UPw H X̏-%ԸP6{FJ! uO_#K2~^7( 3]6'0nt]sk'2( jJe'5rVA-'yw[{1@5ԳS51Iq(子cǎml޲x6fCeZ]oumuDϢ[au:/;751C\%]g&ffHhz`G4~-e{??")߶2ˣ*|1Վ|^̝O#y+d<ç#<'Pbű;-EZ5~k7Sh#yPLɷH2dGm$(5)D/@UG̰;O, kɄNԟr&Gq/QlZL?)$!'d6UGM! \8==K}ZT" {\qZJ8.B 13/1kwqlcA wJ)  Q'T F/-WsxAIϩ^J"hpJ.x¯hN<2e?*Rg"Ȣڗ99HP 4<^&N6Sj-R :tmJX򉚜>19mg(Ih`ёvϼiV]2Cg?Pr^m40OU D~Wg_1LfMPJpLM5. A{@{$p&kQ:.`KŒ, =}f׏YZ$ V}gFl:q1&ڿUFYUzvY[Z[fHPfTXid$f=SXB XaBi7ծvN1h1í]&ͣ$I<]Y(^G^Ճ*MQ*iTRɿ[J~?㯐eFP8qD?),lۤ/WL)8OuP%_iF=]}`(J@ $ DgR<I J gjo>EiP 7tT~rj 2mNz5"]$. ["?UdWx Z<R]RI +TX.ТPQ2#s_QȂ}W>U} H}Au2JC rzNMg< CkLo]_'W 7:qB,ϱ"ƀHϴx}jэ)"l]/>ْb`e-Njr3;cw>/kc B&tT~BR䓿E: `!\ (J\f!h! <cEr#9S& }ᇹkg￟~{4֭f^HՏi? B'|5o.(f|"S+DMiD> ,77e%xEnI _No~A7t_B}{'/Y]YH>]'&?ᓾKNH G%/f1 ZYf. /.b /,kMP2C%fi%,j^o ŴK,k /ny 8/L[cWKj?gyk"q3!dg}c WL_ Ѭ뻁Uv;Uw{¥v | ɕ-!{B˫\?׿߅$f w _tb~҇ެ  Q!E*JuI/<pT\)k_څ,h"6o3|&9]rdYf$s/&ЛUB3,) 2{iv |ɥ)glq݉o?[ }%McsNA#nd07G p`͏9[Mt5ۍ$t;=?_2ז(2dJ4f{5q܋GI/D j48ÞEܐ`3tL:9*S)X֧XFny ˶`(A-'7GxFE%JwI_?AY1'޾-_݆bQm9S\/n#GvM$p - vF/;(a*7ݧ,SOv$?XR%丄Z6p%}۟dۑ_o4})|nN^{+d2PTdWw=Ep GHoMf~Krl>eN);@./b % `r[ Vuo߈I ܓq|l4_]U!~R-%{u1sӄUWc& I\qL, K\}g|tY[kx_~ d$7ZDZ,6f*<'ڽla6}lvggpVC|wUt6|znVz5]{rmOۧ]7pr r[tt^ !(N: 'c63Ƕuq0HtG֒I3ranp MeBAjfփ'y2z@veN-WQ >b;6c"/RuK؎|rLe &uI`T%9(L #qrqdKaӞ-XF}GVuEf?Kl,~eBN~P6rX o{4W7nPR*)s%gN#uA/J.꤆eXwƀ}MgeOE@0=tzv ɗs r{׬vOͺz \Ԣ5܀e~R2?O<<]bh??6 7|3y.ҵR/Z6HՏ+S'ec<d,~\W j]4*ɮ&wxWxI97'Zsw678hH^1mlN$G"D]3ŠL]ck,|3KQ ذ̸ʲs,{3-U7K$g>a"Fw[TA0[n?w>5Um`G,ƶrfš}6,O3zm{iA; OArwѝ#V&Cԏ&O2x]?8ؚQ]!s:C=u˽qGv Orrq #\ 3tj ((R\Y>뾷-{m#W›!!(9xxo;),,oMV$9L~Ic#<a0:Xg " ׷vϪ6[Fleb4Գ,m0x3-tvo!ubŊw(.PPN\Tp:t<ۑWfd0 "IP'5"09 4my%4GppjS$"}Lpb]1ZtԠ墇utC rC3] tCY|XCS5n M֐_LbJϾ?EFmQ ¨x_}HHy2:^%ɵdIoI_%wK@|(j Nm" ƣ:d2&)J:-LgXCqر &L:67FbtjIXM?햝A@jD5$bԓY3G ~c 6t Wn|LXK>'$qѡTP*OE64X!~ynFh{S3GXEϩaQa(4Rog 4g֖b14F>SְuWs-yh:RK?o+J$#yFG be\X47Wxv.@{*ZCd0Jc]2w:iI *PNt+ fɔ2RA (Qz6_#gA)0%$51 RbfI~q`(VSj/nK]uˊ]2zΠPxɔi7Z|8 M.e?wdu#kU==s[vg}bޣN<[ <í+^]z?Oq 'Ipk>Ēq=ubUJbiZ `DGcۋ R:=GqEsH~ژ各Dl>RB$䨄WA4S xȪL>_?V4e%U&xo~]G>!uq:2t|T!8*(Z?m_ڿEDWA,r m3KNɁ P<ĬXyA͖]8[,~D\ ! ùp.`Vչ|k>D~*'.p Elyyp/xBט1&2#RPBiV:#覎b*Ղ/`x^H);2:g`Z\CmI AR4[XpqqeO^PM6 xB+ YZ.5ZZZx=,7 s:"U+w&lbY% ^oQGja}t%o/B{9%6>vۂ)qWB "<\s&Gd& #d_V.ἡ 1E`LhG*֘TU(.*v+ av1l$TۈRz?[<_Ep\zGσKPwt Wal$WBF@ `l|k $Kߪ" ['[X(yEu9{Nvk⺺ UPZ\EUN[ [erZ@«r( ɴ/OH7Mrp..KN-edُL  1\T[Ar|u Gqش{ &dOv!-WwӶ@Rf`ьXwbq("B +Z5\&4Jp=D1L5@H3d~negqV͑ncuLHco)#W5gg ;ÑZ`;k*4}[<:ao41"C6<-ϰ/sd!-F;(<_r7]4=ҌlI3t~gTY 2|FٽE߻Gh=نdsvs?QM>x~Zut*K-(QZU$p$H}ڲSڀtt4[{Y|#l( -Y ^ՏXLޙ6Rf_p]ǫ컆[U)+_cǶ`-Z)ua;aѲMwW`lڟO?|3%_B7ЍNT O1&zGNdEf| n!]VC}oBډa)g!e_Vǹ3(C*M$L IDATe q{6g( qfbO2 [LXA7u6J>ZζipzO'&LcK~!#4 ZgyY1S~kcC# JoɣDG.lB`*jt1v}-C8q>ٗ-L@`/H,L(R36=ªϬ *O4&XylgS$۰AȂ㜼8a3lf?Chjn`ׯu=N @PZe\)=4oW< DY 8@WvC *R,ٵe?]9|΂s"d =4?Hy+vɏX;񛎲[+iy}$7d[oQȃ_3Ɖc%\젍lwuMa֭|mzprMf+mX5뎡aL`X~.JyAO'k_ڋr[o]$KBJ8'<-R+1&scUDG`-FלǸDDc<16N k|@ |9}^o GNU4/-G?D-%`o/Ax ?weOheѰo%@P Ɏd8xcmD'g\Q* y\A^O:H-p9X.PdE"(-hFpOX5[P:9f<\g{eU.F몯6[? }P-"ehqfsl YySv+nѹ-dqߝێ24<Hnb(QI*j.&\"(w_8Ւ+ _|RF0ߎV I(Jc$I(ZSdaM6}{c9qyk`7" 9#!^ۄ3QgĆ vAx>ȿ̅<#mםE .s<ߌ k 1OLt7Ι 2v j:Iiu-*%YN4dwB RKh4:r2EI} yw34m&M^>I%Hhj僌er$%cڑ$~RF@Hj_ıX!6q)&aTP* 33d:&tObMnT5g̐%Ī1R]I<%%K `nΡ.է։1~(My*f؊QbS+SKoLI07N%6B0]MOm:IgIJ-%Iz$SMHHȞL>#|&9IrQɔS2ca>jX6,$$I>BaۛB,U3]3?yfnGϳۈsEUiGSXY*tMw}ϻ{, OpvpM+3O'Pl?\`lYb#qR}IRKͧZdZs'_| K&1{M%K&c$U"!CjKd$I"QN5cd $W GQ(ZlZZ֏2 Sp.IZ^ǵ7dPZY$5fh$lFZ 7Ƚ8Ģq&T(IĎz*%c-C4H'Iu'Q2 f `d,Vɷ]Jp7F܁Q4PX$\T[T[p>BKl 9"c1. nh ;;mJ1 JƵrJc<گ7XKM+Rd&2n#2 וmܒGC_uZw%5$NЦpl$oGrpe,ŵ:?٤L1v W-niJKʤo7T^g6GM&ZQ#2E*Kl;d%D1<`9W̵IFy@s/ `p]H_0ⷕ'{2F6@D3:JY-QNqb"%™Lq.[ %DAZ.ppTDw4cHY?D !`(H Gm/)|CDBB$<t&cA&]+vć@$ DF`ɞ= !eL]Y9UbE^P*ͿD_u%*\.waiK~MB9٦oƲxvV?^/Mz./P MzD&h @=audl,͉1{!0UqJ1V+i.ԘG!bE VȺlD&hJv"/j\trN]e8_kj y8+'eS)ǖ S+ @9am1Ah;bG~C9bqkz_t&!4Y?0vc]dOAf*bzyT#0sњz?q6gȖB瞮k9y1bh# fi?Obh3h&#< ' |<߲Ž;җFlbO60xkEz5=ռ"|SvsTq<.Mr~swqs|pmя~ta:{ĕ<-r8F:@dy GGFr%:w܊8TS7mltz93ntZ5%Pe#*+#y\H|CjRhhh( AA=ܢc;ؒU~E,S\2eQ̙أ6>3 sM7-LgtM| _xVMW)֡AM%:N5.c8/?YdfH#!s$C pnF!5"y+ZW}skEkyl=(;ej+h<{A$xxx/)vΑs:4S/Y ge9. d|7w#a\P%ߙfϰF[0ꦎ,獲^ dCFVzjg*Nْ"!<^C=?(5{_BʁK~nÕ\ݹl@g5Gup I/X4 PA*@D"~@sQSexB+:!?RqdԔ}yҴ`Pn™9-Ah57 Z%wGb\ijܹ~+.W-1m.:7 _uYg==Û[ou`Ax.Gq$ UV^%בeo),KYJ>Ӟf4$-c{hC~ -gICaZCVr~*XcnؚQή/rç^Z?+hd~߰ 0Y<>[&+7׻EQ$ F}XGUΙẐ(ӖbX(JlsU{>Q5hH~sCz/uZx&*R^2,ұI83R&}`9'>âDf$h@"ٖQ†NL&~cȿ L9'xab=  M<'PؐgY*亳_~ IY >y' 5V-wWt z{&q]羽V$HJ,J(E$vx8N2YlOIO>ݱL'鞴3Y:ıtlr"Kh$%/ W jcQPxu{W}o~)uZ6̪iɧ}o[ *]z<<$ѭkoRD@xG UdN"u.8Pt3H]!+QUd,$eL!Y #8!-DMyEF>}eJxuıw}ĭc^sTl`QFdËg}w? (KLg($LqB:[^S{s!q>FMECm=yOk{ ggW~W(/(>SZUip=6ٳX"[ }8Ԃm??_!UђcA/}EՎ!Dkj ([J5v[G w7aƙ96j=Ü8˪k0-dj"ҵ4;N،'aNr(JpiYf#j2WS0 >PUqNyf4Ne#of4=?k'Y>՟9ǣy.Y?3hҮ"E*PJ-Uj-M ?gVa&7%UI$+L?PI_z"EOҨ"ML8I҇3L&8~1n|v;3=C1Qìxu+g/Y"YNn~#/>KS h:BUKov:;&'>2)Qmz&qI-)urߗl Y: t0 ȟR4Hߣ22b20ܹ??zۻQm6KL%FqO5"?”*k'*kZOTt戶ԺlaB5Uelxȏ<75{* ŚjjX3J /ML&Ɉ`" z:$/nR `ɤ0\ xt#9$\1k $g9MW-Gd$Q*设.=8dTFhcGHK:@8BL;Gf2KSv=S>29j}o{ cӦM|[ⓟ${mS2曵 ߬U uuj©ltEѿW}~oRc^T!ɘ'eL2Vz)lϥJ5Br|q̪Sr>uNDLqTm@%Q%8]tMlƱzDݪqΐMp/v^rãxǒfIHg_DQU?=z>w:A: SK!j'D?9bbA2V;JHըt rNe_L{0 jt0GRid$ƒ8x.+U/Su*O IDAT 3T Ox"BJQ=Ѻkl޼7| ~w/-svx-`i1dZ erdH &ypA|FRcΩRqTFbMZn_[ޤ=^Y;W-iуtA8z u:M>:efԲ /v ጺD!;L T02HA,N/fu3'ޝoU/I$b{&)tȔ2ZSoĵL(:p.yˎ~_&B*D Jn+oF *#P7W(Cpp"#qp\;g#ւWt۱0Ip;IMҎ"~ o`OjnI6kחvHHaV-OvNʒo,1uhQL+t_E:4@3ngjsbB<ܢ,HLXp[K ~W,oQdBʒ]CHCw @F*)؍UDFDՈ#Ҝ@CW.)LbT6ok{hcS 6 ѓ͡5?:tγRqKp=J fO5](E%* x,FA׬YñcZ2Ն={~G^Ci"#iaPỉ%e@m|ӟxu ˽!E`^c6 ib Zx\/ 5jOal뛾p" s $}=G1j O(8(.?,,}jr܅j/5R٫Fj󵳙ٴic3*@a 8+<_p5mg ^_; ³6W~$"aB8 N '8$$ {$ƒ>< Hw)pO/l8wq%Ygyp]K&&W8]Wt*5VG\ hD܌03E3.B\4d A㷠yy?b/:w]z׽봖uBe@>;5-D#DUtڞopqQo`Wm?%uհ}v/q+}(o:aΝ<#񫡱rYEID^iko`xO/ ,Sb>ME\up< /N3yS/JX/mFF4VB$*D #mb6k#v0^ :[.x=KC\]3\v:L2)|%i֨*j`: Q%=[ {Z6uo}oJ[<|xGYy睭ӿhń$FͬQO ؉"g?lB.clXW NBrRχ>9Ҕo (]SķN w`V_[| _,9j ijV6su+W,H#^.7*k_bW ła"Lfht% e0!u$QYHW" I䄄2~ާfԨ3>L,JTnZtaR2]زe O?SO]CzK!KK|S^ɿp]wl,URX,}.+>xDB8|0i4aLK캽m ( S=ҪqE-000p ѝD?lkwm&33:%TI6d#sb}J)T0 иU*qjpd~~]BR^"QZF83V&?T:>qLc^C?a4ϫ}U;ptanN9VtT*VTY,;קL13AݯC*AxO9ݮ.>MFTn*Q(O),Q{!<NqX摴3-npЄ;F,L p7 ErOEO,l/瓘PIuk36ބq%& XLR)Au{ϒ^Ѩ#[cj?Zs=؇/VhrwJ dP_:Uş_5t}M.4ZsQ}[2 J)z!|A:t犢__擟$'VGrJb@w|~k&*}I\]OHHb3NQq &ukzvys"+(//1zyvǻOc~,Νϱ7_<{1tp@+&;)gHUɝub/Fvnȳ[Mϣ}u}|*/3SB!hHN/-$ 0W$:\N$(lS{cO A#So rB+ІΏ`6Swj׌LrþDnɍ=׋h!dl(s3clo"HL>F^(S&Pi֨%.(Бw:.L[G'HR$-#d hEH=)N~~cz5#;%Jq8sGYdno=I%O0k 2iiz_yX*zOP^"\G*6BWPȊ?xgw}<_%=ћAyf>?d2^Nmvm B%0·ԶWq8A\Y1Q[Q*"$JS(&t]509L銶q٠hZ䪚"lL EHl7J k^濻x<'m?܅Wי%J ?:(sz?s=ִN9 :rO`ux(jB^Nusc ]4-Q,) K``!3C(8w<:P@37&b7;j6aLPD>`VMD4S5zqm\^OO$FT>."ƔA}JeeYw}|o[^Nu]!E0MdYcU7W q?c;6*"#=*)zM gSކoemE1KcDM 'llĺ|Ngήړ#Sr|ݱ.T$ Ke8F?>CbwnߚRJq#t|G&iqgy,ڳ\؛~ܙnܚKeM?;47#0 2ΛL;vF{ų?7 | Q5tdfV M) ~G"bs' xON&fpH G~zZ+?[nyz7ko?eꆎ¯MAX yA4!g]I0&F`.iغu+˖-kXO'x~GymAׯ_?A>яA+gw۔ߒR9Èc@F`$Ln; ?=@ҍW(a";S,#,sͣ𭖝VoE̒@ u-:3ITN$Cn5KI\g9GurPgv=_x 0Ϛ C7|m_9{7qs.bL|r6B y11d`$%w5 C78]OB{3l|t3>Q3P>Úpy<+D"d#8KS؆F8"wׁ/-PLNz9G;M] (s1^"I$t^Q+$J̺6v{b9M71EMfsRoS1ƌ{(Aib'&X3q:3u}ޘx4FeĐ\g"J#!oodw2N:w]yv}U {6l҇tU" C:::.=tf :<jѸ ('Z=.)7IHS$l?"q)B|M_y8s)No9J)7aYۍ߃4%`}RsӮ50ЭIXܠ3dÁMEȈ=N36!SZl{&/^8`hf9kZ ?'vB0wC?^ A9iJb/S)X3EjjDg]6mxq!L,XsjRJfc;-3R)jJ,S+Ԧk|1 a D0qTYQ`7 b={o>޽{҇5kְm6nV+,w-cO\p$DNh0XnK3IJ S$ ܪ[z҂įμ̩v^]x&FG}J$Tzbbfr"RWBh !(b`:&F@x4 *DXVŠ&@->R,3kO5QbUUj35x2FuGB;d ^n斌,vZe߾}XiZ<4,[a-[F?֝Rnعsg de#\tlmz@!ļCG!3 PXj΃K[10 ̜kd<É@Q.u9[MBKn$pj0ttNekNiY^CL  ,ڮUe|jV[-)*c?GdJꢆ4֨8HSkk17*ReFQ`3LCOB &S԰&Vtf@uGwD{}`lֆ?i4ZV4ZAӇ7#B)_)N{iXm1XŊ+Xbŕ>6.$OQ"Q$C4H` $h _A}sjwd*k&? M)uz“Jgqf^꫍IOxЁYU7œ2fEd'T*E's^f#Rmݬ70t[*QΖ)gL:HE7'{i@ȋ((0<$4Sk&1kĔo,!Q|RHK2~)G= NxƟղn OȱyOk3: n&&py-۴ií6[om8*^(g{0 ӨR/XDLt&":V#Tj닄VٕY`"\9GW/Ug.q9$'It2$MTlJ0;CtN`gmH函j3 b؉ґ욤>z݇I 6p3ΑW==:Y:J*fR=IH.pHVPz m/VJ!D(#(o*I89Y!MIcL;"h*B!z [ؘR^$^XgAV$U J*>{[IT9X#]u <1zmnFJTZQs||YUXE|h"& 1L5Bt9dҍn%(k]k[nZ2V0v'cb+&NŨ:J(d]i(@aQcwE^uN`]szr5GTU#pR u](vB?va$rG88s-,-}6D@plQs1/, $Y]sĜjDdi\ 2)<0Cr,I?"1=+5X_l]`E֜bYDn#Oc\w9م! v|+5%Ғ"BȈ0"4 bJSEo|#4J)\!Ju"u>Kh˩P" u&eȍM{$~*u*g$D%Y>7f'gMNUPBa'jjDg#sx:FuQwի-. -huF>b+&JDDX X@$QDrCŠ8ģ5UA|LMUFRz^kt2(<!#+ȢZԜAyǟaίPj§e_-PNQ)s{1S Z;N8{]DQw]u ?JF7c5y0αAV/{h9û ! H\㞽~j}{^/h$n0 Nd^Ȓ-S؃6l=.D)DmZ4ԅ@KHG]'s0 awLoIzb:tu8P:$. zst/ IDAT; =4"+1l9 ƴ(c9&uhi hk >:Sd Awq%;4գmkCR!"O]]l-iS&D"SR6H 6Z0>+])3mb&L,"ʅ ;clIHHf4U=1F\q[…:޿1+Vd.8㇩OZz},mB 1GADG r_mn7 T$jtgmr+n ]7e鈤%sQOM47ocJXhw浽;ba hC60la h>.hvVZl).8Ø؏*q9F% ņK}m;~s|rIv'I;i2aҞǞ<֒-ݻ[2Vm17rKDz,voRԩ$+DJBmFp6@CUSsfFm8JI#a9󝰄@K)Er. &W.$vm!0bhj#i:z\)G!.,5 aB4$fl u*xV U a tWI.I 낇9@4s+ftQv#T@|I-ZիuUS,=ٻ ^%$HIǹs-Wӿmqyu֖(>TLJd8R](KШ-v@z/y)& 5qx;mRF W8v@hq[\M $TWV`O1G-JaZxҧMxQxMj1'JAl]\0|9T~W,<}ؠ p:lDA 緎pݟoیkj;u۪mq9RXX,q GH2$(ih޹BxDp gçLeřu#QגVeuŏˑa!|gX~dzʇWCt.z"6ˤa o LŌK|C&)*`ryKFVa&3ÎSލ4j:GHfpG/0G$?'s$[}? -ݾ^U2$].|?dljwq&:I;G1DdKlrP-vcVG'mm&;Eҵ~^k%F!/y9[~<*PCOǎk;dfr5N }}}_XJ9CBམ "`_dn ݪj/\|.5- 8|>m\9>GsʼnsI6:e Ld97|Is[NdI=w';1fFHs~9 bL?Rd*3Io3b)x[mŷ|.3c36eƠ9н>֞XG1Y 1:t3N'w%VFLSGBFQ`ZӰkŅHRϽ^Ef=~1VEpOI3FR7@hbӦM-!jȍ][+y?=L )cLMlzeX:[Q^Wz Նqزg PS-vZv ᢮ `EJtGRܽ u\!<ɾ{Xyl]x3H33߉!hPMU 5rD|D-t:yjn6>DdsH9"n`.8Xy/ ~˲3 _[ -Z:aQ !8 jmnoQv6F]x_/ f PPSvg3d ʅ2QޯnᆖFm,c]8SxHLaLmM ҵ q]R+$  FSUH+\w6*tfel'ɹ0_,/RKR(cqHM4mg?ϯ w8/9yP! Ht=j:Tqd#2fFHYEpO&Zun:L=QGEYM6s3U@'N2p~kO7$ M^20(@-_x4PRy.;WHrGs  S=Y~ѝXp!ܼ_T֑9hM.nŷXR`ODŽADTfbԴB.~# 7^l]ھRS6aIc~AH3z-~DO=p<S1u_ۀgN᥃/]||+-6X_җВnV6Gn$۟4MAt6S*UbWd@x"BPta~s?s߅Vc;mh.•%Q4 5>,yާfA.Zc<:;IuԯĴ L,dO,ddF<ݸ>dDgnƨWql 箟jQRc;&ktW4Q˖^W/_xM@HBdm@@|7ۍYtBXWFCDڒ0R(dd:9-5|;BӄVcT Q+n\K87Ep~4ft}&JH&YM&7}{1::*;zm?c~%cFKЇZ2֖-[{bv7%$jBEUV'c!rR 7pyV!$y_++0<-o8FSfFZ[P Id]b4BifIsR JĪٮ,L1W| 흄KJbVzF uM0>!.^v&Y5HV0EsSqC;+:Z.Z}@ sp=R{j tV?>^AR)R*Eīzu348 o]ߝwٲh~W5Z.|fgNta B+"R 2Ɯ)'{Ǭ011\IcK$9qvv~}ܪ{QkI-w j| T Y hm1;a_0lmXhN.VmRJm@"}I\>GY]}?ڢYm9]BWk6Fаi(j5y֭[גhcڵOE@5DHxu^B5uKr湆̒LM6, 0 SPF3 mXt\=rTܼ6eX!"$cb3&2#b30b$2<Mxlƍq:trdu\&]z@FNkv=uyT;XQa헱I"/l[*d.daq;ڸ?AJ 001&mg~*~ΗlK%4|2pQos\G7|(V}_%:4+1sf97xxxqP<'q?)ElƜgݜړ ؏:9:IdbR_~?G9#iI=94^W yKK(FA -wu0l8F:P7#n0xj0 ^0Hk:ӿr?I>$6cROSyp 7Eܕ`%<Rxg%d*[Q%B 5 @/sGtz8v0 {@UJ" ӪD&$ޠKATmm6߂A^;W]bF_yr|?10͔tZ-c6Tyk}'1/x"PPJLܐ^UKl.әiZ[j"p]AD(LBD8Su_DMC.#Eb{ڈⵐ1>Or-/ńS!Bln ~ߒKW~BS"#вzJY= +L #m :$%HII[l'/ZpO̦ی$s#KY"*e1`H0N,@4z.:bm_݆ٸw>VR\@]. |1I5$Ft:/ڀ{m.k-҇,S]v{n,|ZF"4IiE:#s4̚I"KaraoKܔO/.|΅XEB~9 HZRCKkJWi@G¼(*GPh$.&C0RAaF݄hJcr_(D{ТHͤ%,]&m]1Q$'0 лHcs0I]LBAt d fp"$\C#&W^6 0?g?o| ?1Z%h7inEYH=v F=Wr({%\;GB$3)=3DB0 UN1pnCƎ:Uc f;zUxl:-u#2[RIȁJ.,axf Y"Zz4:c<#]oibūEKywMnƳ]&Aq3|;廓أh}ڠJ:MuuMOoO&d.-AFT3Ah%it.ӹ)_;K E$0}4kba{qT %Mg"Q!n9B#$Zqt=#`d $$ND! RĹh8.X ۖd&RVzF(Cc!@s S*ܾ$$@٘Xs" S+HVv^g""LL`\ے8!f5Kd}BB絛14Vj (*}?tRZ N%I6@Rua#8=d*lf"S<9@r2{;?Uęux=lcD@9h=…q= 01p1"+)(#k_.6KL|kaWIL.|I D{\'5XE kk1L 6Qmh._)ޛQJ#"lD ]^eW%bK !qXJf#s(C<)*ȇh IDATT(0]5>DO`$6ǑD "Se[VpoFx*$&|d("t=,@ 6=i1J#=Hwv۪KLd i~_6 0|u= oOk-S5zj֧ſW}Be{~B3PD2Z{ ZOp5 6f p$D6a Xv&$p88Z6-V\ɛ\u)~ kn>B@X *+{_⍡C&vJZ!hhhRUYX=K,t^XV}G A҂7V,_EfE 4M;.cm`xqQeZ,:l+$X^c.Yr4_Ô&ZaiJ/zHDx2~$lbn֭ñ}˱X#1QLHOQ!"Hf@h8mh H~8|Q?CJ.;w|<õ\\.nů !BD\.r_;$ XꫢjzcobjzaQ/,/穹h,ÇI&jdn`a=SA/_o[κڑE2~kac3+`hXnȄ*l (:f܈ vc|HQA p KuuUpobr|s.TUcWI[TKqmNC, $x'@C:U 2|)8h? [?SDwys dۧwR5|L[.ĥ;1Κ:q:xc篐~Ss$>~N;ҺI{K`*D~OP BK%r۲=$ӊF*ZR,s+w(`$CTdiMfnm$wǎO`XPq!F"Y`1{^u^bDW[wb#Wh})m1BRHRȉ[C:#?;%3ϩݐr8.|:;|/2|XmP`􎮤Ns ^bLarQ[`VHp;$G~x-}.`S~h&;j!y3Ͼ/sՃt=LQ_{ݪ㎬Ύ6uA!Kz$?fGwctL@yY:AIm=DK" IM(%( Tā ^#KC坓*3ҕa&N } B~~΁&6i#C/eFmEB٨P^otzhaYw vc٘cv/vV6ֶ̈&y;U$RH/]a:14Hop .3}e IOrf4[J*9ygfpybzCyۯ`hn˴ilq~YvW>084s͍-y omCgS) uoi'H<:Rgmr_$)h,cm uFň3o8_%8_4/D zI7R;Vśq.ـ`. UyqM*ɣ"#*+G%fdlz vڥ]< }IL cޭ]6SE$ϼ$O1s1I\+daՍmqiFߌPB4걦e $g9S&HL8\ZP{o/b ?1+L*_`1vvzW.uw{X^<^x8 ȁ miܷ02uumsxx˃Qsʃ Yl&,ԓ"B3A^#nݾ,MCpuhN' EWu{$X#)sʇV}/,cB;T_£gpun ]v"1"w7e2lR 29$1KdiRQ!:`t UXXH!Au_sb\q ڣs??lbd7f ׳j{Co0WE"!y:aC =Xg!&h 7}7R.Db]1ATT ?½ F z002R!diy_Vovht}zLNB{ު ~`7vYZxշ׮ 't~?KmGFp%xjd2Iok- hÂ#>􏘱31 YLfCDtS'"dC"}km`1f))Uu"nL܉"VLhQ\Ï}K8Cy`) Eϩ =.#6OsEd}%tw=+  7GxW>jI{EFUWYx0V0NMar -z0 eSzDn!O+ >VU–O)\oSmnXbb#.25tZְY^ XDZp.=*|~͐!}op3+}^( H'HI~D/AUDN PWj>əWaUAu$5t 㬉Uh7άi` ?@+XM,W#޽{\6 4u/ h`zU R6JPMA# 8,݉3 ٷf̈gk rX2yE"+dMOqMeڷe8= mi:t6l۽7cN Q$76gDŽtW#gG}ܬK\ :wVfŮg^ݼ{=JOx "-NPTecqM6IkEuնl.po\3Mu2.W=0M}O $npO.:mў c1iҧ3hۛwx=GgC;ƝvUCWB_B m_+_Ѿtӗy) İ@ۢoI &Rz7cp:OP؁Z`3f }1 'Thc1B_>/0n11x_DnrF{;j<Kb:y.[XroJ[=";¼l! S&&$)G,| e ozӛe l`/ 75N[677xb  JahKesN#JԈ̐Ši .܌KR'?RЄUIm^9RHvVѷv&SήM/S+CertK'y&R-եԦ( keoWW}~OR.ɢڶ*?7}†ʖˢɘH,b iE.zSV% "'І4adalabH x8a%lNHR*y( HI̙d:׻j̕Tr>+2?L5}g}426WU<8~pD7!VuތhIHڵ]\ Ġ4ΌіIh<+ވnhM]풮*$Q.z_ho BfbϬ/2%J=hWm^;X俁 |rJ}mؖm۪!Be#U%#zT|Pԡ*0[k #ݤK 4 kf܊Ux:~[^GDU_o1 VpjɺW9KeSc=X-054E+q؎sb25Ф=.Ѷp;QK ꂨ<\sWќiҝU녕adKEA kq%"MIDoH'Ws_|}DwaٸNi !bH)vxO;d?:Bh.+(riKptOHchw}]'puk:\tI.^˗Dix׻ŏȏ|R_FP!gdnI#,T"1Ш2zMRXbWg];tvr%M Obl3>PcjUůzZS:>A~4^;עBy:ۼ?vGm K-"^44+SgUdj~yk oʑ>E{|q]qDZD bÜ "0˹mLH^K:ȡ?#MhE:dmȋS:aѥwqiϮVՙ֧r&}1We1Tg~k ;WyTSjT^1gUZs,*<>*ەȳ ΅/Y.!>$`&BZJ+cOO//tDZ4fڠcmՠrm'Rk/4 YB{o}ΠZKz1s~w0CJi"oR)iCGuݑ"NH ɯ_J9 1&ŨF7!!S9 {ڌl3t-yWui򙟟# fd15c=ƻGtĸDi*#;MZt} ){1IV"y.5eΚaffeצg^W ,;LIIܑcw\zT;Ee 0XnFPݵ綢y:^zy۾k[o,Γ+5\IgI]<,Aʷ8 IDAT(d:̌OcUKD?  p:H"# XVĪb7s hy c6JlȼD!.DhSu_! c- {֦= -6ѫ$$f=CB Mj*-](&4M9 `_~h0}5x$T p$KyݥC-.iAt>b#^5\oU9\WxtWtI%̾bt&Cd|{ y$1e)R=mf;sw?0ܹs<xR+a܂խ1X=3FPqfWoԃkkϮjStJmf`T v?'RUF `r"S)a#zy0rU?kuTu,,Z3|vq6}~ gcs- @x?2{d&쩾VӤ7oMa|٠ N؋1S9NF'!|d qW"Vv=ֱ" +gbhr0xޏ /R%%r$= 4C4wݠTjl5${e834kQd9z茶5Cu"~Id˅m ~ab(3\[BZc pb FH)&a>=OEJ,}RYv%.EXXOt/=|ajMǺhu9 og 4kqRly1BvXWC9;i|eS*2xQh&vh%4T2E\H&ƻQlR%Zg (? 2eFoKe9Jdp% .Ǹ\.nH,955aHp;O10Lt#'Vsq N>mq~a>7}(/ $h,Q! 7ҖD2e!犔ΩrwgVp@&b~B@kun/6Y2xTTkutA~Z]msOP:54e-e >þ"LK J˞"f_DRC5/]]ęXH*bqINu4= zV]jqIO64]#>#i~6K-yWsK9OX"#SKN k׿^x 0 r#P>–  mu2,ZZcOo&1|n{碃3Hߓ"1AIG|/sviHc٭`Tb-扷>NjWPs|\]DۚLmvJWNCF>9J\ht:̏1yEυ+^r+u jG:&8(]=ډ6#9'w1,0FSdrlӦImbd9yv33XU%L<-[ dsS!ޖ.^yɩ$<iv [LjX01.XS׺)?{;w% H,iӈ s. y O5ddzcA 44rSDi *\'1&)pjK6W,9IuI?M!Q`V |x'>uk77zO=Ԓcq9UIxx݇pQxP7O {8C6+*x~Iݚ¾>rgǪ;aE*I|5ABZOc'Ql$8[lI W ?[\i] #$<{=OudO"#'FI-/#6b;r_OKz ri`fɾ\cǞ*6&~PtG]eܓ.]'cL_bQl$h%)eHpO@4V<)`2$6~ݴ FVhl&KkbZV G3l0-$$ s}\gԆ1)!%<3E&IR v 9=).$dP2ٟz _}%S -zIOy'PKTHV jvCe#{1嫢CU-cUl*HPVVZɐ8Ba#TտiVE-YƝt &dYU)x/+4#Yq1F,C" KcER=ZhOqϹ?{%u{~n==93H`9$:g>:϶8m>ywޮ%*٤HTE$@"3`r]=)˃֭[u?:ɥK2HR$I.>D*UcYtv BEu7V)ļ'f BN bq3Gq,gզc -@a°hq^,eN]&Y.PLU(^۲'?}Q;>K[‘#Gxy'xٻwE.2~n$رg}e֛>"mNq'`b)( +3=<[ 60\c|խz(@(QS+˼Amhlx^CtTb*mg ՛^mjK+ >Pnp֖CK!eN Uw)gRy" `"Ky jWNAа,8)5ETFPt_5AZN}3x A)X j*k%kD#$2 R2E\ư\ -hkJ a-AV;\|Go,+>^Eh",^Qqf\!"̿ $zg-n'~ (UeY|ų{:iUE/abxxQay{9XM"!S Za5v+0mUZ!/wXM:~=CAT 3zU\V/2$hUmEqT!ǃ^(2 i˩8 slMj p饗[[ƱcǸ?]ZW.dV69x9ؘNaZÖ u,.?KE'X>_e|] _gY^]jS#!\sVQQU N΋ґ/HQ 2P- EWi<{Aے .mu,(PJh -[oO\<5RE-}YUgdy_+>ZhJ)3x-2/ީcDZ#R:RH|Gt噋iSՐ+J(Pu7%F~_٪[&{  .dT*ɣzx`^Q+ OTs{j~l5 .LASAEAU߂2u \m S}%P 5ιpy@ cecWw 7IM0V+ItpOͣ(aYQ =qⓉK=E*`&:dɅj-$ZRU\%RC$C_>C̙=zhRDF1c:.}n!BAnS%L;C+6Ek~r y5a>^lJ9Y~k9_UFu u*Nn]x(ϐk"PPrc9,z&I]YIO_ cs$6$v 1M/τ㗍ѓwO?FPC ɨ:x/ӫJ3iaWpCP@C #ۻxQlH >d.;OPQg0*x,~&ħQ"V,K&'ek,&A2Ms/t/^H We-Xs?]*"ₓ>Nr{rĪ1Ou2'eR!3/O!,A!X"^Eԡh9ɫO20}{uKv^&r]{h?бrN jJ>+2735`Z!x#vOc2DE9>=F4%DH[9dgXmh/b ? *7d#{6$'8_[Co.YHK3FRȶ_ 6޹f@g{,"<*2fD/?|ǟHD@ HGhi,$nOQ~o rҤ/*nn,7|RZRq>Չ]vO|o|oΝmo;xIjok?ބ0G污ҤלSIp$#{73{/[0yBCcb|śuu[o@ױ^Z'qUse[޽ cf^c$ٵ. 5m Et`3>rA[2?Ԟjth;QLfa\QDY*wx`r' _D1D1:|vD$֯F99KgA<Q@d01U0{rEw]±PTn 躽 ">Ĕ`SΩwC+H71t0eQ"vWr{++\i)؏mб%16Ccj` m@' 3+BuWL h=YvrZF6R½O iaݔ6 OQ"D#QDNDjs)w߲"f.N@*b0SoB۫?ؕF}X $qoj863u'1V[I]@eT ng$S5us.5kN iHf?4m,'S'NdޜYmL<>sޢ*8@  {;;[=74ՙUj*߹޻i+dbhzBE1jv Оg|8]ǻ䘬L6%!^Ui';Mw,l֜"P-.,Pm3CD .*xǞg<˥Vo>dpl-)8&HT{+l hx9(P-N̦iz=cf4bVM̒q*W1x;eZ+dcEt),oz12͠}=\ץ #?@mf o8M73!HG >x85ك}V%|i;M4bv<Ŗ}ۈXɷo-H`(o+!~C3]WupYQKIsaIHD _v=FL{xO=UXC ye8$,n.D dH´,* 11㤉Xu `͕A=Qġ3^˦i +R zyѨUEoA͍̲Agxh 0g4Ǯ{ A<碟Xtu8>W ^`}(N˩&ϽG(i!Q } A=!5Iy[Axy7?BinQP/a{&&z_q߹[EN};@oy}_(P*PճKGrw}gy\w0rr9ٺu+7|3\sͫ^{u{z g4^]!QMy3 IDATb[7FiJ*<\^oCE4Èxw%ʕ2l@`,g5WRُ>UҤ _. =䳋(vG;k  <{:Ep~3D_ow$:2VdǗ/C L^2AmW ,0ƙ 24E@T17zb૦˅388Hoo/\v:;;?I*~4׾5>ϱk׮sݝua}vm۶-[bneeM9wgnY "\3 xH-B1fjfhhm! t^/ŀ(!dPBËAX!%4t3T=5$xUKpvH~d`;n3~ v9o<ڏuosXw S'*LI,"xaIǭa2ku;‹[P*nF_W]ԥB&5/t̄Ӱ3u:TΉ.a>=:>>6u#|6<`Y iAD^[~h>i;!'Y#+18 ¨ly10i5J.qwڈ VKqb \#Lsm83ﶚC.,J pyXjpOo|o*[lK/m"{6l@Q^~cgm-,(lNkǰ6m(hAZh(ZXH; ҈ G WՔޮc$ L tW(& `hfCus?p!=Zai*#딒%7O +D*O=:7.awL}h{_,cmJ̱#T./eP$=ޝu{Mx}d<оN樅$gm3s$񟇃HC}C  |aN^wJW2 }89}2nEZQI(BChq =cZ&2'q6* QC!{w!7ӎ1jb;,ꀆr/쩡ykA!~U X{oyH y2q,,*Eh,[ X"d=l|v"'wk Co__v}ee WZ}4IM\1{ G""9;A0c2kpy{G-~} +a=E܋İ~Wᮻӟ4O<Ĺ΋W^y%W^y%W\q]vD<^ξ :ȁ(_~b!V@Gq8'7C Oҟ ]+jo4W\,3+aYKĹD \zB_( -Ib*/O}E'L)N-p>"0ovp"S*6Wm%u"ߏʳ;%&\+;0u" ( -+w֩HO ޿5WH۹>TȎ`OA4殘KEq:R GAס)ȸDQ HXQ KZL|0gfXܦY{b!LNq=?h'axaE1R|NLR$O&Q n|zO#=N8A9UV_&Tzt>MXXƎ\n$0VXIt!Fh?'gw dڬF;i}a@^!}_}~e'Mm>YD} VLL!z6!6 ڀ6al00 !bhwx6N2$$QtWuX/| w4&q$I$#)t}YM\ Oa-aP!! sIMe.rNA$MhJt}/U:&5'[WRN%B#}}1.ngePƓ&k^I}J.즺BX/ɹ$tpcO,L0>3u_'.:N BqSbWuW:? r˫q7ロ)Ƹ뮻>M7tavv EQ-{Mx@_\(+kĬ[$ƾq\:=K#i ~겖DQ^~/h:Ui6}`U,{}QZ?$At7mJѝ.vkDx~7b/B*cm7}}}$d a*2nŚ,#^\ꓷH^8 u8M*f Mu! y'P]cVLʲDd.B]$)=ѯֈXD{D#X֠E/~$%Wntmkvuh7Hvӽt=ЃqĤBk¾3d5M#]e7&ڨ7rſYBрF+DFd imߝ1rmr$Vu9@]ЈQFkS?Ma ɇgkh"!S<BD($@T7bl)UWQ4/^|48M{DcW<| ]?Cl$PG=0%~AhBij kp(HnS{o"fԷq sʌU4Mulv̼\`s$nO=k༶6/i'?q8Ȁ 5|F?s:zīj:زt?'}FGۯ¬T/ϭ{֭[/`׮]|S⮻qsݝ3"quq7rM7qW;G[%0  t,TVun>IbxrVI8nY^Yj5Kl>[\kUjUI ꜊2*'k#! AwKx.meQ0G/H>>9nQ+bgЃ.Kmv্o' :J*p~C.S@vN>@#'L M-jT:_>.Xqh[l͠qeر84l QWWZl: )TO#\_Jx(fȱFi? y)u ն*$e-SXf#䑆ĪE?-Y 1f-ܕA +ݷ<\CbZ 7","j Ѧ5<+TΚ'^7} 'J'Ir@u_AtD0pt6oHð@ ӡ'TD: 5Sl& 6mmH Tn4:[{p:b9W$mtD} J3ANSB`fEN{" I@=VG T7!ٶPTfJ܌ t~ؖ#P yߎj 2{,:PrqF?zy BvEܴF(upshE P5_LE=罯V7jڨw1mHQ3PWyc<ӣn5滚 >Dl܆oVщy1v,tP|ܸAu*~MH%>J5=k>z-B/knhN%%n}@1}*C0-9薝QLA| 4|{n5Ln Ol6ԜGHՒB{;ѿG#}8A34M֡D~Gu6hzz~|-{'zuOSV>q#grL^S,LVp) eVZXR(G@z5h&I)M,B`Z" nGLL.J9<\,"N X啈u8Qڔ P5|9/xȮDb}&>ڈ 9܌ U{Mm|`[:ay!aIȡ.7}as#$6I֓y)g?;I~q>G?uiWRJ^nu.덑77믿<^y^v;lB*WjRqV0G`.hc:O7 koP%gږ/ںfin]DHQ'rM/AL!]C?bmEXWeXJM?\ (`&w![c@K 6A8ON2L ȱF3SA -|(%  1,"x2$C.˂3Kl< J͡7Q=j`&1/Ļǩl+1X@wBnSN h.h(*Z21 utVRx]6scLEp0e7[b9 Kg݄F )k>rXH"A$ K಴Ŝ1ZVL^CBX ︇:LKኮkXCi$I"ZMCxsu WʻuiW s]wO~Yx;v,"x 6mZ\|У_mH! 7RMU)Ɗ<21;,~p* Kh}""\y#MiVBPRWhɶF /X#_$7x;Y!'a"w邾܅pP-0|َeuBz/ ad<^ __?.ǹ۸ۖ?K$,W.addtվ<1==}^Gx|uT@h"S _h/HD^Rcieb9\><4'T~pMa,% _xbK‘R\ׅW_6,{IWa@sU5xGN$6mZ~![Gxuw^Jg.122¦Mؾ};]t^x!T*uj0^ ^UnF8.@^w_ +-5_o _GͪetGg/jٸqDK"{w>xpL^ OoQy]Oր\G;/v` %6Owѽ7g9䁰y!F8 0e|ܑr#Ucx q5oDcK?jpȨ9ԯ! PѤ~$0_ؖx"qbq_LamUblBg]Sy_;F|$*vpt/^t%|`zd0T~} %Tkt88%|-V\ƹv\#/m~!>u9"ߌa6֋rB_ }z\w9Z4^z)W]uW]uW^y%==k011nmN2cwW\VG!)ط[k7U9o/%ю7?c?gunS}SaCGAwb?oʷq/h?iTJre;+Kq9_ `l(×jUBRV)h|_(,)=GkJLBClzd3R ?reR!Sl>QGf"K|C9'X!Auf?5I{)T@UH4ҧ7?EF 3& 0Pym2_>JW 7bug4G$g#N͊B IDATjK:΢ TpP#h0@ehRԩ ihSqbO9{YD-kDA*9C.(8.m{2-cva6|Yܴ7c=}&2 *eҏYz_"uThS[jT\+9zo2.WSo|dM_A3c:YR i߲)PdY }GqK_v~r-p뭷299ysQ;ٽ{7b/{K_ҺzWZ]].ڛo!|d<`=%>cADeb2)~k(*79{e\0rf2SY2YL\yJL zSOCf4Kl>k3exF5K"{ 7lܱg@tqE1E@D,dfRe I2e)[e* O}VZdGYX`q k4B2Hڼ6a_lS@o38mN0..c/R@F]OHW2 q?,׀mx$YM5Nj #o&:%8>zڬFKh|1ԼmK wQg552)x cdON^Gռ}i`.]PQ:-bq"('$3dP;=<[nF~sݝW4?Ηe!y= X[>qeA0S!vok~&e>YbG5q!!s $ö\GuiJ(*Ϧbw%)2yʌJяˑS(%7Ry[ηx#dw$Bk/o.z}cڤT# E \"$@8 2]չ ujRUIVgu{l$?  5K3f%OB|STcZfiV;]ZqQS0^ @4 ?ZK) /|ě/+b 0*zCxYR՟0䠪+'Ԃ5f/FXs/b`9OB^orqqpx;v8WyZY[u\_xCQR=wϲm;< M|őwDX9$K5;H-Շ*TLMK%^/LсII+\f6XP,QDVM^ߴFɘj޹3_B pg? 9):!/~ s4~q s 7p 7DK꫹ꪫNGNdK7xc<IפτK9\BHza7a22,cTW7 S;X">qs_f*o4TԝtoKHh5<4{h|:_/4 osڸ[6>+K㾝Tew鵤g3 %3|&߹O +28I0dmø '֖| HRs$=u"Y$IB-2av{/t|/U·KP\7;FrD!ޤct\ӣumY+Qctf2DvB, T>AaR6sg͒؝ rsZA5^2, MD5GU~ ] M?rU4d@NZŊZ} v;llrn%X] e. ӑ&D Ycu?;;j*n,k#\߮8@T(0etLƪX9/ώ )*$Ra[N#kvۈ&APTd6IܰjXuZ T6/?@oQ\Tqbe.ߺ%=\T+2UZ&ߒlűZkHE V }s,q\:- Scna5XݮOi#+~TYr0/2HSt 6^?7gϞ֋m/|׾'?Y8lfpp~\<T}o@I轨sgd܎v:-kXMn{H䲂N-XՇb+i3SCdZMW[*cer$* bpl+sA6=7b]+e6M:ZFa=$GZj6zZg~`yclDWBW< #V#}=S6UNZDcħT۫60av| Øڲg*4.[|m@f<0eBh:LFAE \*U| &9p~$)dF05$WFv%z׉w͑X$䅈Fb؊MZk)!ִIXz[3gNU Ge⋩B ݤ`:5 $H?Z"NCխRKxyAP\Xv3n-6PE!&1PZ"k"$IB!%QZJp I{<sxaA8"P }(cm)Sv>Ơnb~HЎ}_8X4pcYJ1D*)T*dl#\w` 4;,xK8MO5'V_[mxB`cj3J_ج~H@@ J?8p n6^W^ϵ^o 8\s x;8묳Nuw^꫹[~vB^x!r/44Wq 6xSzw H{,ôM$?i^'/{qpPP$ eH%z{L%h(Kb[[멗n[ظ8D(dȷj #/$@j-5F0#'Gc5h՟pDr稃7!+~BkdRگ %idO9{$8|Ab| 3su9ԠX7C(E; {'RWaSαF_kmc/nq d-ּ` -Omwwm<-8#H=`6<AI5'>8ڈe-9cRQ 9+78I3I#X X$8:&&&NSO=Ŷm++S0<<?AnSݕh wkf`Ν<t;MMM꓁%[WsjTu,}T Kʢu5pwd2IJ/ UB(i|pZW?IBR$y g Q?>OgL~~ a&'8o>>͵^˱cNuN FFF_J oI{e+`-9$B2J@AFF|OiW<_bNMA(HɯV1!$Eߞ ,%qD72!?!۶mca<NWb>9kطoߩү |b8-wq\%d Hhs5J 9)UZ܋QTcY dp!zgI))~uj{QQu5 @8gK&YvJ޿waza;X{o#W 1ux}{Vu :;W>̕Vsn<ŭW5- WA [ZiNPP Tgav͙+>S<^3ɰerS?~ӢSSS!7|i<oo5y 8ɊD"g\3a򇊸ehODFÍ8Tfpv?޵]}wG29#<޹h P  aϯT)\cS5 ޲u!=Nmy3OEZ~!foi [J0 k>&ǯy=UY=Pճ}] ~@Q%c@:rBsߛA߮QC/Hv߃Sϧ@&a]+L4C ِa_|l_¼F@O$Οْ7={&1GY6~c3Ju*Zc+2cY ]}ǻk?qΏgB]hLCwM")Z/G{Z`엦Ŀ 1309tO%x0DiZlHW-Y#'y2R><>Bӝ~a7'81{w+诨?hmJDl Q;w3:"2eY\rG;ZDԴT() Ճ_+E%Hme4F(B@ fWĿGcC"z{it ӝSL^2Κ4^W SkMެ&-0QvLEQ\|{C9x~cAڌ6fϲf.,E_=ˆm;Y?Mawٔ_USz?Oci"QUw57uGl)=և(n,0u +`_{ghPhy~tJaOov)"lTFc wF5^NA fY2k&שUf !6޸d[8Nl Lgo/Pz97"Y*UxIɸY*UUo験V.!1O܅UEQVTHH.*;J~9(0HH4B0BT{%RliзRZBiRPeԘJ@h^M z([]#a`j*,r=8v#x^A*!οB_HZ < jM5f/إ#WJv*B[ #uGwR7;kט|¹y$<;PB{ZZVadd+w Sݝo70<<| T 9cj,^;[pVJIڽ6ח %Bl6 1="rKߧe&%  a+ĤE:&_!MEg=6" p6U+1q j[ 3LOnp8,Ig|uy>ٕQmtNCAiW[e m gevSB^#ukLh$Jr *#J8J oB ilyb+L4ޯ @}!`;6%r_+KNT|V,@U$I ,0~`mt޶%KlV"#Wx / y)6|uѿ3P$7Njƫ!JhEBYv2fʩ?@V zylK .6a c1*gb~|Pxbg5\e_~V(Q'< PؚL3ԋI߷ d;JX^}II=U܀Cx9puw<)HD8u44Q4(&u\ʢD&uh6ĕի%$J( fO躣x.AiWsU0D$5hjtUE럄d(H~ڷw$x)*.u7l@(̥qhзY{3jBr~Ԃ8y,'X "bp$\ IDAT| ٕ 1@ azE(~nGB ₙS9:"K2:@|"ܚKyF"-q=0{g(WV^@U'Q*%=:F.ɔwYN +mT]0v}L(Y%?0I.RaQ+K {~0g<59L9SF@* !TA$BL4<JM-E*/;FT eDn"^C$|Mdf4Nz8@3O+O"9D{8ne)&ŝ ŠlU0k&1w(sE) ظڐdymqX|Ɩ*._:a.D PesWq oІ5'B(_D d[?Q" J*!fMEVPq$FQl vǓ1оjSB4E|4  05{Q mA#;,JHQ*THA%d¹D8D 15J* G$C J}{M3! 9*#޼@QhU:^Y{Nʦ|N@AlJ" N!h!)Dx!CSV<2-AR96R䐂IB!5"BADGXRQC]r[1t4M\\ǥ] Ęv8@โ(򑏠+N|'[%ERΤٻ?ƫ>90,+2D5i{O|۩A HtM?1_AuQpSMkHq3.9#S =&Vh38QLP Ot|S<*2Ek88'<ԢJ;ZA5Q~U%hig2+ۡN"yRqu`_u`j$B>sFLН'cdFUܰ~a(Dc y JX& H GӨE_Nʦz^lKHf5R-@Z <ظK=EJhB2g܎YZ$@42xA'`llQ]\"$NBBAA?4K#P TۗC;] :* {Iiou{IiTcjjq+q׾5;Sݕ獩):;WwæMNJ[q@\+xK'2K?Hi{KYNrR$$p 4\' !Rzwi"XI!;r}'tz2 q_.97t}{r+eH$B8Vyfny8GM4, 4O:U /OO8pڊFW挟IX}.(BAr$KyxPo =٧q%OZV8nOOU\,Kǂτ|i<ӳGߞ$I(DHO^j[ٞh\\%Oj[ўK1eC~qBF+yE@ 4\iY\2!?1W@x rdC4 '$$ DE oy{.#!I$PEWXJ jN'=}3UG @6PwǶ%7PpK@7Hjn{rnfCKׂ\NfNۋNT*o|+'`D"A,+~AU)[WxȒ쏭搐Kŀ`&J]שT*TU9t]O~?&6lx7p @cp! \qR\!u8OɕN䷧zN[Oːg8!NH_%d{{{ɓ`5nwD.3%HZV[OmBZR='8\Xl(R'>) B$}ugPD+{)|[ I5r?r>N38%r9%XGΆr<I]ԞNBNIZi*s5@9K&#J`3h1ܘq(9fjKlaeS5^hE--lz^j-Ucz$[f1Gl5ZQu/RTw0,s16=hSs3.:+R6JEt1̂d鯮'ԓ.fˊ_>CɎx-ywD4I=E9XW{ 3|#VYS!FܤuIUiw{B(EOcrp.;ۉNĖf+l۱oEr{/"hK=COV9}~en*GdM[L-ek\`l/)Oɷѭ4KKY]?\1 %J1kf07yeJNٳǖ /a.Ϭ%qOcJmA-URAAȺ?Hp",ӠCF h'L$} <\s G=]D"DGlxBkmYcS:ljؘt,zuod\EN dDL`nQ}{ ,PR쓭k]Gk춹WV)o*Q{P(P(|{{|Wէ;ωzꤵu<hL:f_~͈;ާ11EX C9Zflyb%Z,d]?B5^. W( Ħlz| ̔i;ưvYgQ&w"BlnD=&d=0w4F@EiR}C G8(aZtMqH9v(9"(ކ̈Ys_0ZN=kA5]Х@Uݵϗ\r,Ţz^5nh~]r ͟kGծ /SxKF`b אf۝;/?'O-G5U ?j?F[?B "-Н*g9D(v:$MP4ݙ?Z c9;,MEX9]!uST8'! n,[xqH^`lA)`-`~2GcfEt_l ]yγ;YnJΘmX{#>DTkt"DB F"Xop/p_C&eY:YkHB" J/-fm͇8ϧ{jL6|mr0GSk4Ә;)/_`_,I *1.Qߙ4vEFg2^Kӟ6-2"<GpL4neoe͝kIъFܻyX\^alm߫'@=qއ/,ZإX^DEAh:[0o<b!!zѰ>4\"{`?67L_@0hˣ|W[F*H~u&IݑA.u2Wv0aZn% ϳbwя~Ew\$o|IjJ&a͚5>+6H?Z+p`"QB*"ϒT3f eVE߸n>@ Oim> ʿ[$/:b^XzMZ#2E^(]}NNdr`ڵdYj( ޔM7DZ/F_ǘ4~7Q z`]݄0HZ< bn?ʔ-3mg0bodk'NO/ t=Mr6j0UwUhnC˪m:=Gsu|v  BL7M"}^]C<ѾX`V҅EZ>N'smoucDP#gE__e{^CF`v4iƊX{8ZRijC58}b]:VIKYDn!ȗoyZN88a7BX1<4?qc4Slg D Q D|fG-o'1xCp{Xsoe+\!<.mmQcz$OrAb2t4A~a==7,| ӿ7AH`YnFg]͉5Yps9ڿI!RɓXܾ@Hx:̖[8rwvh+vB rcXe޿K{JG8{OןOmUJ{ ].}'JHբʦl`X R7 !tH?3Gg OFX׾ğ-lJB۴ѻuj =o=p]D 9Xq뭷}{5 7pLz&)˴Me"9/ƊjIH$˴nj!$X\xG|~}x54}4N8v^#D,YVT75 Had*, c3鏐V 9ߦ+gt+pJrneǍ絛;a5Ie~i[6|e %[ mDsWǜwyvm ?xOZ' puP ~_i)Udl1+gLeq 0 ͞#i8p~u]+ 9 Y3Iss30q(kY$ qI~ޏV}4z)e (/1YB֎r^!0@M1v\= 9R P>ľ3"ys M 8=n=ob_J"/8=uWV BiGUǃ(UjVcG1N/l6w]>Sݕe\|C:)mN܋(3%&Ǚ8Kf/!^L˟s?RJ YlBr%>^}kesWvo#;݊?:FG~ujO9 xm{g*ݕI:՞ ڛt{;!SJYXbmlr%t-/0%Mi*keR^FAA(g]O7ޅ[s,N]cuO0|y)ߏ97*Jsw,9Oapzn͹tFi0C5RSԏi>FS;7}4m_;ؐ>~/F,G+T\e]3t?e=^ģIGޘaKJ.%˲*]bď'7 ƋuQIߵ Bt>F>,"=Ө.|k~;bȺrE j Z"|ShnjUԚ%[@h8hhSW7 ?pn;L;mԚ7zhKt2, rjc*n<%O+D [@(2"Ԝ嵶g˲Ы)WW]5PBN^21lT`+a Yh lLLk>b 4\,AUPWĚTq^V&,j/9s nP.Khz$I(H&-YdbS-`lgxP@*IWWA訒mpK2(K6A@)x@ih @ȈRj.j 0Yg~zmxxٟ+EB|3|!dxɮ$.3Q4M,BDLčdsxYP4|}[ Q]Vi:(saXaŲ\VMv[ԆkEnbםunWm,AvҪmk쬓:bmqb5u{<}QwfcNЇkѫmejnjxl@oZkͅh@(ԩ{jMM OŋKsM>G䓐D YVi G f)Ȳ,躎੶2 4τ2n!kb8 WSZ2wuVdU7 ,4F┆u:x7紲d9S Mg *2׹_mư UWY*ߓsƾynTMśk;^QìX*Z4-٢1ZGU9iZUCdKxͥ5tnDZv۠,9i5ފ5{< 1=K4EӭzШ0Mi3wo*_lԀSB(@Яӿ :u ܍qʕ0Km#O|z8ץ`0H__jbԯuhkܫʣ>ʻפ466vC%Ih:N@Яӿ/O%Y;2/_c?_t$Y'L.9ZbΝ,꺇5M?>k 3??à/nP bg!mJ&i(oX Z+ϴBu꾚)y 8C;nX ^j5c4 j*B] ўjIp1n&뭱y5$Y"ܞ`{-MY3Ѫ^ׂ5DRCT|ѿzPSv5UVlv&+XA 1#]oUԩjPĉ=V(w͍;a5/zŵw`+Ovm\E_#TGKhȶJqw"ERss#P(.u=CkT*[nn-[0gJB^硇⡇ "Pb^$Ǥ:TCAKE'H*j, fd=>ZTr rppʶ~F^UsQU+ S_#I Yu]_Wx{|lR n:!(D- Ʋ./^s]usțY #EL]ஶꫢgݶ!T| ylƛiUƐ%uɦ< *0<ҬP UCmLѤ)b!X&W1'B_p %WaC^g8BO_͇)ҽi!ߠ@#15J" Y;}M-u7guPp0reh͑eȆhw-uAU7hop6ZqĊPo*]924B^Q+\A:ƨF# w)O@l'֛rfHwȍ}Zxde&9%u ӳ܇0 6J.%{WFc׊ϐR ,M3R%]uwY]ulf,E\5?%:ݜxxmu]M%C4 |W[-eھ~?=l}yDv8CPp\P"S7Y>S|ccrr${C>AzaĭjBn"K`k{Z!lMM#-}B'M`癦TV)*zwx+XTn)QOrQ $ۀ4(T0g0LA*"} Eb=f' )5q7BU25ǛֻMu|GE ֭5֒kM77}$I"z0^գ+bNDʯ _y~|$ߖbU/'ZF+M.e;_i1%T4v*ŝ$L|$G68Jk\\ުmfo\,Kfg~Fkg6!ە;(6kMfbKII{RE\L%6ȆNhL~b-DS524hH݅ Vټv*Dk`PjEv{cۜ*5249o*w,El`ݑFyXIQ馒To3Km5nPנּE,VA[ydJD'r RJd"i2 y;ӭ:O!hvcoMF#D |F7Ehc } 8JmsN4}i6Bċ뷭Q)#SWsIb/ƷkrEWZ-bc:Hln+n K+$fz9X:-;چ`UٿtR?> 6>\m[}l@%>J2YOC{p\L>ЊT|8=>b/̲c\ETKZFe 5XѮt,BB =c=ά51/Xa Z][3q/ H ?cV|’HA";cq-q0؋?`haVlڶM9Yb坄Ά;[V<>d?#zxEndҶ#|_ I킫>X[exCQ\ϹqȃP<؊jM|Slli㶩 9o/z.L.y|sAN"\V~OcA-%+t/'HIx[ w9|0Fȑ#Zȱints3[/ YǶmoƹwa}}/ UœWWualwOO:3ʭd3o¦(1vav Me;@!3MQ61K,ɢ,1}-t ;WŒLpgdPSk^e."WΥ`$DQ԰wS܋yX h^CkL#3pb`-dHmzqE]xE(L>ܐ~qO'cr@'T J,?\; fVB8{M _Lqb+K*|XC$ρ-@d-JW<"_iX(eb<!TZcxV?At:J߉$fM_2Pܛ9%qO݃ ':͂u_56ָ˄OFfU@_vX'X:@|'>G ts_oi$/m^^VQ+~g_\g[Nσ>[^;#=Î;ͅlOv8Ö GW ?UpBlIPpDG谷xˤ(/Y(+OZ n@7X}&=~kAc\{%Gx0mcd,vM^U%r1v1:pNy[zÔ'xb/Y pZgu !s¦d/ްKtF#.Iӽaӭ[cх"3wOSgy%raf;pyEtE'Kl}ӲR hsOh:FdPZ+xmGɉx^OQ.(u{E$Fx+猗3] ~Mku雦*C朶G3mahF3/.3 GUO}i:s4x2HL۶V 靧6*su6YS{(x*"$sCآMY-ҋ M M7G:s#Zr3oeS` &W??*VФjo=H6!r YT^K1[*/8A|)?O'={sʢGR4h#c-bc$OŐ zHgUf A/DQMzӧP uwOC~asr[g9Vްq,uu~q+1feo?(s"T\.}ÉGjL 4>`F&c(:tVrXt8:'ݔDiWola{ߒV~ Ԡ*2+]zl^iQ*ƲAF0.4 +o]$XbםDBBvC"% ^ |g/5c \E`S+sN Xʤx|͚w{4bc3)n}XE5Z=Anԅ('GitIVȶB/C^`mj;_ 9ޯV%UxJQ?Hy-qsT.֨_%$r v*?cLXMe]pWa|K:`#=5ߌ?9 j}5t[2-\E7pw  oC,>k݃+?!r!J~s^lo~@  :|0> M}>Ɵۍ']gzP-{AlѦUدAH/cDZ1|:ϲ:B¶-uv#R:?{h(uDYDx47YUkDe9 ! n%|5N8c5CAjȄ!&pM穊 QxXρ jw.gc9ʜh"Wli7ŕCPv[""Ig̱/ RP"2!Pr*Wsz`b-UK O'‘{'6P ܺ]Pךg)eT M҅1) $IƉ4ѧ,p TF(%*QjwMf#5p6"+WEbë_:H q'x0XRP#)ĞThSr'"j Zsb+ch:hlMm6tn9Hm8Z-nUi3’7 %۶b 808[/ɦ6Œ,,rm{H[c qkoR-l¾ZQtCo%ؘٴJph2%[v@%[QM-XZN[!mgnr;nQJn4/9 4u^۳-retkx `zM'v~,a+8ukN-^KA!mmb[1Hh]oS5%Qײ-:.˹x7+6DN=yC^/uuG IDAT@j|τ1<>LO2 Z7܆SZ, "vbD6DakS@e[U۶V!BCl8 x:[Uxű9sn#ַS[e;2[e[_ⶍPƄ$mߞ{~ucξ*%9nM8wO_ -oy _X٥%nXV]܅A6Zǻu]zolqޔF;J\GG2X Nյ7ewE%dݚْк`}wJ6fUPShC[h+g+se]h!C1Ap2(M}j gt۰q_}m׬O9ml˴b H5`oR ڶg6u8mlymvB]zuAgϞeee#HW4ĉ*/^|Ư0y<}+1ٶ ӯ_7{!^#NruH P(|L菸;^srg>L__?s;vpСWK_g__Woz^$m}+S2%zW3*4Ic#Jl/l2Vr2z 8ڞ Q :/׏h66˃KɈ2yZhl.Gred ]Njv|+~"vb@1PDFg ol 8@T7憳T nMRzNmcZN'2iBLOQxD킫(Q;'?P8B{[ǿ zYؕwjV0l5!#W/cITr3ԺN@ dmV/]gc7ڣqScwٸeȫk8U.Fo833s/i>sPTedUwKbJ{Os!߅ 8uLu4? #GU)|˫$-ɨ$\}.Oͣ.lZ, ζVV`F*[ nS  v~; UW+>JWޜs7u>;zVn^mdˋhW..ӟ4}}}<7? nt"LZF v٩un[›oςH@n\j(E N҇ ԍҾb`\ ̷\WJwőo:PEeH&/NHQP $sm.ZKXٿ Bu]k.IHvBWurO vUZXvº6I-tm:m( ۶EjvUTP$y]i50^gOzmX^'Q2V9YcBɶP,Kظs 9 1б :ZH+zDzoku!ؐE.{g0-@((uB7>cڦx-*2sf1eP6EV5ezr6fm`_ O7N.e#Йp "D츅ȌX[lX)nDZc62Ž/,߹.,F@6auCY2-,bic5 6̲_[߹ m ']5Uo0&bZD*}:wE% jרu<2)gtT' (cLrԝwkHH5Lw[Cup{H_QjWHus؆=SCRs:}̄v֧0w b\mL%VF@+/џOdbsw`&RX@5yMq3K,gx}ʏ;b30_1"é*w+ "$K 2]x+ Qy_+tRU.u12k5k3||'FP>1a'\8=W;ܟ`Ca#.t@>5&c{H=F 9axn\Fok/'B717ʼn<2G$K>_`(jnܷRP)XҮTVb PĮ'P(5e'0k(5GHUf3HKQM6S_ 00[kʼn3~J M3Y{-; jFjc6K|ޯ#\W(b-܎%#R0C?2XfbM\?'G>#rh߅Aw4+GcNw}COI閶 'x.x0:)g]:CO|a%NjZ7v`)Hu6-3aP[Wxc6I{Z K 3=c/ʤ7f=K%Zh Ϛ[[OE&3bkGĿCSahaܐmSoH-i*J54G.T#ciYyz|yN?r^!2M}C|yUU~0*Q"ΠDYgCG)yK>AU6':À#axj~J{WF6g ³\K#ħ{ujM&бaU `yp$IG0UXgݔͲ%(zac1óĮ~qh8sNU^B8axj#܌휥! O`eB R^"r:Jc1\[\ey_ $$|;6sf׏:3BFsA}:/x4DuFꌋPgbu;G ) |n 5' BgG~^]>p3k.KA"{ZݶbP!Q'RHvv?87l?cFՈl'xcǵeSv3|ZveOIjpqAdj4=^<#d?=U;E h|={?Np2h6Fk׭ט ٻ, &4ϛI%buR(Uג0h kjCZCc3To|8rH]jW@Cip|]c(9 eK?'[ݑyu#^~WNt2xJ]6ma9wp``@)Ѣrhwɽux7{Ύl{a9p0Z-Y8F|D}X9Kx? |%&οk旦X{Algt=^v=1Y8qk8ٱZlsdB 8C7WͧpJCϏ|rשT/!(W!&w#!o .KܰnZ-/^)wH0ӽ[msX uBSۂs~|4 ĭ9Scj9cr˷ހ'הJu<n [.wP ! vg[ "g~l;g/Cg[XZ!srw\0q{8y`'ng?Y/^Wy5(311ֻ ԕ ՋU}~77;p+|< 8=VWZ_pJ"c&;E뭯 {AD T+{q9lio3qf1- Q* !Ovq`UnGckgM6C&6:r+qr{=#گ G>0X|طSuٱTe:ؕքӟ=Ж ソ7nYg~|ȁn&Xn>Mq(![|V[&Yۿ(P`/o]n>j*?p4:?*aVȍld w=- v077_~?'N`kJ$(LNN20Н%?O$I|#}{߫}Ze/i!'qC? >) I /I_eO~`0HOOϝ+vΝ~/`{}(B2dee%bg?Yy_wK_$ 2wモ~'5uyꩧ^hAv} gϞ}ٜc>s-k^4dbhh;wd}KƝ%#--f'gmqwc~:ѳ1zd]yrPQqlhיbZn՝+J/C%hY &o* Jcl7 3} PS}\Vn94MoCm#`Fd{l+/S-\ 2xj$zIJ k:jn2b@w͋{ټCsM~!Wpk^D p!bPWxJ1UZۯzooM߷N:_wGطMf_wۓv$u1MR7Aʃ%#]#5XSmA}Db@*zJ½_-Ο o#3a jmg$vim9Z-W[Xy WeǺ"ZŰ 2ׯbNT5LjfMK6tui\bSX @$=n[o9Kf滭 IDATC{|}{/$HD~'܌i,v'n\gnR5JA!nHW,^M@J+sf->@#HuTW&J۽)*Sxe>K{w#x=L1\ڤnunyfIfm1 Rx jY%T[H\L*rM&6s#r3X7A5S!Pk(Xy"}S#D Q꘯0M]{O1$">BXVI G(#E·"w#> X (iQ,Q(R>+LR"х8\m-=t[!AtEv,R<۬њA8^DB"ۤ k!* c^t.nfh-ōo]2TPjAbkE+zm**%u+}.웧⫨cڶ{K9CR[d*FMb[.^%f>ֳDbhAlan6XZYY(\ߐdy b]$\`*&@z%C0Bif>2G%P![L:k<^XwW6u?p$+U 67WMu̝L?Md-d*ZF+ YL8K u:ZT^W&|ߒ$~HlT~x]7ǿ@-:Nλ|S r̉,J."C- Knoc1<20}~ Bs @e1)68Fv|`K*\%5&G:xYqh6ӳG`=;U4^ĶlC9W̓~O/yymMU |*BR"qz); LZ˃o0ntl}:ƈAj)Mf>4@s\=[h$"At+Jc3ZLZKs)&lG.t& ^V_ooP!5&R2M|ܑ%FnILS,*g(:fa7Q,[2^E{lԳToh&H\N09OIR !lq\ p6iR<~''z#L$ p1G8c6I"bސ'{̵mQ#|2 aҡ"n?dOFHMi<9JIܚ!<>"-c9Aύ,~M)Lj`.o2ㄆͱ-OL>Ӄv6Ge3~}D=EχaMN%wt*iȷ}n#XMW#5R8 #OnC[RnT]oci[]稍!J_1*&z%0;H9)+įljEݥq咺& m4f??}{^zstl:\tߦCGq$O^)$SzMvލOQh:mȗ9KؒoB&35z_<-ٴnIi@j5Mj%MVY4"LPz逸Zį$IL'il+I~*ySp~\m_ $"'r*#߃wτ4 m4t''4Q'EUb!k$C)G[?;GQ񫹊Kh KbGx0}x䒲! z-ln[:ƼؔYߞcf8Mɑ:˅P;Tg"{~q!ST&oh̹Xũ`wY4EzTC{'PCshѴBl%Q:M\rKn]2݊ 4 zLsDnH$XhcIŷ=3.)QNp͍{q҆ƣy1Z,60Z9H3Lwps1r3Q dܺu˃~7) -1k$JWit%P<5.ԛ_ny8 D;meөd4m\ySJ[lUxǖ8K"5%T$o7_ŮQg=RZHm?bNȦu?@Qpq=m؎Md`6zo \XEKkz\@4BkꛞT]Xa`65_ 1(x@_&W?v7AWwnך8CDW$X2pCvlD!/j(mMyOgBD:!P\ &P !"RxW8rAWuĘym*.vX#b uYf~ ;zT Y>j` LBD`ѓrV *;\s6h :’#0;ISmsmM\_CAwV_ AdPZ v|1Y`9Ϯ?\͗]N>/aȾ2]Xauʝ L\ܴ%\|y$ \gy׮g!n)zz򵸖 $!?3}-TG*^M$ZSIp.{ҠHcoF`Nِs~_J"sc^`jgE,q4,ZIcRgK׬9 {eOIte-H6** _S}3.<6 SU(' ӎYedIB 8'Mq]l1]BGk0k,cG7W3A`۶m=zweÇ v'|bhhwqwkl{d=}qa&&&{UkxyxԒjF0dbw>><[y`)dFx! ۞'1Mz}=U4#Om}ex@w(׻ʥE nJfMy gP fbxenکޛ%/V0 1}=x'XŹ307&BV>#. 2n^4"jJ?Rwzҏcl/su~Ngӻڙ; .Au{anqtvcmlՋOZo@G\saW6F; ;_9E1PZM7k\^Fz/u:gp rQ'w)ut ac#m#5M*=*WUp6eΝeVY Fd/>3AD݋oc*;[K~4&{ M:2S/UnSc\{?;Z랫gUZ ߴGi0"2ؐO! ZvfVDpMDH8?O#an^9,BRDZn;h .۟Ix4ʷ\1Մ0!?{Gg]92?6-.**U7"{"-OAߕ/ ?i_|yc!H&^?ݩw'*Zh=v~̾}"bm??nF_%adxybE/qsEo:AArsf#=epp$ᓣot:vR%ƀU8r."hK83#0ۉ.lM<3>M'Oe=|i/ 06=N"C;r6 H]W:R od>7iKaGo D#1iRPBeMU@{Wɿ6ZP-۸O^ieff=#҈+6w{ֶ>{mBci||g&ZTj:g&+l̕,Y-zp44'My(U/_,2S IDAT,r`S~$Wq"oYvrYĶwZy;o3T_"d鴙A[s_$8lVnC|vsk~}ZDgwtd5uN=r%3 co^]/ R@ -y?BcO\`\ҳ> TR׽N\J1|q,fӬn[&^JTVv/xt],= ,\h> G89D,ZͰk,r[fxr=dndYݳ%M|ms!8erd%:طkH淼k~(WL)>T쏜obD7X7|>yҮ"v/~u&?ӬN!~5<#2I_L?Dl̽6ˏ,2Ǭ˯]d쓓Lb3CȭͤFa${4rH&y6mykiFNv8 V>Hgz |E#;ko\NKކlgV Ԉ|*N+1BԿJ5M 7'a_gg(~}BhS 6Hy$%,2 ɳ)W̾`i2{ 8\dzY۬*}_GDfiz?$|#W his>s'U`FIF꣗g'鹙%s[\A4w47%櫴-1v`ƻZ,$D/qE #_Fɛ)>?wTY9L -9vgw9%v"N_U(jFAx?kSO` YMkg1KJۏO6/n8ÁH]K:Y+O][o7.<#ǻ7 ~}yWC:S=AjCGYE ͰmK$gR􍤺X;ρ/$5gh=gc$q{μhs8C\Ij,# -BgسCo=rfo~D&q_ J;kT^QB~ Nm{_܃vACDR7e kuמ{=.vP|'5D[C_F$d[Wֱ)^2W461Xf/7k4tr;W |=qv^ueNn.A}E-RS `oD E " uFZ~C ͫ!l|Cu| iCD06X^C6.ӎ8g_I@yuz?O|WW,Qev~!+j>Ė@O4TpWt8'\#':NSzA'ث Fc==wnomS7Vٽ;o&M2y~'FZ[}@^*FlQr[)0=:kOV} V>Z{(}azCwۄp u9;}d.xz .-":" sM>-usDQ@*K81/ɾgm!zv4MZz )/"VE9ڜvBA4{G[2){9'g(k!|rP =rGĜνyv|z3myKoؕ T$x?s :A"`X5я?{-z)5<#ڻB^S^_BuVQt=Z&C 4C 9/uzN-REȧ~X§Z x kßن49㥏o/s:b4!ݤ}rF%6x߽nPu-ԡ+81W o:L&zCkd=#]Hr[rĴ5 U,](^@ңx:.G}wM vݦ*r)T][tDv/[4/>cYGl^q*@ ry=4IrRN.ׯ4 /@-c ([gy?3pNը1ôҽMA~3U0[&*{HHIS,C|OӶRu؏zOe|w17ב^8a kƯ. Z#3S8 n6 j#UJ% qmb]q_XXcի< ,(xcmo^![X{tbawT^&lX9{l-}RC~ܢ%m8EY?_`& Ʀ!\ 3kۿky=9$%/ Җ'a[^N@}?QwȩaY(gJ̦g!ep&^ 6NE GN==ך[]lѴ?5y zҹ9 y5zWIdc !R~UM_{\ #m4(V=٢-u? Ỡ~յ6ڽ6c/lRPƲ"k)[;!w g:nXI. +K4B QAOh +`jH#WFC:zLǖl5HoZF."E:A`HqUC .kqN@Siz'5-_\*eeУ^_&$Qނal{s[1Қdx-:$\Id ֣9tI'()[)VTeqd ÿh췑 6XGjW ؒ\R<"ΩHk5P W6O} ZG8lW~ ,*"`k6W_G `Y &205S6Q kwQEK83hsrS:!@9M2-/&}"\ R-jc3_huLI$7 w.hj!::R["u>dII9^fm gƨt؄V„zQU)eJ#-$Wj0DEJknT|# t)S}Ȫ0n:?2jUʣeE?:Mn3h?=ڦP` ̠?wW(> %`McEXbcu%XÄ" }40J}F3;dN6u;4ztkA:#T= =Aȴ`ER֢~Fs~ޯ#e二;K>Ӄi6!UQ{LT^YP g5ZM 1䲷X!`ƣu䖌1W5^eL|F#]GTRH[:zXg}2a$ \E- @3`sħUN_GZ fQ(9;J}PVNEm5J X! I-؅q=TeGt~TC5Rۯ,.͑~!L#_n)8d8:eJKuqV2ii7I_ :"MI1^$?ζ-mOwKE0Y(#$IJ@E n[Q^Z#MZۚ8MJ_N6m6 >!P1chCJB9PU(J# E~:mu!"ƨh%5h!F]у:-wjAoCt#9|c`$D#7|#9aB0Њ{T.b#:MB&pii['5j^#ȈAF_-F[2bIwڳNơhrL !jKA|nX{Ԧ@DTܤ5iDHEN^'8m|Rq" ZܔiPXH],+؇,|k 60lciº!JysRyDsxX@?weUݗB0&h4#Hi$#$@Bc - H1IDl HB(LRO]U]9o?nuz=eZݽ}绿; X"֘Ni2z21D!DQbf \o ~R΍ N@kiiZszZjL̀Ѝb860s&A!RV17l)4'W0N[ڝjP{EC4ͅ6%̊B7faL̤ԋ3fGjR15h%-P较+ !ȣb@$m鵩ЎII>в@EOkI7`6?L=m8^#p!r6RQBH-B/V]{xL д$t uyetUО&誎iDQ6:RU?oR&fdm< ,'3jbL̰Ir1EtlR[ xL_l3G8,`M̐Vϕ^ HČT!M*[G7A&wM~=1iG[CuVe;Om3 K2%JKH _vĢp8m\QqFPGVe H3D%+̘5]#R|f1&=2HNU<䰄2wu]s& I\ 9Kzɐ-$”G0D.dWբ9ĒMU􈎐 nQJ rU&v:1;ac'le:tL3^ڣ/Q މ:OTiZ(T\ݥnA̙  a%-` Jb!V˄W@1lС Ss@H!J̉![gҿ0@re:F Pt wA !t)Lh%'20dBaS)#v¢;l6KR4Ma}Ifb&g|= #"l7h* 䧧]?J]]! <9qch/uU˧bAD(xcm>xE­y薎ֱ-z>GGJv)G/Bh( C'dzF=R#a$0{,M}m{"5iCM( Ķ0,JK{U_}&Wf?3lP.`b!`"ZǎyJ ٔ Ø1(&]H1Jl%q31U>Ry7]?((C62wn+ Ll!gIGQf}M׃p~iS&N՗HyѦYkb &FRhܠK&$AZmPgwTC:Ф9DrD!NUĖ?܄uNX0' 2v(7:}+(!@Q.XI uTEP6uijJ\ )hvRCDpx.]+J* lllܱ~?GdY&O*KrWuƍۣ 0::zܕu]˲( y* D $I&$ qWRոz*2Ni߿TFwܕo-wrWbYF00 +:(IBщEeaƐ_& ܕ?$W_'w=J.ufa4=˽زsǾղ@ bAd!띅.W^/b;+`= 9?pI'}Ze~fxه 59(^v_"$vpX^`QpoJ9^RWtO|-ak(QTx)UqKg 'j >}KiK"ݢd=Kp}?uڭS72W_|܀KP=_)A]P![H$pmʿv3WS]_8v \K)v'Gm8&O05ӯ{;`dotlk_C` }ȶOs.Mf^3dp"g&M8ovF|?Tk4RߚG.fB(($R \{7 A8{>x`[,\ P ¸a t-7pB~*V"t 2c5-σ'C#WEEPȈo}\|95yxǩ=K+}e'g _D::6WrWznRGpXr=y {?u>ݓ'x8Aoǧ5`( w.h?A\jc4A誯SOrUlAANB㔻KuEo@4D&`O}Ϫ'ӃB2U)*$:-re I3 ձ#neXٻDl}M.k\@#!ngӈΖN h`]qlL4uIc*J6b ML.%\A%BmQGsXe+TU ؜!) RByE?0<O̔sx:^2֩:\W0Esd8k'|wFDb(=2@ձKu'@|~3̎=,R8/":ca*& FǰIHlnSO(o}s!Կ͢<Ñ=41/LCΖl.][FȂ e!ogzyVm|ݸ[yZg$S7Nu:sCma4=K1-RZС5 IDATtU -Zojbx2bH$Ypv5bT_v!'qdaF-jMC?J`eK؃6q3xF ~j߃@GsAgXEiyfߥs7~̄30;S<4~QOiW"]mAOU1ш-MLL_gP;DI߅n"ٽWhH g(r@}Kmt1|5_gle>b쁫?k žs>zc]S}3|?0ύEl ,߷:7&c 6ckZf⮮o\~} %ڃt؜ o(t Ư{( ]_d9ĩY%ްIYFgѫ$K-ic/jN{0<;J-.bM/q6kLzHu1saWXgQ. ]bF5ȁygGz(ȳ i웛H _i?"d ϕȾ$6ƽ:t|~/kՃ=0Kzf_ԇgeRKinޖ'%|mOOwmp%B5\Plk3sT* GI3۬N.ceMD.| DM ^2^# YŸIcM[@59/pwc:Jn.S/|կ pߝ%f OG)SxKm#v[1 rK@mS#t[~EOK9P}W RTZ½FImi/딎1I<FgL~rnD2+=ՏTEձ2 3 ߻l$1E% ;8(39ܰC*3e2_,WOF`͘Jk!Īꛖس??(r#A ^~2!-˴mzOuʣ0G>k$GjlnzO' N ~n}(mʃe7H|̟+a 2QRɐy΍C 1{̾$lU1w*W>x%y&NA"8DFQFoVpn UU8dz|é# Id 1oHj-@ 92|ifߥ h Y?Fx"e 6tvkv7 V޸'vfo{{>rħRD9P?YH?u76|ܕ^B ߳Ih>{=sz>621bn_gle{6Hl)N%War9m?qދXZ$s#K͗7|3qqޥ~Be+ m ##WiDx룤 iZC-V&-tT0'D \@!\oF?!K;ѮWI 3 D3TR7;G96AYRW2H 8B!#/)]!}==4($? x ~.rUŞ1i`sRAbUj^a''H}*C4y_A;$dO99B!֋u;wհlzKyD>\_ @=B_Gf~zl%px%ZVf79$}':ӏtW/mc/(5%$-8SIӌ͏;d:`yZ-ߡ_1 p <s$,-zi\2g5]Ws( 噝?MaPxwh= y$ޗB3gr$?FD49ȭD!n,,Lm\GPƷ". nixk |_vp Ii[Mgw_W Hwr藍[=Lu=O-baGq(X| v?;ɀ52=7~~6w3럲M^Qjs$.JYavqG:c:Q̽ ԕ>)5>Ԕ<};m3,P{MqY1g-cW\['߲>זB!?5`eʓ%yɮaeA|* ܉`h6&9}e<9Vsg8q˝u peGzV\bS3w`3dֲl,'^>аn¥x;WBy!7HVqE@Q@lOo0qļ}oQw7P}4JM=զ4SxM_ |Z('yvY<@A] `WOc_e zu敖3[,7U hZVhZOftY6`55lfIS{jwhg+zX"?D? !?rgfK4fZ!=/x@π2X.,ш5Volo%)l4&v<0* |<;_@@x(LuU2}O.l ˡV^=h1!uuhoQO֐|v/KAwЙMG 2ri [))bОn.`OLp/# ' "wEP_h=I8$1;5kfIRjm:Qu\oDzoYln? agF)+d??Rfpt>%{f4RZ.aݛEh  +!,Y=L3d /GRkha hh` =:l0FKa#h>٢$_35 p;S504BU𿌄}=xw(@kGv!8Vg CBdI%By# 2ҰK4M~uE;nC:kǖitl-tWĎ/&o!!I:P`Ň6 mN70Bؿ~e%}3F愉;dl#Ht3Zw?IP+21SlJ*/dvf7P:.X_g!\{f NDQτeb͇0mmm5(b. ]+wܕrWk+ߕrW]+wܕD04O  ZL8O04$]x7EtnP@C~{#9Xa 4/ Z4!rMT Gq|73r҆]?v釞oփ+5 gd]v <dpR=Tm,DYCD1]JwٖlЎV.;3c"me3 `;47bLHXnˉٸ뺸ӿ;)u'':|n|*whK7_ eǠ"ni Q,׫$7+n.!7owN;W))^b$}%7%ʍ8ŭA!/vJY[p¦ؼ}7"X#9ݰe.ݒd%M+ܡoaF ٔ;54oFT/Ze!юXA ں>.f>}ʘ NWo88q1L xxHo#GqϷq~e~0q%co^nPdm IDAT)٧sw:Z{+2(o\VQ-$ީE!0[~舨;#]Rئ'ҁaFMDE@z[bk?]D[pS.."ed"j,*#ڲ%kOW܅T%WRew-`3[15&^Il/VS6)J,/Pp%Z\{K5]eޛǝ7'D|yB/uO.Q7k<]7J *2kC:F*dց6Tl$UMo.P95&fhnZrt6KMq1bzɑ0dR%ȂASm}(ᆏ1ftNR Uƈ8Y/(]QOL>OhO~*n j_#봩5)nzxoxl ~~qJƻ@^I&Eui^c5(8EG|lb"A@Gg?:H,G2$MqbmwBФIc2Np 01Nq.ٟƪ992T~/ xGsAK!V8 ( >kQ7C'4s ֏Q)AUfP*vآʍ7w6!BYUjPAե*G$-i*UfiٳnZao%4D0 F~e xX{ *o)aLFTkƤ:^u @ }kl7` 995v&wWXztLTW[-Qd;@lS٣a7}\vQ𱽪bNg|)$lˣ@rsH|4Ep3O[7!z"C?>}SLX=oLMh4`7B @sOwl`Ƿ}4UgޱV¤ҫW8wR2"`&M"0ӟ؋zTڃ-Jk5KCmvU^1ڦK }=m6|h} ,} !䦂2 Y=h"y-8v5XYc:BAu}ic<ɤ\YAgU;SOX=B!'T s G23&&M/@%PD;kJ긣.elc'tJQq߯=0ۂMM14!Nӏ}ϜbhqX5,)ؚ9tҫ˙j,/0z|#3?=ԅ& `8{$3/%ȡ #q:1'FK>|WQLQnӻe8VUDɫdk94]c[a-Ʊ'4mTiг;$Jo0aKA@j>!Ҭu;@nԡ"ٟ#J[EN)leՇ/#e&?>Ml5 YL.k ϝ&~B_"X5fA -b8IσB诽:bbXܿ'R^Fjԇkl-&4v mH B{6I\I  nԷȱO0PDTI-cLdv7P_ I&wf+o.ўj1)2CE܊u$^Lbl.&~a' l|* ﺆSdރbmDS"|1B{oAҁF.mA?(3GJ/ 9"K7"DV'\ RR6l3X v8Mԥi2, ,i9MP .,* namh"Rٟإ] =7Ll%A"Lh_ mGsQNײ4Z%(jKC$C'"(Wpi@ǣ-cT  $$Qf]\CH$.t@DɮE"ÿ=FRS*o*(c?>`NyAz845o/:A^Wh`޽He R(ҘjKF1'TAeQX4\S:b+ȺLPG'{6ӌo'z ^!7e."#_EQKUk =1ܑmsOs1xnH1Tp@che!BK(ܰo5b.+o[bOXhuuߟ'B?#|PXo7@`MxI:Uu]LvA &Ha BSpXeSf@i&)HA8kbwgfwﻪʪCTuM>of}&/54[ըF뽍=b1b8d~#e:/uB_ #DdC+d7{ ;Av:܍_ÙStat:$f&4zSdn?Y40TsC{~cjaQL1$5sS_!0H b ®-buʳb8zbO^e+sidpݎo=M~] _j,3f¦(afM 89^gOng= {KD+QfVgxY^(o 5}~D y7*{%L:TUvX qA1JT-6OrWfbxX^$0i @ u`V#E`?{Ay(1ѡ:mž_Ho 'g3qqi_hQX +c2hV[خ?AYE꟬^ ~+L liΐne;4o(?&`7G|?=kJrUqަ!4uD [T8~D"'#+u5ߍ!" ~*$x{23tUW|5_m\C$H}w D:\0݅v99t4\(ȅ/" A:S5ٜ۠o0z^bͅOq#ggAl_آjpqyp#kˁG8Ͻ.18kS|t'O<{vz!^ܤ(&4Z5 NAk.[lP_͓V3"̎2ԟ@MЈfpnVhD'ADg Cg7eڰ>N%&^I` *ȓD1u)ie~H'B`H#J#U2Xo{bDc]^V-3C Lj-DY]&\06/$d-/1l B>rvȚ=u=G?&Sd"+3VXV K*1a[ډݸ.߫%RoP4j/V83D@@zyۈ4~)0bY™QO[> wSi% Gƶ3'G8Ϭ &;FPxg D[A 8ҖL86fۖGϥ 0ފo`q0#a+cJKv"e'ƻC_?j6K< ҎZKt{$: 9}'G3˳hdzgUy$ZXGTPjȫ vD.x!'.FK'SC3XЊ4YCBa&V&}Bo5"f 8Tx݀O.> 3|sAڅ*V N@&* <ıu3bEl}cbWHz~vRgKVPLʥo<\; [;!BsIS Ugwh~3kafp4%{WjQT&;##!'*N.e.k.!Lo*;<}3 3Ntuo5ؙ!nINֻC]6։ iNo~@}F}p=t')[pfhsJ%G8B扷դ爱;XL? LN`+Hqp,@/= ra|@'@=PQVggIBͬ2Ld?)qz@;3SX<"aD)E0^2uj0XQ4P9o)h1 L(BTYNB-D!>&NWr)玹C8YZi 9*2R1.28ލ/D/#f`M\qqfo%FnHU,U1eTU# ^0{J bc1%VEyxeS=q2!bߛwu:Q `W`(;=h:ƫ䐄~Ǡ֮2Wㄗ" Z%n/[ޒ uKΘW ~iPLM73ndmҢJ䚌'ȒD01 xz!kRC&CAgV!@Co1"7hGAlͦp3r$rNFaRZ|yzFdlWGHmQ*fxăOC>/݀&;%DŭZXŐ "ZP 9$Ϧvަk d\ eJ?zM^dUARm.4Aӱ7$*/D@RA,s5Ol;С !gxGsɖ:,լMKm2Yǡl:B~ na76皈5 kB-T/T&ʄDpf7r$P fʠiit:#ȑZI#M2[? ZQ/v"k#:?3v$_M#o\A7RY(k> xA#:A(hz+8CeLZXd8!,,Z~ssHSE }kv(!G#/(PGI8 vXzġnjD.[f#+8ƶIęfËԣ+)&(MY~"Ҟh}i%Z_e7Kɮ(Vt1{N*ڴMG!yfYl8["VHdI;Alզ*szd i?! n "E}Fz)L//'05bQ2aC{YϮQO0VEN!\tp%L;hꐇU2:StWK,L,xb7|%}P3ܗ .z3e K]&h`GP>ú 'URr03I ĭx}'yS@;1+e"!uKa)$}6@I@t@)>E.*G~2ݬr_AݒA Sw]գgrqvnv1L?D% `w$s\UP>':<qBM(H<75TK5{J@DVQ64Q:1 )$!2 o+}:ƨp}.!zpr|Xկ+/bg{nGRj}&}R.%xLVI A;߳5ē |/{} vƉ^FspQִ'zo8 AOFG e>Beg*lآdllɦ} ~ iч[sYю( IDAT?It@Kpmex£~]ioI_G`_ ISߒ#%muI'(h:]l`bB:6m?jכro yA;ka'kjYCuj8v^"f; [ZW !Ǿ>i(QO@[b&I"LPãk"FE?uu}̈ V_juI  [2A[SЃHNvhOܤ'Ȇ5+#[nڽ9>ei"-\#u"z 2BoC'a!?67+_`@>9m8V=+V7`?m8{]΂!#9&&ΰ!p~Eu1*Iȥ֑$[t  n(I1 ~Oޓ=yOޓ=/MA'{'{&2qvcmT1=p-T# QK";Ѹ1nw|?^1UzJ3RR/𳛢@[Hf w2;Ce컒 rp{:f3GlXO8N@X-N`Wdԩ#:e &A ~VDEgCN(Prcb5m䯟 ]pOK y j 3EEm"+Ѵ c^)vBǭ{4i4}4ގ۪O0c&jU?-lI!v8.x-@'@3ӎ֡ 2zK}X=JKR9Vho=̧,=y 3 Dg@R>Wi9@LFT{qfA'ԁɑi3Jbm '3 {6M:j9 1#&)qxmuD#lU{O ng :_Ƶ\ܖ*э1! PfяOBIBRٌIi5=j.sÇc@/?Z23kc Ȳc8C8FhN6$; : vh8ݯUZW9|t# rS&X}儎.u !"E"QdTjLcC]q;DIH zx|C@7`~sql:mc>k  470_40^PX7:jH%"OqĆtkm /؛//_Qձ-joE)}FkT/V8kBs%s-ZMGbk6ZC#P}*B{:#GMhZJ1_)n癿Rn <&1xȚXҋ8}H$Zϲ}׺K qniLB'f2w$ j+}zKc`C@Cd l6׭=J`c XyjNuPח0c vG#LޛbqA| !q 0$P,ސy~.L?$Bx a-_]dykIF[89VS<5П>'>%(Қ &ת.* pv>! @Bz?h4;AM),37 0 :cjV;*6`z*3ʗ9/bK \H )R4vr{j.1+YR/ z.i'?e(o~)#|'Bkyisf.&$%$>G7 bs_bib7@݈ 'w<6ܝy7 9RoK^e)vh ?O?M $&tgh]ya9Jdc濰LayEa=՘S!ӿw=B9ܰ \x,0sp0zw#8G(Wdfpٿ@4͋m _d?0͓ISĖblg\Bar5JV{,B\OXU濹_._M%|foKv=3)T*lnpOR =ս/.n@~gP5X#'_(Q-s+y;#_ŐM^>컯^͓>t&2({H;3n]DL<Q ɃfQt%Mf{n[;kWP e?p@c>P++L 'pC%~;A3G'QH0<5P|b;\\:Y#R@b{ӵ˫$̿8vKs<䗶2C%o,0 Eэ1̐T/}Fw13 ?Nl\s<&f<<ţ!vJɞ 7뺾N=\vDrcGmfXdsxSgPl+slшZE?eyWV\X`4Ad0rlNԿyyLq,n[6n[S)常Z{S,N<{ɗ"5$ZC>f4NӃH|e9sLblcbp-zg;9:6Zi.f8J.'n wmj둬.,c٣a ~''gz,H Pq0O`jezC5&&"mCiHr= h9Fc.d*=&&w5F#?Wt'Fj s0jĎ59,k>aO3}yTʾV%=7 f{N)v.|_qޑ_j,j8apO,܀j"Qƿ5v7ϑ3l@FgȞVI-Y|>P0žGWѢ-pox>(' ߜFp 6Eӡ>Wc k|n󁉩hI#ZFPWA5:?&a'y )b Oht|#|3zfZA0'Y4mƺc.#ܠNA1 ٯSĆ|fnbdw~+ev,:<'p^~"v"0G&q?t6EP;JN,Ù^<$H0`!8L?{P3D~ez }F* dm;="QW'=maG17Ft?(kř|0|,cy&]h}K:o zTk*}!uurI_ww#s>wth>SoOGxr|lK6z#Bz;;P|bD/cD;'6DAޠ?ؘ]l)e) u(Pa.xSyg,;Ve.lǘZO-?)3|bh=qN{$j &uwjѕt~i$t3{;ʹ=!2Ǐ;aDx {9FzǺOJ/Q=F yF67֞`qx?ћ'Tpg$l@4.^mIL{w8o0R]}ϊg.6)hA]$gڦ/qe%eykW('P1gzY$u(䏰l$ '@S34`:_ݎ4$DPeɖnfKc(۾!ATIHaL߶?D7c1$6 ]yPw41 Q2xy_iQjd&~&"217A| v"c>e0 W8kL~yvPhH 7po4Ν ths1G131}}X[cݍ0Do(Cmù"_:ހ xg&P+j.-{~ !eH/ ;= ` ' < ΁g21M< o?AWm1:\*noq]vN=Bad; 6sL/ƻK}}Ղwfxpހ[`|kT5j42nUL8l⭣Й렍  x۟.K"D! Khⲓf BCuUQY9Tq1JZL/VxAɇ?2_ |ޛ%juޢ8*x^Q+v}m `?bb;mqEO-6m@?{t!lx8REB8ͪ,+D]xt>,ojlMXCk?"WzEzEq"21fq:$$#_#{m* `T:7+A=v}_{FOV9ws޷>tg y2;]^#"vb;͆N /Α) !z׾+X(l) !QJwxo/Ot2+bq뷯Ԃ(!vD0}gߕ~퐕e :oǿ2I|_rA3gv ʅe匁IDAT(>r&Rv;\ !FLĪ+3<ӈWR(ދh8qĝn58jc1ƛ=:AGt7ڲ#lyTC2_K#NC @1SCm)8S>SXqS̃IENDB`java-binary-block-parser-2.0.6/logo.svg000066400000000000000000075770311445000440600200060ustar00rootroot00000000000000 image/svg+xml java-binary-block-parser-2.0.6/logo_sn.png000066400000000000000000012144201445000440600204530ustar00rootroot00000000000000PNG  IHDRsBIT|d pHYsgVgVڨRtEXtSoftwarewww.inkscape.org< IDATxgdU'Si2Mhi(t $Q48:qL4:U˜0Hj2JBwNNî:Uyi :^{%{fE7QFJ%R3%-ѷO> x$U1f<0QJѴi3K20qw5%33:/ O& k[x{璉>!Q & ՋT~]_[f gFcΚIVJܧ{ %XRq-6c#<ΠFJ4N>4R҉N4yFfCci#}s{vHLM-C\u?4{M+La?v0H<:wpo:Ɨյkt3ݫRS謡Ļfĉ nD;~gzqELC ImcYEL}}ZoV.iw`ܢ2i߽c2b*2"wƸiN =:kHi=eLhǐaZK0n]950bSS$c1CŸc߱ђ2yțH{i:7Ơc1l7)CwU?6Qj70PUIGҤZ}|`-agzL~9ΙG:ƍLSD&ֳ{D;4.9Γ߷FL_W/rYML-gSg>5vgG0ݯ/Ok=>e4#8ANLF*?0*i$1X( q6ZϣrY :(na^`oo`=-~_4jv0M5ӑM?xwNPaюT/0mk)֯c1q62||GH&bdGF({VJZWp\VCÀH"7_b]s׶P7YT.;4Z6co5@ hsf1n5ֈv~~]hwy@'R#\om Ub)Ν!UaCcT~YC_'MpC^hېon`ԅT~VøBnD'ٙ6)G5>5*:663jvJ f{(Omj @ILi cr.ah=~vNso gu iOb]!0M8'QSQ~l ;n"-m$[m`OGxدo1vvgm@ 2=脻XW6J:ާ)>L5yeeJ!O089$#)#yu@nxa70T\øDFwz/wIԾ=zi=۸e3dfwrʯo$ &/`F y}~x^L}ӌc~==~I,{?L R-?%^agGkb^m<̏1#gGo;vj)]}vC.n(YᏍb~N2?;AQԈv&&^npoK}zzDޑ.K98أ&c:.)l7PW1n1 Xc0~'i!.ђӍ?{{>FC\C,t~mC’фM>BRv iC4iٓhoq_ %t]"2C;>z=&U1nWVy4ϛewHDOp9L]Ok4.|s%7 >}vs'p:Og(o40</iyE% y}  7CGu$_"_%scјo5hpe㭵"ƗϘWYԾYy*k o*o?/rvyxλP&F3JKC{C/JQ;=sWPav70@$1¡h6Q|F3\QAjnSx>pu4L; )XVqpIZIX;T[G#ՠ$# +mF{` yLCx/cg7,/ I=;B9ǯ+Gx J2s~wyqr:Vla݄?/H^ӡ=PS[ 92}:zd4ެ+D[EZM}ɖ)tO}m_tFTʮwOJZIp^&$T~ZCyN;yK$ᝤPf%hhIsCZIh|a8*ߢkE0uNzB{D#د>"wRΫl闆!]BcR%}8{Js;RI~UCyZ{pswE'{^ċ"ۤՔýHcwz^==M7Xhh˶0\QEyJ;-|_ϋ7)c6"ɂ$iChxDO^y־JYWVPԶx_I}:&p-ۇ LG;F}zdd}ChՉv){{Hܘ|[kn^]K3L?!oPz!>ϨT~yN\ dq}Ax ,cq|=hkHqv6*h S*R*lR_B%1*xI$Cn}Sl>!ɐߐ< 9" eR&&h`O2`^[N\r[&X!*`q~'%C s%DKHHƠ* >S$&Nh3R[¸Dywq^;%'^/x5piKCz"0A}RI6|1F]uU!-(ٻY>RvpXD;EDB;{[PU~>hBGRD:ShJ{H'US,uH扱4,Bj*BOjS{ڧ_/2Lt@BdґwFpAyNXoZzD{cm $$$G&^#Oaq!BAYi%#1d-r[&\ rOn76~h. wr7kXW`$"OkX-1׻# !D ;颈xaDn$C)q`IDЇ-<}18$ 61ʦڣ%?1KpOg@2d) R[&=$\ }eݩ B %cDyV!Z* \H=;!\tUKH}F ^VHg'xGċjiH, )feCΟ?#; ^o; DBvdE1.FOhȓ R F&/c c?h!89&'>"NVmeIȳ2(ϩD;w~50[ ~>NAv˼DG=ј٭6# !(w;%#Y0}DGrmN*)I6!,X#7b_b˄ cm:.\dAw\O跚Yn/ pO_ؑ#,DeC}IW4geiM N`^#̞Rhۈdc7(3}f IhO1?Ɵܭs%'Z4$G)fsF-~'OY~"->MpFJCt;-!Y- ־v~xbi߭{C_&i)T=:*`\oe[|%Chف$^aI|k@2. IFl-*3 Z)IMOWQчw H >#77ؑNZfMTzޥH9DaSWQsJ@Wd[.E6EY}\͌_*+7)ʴRkFEcw"\0ZO.z#Kbq wϟէԜԗ%wGLpYo}|RSGK$ c%!IEd;p6$^PA+h琸FYW9资pLP·H=Z wYO2A{L!KXz3 G bmz@%CӾyV)\FD ' w IJ0O9XʌKwH%d$!%??KNROXR$rVm"S=WL)=ݤρhǐxiJ+I/;րō;N|JOj15n5d^\sX ީgG@8"^\͹5_ގȓ @^CH<1`Oep $C%:C%C >R-½dnfDL}+~_DNjcc??2>wC,z01`܇Ҳ2ByNE}b`xkhЇ>D65k^ blUV*[e$ dlUV*[e$a<$O.\gYZo /£PRHU¶Hm."IXSKw"PUq"JBT}B+TRJA~NIscVSճIK\A F!7fWSH<ht4xzZ- A ?򄚻O /R BSٗ a(Tޤ-`ʅ3pJ ݪJ DHo,W.TZ'#1 `@4ƗZr.(XʅOBxTU aA譡 "9vz5$;r!D "40F*lfcu[V !|ٿ~QED}/{L1`*BE F̷:gBw U!e g]ɜ&#I!Ԫ<8Uۿȶ\H~.5TV1i.l+ >A[Csj."kqLJPRKHRji~9pB2])sl a] \_C|jI}ƚRr ҺCs٥-!JRb5-@K_!RUp˜k;saZߕإ는Hn6^|l9|D;%X)?P,7ba.J@_ָQx,&5S9^ĮÝBUףƵ":{xE$jㇻyĝTJ -V6 ARv:XN[jTg'poog3#2nJiV*AK"ݗ, `]YAyR򗼣|E<Vj_$ c>>V½{F "dWvq# w W{;v0EQ/a9ZpoT~>>ɢrODL2/#8Cs~Ng\LG@pCrt8|Wr"}*W:TWxxGɭJFczB¡B`;T\a~nK,wF#PYLkH%ک 0o'eQL.Z]z!0X葌'"d"&?=ߣSi@/ r4-_WsMw "h{<_9,+g䃕>awF@ "0\0q]]?(/Yק ޫQq- V$H* nC-.rWx X)H4GyZ-AGEg˪XWUs`./c- Wy9?7QٿL~km;vDN i|~V39"`0Ob }d]C>CEN2/sLhAD Y`mHyweU0Rm~bz:9?#ڵo507("z Ǣ/fe߮hC|a: r!#Kg>*̴@ݵf/_?%uQF&㱘p1@8/ʇnZ+;|wsXPm; _ƸY%#m/"'T*?vk?#&x^D" k^f`]Vr".=#ľ8HoR3?[;1.MRKTW"#_*%NF/{qɓAJhe7[ӪEUЉwoG )#K4_t5~A*SsP:x94}r"OaJ{uuT g5!eDG x9R2H}2\r IDATA;n/(-JA%XZ)mPW7 +(*Q% K8&N2vvٖI!딬?.(F6cZVVR13.3mwhꐦjv]cQ;;ec_JyJ&\'w0A"h6CbWP\I5a?721˫ݓx,akQswb߯㼺MZV%&bLP$St3kԿ:qAA{ h;?LqeA!'-&5V#>=V`h˪6%YM˪H:[p}z`<Ja;FRJzgyVa vo&n#Ys艖DKC A C 0Ct*,v.6t_!Շ4ۍRxIwHԿ(嶃w!Fma Z4- VȎ![De.'_j#cJj.YgY%Y$C )LV+ȀQo2̴;u;9_}NʬpPɕ_8L׆ұ׵}swP^J$GCɗ ]]RZ`j]VTQ;@r-&a.%A&f10PA=#E( pf?0~!ZmTW$s1cef%iS/,)))m A ^B7dWhDKOs:/(4?0~y%n_(*wpXqNjad$eoz,B$Obh!&5f.װ_zzN Ihw7UPUJ#]ґ.# e JZKfj OYzËxaD=3 9T7G!YM qH|%t%񢛍v^H拶e-&-&-XszNc)6<,B1襝/^z_:sf3zz!¹2q s~qqNk[}v.Q'Gz-^R?%(ڝ"ʱDhǎb7[HX]Dh陃NF%A5~qMLJ(!W{$z>{>JZM\\DOĸ>I̦n4Q^thwY,Kk֬I&^$$rXVc.Nx@Ī= j *2) 2-6yBUDاFʓ Eº"1R+%5`6=erC76 w]{V\Gk`7:1Bd4 hi['bAx:Czz1v F.$z,6-i?9EccS۹<7:K> ᮢ<% yrNj BCJ͔pOfsN![{Oh1d$a_3;DM yRAyB sD ap{ TdW&isFvy v8}(('trp^E[7v0UDGgAQ?0\{P_Wphs7>eijcmἮEϵ*\V9Lr4)>T! \X>EOZj{_B}D#Ih7;|V.3D;d8rrATGدkaClML~R$j_+,l&INrs"]Avd>ˁstL w ;i[-||3! oQu w $>B+֪v. v8uBZΐT,#%oQҲ 4#} wPTlA[q/lID s91@"^'Hq+"I0޹iPx-qW_Va,r|b2[ x++-v "1W/ާe?Ogȓ2r_&5Y8 Pmn!MEjM LeM#""ˏ;qOs9j+yL?5) % fdm%mܺ.ߡ3+?=wk+HߺOmNz]7h$`?_J;<}v`tHyuèO$c ޡ.{ زpY@3Y8^r%"yk3 >b~9Mp늊 橫)3%r )O ;mbM{CT~RCM >2-:~vyUDl$fcIMk=أ\~ꦓ{iTԓUgaJZ"!f>:2]!5k֤H %7F9AIʹl9WHFTI+ imSK`rWTf ĹR4iYE6@V#%8xR v.LMJ@\d*bJ.wDA)`KꣴT0)TɉwwHJ.z)mPŘ$`%OR07锒*.ԕ նi#z b:VA#PB?ߩJ-PaY;V|VZ fBS UYՔlS)%(99wB4P![)Pd4,N%1Eps0|qDViH`ĺ5 %`F)Git8ᲠPh^g`:k_bxCOP-ERi!AZIWuVxI<`LDc!P:g5I)&%Z!)p0ڿ y1M$ ,=o5mѝ`P`4>~S)D_"-;d6h! hiDpG{yu */Oo qVA٠_ w2u]Ch Y]!!o}E b yD8ix_-@,z{H- pّ!HH>Dp.!z] !C|K]DMQЗx'8;EJ%wK{~7N:_C6\_ڿLO<"u 'eS2/~C=Wt'8m#(|s[y!TZ. Kj5;\_]E{ڷʹJB{]O:! Q* M=.#Ԕ *?VSh!x^L&_+,sGHI=Vl5}BF)5aZIikb"u<=c^iE%dVSjj$[I &DwR+~0Ng)l% X}Tvq )wkY7$Ҭβ @ͦfu<_ߕm2:ާ%𛛟׷7EC=YelS=u[?ze! w%&v0}LD??oaeC"k+ \hHtoٵ =\R+l\XQm[juƪ}Qz`BԼG^Fk2 El^eZ^>XKDK"MKR##ֵ3z*ڃzcZ鳊WN?1|D;G9Xr[*JMkpπ`yo#(=#771wzI]i%ߔB7d]LJQӄ.χ(zOW q0}FE@9{<ﮨ)lwC65I~Qڞ#>(;Z )zj"׷cPf:{TI~Sȓ L[JJR/:v>b]&Ǹ¼fv5IY:y w i;~Pfe?iR=5/FiXLf>I+L0WA{6>\{נZDBkMKzXOuP͐ 97ut IrҽL#]c:c \DSJU&Ή6 z3q+3fTolo!:~ 0i$_*Ǥ>L}aڣo*Ҡg}eꢕT$?).zOWP'b6]KMTYC͕hLRfViLnIJi{srwܥ!imU6{%sf6J32T~Qm8 IDAT|fS.kڢ-f9fsswC\-M G9sKCQu朷+&чr%'1Mf e.-߮ =nDlm$6`]Qo 'ҐʏjT%wϐT^-S3\bdhհ~[A}|WxG;T~ӗ@m"ZPQ%ʷΛ!NVJL~qYPy1>{ڷ䃁hS=c h1` 4|tǤӟD2?SY#9RhUlٕHN5n$Ux*Cbf*Ihk }\lVJg琎7.:- T)!'n0iQSWfzzGݾo+l]LQ:4Ou#9@!ьsw'>Ls;)ZO?珊IcCY^ sMԺJj#i%a#Dۅ=r 1r8 hоڣu,mԱrNf-)u;}$&00C y@r$  &PA" ( (( AA% 'عQ{wg}{sԮZVW%#8Ea`";z/pLUn+P Ι 0T3#ʛ $m:k/PEkȓE޽cauȾJ֋}w9y[Og~08P_T#RijP4ΨV_m!@#LgXR֡~z/( TPn_ H9wd;^6o+(\\u0 B_ez(y]Ծqu-)}<1>dApa Sk kyLLf6u8Z@ Fy?qUCLczyB7_3mw*(^XJ؝ ~"@KFMD:Es ˠ_&tx(oswCO tfeFT:9t0ec&H |z!5Q1`-<2Q 1zP9/]Yҙ}Q<28!2!2!2!Y  ϘL2!2!2!2!1"lk>"jn}־:sg6՟Ú҈|E,={s>cZ,?TRV6< IEdi t oCbCDf}`&Em[rg% 69]!.Xx_P1 '1"ͶNz0zyx[IRd#3< 2hO= 3lY`+IQ>,pp! =,DH%Л71=I=D)ޚY:[+"͜r\" i=ɔz|"fCFϊH{Om):2Y\Ig 2Ç~*Wms&ExZb -acg1sBh,j@mg}ۆڍ%ڢLoې4*k-SzؐҳlݥIϘ_l&H*5^̆r*H-J9p 74ڢCM;{{kBk` ޚ[y sG'QwE;97oCNLbDXB}QKqmmҞ0wtgO_[Bk?3Y9MP\5|w#FC!u܍ iO= 3/. aڐѓULJ (VC1k:&t5{>'UR9_2 ., ۼSX " w+66aMdL8SZ7B}ĩjHcSȟ(jifK<gj8:`ԱxeeqܭP^`ւڳh DL(orLfDwwk}[V'Оle"M Dx7!ե Sk?n@DMǪh@0@^&Gȴ@V ?U8[GDc(߼O7lN @l6rfCy-\@AbL&X!y㌮ ֆ DzB "/'ՠNm6U{pv!"'&9>8/`[;p8` ՘o>) k 6cjGQԄC/Sqnz]]8{PQ;ڱ~t`o"X@0曏j&7+ ͓k?TngC B5OBlh@0@XJu(6,dj'BZ7tz-)FOc39&"jRL4"yPcwje|&CZf(/j˾kV'x$$cW 1+`^&-HXT F2aHiµy8' dB{7+26?lco#)F.6*K@ oEcyОӡ?cZi9E+ 0BHqD9W~O&#4~ڣcB54Y\i2Qa,ox`1 /n! (o+wP^`062 C mr H= Iqi~>VEb=szē>`f!<tc SSI+pv!5U`ܛnY g_C=L ޞܹ7q{/% DG/l@8+$x[8|}6bMq7-q w;D2 A~j܏ X6yr@7܅N4w2LJ ͻ[:#f.G (PvTmQa9z"tm9H+$cǧS iE!:ygF[".j?/ӳ-h>wddl\%Yh/3֤n7oB{€Gc= czYHKe pv!r+0"EOYhG!Oq=ee 87lۣ{Q}9gU@+4I2NBѳ-8~ {7b-pf\b}o)%_fg[,df7ogC2sBqvbJ9CD24\.LX5@ 4!v Q@@y8;c̸1-t OdweS7+'mp|Ȁg}r . 1mࢼ;%N+!-( }3LP^'r'E1CLb_ X*d OBySA4sb[৳*.~?_nGg'6 88bc !wu+*-sվʝ7|K\%N"qyqr{ rΧz [υy|U L㰪d6Y(^To] cx(\^8*nx^ hM^O%E(qXJmi.ADFWQZt2y1!&!I)ȿpY$[XeDsqw=)zHtj! |kVΟ룹w+G$X= :{2tKWhdfwcմ]>yϩ @Ļ0!/*C1qn}c tF2j@Lp~ƩUN8*\1/j~Cv)8eܙ7 fikJ?Ϗj ',оw6R Odыs\_CxWDWPĪ*]Կ;U L덛 &smg[uwvlc2K8_oDzjL#_Q+)ƮF dJzMuE@*'Av.qZ?doCHC\4 Ϝ4-Z }/1bUcsBDBm_!jߗy q @), ̄a1`:+PhS` qXȭ+Pį e( ( BSG> D ݷA^!e)=XŚK hOW@tbf\Lco)*_ `y &!Ή(3KU0 8BDSMb*txŘbEP ,t8,a7C_\ 3VBy /j򗖠E-'saC8c ݻ38oP0[OEX+'qDDUd~E|4$uM~Z=v*_`$H! c#]z M.B= 8xF{BGdOuoA/E}ADá4UY-Z^%Zdocz`Wc3! -"ٶ8;ڰJb_N]TOI' @0(F7,XӴS?-&PaRO~Uagn-x]'q~JSJ寊cw74d]@w !6Eϩ$6qSYٛǢJ`b1sQڞ1Dz@҆hEˆZG5w.S_C!cQ< ӍTAk3W(bzB&dB&dB&dB&3&<2!2!2!2!1'dB&dB&dB&dB>c"@kDSP@Q["骡iULLYB gdNB'@)G^&-y&{TϬr?Odс8*"Y7:ofo$x =9wЖ` Y!K*X>VFLJr\ SL`h|?"rwho sRvFLg$bʫ,[NWjos=+X[66ysA։\Oz46N&KW|(kGqLm,IIL]Mx$4BO2ƩU,~:V*p~94O~@\*' y@*ZO_ۃuX2C'yXdmkj@ɼMQmM= +^n+SI,CԤ+/Grn֢VzpA9E!wٞ1f0%aud>"(\`A܏/+\kIL/.٫ Qt\K`%A#t_\zRԾ7A; ~ƶ ų_[? iې">:/.[rJжkz`}P4GE~UE6É]-q)=Ռ< 27Z8 K IDATgRϥ7 ##{C 6`-#g\܎jrqb ~ғԅV^vS-Eq)~&Sg͹U (~\B"ʃsסApDX5ngC{$o< yN8@tz07>q#6ND:V`0䆮<9tRdUfA 8 !AS1 x[?T{:QІ4MmIMhVEgX 0UD#]W%IMˇScn$Cu>gqsh-n sg6QQ p +@g ކ.tgԂ?LJVoJ#^_ iHN)qc4*0h]ByMtYc_UȄ2="Cy!,6qa@Z_j@kWe(?FyhOspYhFZG6C:v?`(}Lx5oX0Dϵk܁\6WZې8&gAy_AyOEkq.7H,G{?jzG hpĪ&%˂orܝ6-@CSIP^S2 4yx4:ohƁp`rڏF o3F0߄Qai"hI5.DGf{ s zHH Ɛi>5QOtDuh 6Կ["-<>gGq=f"| v6?g :ڰmoq=4`g҅q_3棚r!?R6tK&.DSDqWuTcS +p (jB~N a-P_!6eoag',z1((@f5m>lM5ԿfL[90f{O.20o"|Ă$_䛧@V DoX:x׏v0\(ԗU u~ Mx8P_a D~w ' @97L:[c~d/ vh(h&Jc/4m݉! L~Xs>t =o G/m?bc6W(^"s|OCgEXP&^9 Bo~D{܈#y aۄP:!n!ld4»Exz6rQ9vdIbxAy\BR&1YlQZ.APP<f'"@`ܙsAHU)Q,%P^P& `X~Z2L,rQ^2-e͹ ܖKE~GA e%fSqўQ<2.F.- -Ai0)@T{5!R-TI@Z!ǂh`F$,r -bUS`j ,`$ޚO8C?FfKezpEVK?ק1bL.pL'(|OG /-)!{Z`>%c:?9^4vMSh1mp=UPSt ` >A'S4TVPFdi-.L*B^*ß]hIӰZ iXFU%p^C-NCBKD1O@Pr?"} &0XN~w zN>X.+x | }1L#lGEx)}XByI?3ǔ!pvd[+QE4@ nf.}T1GK{JGRNY҇!dWU$0N iA,+Z<487jK˸So%}uRdkzRw)hv0=yOQS..C auBcPR;T doP -y(Uk rW{4(E-WR|sݘ7,p??$z]7A 4o&3 EM cENa9)%ee% f*iiDF1/,A}CM1Ϭ@{̀G@ 1DG%N N$n?B]@"ޑE; @&G6o88 VM`1 @,QP>}?Ӈw|V. }sA{AGRW mCoa&2=:Ӳtk3: khц#&?Lիjj wt5^/YYp4Yϱ龐6tgw :2R}`pD@sY>Z4+ozN]Vq}X oЪaL `C(ӗI`Xͅ6dokn6,d&?+vW,SػY6p"Xv.>֢h5ƚځ` (YIWi? <w;`A``%6͎!d&ux?MO<o}fut3}"y<&㼌ƅNڬ P:t}? z.Z_~wK'Ix{N&9MQ09_O)zjlN,{K-펞B3WEO+}M*t+b|j?Qz _tR?Ėwa\ /48t͋a]V1SO u\[rP_mOyN}4O!q2Z`)C1}ߨtj?:Mt}.Z;ھz>GZ x p%1D/7#9XJc5]CwX{`:3gM!w p"V$+1Į8 YҬ=gcgAyC`Q}<B BP8id눰L HdusiHAӷE0ރ2hiExe6PdP`P_Ԣ1x-H@׃d-><d-G2iRC\LކX:F%Nƨ#\}=H4!r}zX;ڐ>T@  @:uHt2'C_ )u<3} Bb\ɰw0w!Xk0@5#3|s}kz^]2Íqrzg{W-@}RuA{Fi.6.B ֹҜC`Ղ55SdNKZ 1wv!$s#AKlgtHKIPN/`rtw" cܕrd5w{A%qOCAyE) ަ![ŠĨ)9/@{")?o,Q8b=jego GW4>2hkuǃ?g&;)OD[>od&O!VEKy_L:)}ٛ{-QޫCO:,ĆbJ!0@{<<x;PRL%uLO ` Q0IlLHS 9=JU`an@lJp;Kd eԗ8t2-^߅ϊ:d(o a-jA}ISܟ:[뼻=BW C0#ytF.&c`jwT]L *e įi$  ZL3BL%@{uA}^ ơn@h_d#Lgi>ou1hOML5(pLY\NRy |y&G_W ԅ>d~Z)0ug4͓@k{ 3}Ou}N, L9ˑqDk2ˇoCo69Q739_s%,sjbtmKVWbUgx-Q{ +ӠCL{S;J+~xpr)-Q`y ywZ 6EЩAFv@fdnKjC0Am@wy` _H䅒\dM.CL-Éɚo";Tj!YYbKtrSңx m@Kd?VQ ~!H"ZО!- )PyC[b)y^*}FD5m1e#L^ГpYl{g{;1H֧E"8b<>ܚi?ew>[Krg-ή=0}AK$ٶn$N)`m$3 'gT"J 䰷1SG1I37g ~WՅ <0H\6lk6.'g4ʋjD Q-ߞozpMc>sZ b=?='.pR I`I2G! ƭ|]-zy> }>8}%O^d&\" d 3kq+A6ږ U o$F뫍D d^ ig!RJ*GץEA .3Ww*O,ILGTBux+PpKO{ 'kv3LKHK%SoK^W[7LXi =lLD܅6uf'nh%InuC0b2ÇOG_[ÚkM~^l_ǃ0\2㾎kK LBw] z!0'1UQ)G褈 2uxRO{YV#WE 8'"o5{(\Nw8Os@+sT2 .F{dek(OϚOgYy\6WT.L jgvazGƝ=0=gG[* &scp7sjejWKG#bUR9h:>'U!ԫXOr v[}EEq GN-c )ߟ&xn*S]= _Slpqj N (%]MT/6T(K :Оא2MV,S ?)rƮN)((} k0&T9}d|lLn#_\6 7y_]Z)U^ n"Qg|} z)%NMھ<6g4N_Ӈ 2NS{? Ce|85i_wgCMzөsoܟA܆[FG??zRj@r*OHc"'&uFpysW=#uV?ǃic7bj cWuawa ЩXI0v^fzۋ eH?%ϲߗs@b$bMDyIz DxV:c(+ ˄yPiϫlPhd AYdnOc`.n޹k[җ{.I(FnYw% 1 6 dnEIB66W`քlI)L7!S4 #&t/ xI;Fem=+QN=1{c\Cmw5XV9kJp7CuAKow vdaнKQ<_`ܟ Umݕ hNP>܏x뻰1Tc?B<Tg/;:[;5!6Eo kt}_ 1}ц #VePdJx 4@ { .:uBr ڳ:MDqWko3@8 }a科;pa0 ,]<LTHm\d|Rd3 ȭQ?Ty0!1o&tv%C8 XB*([Ct1*ݓ<mA !Q?#S1D[uU mdJ&[bZCG!izvu{`ޚ@ D(ї:uq3o 29<%4On~JoC:|GLyLh9 51i\"gmcP^V{.-yb YdzϪ$s=ti cqyhPCGu4-3`Qk??AR7I;g)w2ۇyX/ q)u*%XN |i,1fC#F@b+W;ߗY:)Є&H~w..QB(3(.'VQa j4|ۼu&csA>UT:}Fw j.=qv: ]D2OqI]xʗy)/(\M:&֑u#S =L<ODGk0zo]iԚ9Z'OnqboA~GNO{5<5!.&-ܒKMRbc:-H8N)vw {YG\]BV#5AӿSlOpIw][:u9 wc(Tܱ>Z4 ]YH[<2 1s=NZBؖ&saܛ,TO?f߫'_)Lx(}8hGB?~P$(S Hsa qr%ӿv !A4VC4Y{d@2ܐ!}zJIKٷ܁N|} DNCp%+ /pv;Ji|MmIԿk @M8j00F9l|P^SSOf, WCJiHLۑ~Z=u@"WA5qBf㌕zd:_k)Wz9~$a8T, F/z.\Cr8W`Xi٠}?UOk`ֆ?)#LCQOamO Ac{k |=Ypv T|\d~4"LDk`f- ]Zxpk+Q(o)1zҗ܅N'"r=> wfZ 3T28 lB~Wi!ޔBa+]X xܥEiҾnA+#;mC{^`N۾ 2B2 Le(2~g0Cl"{S'RO"UZpvmoի `w o}w1x"]=SSt>!!XQ~F A 14 MK<:$!X@O!P^V;bؓq,@(,0[ S jADrH4, _t=0Ay]M28r8A( -8O[5@'2wo[Ju$dC{ڀ.PB^*mBD 1jқr8[j.hRM5"=P.&p7qAf(u0IP^Ң#{0;SIdRN:$w]*'/:dSP^MbdX=dX,xz#@yCs10Y`t JdDws= kqT0n@y]7?ȿw9:>9O?σkA3{z湀 $ۄzs=?_i)omL@`&2;؜M#2Tm*v< LTtvN+Xc+{B'ކӹwSqX:m,CTt&03}H( CWUHm=[za79+3lj#=@!p-Qm郩A9o F1]ˇN`”G{vU9j{利ޒROBBi$!$B(RDPrA}^` E)J*I9-<.rkWq>s1˘F[I/"ź}Qrٜ6XwG5oH$ސcf nƢ-h+pQ8N@U E%b]2Ed?NAE rDNm::/_ ^u/xO@pneF΢7~ݞ Rvvl?[-Kx/cPD~ 8$';G|cB1ؘCGCKF;kSⰌްa.8 wO »b.+5Ϋn<̌!/(NN5%8=+&5%Rѫ /u9Ik o]!.AZ >aИ~іܷQPxGTa5Y=uLJ,(z{W.ݗp#BhW{Cb+GDw)w~W!r܌.C4ὃ=*{NVjspsqZԌc,JS$HCt^>C y(aט:A=ktLU5]de7Oq873:WDU`IV{*`=uӬ> C!w]A2Gj/~;ZqMKzN]dio HQUa.6d-<=@hvF?o PC1`izYWaņ:jtm\;л;$}^~_n:DrZDv!;mb.^𠞥90epCrѮ#Ai@xg-I[Z YZ@D#}+P # -*ETKx|SPtӠฝcr%\d'd:1pŠkvj: ~4z]}c>;Z.I<Ԡ+q:O|b/i%rAsHAq\HZ-;ckq)Iăq_/ca f}<إqdb =7zC}?:}(Cssh}F?kN9*'ߐaWODU%۲# Ex1j5]mn%?9d\=櫲);Wm<9HTzsXSsguRzq*Mb 81*a۽B3L {8OkB s ;&- j9CqB}bt1iBqY=DRU) BJA$^8S!N|:i-7^A@uL!"GY g?X}[oP5tSG>^j#{Dy14G3pńL˻?ֆt\5zL'#j֑ TJUCͫ r{'wۛB?~Q{!sxS[?h)ԣj)ݤlhmP9А;x)]A?ZGek G C.v!S$ʒLٷe!?(gK ]YqhxLORrMzAy]{C=Xc3l KrE?C17fi)q3tȐZuUmѸQz;Ktsv%?GNGfIl7c6h_kz*[i/N1'F=)k8̑}%-b35-;K|K_WѰӖ؜k@fV_v }o,F=rm8C :/ۋ^i\M^UmZ,p# !es2SR Tg }6Ŧ;Mf\i_Wk>8Cpg ld_偢u?V--'e8z֪k:8f8̒>GvBڣݴ8*{ڥ[Mk=7f6 @0oeɏI/bVjfg rЂyyBIۖEQ>=]vR*i g1t2R p3Cuܔ ;ıV0(6e_ǩsA5헴1r+ffwbpt_Ʈ2L}x_LmcseV_lߘTX.薸 ,,f voR ]Q`kUmZMj]fH/yۚE>ݾ~4uuX Sf,%Bfp+}RHk-/P;}>y2eG%`Szt|qߵ*|IzuAOËȐR twSw堚CA; 5̐٣X7?k\ <~ǠA M==`B>'}`K,wco7.bI#3y.4"6A-of.ű9 3>h9׬myLVHnff]=Z-!S:L)=GEm2˲W޴GZ!›ct}X?FP\L!rKJxa Ȓңw:iusVݸwOM!b87 7jIM_K34[z3D~^]a_#W޲_"zSWĈ'%Ġ_!$G`[CS b^n}z_ƟL_Q-A% ?)yr|M&(6d'f5pދ:IKz^\z/>s2?i{vHKpZoԕΥmseM6gp-ݧ[H [3Jg3[Lry5@F}a]ApwL!_G!VJGw:$"NzB#izD!Qs7<} D {nJtQ[L_]~j#S;'V(-,`C~bJ3^%~^?!"!'BǨwI-p}N<ک@e}KL}bB.ok&x(`_ p{~BmQr~~g1U _͢{"H["My- ;1%;Gk=ByԽ$` s=@{+:CiCtW-{߷yp{BMtHѨő>{g%Qeh^@="TۭeئR_m֬z}A{Y4h<!S> ;.jA\$;-9Ru({^Jm2}H@ϫę]kOOM> )KemJ!ӫ5٩)wfg)py.|kM»C#qS%o5|%ml2h^?' 'St_٦&6vk摻%S!.An93z=4]r'15b `DŴ~طC.CAbf5v,>_=V73MtS|Jag-7/x Ly}ԃk'_k"KL+aefK-r8AΫ L]L'y9gkfFXx^»cZ&1s1v%x4D3m转Αm ƪv}n+4ig>%A9/j{yIzZJ9eUycYnjdvoM;%fһYn, Saى).vнMBOB.^T4ҋy,t%ZDD\^ԫ̦@+-{Uъ>6Š QubGv%^@ 7-ߐ w*Ce;t!ѵks r^{rK~a9 `Sh P\a״;ltNKoHh\6t ~ dM$4-'iK.)OO:^ig`pAOh}hz[ f߻] h,{> yTJ)KqzFrs?OFlɷ@MHAzq\9y2RI99W] n:\sٔTzl}U3-Ζn;o3ƧoZ֟csmj/c]IɏϘj1)Ꞁ[I^yV3%‘c/|&sc3x2+V/8g~)\3 sU8"CvJJ!jZ[Pnd'=aԧ` IDAT0ry)>k}m jM,i=\x7?q;*CkT?/W׶>6RSΡt_!?)cPMNiH쑞#x(S5'aD}"AL2@qdNz~Vܱ:כ$W7O}|vR$S#:-387vH[l7;BmnA^W{^=r\2jp 3S*'Ñ! |[W)kۈ)SsSVN}/9'ɀa+{z3ܟ64Yу]C`oG\(_DL[ȝ&'@& u]\ZAt)سv᭑/iUmf~ʱVR!J[Vh"A(Nˠ-LlP#TW8>!L}eT4:k:J$I)nR,Wx.J7|\zQ{1Q<ì _*cvmA.r?efO)D59H_؁$)B=PQT0 Q*scم+$z`G% g:?_Vi ?z<? -*[VNpb.r"QHP?+ `MzZѓ4OPO$7/CQU=ppofEMd;LKa̮eEw&%t]+5w`3>jl:z/ Iw$z4T숂%D[0񙚂?y*ܘl=Kx1wJCJT~T6ȝNu|8oR[걀x kRK."4.*De:Z^]مT\kп$ 6£QX-;|KG $fӗ{O-7#;Zq.}~ࡀp̛'y+i+ڈd?O'8g=NY[-+Q !83G DtڐO+HA($D[nZݤ_a7i@n 2emI#BJ9 E[aڒ*CN]j1+%:NrD2s1f}_Z;z2W̓|Q4+-!VU~V98aZ~Nt]S?h0yKPue=8D%0^i ec  oXx/]l#yEbo+_[oVV>D`ܧo\Bw%EU4E NZu q]mdJ(,$n wu>OOv!P .77+Ib9?$%^_tDwD [b#\;^ 7DbAx!S~nXawDDYGqbFMm[# =1Rz`[yx7?7MpYگR+[<^XÇ"ٜXл~GwUXxODxsLxc˟3kOe((L{u)N|L9UZF>>_׶χV s#O ǝ5W,B_axm~ʠeC zvI`<!1:*}8sghx%9ѨGH>$L=|nK-nE*R cA5lQC nq]U_NfvI8ܯ7 ʖa׀_ЇX&G\`3"Uaq? 4;k1<ւg݋eYbp+mEf44`XԐVZAyg3bej\@3d\>;%r(pTh1{d?5Pr9VROFjzEȕY~RvvoRTr"uܜ K|a! sMLYͻ9mZrèF,E9N>6S[}MUs1vUNY5J@mZӡ[QDacFDϙR*A JQp Z=x/ ^/0 (7A7.IsXCnQ0Om)qb[UL|[t:zw7wGmky~}/2x_1b@i~E873EgzӵhؾI8}ӢI}Ͻ1怄9Uф`y< WƹFONpַ AK'łD>:! b5'%gp-vE9}cp=aDfr^p{|@MεLMU@<0{K,n)CJ0xy!w* H,bjs0us{!A[i1'h1ř8tç y"Ti{lmS Z P7'易t4Di\7`=`/ S?SK-sܠ-s?@sl9^;ox )h3 r383qSIpa$B}/)bOpCqߐ"0ijn>}ZaOmB-T_AG5_!lqWv 83'5QbN|pADoԒ@&9^c+-ybN(0tu"RR%͚bc9:oSK_cg mP&>Dc4~,|fd =VZpw#Ĩb_`O0Ǫ-6D n`4fC=UXgn#Z/ ~'D䚃 w-s$1bi졖R#bPOW>rQ^ fAޖ]c`P?TWLj՘# ]x-sƬ?Gnx0'FqBNqXN.';'vb45f)|fQh6SD SS8K߰ybstRR~dz(VU9Ȏp8f~&}D>PG! bC>8@푴>Y_́}d7LQף k%Sle|YnaIܪa4ņȂ\Vs3CkZC4z/Csfѻ6__*ѕ  cwyu Ԕڐc?Nχ;"W4 IbKVV2de3S{̽}i"Gv\J.]7He rl5l024o8H5+ 7>CQ`X {8cr̛_j]3}\NqD^3>9Mx{@VƼ[Q7#su9-\Mk49v ޳Xߚ]<1Ps:9 2ٱßY%oZj_y%9;3EѸIoG0(1+dgYJ)s GܔČ9n18:'?6#?>,6,}C^)NjQ9]/jq oksH> ߜ {$_m֒@S{d3 :HӡC `7ģ=)V@ZgNɮD_Jd 7mA&߯dW)sso\7P"ܢnA{?4+J%f&{MJsHt+,"qOX`N/h2򙳗d$CWgys&x$ }O֥soOL%Li9įj!v 6yT7J4?S vI4%)3f0E:CwMhOBO5p,p[Bs)b$0䗦2ɱK:C.͛[aQw*ԃi6sV BSP8#cpD+5r6H/ )f_#v A\%I  $= dbKmmf~ # @y[C jDC pYo+!8 Nʚ>q~89`O4%Ē9Ev#49Y)tC91'-ɟŮ8/CV5Dg_1ߏSqAS =, tn4тH d3@pYLao5*c6޽!II>TyCa!x $?νcbSN]/]bcNfjk`[%獏y}TNq`A~g]Ȝ`,5D7z@3f'%h}zsz=挢OYCYe\7$ dh@bnΫ Jsn{ᘝ}EC/uOG,1כ9ܐ2pΑ"w(ss{7 BnWdƬ81)Ŧ?AUs:TYfѯ/#[}7KnZQY`,٩n~<޾qr6s衶*Z?K~,%q:0 N g콰CrC$E ;q[FD`v \콶M黎n149ᶀ/}Ϗ(+jWH!#?G0ޘ ڒTn]sWy~M~<3$j.ɟ3cwA=*CHuČ^. ȏ辨pb_\BRdv'^A@Ok LءDk+p+w].1d/~?/9%rO73~ P䇏d͖"2z[]\!HqM|}ƾ\n-=ģHWY`J7jO!T/oYr=b0>EPwI"Z(Gn_qٝ OVO+I%S'-]SuM|ʐ_Wo{BGr"9dS}5D&$SJYV<;cx.rq"zC }p|йMPHfUpZ05N$9E(dľH>}%HMuk>Sro1昧W'=ؐKpyHpkT)I"o͑WG}y}$hJ̈́ȫԷCV;t\1yHp$/PB[{"CmqOCv!b/ τ$a%9 IDAT= N3?d6#T%C|C`w= ]ؓ -{r'`` ,9!< @Z3P`%0 Kģ{d_c L"o{*x=P_`rrv_ 7Dv$fuQPվYCW._o#R1i?#2k}覈G|ҟ 9",)R $;ߘ!R#J8Iw/&+"')Bɱ/] ;9 ye# P)hnڄaDVIV޸h›y><%k pw3l8WC#&NbHi"\}=symM!PZE[|4B>< yo+cLtŎHg !7ÅX`G ?$WzPiwy ])u_@*Ed ?DI^W+q-${[s5,vUXq'礿ցG|S!U!7*il)~+ŵDO|8XRdOo+'c7 3QZK ̅E }%KyY~#b%JK1UyBtOhQ_k[͞nd||@yl wu [ @~]kMdž&%wpK!9"k`tچu]#B;Ң1;Bx=4mKsqDA\'U8濐vZ#wxs]~(^pUuarnqRo2@ 㴜2$@CA9b G׹A~uxGp@B/O߃/l  r'L|= >7Xcc I1)-K~lJjbc"c{+O@!:SPWƷZDwĨ vav#HdᨭlU y]sJA4iO9`"x2 꾐L}Yۻb]lQI֧uȏLYy}/%<s@`֬uSEP)G9,~ݾ_vaB 2ʌ*z`KXՒΞۣp>XnB|yVZ{ߵ v D[嶀k d9Kv@;fG{˸D_Hv$jb| ]f齴X g  i, xKlK`_UVk4]ig\ d'fߴJ_Өg{0+4pEn8#0SyvYž]BbfBD3s y~̋j 4/&'WdWp9/s]~< Y 'v/f  JPYem)2GmǦr_|`dž'm/j(9,AMg{s7;p-[sGbYt^e-%݃SYZȁz,HoEw}z\!F"QynQfr]3y1L/RQ)ZN8v޹M=18KENU1gSx5ڷ1ځ2X]8h~)xp&K8Na*ky:|sʡ|TgJ7H͑g5VVuސJ1Xn@a|V}N\!~χ>yf|=u eE.jcs_Oe[3=(e=$g ϋZ~^ؕ+eACAŧb0Ϛ(\8PHm0OOp_Or/wX7|u+_%jJ\}L ?e>[yc.FiD.s֯n]Hjh@mWs6q՘.Go`sup{fٳvYڿ["d&Lg}3c}= ӤG~GZ6 ,}&}DsYJ  Yg#tLRZ,6$?D7M Jf1v lJGGzmsu{x&"׽ Bzw6<v?ٌAm]k}s{.1/ {O6->bgqLrɽE;uA(ON7Ucj[,Q1g)N@2af$3ZÖEwjv넢tT&Ca an;j 958޷@Mtŀw n)z`Yk qCqxO#@hWYd`bŴobuy㛮ƪpGar7 4nnWW%9AQ+~Yk_]ǂ"w#X+PͿyPRZ͘&+JM0[Exlx-ew\߷| =찞ޣߥ)(zz%>Ֆ셭TA<9zMG__-=zkr}LѴax{;;ت@xw}`z -.˞B=3̘A~99THwj 7NNobۜ|aي!9I9#A)4?2ЇHNvs(#8}R^o ;lٜA-W6WP9\1'b_f6p)Ra+,OKCM]Q,X9p?]Ć 74 !@[\)f{\;6r, [*h# BBvV)+yNb^bnAӘqV7[jXL-7^_*K/纅\=am7;Ɏ!swcC}s kԽ{f/G! -jtSE1Ȼ8Y/k&|Liinlٰ8ґz#lIq#AS\\fwM9*9l<IYd1{} k;Т٧aA иksNʰMBG }x c~ۉ0;~i+mb 3sW P`k~ vaϹQdk -d "PTy`_ [X =mArdxNm-r=дf7j̡-bo,\_ 0h&!S =-#9!]JpM l@z]35DuUѻ{aCqrFpmaMGQ?sǧ@<)ʠxpYoIi헹* qV  分SSalg-4R$rB mۅڑW,^;IM +k7ǨzVKZˆn{e'61Ced/LcyT~:TzukzpBzD.HNk8Mx4&6U4^R;tYAe*̒-H)L+*n9篅=LlwАZOOnh}2]Zho4άQlSYqj[d%C쟢-Js?9)qMrLSo) Oe{瘭 Yu Eqn $$/x8jBivZdSR앓8Zh!$)̑YIi  2]qI{{YxȮ*M̐!;Ìu*p;_5_Z3)agtqV&4!,h[$] %=IvhLp<|zvbjj†^'dC%i^TUW젔ԍV/w,\.'f&=,,Ԅ79yB]`OvNR.eV%ޏ ^-9M+C CzX%+ \(w Ņޕ#&B9]Ŭ'*} ?.CLں).DR(liKµAAOpyOxV1CO8V.ԣ:۴mJnޝ>1(+$% »0Hc#KqNwY#`-ȋ9@Fq^.\/)[P<.]6,"I;P[AgFa c*BiiVzD7VUL7'aK=r\Ctu z sE Ca-^.?*¿)DoS6%U⋫*|Iy̫ `qHt,[4nRpʅCXIiu'}*b #!D" \8v Z_W!w0KO^XUlS0b+is1d6{+}[|LOR ޛg(}k3n5x{_#ɞ(x8,'}MevKi9Akܠy|*q9@7nogf3^PS]z="H0 7pG1`=Mԥ>\'{@]&hx-jpzC=ٙ , Q9JdPw>9 }ԝA1x L%uWiaqDpDȵ ؤ=HZ KfZk,*U@o\gS| ("ffT 8*[j};ǒ~ww}Цz9MkPlp_"hVx@XwLX 6ƟOC fP\!O;0= cO:R[+G#On( .F=QRજry!$˸]xZ:}k%R$y*H[렴&S@$ձuL%5iU'BxS+tdlİ`^ظ?6C&+ eo& pzֵ!jUަ`` jGm3]z꽢h6^*ѵqiF[-̌xC5oG7P9o$/M|qڿ/M[V{ ޵W*L%U\ h~x13te_h)@ܥNA:迭;P$+Ed6ȯŠLmv48MC;w~wlPT oTJy1MxY츞RY7`uu%_\UX8č`ъw{2V k_rz 5 Tf\ v?Cӑʝ9sO+¿#F4Vﴴzz`000I, n#V"PUEO 5Q`vh@SH)F?x p wL_R%45*[W}G71c^)kt5?7E jh `( n/F?5T3fk^?MtmL#% d@On˜ h̄ ߹` }m1!:?zS ۨ7}#9&߷wV};]rpgש^߳%H5I|CncϻB &zF?4i[gڠDO_.bf$8wϙﰐSAyZY2b[h}g5cZ(fd U~kCl%蹚O#leN:6?6ٶ+DQPrٓ∜/ZvƗC] O(*!_V$RϩT9^OTkT5XԾ!ƴG:$SO8wȾ IDAT}%~bmh-x !>0^PҽjL Ym+@S~z~fu$"¯nј6/='1F~ @l8SXP!=?oέŬot6zNI7rBZBa)  !nG:ΐ+]P>~2 ES|BxCy 6?VsI8hS86C2`1Vc&*ѕ2 p(Fe*wjwiEtZ݉ZX_\N(^''ZL bgkiMt;1u /9;#%?ki yM޶b𙵥υwD }^;EzXcas[@D9SƑ Q[4`x!v`9~^Mʕ[Ҙ~zkoN̎L~mn? ƿ+(ѿLwcA0ɱ HHzj}wue=n)B?n3BJ{ F>7Fpު`'ח?ҀOt鿝 ݷg{UW{Zx8^8Dtyg2 -[Fq5 [\\IzD?OO !HOH^lZxo=Y `[:?dc%4^Wy|qn.-4.(VyO?Iz@y!r&߿^nB~74-pzX5c=Wgdbp><{cSy~[zytlf{}|&egk=<] ڦd6v} [y6l=6M:3z3h|{ots 6Gɮ3m)<'$G6vt3ɠtS9sEGrݺzll6ޜ6+}smx^~wy|^c236f}qf(v]l 6kd߲廉M9xz}6ylʩcO`9j򳡁y ko*߫5ʅ;21$=VAeOlVП4ZxY?S;_Xi$U7#+aq6`rxu ]lSlX'JAsFC}@S*εȻGwI6wC% m }y[;܄hm^_t| mZX2gxNa5F"3<Ѧ97 &oh+Y2i uyb+(jad ɟzm" 6]cKe^_-I/siշ'%axA[S p 8yy3T8! '=˸bd&p63n+䝪|Aw' `keAJB ӱ)0/L|/s<먜nu[?)wk*1䧤`-ƠÁ)h_m=8xv8WD0MI>FxW|~@FO_V)Hj%'lev%=IsѱT~<~/nPOz}44aNWR?PgsOWVKg$68\-U1/3~[ C^+UgiQw6+=[ȎF574^Tƿ7#eoVGP30R4[o*ۻqNb\}!^^1c uԱ] IA:dG$ 8&F <++s. i}3by~߯Є9)~ gQo-ɋ{R߻f$'1C3D-NA;hLhsklY5bjo$L;bzFB2CKKJj^4*6[kx r)NN1ւ[)s=T厠A<- v;)ԍ>U/{o'P>jhJꯝ&-{8pU޾bs3S!P B>!+/(^a0;vtFY٣xsw5 @C>%%}V8J8;.2{[zX.)^aIvIK$ DR@.qzW+雛ݴݘ!5S9rz-f9-j&iYG}Ĥ88xIoZ:Rs]?Q;7JKŎs[{VSN:=K -M=#W+:2dGEס0{kAq>@+,̃)6bN);XԭNRLlB`5_r0* A9i|@gfT u99nc+eηsr=9OKJY"E,ȧt秦 R~(;i3RbE)ÒSm}_M[\0UCrBsw`JoRl-+u͓d分@-w֒xRbvr2Pc+Du}z"7JGȢb68F4%ʼnQ9ܷ ?#Ż=@P }p>V juj4d$EU"M~J#?1{+A+^D=ud)1k¸= 娥UUz/;g *M=`uʍ#v9})/7[4rX')^%.北)A mr%*B ңy|RV\Mŀ`9IeYw{)ɓIųݷ \aYX B!]vrrxX1#`+rފ#]b4Z>r$}m-#-4̲.ng͐[:MM2vصeEYM_{z=fe[]s>4lt:BR/,n>C5PGnyi#O(|#ҕm+f{=f꠾Sz PwU ?V15NE"Pzfj4;4`)5,aTL 5}GozOb=9ra} o/*U)t\utzLo@xmDG蚘z+ 8V:BgU_T֢;nm_?WFPkŶ嶪?"Ԋg@<[jސWFeDdzZ,BrM籁˫_X}hlޡ{rA}u%S,wK@-V`܊ [SdS_QR~V~@krq{Y,^+hG. #=*'~F -w<(gCȦ$7y>K.s̸f1=|rѮtV0>2_ r`D[,5  C}%}w.9>GGdD,6R| ɑvFIPXܦK6Xߐ*_ ]_ 0[hhHGTv}NCQLi7ZSz\!%5qßG${li+ZknzFZ|!tˢKwS= !!}*[rfoBޤ[)T]=3V5` 4^^x`3G-Rk6 bĿ8aXAKf[lhH5 =6O+7Zgi<- Q ]2mT[6AP3va z)*ʮ2y]&(gLsbytldzA1V֢Si:Yjo"B4xA[j]o5( 6~a#$qZzxXm`]jawJ8Oc=sQ=^ld~rJ0QL!uɵgM=_V-WiK^m2 WU̜f-foGHlZ?;ԧyr?-Zi6:a.JZO۲URu>!I{(Y{3iXʏmo!W'^X9B '=c5#{G/t5j8rd]"7(Ĕ& #V ;d횤+%Ź 7#ZmUۆ3WSl#SfVU9IxML|I]=嗎md[]*WsW{u-i|mC1%X\-)NOJĘ9;f]5D"kw)NPP_v[:e,v̠x;*OC&ޯ|>z28m/V[JpYMG17ǎZ2rbWcn)`j4n#,H [[z~Q|Z.1c|mT M bDMx䚞\" ,sjD6~8Ia7XA`;& o1sסXٚ304qGmqc:切*I&ޕQ;ET9$5PxYBf4>:M\iACr,@vX*hu agUCU(#JErZOty=\-@e0[ΠvDq P *, O/`dq]B jeFӌ~|bK#)tq^] 9"GߺNn/j21%4fUb0C[5ZED6%"sN|d=C_EMI| ju-=aغuY *sm՝=]񿚇Ђb8ڀzʣD/t'6DIL_Xuurm-)@Uttwbk3g촃KUH|Qi3OcF:B`;ZnM,jsk}&뿾Z3dQ=eP\(DC"[kڟdo#Rޢp9@tZ_bR5ҩ5 -fƆ]~o}Р6*cLo#ctF&EF iKtIvN=26$/yv*!"s6=rЗˠ{Bdп<=>,EeRr6?-qh[p{5}~,{ʣcU{8H$?q}AϧPR0yG KݺI.>Y>< &wUi.ZVqxNqRx$,?7&`lqJFqXIೕHݎ2|<C_+k{F?2^rV(~Į4QWcn낡)qLe;fdmlknj3N ٍч!w{op4FUH|#~}H^""։Xl^D:n<v3l}l򗦴c-);z(0[5&D_w̦Ylt=J#^n5hW I=x]Yq%5(dۙ -yPZ]O!ߍ]-niHڠDǻ6Ci~ZahTv'j-`$E+gqC~vyj-ϻS&P)#_kA# Q)JV{$f) Dkk%y%4_ZkK|Q+A4QވgҺ헒_Noq%(jn P=`x IDAT;ܣĄ7BP0~\zwEDW1dWOSZ?8V*XSLFQ*]Z'@&]3QcxЕH**K;|ߔ WNzT!?tl} %~Sp4Ӛd&]G1N*j͸fo>{HrBI-/JTc %LXrK9e,Ihîy"?Cr.=z?8|N{Ei=Kr6\OCxy r~Lk@b& jG$NyD\]۟B"8$G=ϭh(_nY ]夙5̰mc0JIoMa9xHh|Cf)@=g#0$T'>Y* o)canhܾ2 yV9L@>ަPyX >P6@6ܨgG$mR%Ļ)s-#4C+陭5t눴e$iVZO>ܹsi ݿX~s0~%ul O,)ӘQTۿ.-5g}/~4>U]않Ւ.& -#0F!w ({vUNmE}mz,l1toZww1Kc:ɱM:ȧsط*/k˦I9{d4Ng?RiحYۋT훳zǂ /Y/fAY>T % v9'qV2p ]L|b=6p4Hko"߻g{Rbcu']OKdC2~;{7`F:Z`5v*1rB|E k+ yӌ\0NSĿ(AHKS~`Gm 7cW'csĤD>;I9FnAUŦ)TP^RjdD7D7;4OSZu/Ο@Ip9R$'6h`3cD7DŽ7Gd'Sr|־5 9!0 ?' QOȏO@Yԃ>v*x쌄ĬԿdG|/"U_tU0i&H;bNOЇxW9;m x7xdѤ8*wd-V=W+kG&ժ}|㿬#;!;yiKBdkL~|=cC:!޲ ^T."BxkZ-iY#9Ipkog{4^Vc[#4OCYn%U{B%$G5Invyz;b<>8lm+q;^V$f4A9€ ,\c_} s/kK $,HI'uNէ3ɼwYϙs{ڵVZoKrd.m ̱ځ?ݍi@zU9')WI»'|LpkYs:;B]٩)_c=cv'ۏ;sxT)Ⱌ;e~NK)̩~AxgD-8*8.V }!ֳT!t7­Ǩ2Dg 8VwDВP@a/+~֝ű9rZ2mŒi_؄%Rdr4CW {K ;XAX JcTW%=2 CvzJvכ͊)Q0%5D<%/Z2u[e,D_%6"Bz~ ;Ukftw*Tߛ9+u)AQhp[vԐRl.o [e _,W1ɏ˘{,#%6&6FmpI\ź}^l ྀĔFy ʎuf̭qܿ%(C ۨ9 ow<8b >dslv ىY99իk$;]E~Y}'A v̐>C~|K\}4>O7ܞkfNzyff13'!,Ʃw&!~#92呜!6v!|d'ԩ~en΋[$v]))W21گs0ڦaQ*߬0 Z$ufFJD*HJOH%;p苾3xAf D.cit=G'lM)@A !,YhGUFlNk`ӄ-[Q5 (mbj?D[jN 2VbB+֐_x7=0]Y22Ew=LysՐ*[€[CS`n6DWOw!ČdFN{<&_A '=& "s)#rZe+,B?;})r8(.'NI)Y)Z0`43ok QPSrEc\k7'aQG^ y%w.# c bN+:6G#I ?;0~5Mz&" t1<TU/kP%&?MTI@9fV,o!H^&\]ӥk%%vs.cl%樼Okņu=N)~+%58Ny4X8f D${A]R}C^Q{Hs[ (D_w+Rh)_9G?\ofXLb|kFW $?:z=#Ĝ⊌ʕMP aP>!G|/¬X5;<.SeXMkǺDfj7?U0ajk";%E49<3Koыؚ3+D/d$krj=`!Ԣ&s haf! n %z*)yQ>"'= \Ws_IpnmyxNNv|J]OIq$# zr®o8,$uphWiaԭz譳Pli,[+0<ͱw^ Djћ4L3 RW):GU80Ly6D@^⬯ K~Jڐ3^qwZg05Le;j('s}I5ߎuc$R ~AngZm&^(|KN]!x(t+;8|SF-{#+m ?7~a?CIӒW@HMUOIOK}c*aqtN=$=¬C[z ٓ5+HhSjp[D~R>6 ī}W+:\7.٪%?&Q9 TQg+}FbD*69MxKD j]pYkҹxZ;ڸW`Y/guo@'uZ>DqB(.טv!ZY1t#v’6!3u:򦾫lGe #3'}:hIdGr]WQvĸzxoOxǀȜbK:@#yrP;/hQxB)l??M}®r+N#p٦aP[̜1vvW"N<(b& m>rFV#M7Q FQȧ%fյbF,+o:SZä) 3cur0wƙww]7%;=ESg/+rVPL9knY63EiS\[I)[^H.=ū JAMq[r E~d\b[A9硗kFYEm*P *ե v݈z8&!+t/CO(gi\_d4``π¸2mwr14cU!x{\c!7tآWjs*W0@ cnNR=A\ P(S3*_WhMUO%yutZ\yq%zíĂnmm.\SithO]8^)+%эE֭Iw]iaEׂ]@OP H"&rm& -zBm1rFb+Z~]dzq:~珠WjĴ**ՠO '5zrOO fR?#I,^C3ћ5pʃɮenN:nܐouUЫ\ Ϲx{%߹*:c3nxpXt)b*]ePh\*v@DsZ{A&nU>?̈xNdñ f"<E<@U^:BQ[]`BX_P+0Kr X/ר zX wK\d"[ɥc t$' N+P ԦCSNNV_:G LSv |Jpո-61SҵGjQksҋ;T?]G/ըj] =-P+ ⨜?+B!B>媌qCvaJpOL=JYb+ؓ[+5`V:D3aioVR)Xb3n7K iNrtmGJJˏOF8[p!3귥   X_Ы4e}GzjGE=^O]P4bi/k!NW(~슄bISS =cXwDeǗ Ucs̱eKhAIR0urA9*g\6 ]W>&C5*_*"5eaxSTR$2P*@~Z>'=#A"𬏵GeKij )r>)~ ɿa)^1t=_S-N)6TP/рsrWLIzTXT\u7sԾнbPL3UɎPX`6.%guʵY[ :E% )~P!ݏRyi wݞḳqؚR=aZ1BCqnM .s_GT q!l!\=tRHKDd )?P^Ʌ쀋%zZO ?&}! K%eh1YύIj ޏ;2)!rD .ec(.-24-g\-^)j깢T?WGf7Vfй=[Aѡ@VYpnGk-fFApgzzpD} O~T'<4w;y^%xהy8<8lAoUf2??]ԃ"<跗K~۰gf[uZ+venAȥuڿ˅d 'G,Ű@>mЂ'Dȇ]E5۾LIWPl(PzGOxs_BPl Jr)^wMߣ"!޲ UZ@kB z"=E|A7u_nuK_@bUA~mm}Bqf$wY#ytkDx{O :*BXHJyI=@{`a輨CxO{dtG :2MrNk*s9A/H˂;^ Y~ *پRn-FJq>/ߋ!W~ gfǎ<N###q@@;{?KZl~;q=;(󺿱YطOs_Aϴf=wqa3o_AѾϳ4ƺ쇄_8q_$h>_5ȓvqX,v'8``ff3_,h؁(:k'A?PlOh<Ѿ|\xu?+:CƳ='qx{gNPNiLBb ^(hϤv(m8ߟ"`ff۷?}Ql;Z8_2Nj3T6y=_% l  IDATWS *.YSEA],co,p`w.lO{T6$<#BDEPyGy$-<.-G~/%9'Y`%@~۞=ѷ+?,Awy~6rJ2ߖEQ@6md]k%([%;#!/p -))+g]Zj N&5Xk1u,Ŧ֘Gl⌌i^T۽^#]s%&$@ȒCzfϴ ؛& ]zb{7F1ym:WX$Jߍ#~8ƖKHN|kȌ-.ųb㦨)b#Kg(Aca=2_V`/A%Ls7"fF{F@dhNHht^ CL3_7 O|9%B~I^S143\{VXzk+T^sJOdRɀʛI`)h Nd|NUI[4н13?pLumdE~[7 ae-{@t3څ{ȣᆃY}PGdP(MSX옡sha\t#j}\A-C~fff'rl]>荊} r:y١؜? @SK7מ@Ԡmڟ{ Ђq).+t.i׏ǵaQSb<FyZ%}DqֳU8$m { r2VI9rt ʑaǕt9T_Lj3C 3.9B0Kɀf8 ?|l`= 'fo H!IA ,٩)@ 7 z>^=^[2. -[3KhkߋBȧ%c2aÆ4wc>z"p섔co},/A O?kRT bZRӺr8L0 _`7kgȶn2dסB9I/U_`ت@) s;.:L`pߵYy=!e6ɥm&~s)/ oaԐAWI?Mt}Lp@OS9ԡ9c?I5[crE6f n { gSTTlE+fR@[!.s83Gd9_eA@-o/hߌRz⨌ :xČ?. ?=%=%^=ͳS_X*?bKI/Gӳ3Mp[DpO O7cѹPQoU 1Swa-S?ФU5+\e7UlP jk^С*"=H3=L2_]qm_kri3|NrAkXk)ϙ{,7~'ƻ3:82' n,!ѿToQkG~|Fv^;Ǩ\S%+,ʼn9jCAtkћ7w6֜?YBdN~^0Dتew2 jq: H-?%C\P&ŋ3¯ƽáJ3%T!RI%'=/)5Brz ( Ђ|oW]GgeDyD5^5c vIg)M}[@dDߍ v.i ;nJ#<遌o [qQ*ߪaVde.c~ۦYD n{I)Sxru莈bKAzy9</y^&_׈nBR_nHP|bJ[i*(d[S)F):o?PbࠒP11bF z"- {>{Ǩ}N*NbsE?'&8헶|m㊽&!}~Wա+-d"I|]`G^NKmC ePh\#;>4ᎀǛDs|6$wsYಱ^?M/ȏL^ͼu/תTف{_j" z9o#=3%3œ7!wz[DW(*6d$?0Bzq9Oز2r8ՈoZ>ꈜKQt\ߖިn&_ڗU'&=mغ%D7.N/{a'/iU{B֫gn&!*;o,%CtKhy~v}IF9N>=;3.dǦ$f/k-Wcڌv)k n+)~}mc1\*f1RtF0rQCqjFב.紐T/``8Vd}hEqQ=,Zk<_Ơ3O#W µ$~,lXa{lq(}>ְ8flj쨔-p.3m:cVm aӼNIid^H8p~_#3OGJY,5w}DEPlI.im@䰑lRpVxKL mm.H.m#ڒGk'O99#a cR2F&{NJvbF0ݧgvo7Ri|xU8¤.Λ,qQl2س/Bb6h줥yո0߯P +')Xpb d!}BON VZzJv!#9|'ת[Kk*׫uoc-Mst+B6)@ vM{R hmu߂ G/eH1C~ZFW[AOH^UCx@M}AKOϊ#rW`[@ ",jR')Bt3]`5ϐѹE u! bu])Hv >B1^xpHSdga>QGM16uC~DFzqa=Q4;:{ո=C*c3vhc 1-Kr^nV8>_p=Ȕ_a Jc=xow]0wjeAvJ^hõG0-j]405sS-*߬ zkwM-!D:Tw@M.ڧ]Q~lF%-}sJ2K{~0qպjc 3EH1qP^dg%0$9rx>H!3.CB\g 7e iC/0vՅ''e;1>qio3{衇عsyGv162@Pzw~J";lV& v$]qA\'P rb2,"@D]!GvzJ k,C"}˧gs*_v#jE ޿;GS R>Zg;)E/T?pNhK~|Bo1!{^LJ-EaBK#W89sn4}X,f܀ڜoGОa,-=al2!f0j u!@(]jUAUK5!>UG>ѹtXȏː= & , 4DD} 6%"!=%A;1M5#Ws?(aFN9fĐou 1] )[QA5#ߚ=8Ttxm =n [tS;Tl͐{=w2$оr`["4HY 9juG}dG"pN^&#ؒxB0'NM)SC~LOLS!$ٵYkI/mI)1#=)6tuX|w ޽?YK C L8$;HQ9fvgs@Gr{,,R$em9zRnvŬ@m(v.d o=^ա+TP%J=] 2|&ި\NWQ[ s! [ @V^[;C1'+rtXR>ZW@hP;9.dj&TRdgnwUUOɥ 9M:Dw;U^16Aυ+V`At<Wh9v g!q+g (Y\ =#Dmcm!~Wb ̈? Km.nzt?Y6;y*.V|k^n`~|6T#\ J aˆf~0OKKu^nϓZP'%$%SVYbv{ yfXmaVl)Qf-K4Pb `! X0kU+R&ΗnVCyZcP֡}}AY=Ѽ(벋n2}W7Ljd ^# pc%wcQO15M㊂/+oomM]ac?@A)uy{ C>bwșoaOI= xkAJB 'xfE^NI;<+U_Fw{ Bk3W߲V+̤v{kpZıum9Y敎\=߅zJ.:(92P;cC}D Aqlx2 :@mO!’UW=:XV btq)6ٌ/ oG~c3Wj~ A ˇ帩D7mOe3نҮY}B% ^퟉a^ ~T1*=f[︽r .$Ƈ *w_uCutc ٱ)s1_jU*^-Ʌm2{FV'+)io/,ͩ}A߯bSN6ڈG˼)ĢV+"[%\w7ȧ=*_ӏ=Z{`+ɏKg|zcLt]\z";!؜?qԨ9űzW:@+ZNnՊiI^PbV aNT?S&}A=l>LdY'&o7#Oi3SblL/U5j"=C~Hg@eyjv~ZSP׺kEvVZ[]W)a !+ jrq^8ܺN.kVT?juAvz^6x=GBb+V\FU>$႘C :n<ƇF՛LPlIQc ~*>䊶;x{;nR{57S_T 6Y/)e{.5/&[Gr[r:/!Vh:X\FS'p%΃W Ŧ~w՘ .w0K5:*yfPtan-9㿽;Y~v@4j񖑿ݡIAg|ֳ Ϳ+nڌ~h} /m7xW̕s̤&緩} ޠ=L\m)OI *yt~\J5sn ;A ے␜2tzn`[D5>CzNMRzՁX ݹMtCo:X Aw^BVe?Xh b6ѷ* /jlś cSd&{VXҋ*_ad&JUBZ#x8›bUgI!x+-W?6yz*_Q&-嬎+0׵e>AzAa =I6~N=B(htDGF.`QಱwRpw~oڇqHlɅm*tɥm&-\!$x Ĭ̼u7,!Va& e}>i}qm%ynd_Ңy( nD/+ȷD8r#;8p~]BoHioD&e3/oA&.1A m:+`}ӄT n̘.!w^آL׌khz1N[UD&$;;A*YJiESųzvEH?t^ڢ1:!^겵q:yhC(i'4.э12ss62tvfBxkv{wyۀƇF\[E(J2wEea-C>^Ìvf0ۣ1D"2Azi#?8,gM{x2'OtTHNJ)bNJzZBs^߭g$y{b.Lv㻨j45s,y|8OЛ jn#R'kԦeO"/H?$f!~$ѓw9RHhCvRҟ\&ZY%z:F%4\"C& ?1Cy}_WjdK-^/{nQOE[&ٖ-7p\!m: -@ғH!q@Hi`l\{eYeN}fs9We X'{9gSZo?S{w]$FT?1A%4Kpm׫X[įl8XRY =)k|z5e][_0YXZfZ"(([eq{ )&޿e'\Ȇ&zC򗪝hg*$E_tb}^S2|S J cJ_1tOf^:a >+8[].ى wX%!f>k*hXXuSvkI "|ASG&){׊kFjs-c$z]d6[]DlowLS3cD"0Ƚף%&/I`=m[de]>Qb;5%o1)ޭBPK8]Mő9o فuq1Eʦ~0 jiże-P$e <VFX#W&u΀ߪYT?01zPMɹz\3Ndy[c>0x%%9# c<>ďԘRrNL>0ls옔lSU(]]B8?&% LYGmI9 .$Fkr*Ǟa}@l9š9F/VR?zM|}Nhh?DѼN.*B4Q9D.-HN5XeUcN!coFQ֭[G5OMben6#a eNiVzpk3/XU_1-1R;;OOKNNqlao.L쟻_УC\>Z($/(V}`pW:Dph#ߔ*Lb;90xzRB {?y lI"YWV>^Ǟ|0R[ 0f0>Lߘ^[e4pY+S6U l/Rx{ kH5 '*_.?'G< GғT8Ow_ПA xz#g#%#>Nv@cL~M]6tkJ&9y0zI 2Pg}5Fin3>/AVPF(W5wٸ9ͭ,ENJKػ]ĢrpМ/װt|2/\@$?6%JglOSwڸ?'뎯^c`Zdę9<R>:Huo`?؁J!'9'~ԧdJj;' 3pP%aMzO bq/x{"{c.pnu 0o \"Ϗ:GBGfEOڄsAmP/R8;\Wr¶ ;'y}3an }Y{yEVYkw|0AF+ rClC8O!-uW!zm_}}=[icߜ:?t5][ZO^C4H!ޝA_bI Vl^[BXYvDZ֚o ̘߭`w2m02i{e=m/Xڅʊ⨼d>W1$=%&h|}9))Kpv:ȥ17.8.AwsȕԀ=P Ds"x7LӯCՉLy(E0˟4rEArZBqTwZa? 8zI^,JsMό1r} {%=YSW4~{LaػmOEvlJuԸy[7%{\Sxq6(=mQ9MNHimBI x8:B8#y^L|kkHgvQsO#p PC̏IqwzDoP3@Q?{[;)r2lta[\<YG4^WyE8Ym{>_1YV7K8O; 3"=.=?~SV A3qRI|qѽû#o̢(:T?DbmN34vN.pwB/-tʟ0}>_esmLN ~Kk븏z)xx<9sYqrڛ 1w/ijB )bj"}AB~j 1n+nUB;lKJfMZKǛ7(W9%B-~9/W)=ѕ"CX5Lv7Wg^0zZL`Y{Ndv4rU[u\Rt%z:l'c:m5Se(Zc~T4G6,ػTIB%{D+SVD8ڤFYAX g'\CVK$Կs;}Q7A" zi3a3E>UGŲJ1ᥬc- 6fufO-pc+"x>eANXyhZ]rcN׼"5Etlz5:h̩ @-?%5>8!Έ>K=- CZnV;+s4ymB[aeӆrIWW"x'ٽ{н>[Y`]˶M/V E[Byػ DtDiWgJ쾶N6Io }Ҵ̚łLkyvՆXV[ 'A'^?(:%jS_wוV]uփ^M-,!pvyIUw $Ӻ_M_j0^W- ̿ia 5AELj"}\T$ GES`P2c!|Ǻ۳j֜YKj37ʭΚLK\~R:Ox:DYƐ\^+1@5r(薫 Ch 3(~_?n[{>x?OZ4 ӄ*Hgqϣ ,%4}.`jj{9ʕ+9IsOYlW>} {矴<tTI2`!rγApy&y|?Y噽!H(wdLm4?m̳(xǟs9cccl޼˲X~=wAg/_tQm̧OQ{,I?I:Nq k)0Խ (̑c{ݗ'-rEaE=> ظuw:&):,xw}!7bv:,+ D"FFZʉpźXbլ!D l]1ZcouSÝX[ K֬'p5zXR5;O8&%aGBi4PͰ`d'hzEXQÔ>q~EoZhN{gIIk*JDT+yT?5k -<ŪܘSjG}ojB.+e 'rUA1+W3eLe&v}sD[}u@r=n_g1u ТϿskͭ;('e"GH`DkD춰\"  7H]E}N>dlkײuփ:TJTˏ^=}ˡ Da_VnmkÑQT4^q{xL,+a}Hu~\4Γ-r$_# pϏIA !׊Mr'7[hOGDR:Ma'Q!8pyՄHv{&;i`ݟqU%h;mPJwC< |Sq0D}ȱY{aÆggxLFppxs {aB1wÑɹ1K"bmI rC\%qXZ~EL/(Έ^D 'SR4Q3{\&IΊ)6%G3DyNJNLIOOH΍8Z-IqhU .}og$t;4arE\!/n&Qz a r@2rrJZPӚeeM~bf/Q%|KF%ʍԞ@ )k/MB@+-%wx`e'd'&-U߽RѪӕ zibCntW7z̘ؔlsJp}طks)X1jRad[ɡZl)6eT*_%WdltEpTjS N(==1!;>8$C.ˑr# sv5uY">[PYLI䊂+36"PK%qrD-5 -# =;8:GȕbSN(ebcNW[ t䬘ؔyhpc =#&?:5.Gdg/P%=̎J)):}^$2g-씌bmζ?qpڶX'X{=2㹘oR#(Dۑv IDATur-wf- 9rD-W&zBcuJ!y1Ŧ1L{@9bCZ%Q$΁e|~Ѥ&r⋛)F"cSғ5F"8TYϑg= O>Y'c ۉ;~pufL\?HK %?,C֨ yLNVb 33a9rEZ._@I=Lf~vjPCd~xNqD\!M "ErA nNH;sZ(N7 `))NOȈ/6iI3 Jӆy19V2SlU~l߬soclNE-T)':Cސ\SV~lJ+%͡PvJ<۷W7P S&XGjZ)IOIϏ ..ؐsFWk_- E֚Ƥ'Kb=/%;%%zC?-̽cgl)IFύ(}m`[k zlIhӖ\a氏=MrYs䥑XF+Ļy4JDCP?rIOGhS1DSPL|NTk{%^:N fQEMC]ψ_ޤz8Kq&!.O}wO|lsJzJ)c|.k\(>8t&C *q \[2Pnh2|M2U%D" 7mD/oPB2VAgCfc^h%E_Fz;ey G>塝%#>~¦ _EX@\:fRcӡ"vf>GcMTevp~:h 7J8x#/@.(}LbA rmQ?м3*G… vZ|*U4iN4ڨ[wk@+e}.>%ucN䄤 WM,nI뵤ZƺסÎ taڹC?H_XP%Ioem \ƹߥ>u+O((=|:cX]_jMɃ}֐)]Q o|D?]k41 NK *q"Zy"?nN5W˄6ذű95BE|a2 }~!yi܌} q4Cps (|>zK~ UcS_nF[ڬQ NxW;%'1^UιV ==A"~ܗ[QEVڪ 45GU{@]sgYvh\*iZVzxp:4=KKtYQ񾃞A~k)toaQ{, !KC w jGCK8Kg)_Vz8m&jٿ)Y.̡f8;{W_Eϩcg;,^z%;.c7F ~۳h4 GQMff-kJ}(ז\$ v0v tLĕ j BwB"|x8#9qi5F"b(y@ܖes WSEp]hvҺ{=5wBm2w\Ƥ68QyVSɍ:D.į2\e=ҝ#ngqdJ:.7C}[˲))1;ntdJo!n pq6v0 #ғc=¯iuC桞 *a:3JGFSco{QFZ}r/!\%eדNI֭[!2lutSM#7 tUr:kZk¯DdX5m.ʦ2(}bLǻo *~zU-Ӌ^8M%lnE]Ǟ6(!D71yqD)ǽH@sV?sg~'5i[=ČoiQNZѽ-5wwƬ9eq VkExCɔի0=Sr-t_9\WVGiߓ'dtW!j;8]J,L(ݶ^@*,> OoH[2ޝ32DCb8OCT|1ZWH.NNRG(=A!-A ɋcԄtUy=H|IR߱nȅU[9hsj_NvM2nw"wCNPeIS9 /HMdzUmB/(Jo:Zi3G&E!%w}f3k4ucμHGDZ?9Bp l@ 0iJ߮=alӡD7ח9#A*9==\RPYJ,>! Uo}Gm5BkCß1[9Z2.2?_`eR2PUS1r)i]`7ng֭[c=F={vL1͢O?AP MvL&Nwa<7C}ݞ=J/.,/ڲ^jt[3=%u'Xo9LO䨱׵nFpsg5u*98'^J2þFSdEO둾+]T.^s|}&NQ*V)/,LkJݔtCe]ז)a ¤e%Є_+w?tuN1eW٫*|h\tY#6?k)-{4}d03V[j٪ozO *Ǟ ;O>59*TX(kiP5s _ (]Y^TEApDBo1"zueMD;owy݄ɮf_°t֫ E5ScD/'q3 )ϙ}PB/Y-Ɯ 98PPN^?-ksܟOѡ%a^1&S&ϟPsnt™?B M+ ufOMs]R$/ό?U&j@qS miԄ3&,pp)5E#/HJ>Px'@@-T~p D}2_9i﬑nIttNBC240%@jP"9!yqͤS- =C)r4iEY !=7F4- X|F8&Ya7),C?N5~1+ T>6AeE2i:bBU%͋nDoms&m!wVBMwf`2m[sev|Jݭ5 ҉Ք+43$)\LY 2եN[Ox/J+T PPM_kRx_l7TJT3ygCu-d-k&2iknQ\k9<}yմL-WPWhُCy=䈞|:fr6sGq:5to;&綅a :xs(^11XZ0h ;З dNcGl%#†B8x(z㧆 {MpE ˲ ߟEk,e!հB㏫T ?7%C1ss64?އt5ړD.)T?`TTIWy5`fal 5Eш-s jVYIvf@/Іg .0~̾/.[/^HrgS ߙ8, -=ߔQw -2~jd/ѓ O":MzNLs}|g+ԶaN"g.0xv3&W7ƏpF+g@VKSm֜\Q>/6ijRuc? QK$g2c!{37rxĔ?!«K*wƝZ,}aQ)xB.>?^̂B~d*F/VL^ZwScCFG15dl?2 '˙3}Y|??L2n`3o5k۟&Kl>yh(yGq8>'ysדjr͚5,]tMylڸ=F}n#M?4˨kQu /8 6zdL{6%ϥ"L"*;?gO.vz͛Xp]k#vŋ;Ai׶QQ~cy//靠7p[iA8y *? f~=X^p*cݟ<>ӖdiZƓO>#`Ŋ{ׯg۶m3x<ٷoK,9ϏRF)c<ױ{e_/%0;%X[`=m|w4+;<1x0F-;#u2c} #1Q0Ld( )ņ~sKxU?[-% p?- WOڥ/U]M| s"/& 1$IpԓsbV?/JRP"Xauk8?974>F:9 NLP++0M&}yScdq$~\R5H~/e% {m,rؘՅw=/ 4#H'\#IJu4 9=B< 1RMeu\hcF(٦b]5kvhZ.<ГS4-ǿ^HOK_<輦YiMg Zȥ&3di֭[l2;찃nG=Od''rkc oooؘwޏ`ЀlݻȜԿtаziER& !Vs"TзB4xnɐS-R'%*uo1LzXPja;ڟQk%[եƺAg'1}pv N?Pن셩o{m{Ǐ\WP_ԔABt;Ȅ  rzad4ETdí|:Dn?˴5=*C[ #t WPp%8p^NK8( Q6BDHO2VkMkS\Xuf\.qxjoذ}qIqDAqoIi. Z s۳H5Vb9kRl(ݲo!WX3D9D2VK%*KBK]N"Ը9}6˛X{!7 4x tI|S@ls:ʏ\)i!zC~AՒ@$, zmHRw!>?<#~i{ʡ&Ӛ:m@|,8%9)RlOLMۼ""Q;eM* X&P@b}AzRU\#N=PqHb50bUN|~lA9!9!AyF袦q`qvv5KqA49 @g7B.+ cmNɶ$0'Эώ7NtЎ+uK s ս$?i Woͮ2$W[1dG^mC>tyyRV9S:>FxJR@bHQ3Ԥ2YVY  Er^<$7ww:&s.!|rvD0qNXbM8\&^>mLHOIQeՁ/cd%̦j@IqXa&!䒂uNzY;dS^*9e=B-VO;S$h",5 % D(!ڰ(W4CtܞɎH^B75aY-E|FN3M љM]GyG7@t/ &T$G77(KaoI{NXO۸wy?6Xolq8sn^eڵ!D|k^&}Hf6 [/2׻.+m.u!鋺mD74ޕ]{+D#e]FveK_!!<vnfx~W ^g?\A{!|z`}`fSЁHvLJ+ *y%Z6~i/ c*RO[Hb)zĿԤXSϪXucw!oIMup5˪}9);eMYXS=<$xݢZ {M[TY!~`i9^=O_ƛkzx? xdmL51 S¯qr"P4?%kҾʺ9BKE|f|]nG*tQb(_V}E 2*-4/ W V{h r5ʟ`q(f t_xiejSrR ].Դ]ð^AIéS* q@ rI.i֚^rjR/ J/6C:C*nymhXW(G>к>FըbZoluoKSb[L2{w{ƺ{&9T]5F֯_?~P$q}qiZ9=XA U=:stqfRJo 쨔ee#֧O=&=93 ،+%lt"zFޕMcNmmjjwʿpQkbS*Bo(#Tc@!7Tn -j`H Z/paϴD+M_?{Gn(़'MNQ3zMM59V,޲jAh=Q*ѧa)]QIyVE~LFu*m/o,k}Sn+P Y1OlwhT1i>'ݫCO(ݧCGmqϼIdvV&NM)VLw".3]&)v48)[ zYX1 5k;IĢ!c@$&HBLlBCʖtND$,(GܕZj}mʻu!3Uv.YE @34eÀ+ՏVq!@aeQl.kgkS@fbBd3o꿚kB 4xP1(t=(O3}8VF)R" ^0GxcH1S&RȗL^&>j)}A5KxUFBz" Z~o9qsjbcShC@;! aL rd҅X&~źp6Mk4@uZiH h=*+l}l-=qR2,S[kn4 5iѠ3L|`"Zʞ(zbӔBp{ɸ,ҭqA`/'={ln-жZu(yFZp1`m@ Ofi] ۥƶgBC(l q`),gjZ>=tuyuP 5n0rU>C6eMK DRuCu!\ wRCaH n ~t{@>5)91WE^{TNm֯y2s(_ut59Sy睧drdbwcrLb:v|hܿR0ŝ "m}C}~ prukwiI^<Nj ̲%rdtd<2]pi #|la9ߞq=5yOvqs89 tlN;bUF1RgLgFD6L[RarjiDρ׹V?E1ى]gj%4cǞiqE]nƜ#KG^ЗL.o778\Yc;`&/%{A3Bj1k0\[ lX'|&936IYkGtɵw֜,O 3fu]s9EPe; PwqsUӧ !H B*HQ+boW/\{r^ WkXP( %L==̜'!T'̜Ϟ^{hyd-:ouV ̘KjIGv/ h?"'|"BRx%ŘN4JpwZz< Y[j6d+G%WHGsI'ܺg{ciu]=Y1>AT4_*,Os;h m}:y]&͡F'ǧ27uq:ES2&R>[NWTABԈI^;1Q?!Z1=<"MוW c5N<+Rm"xǸ4^ZU/-g{)T]XC5{?!5 ^mS:fOBl3miB29*L'x6V[37WTO8-eFT6L –nwk{n"8ƧJܗ}O@;o:GRx7qnNvdž0)\QJ}إyRCL=Cx8L!' J{aq6D/!}dMѸw}]:'%)W%~ `gSQ(thvEgdgkPJN|5gWT.;{_o[Yv6d>Y<?O7MT=+q8;6{{ذaݛd8nw1}mM]oo%wڋ^cz?:1=&|wwoo=y6kTSM=߽{}Tmٲ |`6nܸGt]qU{Vkݩ ܮ]D?pۋOP܋'m}N`}ExF\_b)2XmNkBIc3e$ӳŭ"ǭyc_EpwzBЋd tyG. OSw/gG4^VK}fߘ?C=A4@_2X|N2?eT4I h^{g4Ϭ#cIe8Zi 2SL o\"킱ľ--$hN:!} R*pnȢ7q ;1 \B NpNun`>l)M1#}?!WYݨsG rWאw.,/cI[yh&WQ NVy=%P2xZYo֟6Uӛ#q Nw<';IgfOrDUn/C1fY/S'o10E=6jf͚żyP y=sda)=kUA8? 8O3}kҺJiAߦ`Ѐ`i:.,lp=64^ցw=k8?|*/gl#NBy&& X6^;2TX:,]vj7. I|O!>i69 8dW)îq~DUu"Ѷ8?ϴvّK(G!%.BHIo3),K1B8U=h^TCN#@u)|dRi1E+$;1f.(\^N].Qu\2۵:~lܷ S_S%M{&w OMPϯ?9 $X꣯7Ծكpq{VL!@F+-2רx]}^4>CF- ݳ8f1W[-ܳz+ʤ0q(ٟ+\>sn.p-.][(rT5Al;B4dUc*V`ylS6s?)xm̵YzxZLxO_JNPzM2Q !h$sCc$ȿUJ6_QCߡcTlڄ{FhQD몽Y1Wqb0) lij. V9X cOp: & 9LpȤxad,CskGMcI<3f,~=`ψiIk 5}IeUE!6Mg"s 79VD~^yF}LA0ht%!.F%hIHCd*C&j c4gB,4_#8F;9jG$bX+k d)!k$b컜N@GT'sCs{\y0_Pਣi'mSTקx,;BJgd̎16 ]"7*aU2kܓ?#Zb߭tj"3}r|i/ +vdߘKd϶If:KhM ϖ7 yjl6APB5U2ؤo"3!';IiBVZM\ b36P9pdǂcp6CI>.2J 4Qg1V+rh>D$h>W%Y9`>haYT>x֘ d1!8%\`٣£}lgd~Eĝ1Lf繘[htE_̋w菨*A+8xvD2&xyd$Q:V5ZEhHC287f Cy U[#&T Jnӕ *٫vmCT/G`bߩd4ΫcoUt=3H!!6VK>"hwE0yۆX?E 00R{V2-NA\eej2hFD1WZL*St }Aܲ:i>hbIJ ŏ,N[-6/SBVC+M&).ڄN {J:K;Fً͍0WZ+-1\%vCe'{UnxZk*d~ú&n+<_fw"xF,$ E cEH4?SyBQkF)yGM8 )U6gcxiKU6;zMo{漵Zi!B(a`QgGEH`Fk:ۦ#I3jVxlWq͉\ kJrd3#E 2Xkm5 ֽq7IC}J ZNbdAǺ:2ub<]6s>#-ՖY~A—h5]ed cz^4@ "6o.XwOŞhsi>Ht\^VI&ejA|@L`JrΫ-(bbFH/QHD'!گc=Q/)(gK4_\c \^)|Fۮ!2q5l˦"v#WEZ$~~*d~Ǻ0\25VD"CÒ12+UMMPU'VR ѢsE$Df%o !dEshM y>ƚn>Chdύm%sc@?`@$^{M$Q GqsERHpl0|tEܭJat#\?ڊ }5ZskfK߭:6:xF:;5 *0UgG)<}Nq.Om9Dow՝a^(WZ_P{[#Kb.yE\ʆhi6|%hAئI QMsXMeCp~E۲{dz q/a1S0pnȤ=juC:P'>o:_Sط:Ys 8;}o!Sy8t'mjHWmtbH*oC;eΑ]+D4+IPc2E(90$_M>`dZFR$m"nMr0 o0֛OR ($s9x~}]"T$[45_,+g6AŻ@|(`HUZ@V:M DN5_Ve{ L٫7;A@g5qztu|/EVo]+;FMP MPpLC-Ywm(K @Ցk dQ⾠D"ꯪR`Rخޙ=sJu^DKCϩ3Vc~@rP8}GpAeÆGǃe˖=idϏro立l(3 R ">іj-H/a`e6* 1^g|RsKBWWDy2zDh &ᜈT Yh/ ]@?fO Q5c6;,!M;08KXDFG=56^ rj!>. l$8ܧ CZ;bR$ kXwXo$}oO( R: $Bχ$d\P=B ڶRBUT@X_[ @fP@,Ixh@_lmz^6;ѥz^aM9ܳ r-b⣣AV1q^0e)L|dm5 IDATsByzg$<$qΟݗ*G"g$٤]2 cAhOm&h/jZ֤=vv _,?j. HՑ`7dzLRVRhM }6CS?6T[夯^dQ(wD "Td &LHJ1P!4"жih5 -&)x-`nX 3d4GXy?IU2KK/2#gEKU[l<`x] o2=*O&sR(͉bBk+-*SG)ʌMu]o )~!zNɢC.I="i gD)Z}U%q+9B&j}zzj:ZES6Ļ!WװoK[вɴQigvP~x~b$3m:<Sb%l4\!IcRvJ2b/|Ԯd&L \H1zqa$YMGۃI s]ۃ :e-rS !Ƹ _P%9L/nPl]I U'Z;!Gxǻ$ *ѠlߨjNA_HW'3)uF_l:bs}=\L=I [dnIzm2 iriޙjȮ7֚d~[:[ tg4/;U5eXkȒl{4ДGr:<2=07sߧ:RJ]Iw* mg/M#μOIPȿᤙ߇cv< >9>MEk1~bLH Zu8;)+k$u'v;88qoflll-QbzZpQG\jvqA-U'tspOpXOO;:mIK8' ai>NsAZȐN*1$ v#9A exXm:|#~.9!ӯ:JmL̍:m֝=I ~@ W k1X",E/:_XEI{j[t2?ɥ^T!Zq1Yѐ4_"n9k!%htVZu6|يT?3:##;I ~͗Ek$+$~2DDugDS{g38ﻃ伳qkcXزe =jömN8gj5m6n=d2ٌlx>J9 ]u\{Y@e/,rq`&yַ>:;F*py/fe i Tʢrj#pil@ST;Ni6_V)8 Q>HCҼV5ᾡ?k?PKN$ Kx(w^2N2؉YѶPW*PMk`ƌ jǂ72g~75T@@#cH5&+d޳} ooz8I)U7SsԖW0F5||m! o=PٺG3]gl:j# mF*: Vyg3s}V 0W>X[zs[2H[֬D[c_yޤ4.P;fZhÂ`gz1%{B=C*y8vv0$oHAŅuAVO {xNV)#o_}DE>myf)~j w( +^{j߈o>|x4sۙ  :тEOd~wױDC2[;Qy߮m[+};_V2K_\Bѷ+4_T'l?ޣQ"Jcv|kw i熬"\=NAg5wy5r?LA6Y&:@q8N|dcI eط94_PY=k VR<-b x0 F/x \%"d1b٤6ژ{FY )cEt`@cE6_ d+nqWJ <,Ys#&>=;FܐŹŁ x'zd~kQ6Hd;I*a!Vx0s8eI'*3kis"UW.=;A NjRsطg,dm5ViҐҧUP,$֟lWQ ٟqnʠ5ug6RU_ O\ù9w{FSF"2 ~_TVyvedpnxA4}"~ 1Nj|nϐ 'xDdZՏamnKqWxg6b,d^L;Dj}c\{wa܏ 8Ϣo2o|^,ZCe~F4.Q0͗qn͠OfC c6?;@yj+<"4_@%PsgD_X`m}#l)q9昿94M6o޳h4g}uMJq^]zEΐ~&-YZW8wfhh?E70&y54ܪ}p/oP]O9Z}Apϖ771Sb9dfjvv{9mNáDrdBWl]e7Ƭ #O4{K2_&>6uVщ}bU6sKxيZ\i!G$yoZf5by9.2w4mw/%Ȼnou /Np~\Tm sg5117όv.?XgA~q_d(V[i<}ݦ T/g6xnpy\ˆжuT6DE|_lPʛ3Oqh)ScNҖ$%G] .)gpW+h5UD!2~v7fpW4OvSO j3b2Wp諸QE#3cM¥~j^A4T(sJ: с!&ɾQ;dǕ[(|DB qmlc=QtkkdˢMA2d](D22ӛ zMZ4 ʗ5榑d6A4BB:@%C$+E/턤yUNH# HM.k@TR&6>oiJTwGuF?CZ)M$4!s'krvR'}fXWLkɴ[.ީMT$hQvQ}޺NU0,i+g ,TAƑ[dˑeKA7b a~lE{pph@N"|Aqjώx\ORH%9!|0&2=>MARit2u4䠼KB*c3ǔ,S sBPB5̵y,d8dFG^zMCs54] uU-DdMDt]oa}hx̴@A2}< b Z}'+ }cS 6mNBel)Yfj8GE427g)rF5>E{N2P5+2J.c-1`uWcMssKTvf|6]z"# &&LMc^sI5I=}8-2&@Ws1!1Hw<dUid˗ ܘiSFqG)}t6nkM彖L!|%LpkO(#xL uI,v8#/\o6>:ZUC 7'!HK8g o|cn''2'~ײ!>C-P9d&>8Je|OK[D (ox@t[jrL"(~nQ|@~tIb'+3*gI;wV D1A'9WgId9 ^ˆnݕ[ڹ 1)ӷ`iyxp@43\g^9uxc`LId"e$n L]|]<]@jSK}AS>ǃ0U˺¾9IǥD3#S eZ'6͙GOUrz!40Nh.o[L4:s#!Տ!,J£g?[FЉ&n=jjA2?c6t qn(G%3b]>oOp\üR%&4S 91iHf!G$SҒ0e[S!;.jcі|۔P-=cݦf…M-:][B`t5#~ѼYpI oIx-  'Vƒ]6DPwt\%??Iy͕1$v GyxnJ}1%@c=g7)}DyȞ,V6ASe!v}#{Vc6-TPo-+<'?#\#j15K0VYXSm͌7)|4??'\U4#D 5"є6Zd%2 QM<϶ CuoǍ-!BH'ǒb )$:֨uА$7ED CE*6$ ޙ 1}%yaG-xd!Q1}Y4_VS䏁i2+F߬Gz# oɴ -,BWWT̅ т9`l61xJ>!/ԱR]yAI5HVQo0\$Nr"D_:ν.S ?ώ #Zu.c=¥!ᒀx-VF"Y).ksD.1417#,Y1Z%}՜U|gNՑo-R-͆H꯫bj<cl4! I>!8'7ºiͭYc:T\SW&X$17mM٪JVb>bdjߗA˲?#Z,ФF2 K}S2A90C1Cm&3V؈HLWh: e{Nc*RxvQD2϶.7[ModdKC 6lذGo$XE-=r^7Dژɜq~h2Z̈ѷ:yU"> chAKk݉+4;%Z)/H^0:%;l՚7 '461= O - Nf=G[TxpO( \WuZEC32x"&C-=Ҫ%qr$\e/Rk:=V;$K Y*HBUJU{&BjOw5UuN--Ԉ%l*Qcg6ѷhFkҧ@f$€̯s T8M7&`QFP1 *-dʥ1bSsXijYf+e . cMAZ̯;EA-V[[3},s)EK2  u'e6U~P:l][c`#ӣ}9q~MBJӞGEK.?l3iR!aolo2jqO(׶mN< ه~UVѽmsGѿ^{אFAۡc6_. M;ܔj8w:}ʘxxd~K%+"`Sib}!3æ> t[*ץ2?Ͷ@* mۮ|7<žP/-xj <~ºKZh>f c  ¹>eLeCؿO7OYӎwʆ02smv׆Oc}A=2Cb{:5;FS#In6݅>' FE\wuច,Y}I?/^<Ն݁{W~B?0;w߿f͚qz={υ?) ꩌn=d~S _ Q*cdljfɶm۞=uTX;^HT/ h3 u]k$<0zHFhGI&6S~`l^f#}꯭ %;#E* I2-f㣩{,@w'4^\C%#gL@iIB*C( -Nk*KZqp:V #MI<+fã$C1 qNFbo %SCk;vLU`l-e$Md)$r$a}c,biK KR2M2 rL_:3I5njbGLI铃)ʼ;ѥ*I)aڊY2Rywe^bf"DcO0|t[IpOPYJp~%B*F.Hh`_Ӥ2'U܊_-U:.4JBp}M4vRHNSM{b*c⠹z!v ̨Gлd^=ݥq~3YXaC8Cٖ]6+EyѤy"~YLe"uO9ΧeZNh }L!ӣ\rBy2?ϵOʴ~ah҇ҙI!KyO_7tiRJ]'\{NFboCDT.WG0ʆ#,&1 !-B7N!!wd֑t⡘+GX?;to r dkdo/$KiLetAǃ1;N'1耈GHek͉Fv#/AԆF8[ .Rxt}[ _0Uy{TV'8JѡvS\okLqgߑio eM;vf$ "MYM:L%;A2E}2pqKHAɎomeM#]E#?~a7dthQH?hSW^gCb#/pV׉g5KnBFnf1fxd@_':2 8,?%.07eSc=V Iv4?¹\{Mn!h^DWDKjD 0٫XȌ?E15d!*+]:غ : 9@ 5U-"/mc3(}G>A$)&Q]$yzx@] 7)S,EG'T@;T ^גlƎV1﷈Gd~i:ᲀ(B:8_{krh IF*Dσ0egGCP`ڵ{tnu>6I1MP̈_3 }6/^g.z$3bS=A{[$`#TfnՈdvw 3㖙l^V*=~:s"weuy^}N 0 3T( bF$XbLnbMU1أQT"#e`:) gwkf^%˗p^߽γYFTŋ=֯Xf/]+]C`r3KbLt99hoeËG\98ufKSQۃq d@Wf}h5R{M%DCyioI,Q฀'lZ1LnΣ?όp/g5cbC$wC-¾\TP=i~ϸ3]j0pucy@$u.Z}@^_PӈE"k"ǽ9f=bўБ4 ,p~Y-B=i?i!Nwk‡h{c}whʈB$Qy+~Mi+mt&?6B? JbcBf̕^RpkK&DޥdI}y/bΖanlK]"'gC:t_m>^hvD[rb|!?n }I$@<=2 J]IcK?ýJ>Ƽݢ.ouJkkr]-Ml w&\x*L(Z.]:!&5V#S*k"'{g¿?[^lTG1ÿ8߬2L[g8X|͒X&hk 981fa<|鋽Gl+ŢWUDbf>ɾ:88Stosr锿ý|Xl!|#2ɜKOh俟}>1D:/-0w|/ZG4mnT>Q&m=1H̙_T+,9shvW)]ӋzԘxב2kz?oOsG S%l?ә50~4zDQJ_뒀hoݘKe+8A@DǺ]]:6 v1q}?BLKgOhNroK%ᐨҰD%{pQ{C|rՇJ<:[󘷧m:K'8D">ud|Π bwBem:EWک%$:Hg9T'q1kĹ Tͼ,6;A9 JȵLF΅6rUF{FC|Q >!q >VIԲ/-ԝކ4ɵJW(c_U#\7fSۏFr$SL_eb2&u¦->&Y>K(_'?%1+2hnM!|U6nϼ?˲XzjwMK.eϞ=vlGFA.)]}k$Zk i$NޒGTqq&I~KEh,qb$]/i?ُf>$0ޢ!=‘osJ O@BE}:y/l6ؿF$htx`/Ym4fY\ms(C "TP 7Q15|"BQv/>MbvAKGUiƃ&: i'.X᣸ I9kwfڦu~[A'ƥ,ce6! FD_T5xJC۷'oo̜ ꠊHC*?"yѬG4?ۡmm:5u]<$WPBV2.7z=1]T{s'\=!yTA d$_*@82XM\rgGanoӛ܈۪N'Ľ1ʀ/j1br qaU>;l~Sæb,l 1(*kH3MÅ9gk<\%aæ>/ n](V<-jش?ƼJlU6ZhAز"}]*({UM葨 =`[[8ѼxhKԐj8Wo>o ^S_U{G4;$#mӑ| ɕI"4 *U-F Y*WD3#lY0DFP0n@# J]eQF2-¼3orq^U'X. 1Y $L ~`y r?4l^ԽrUF% on':Zmٯ^а霐hvæOH$J;$8٨{UQ1LCm6}keΊ拶-%yY.r'9 ^f6l@^~drz}rU%Qwk RaeB47͎D6Nw}EO(rMA9 rY AXw[[}~}XUsp{ 8X衎b)('|vFE F'1>\Yts ^py>K'>']o3硹v}Kϑ_}M)9ɦF1W`L}|ʩeRdǏK%G"oT[cPE5PTjXEv渉F̡5r]&8. X!W[,#*rH!\ژK1smy+.s!yL` ;y$|$ _•䲌wC4#Y?ϣ=)ƾJ`& {1Te&Z|"?lBYY~O<-"\𛻹rM&8'\jxDlƍǥ;++:\OP5uEiJڇО>Ⱥ:yTɖS: "($!֛>DpO\x܇hG͌9@ڴiSJ9+"¥ٷeG`\r]Bm ϩMpq"%2dZ Cٶgf2Bb$'wuPEݝ2D7} bvȾJ xk"ۡ ٶj8RO62iƳ#]`k-p$S=&gbѬ]k(&z҂$7RRoeGY{Ro&ą<~{5o}'ą[G`pm9Nl[͊t\NyQ\V2тpV~Co&$Z٥S+d+ Xt6h+dvxj{'.v#FH_Lp|hiȇ6O2u=!=!<}3-[_Ta͓V%6gåT2%i\3a0Pwi(H`q e)DZ]|66cj+$$ȉ !#\_eџ-iOL\{LnV`%mfsw;,m߸^\3hnD8O\޺M%U b4Fo&x$fcP2`*D…߱ j;lz>$-2j݇.=1[ 6Aw@q~7}!52ʶ֜4آuI1&Xv=j!mN4)ߒl޼Eᬳ=6o#.\ '[)MiJ/5tg)MiJsz'^( k֬yI; ֓՞={&]UdJS d۷H<@OuڬةhVDbH59ŘjWl3V6a~87-A.+# [q1%Qfބ CW}y3B\zCTݭ̈́(OU2ǂ bʙ 鶔!5ūQ߶Avq\3;5j77"bQ|4{_qIZQZK/2rUN@p\-u]-P)ږc2l*)43GI)_+g^c 9sL# R8 j':GC'b.m̱S1 -@J9244ċщ'릙3gR*T*Gp(b K-.1rUI?{:>C#=V%lE|LD1h6c@=xs]qxV1lFLnb)u GCD3#+NULpbD՗-ؽ=!NT  j]VxX0ouqq/pp#bLf L8rȼ=+`ݑKx]sS}M* ;K5kz0ﵐeû P]LK.i.4nW<=?;M]?a! Vop|?s#n*&Xcaaݜq%Nsz#$?[ \gh/d.]? IDAT?ƒ%ԲJ4R9}Nd%;{q5}um<@ϴww4/uĺ1ZU>ޙ:A HI֍yAhs>띏.:.;M0Z6]RZ n Ҝ) ќ,zѶxEbNw xuGL`QC#=56aS{?6tHRdQdy|k!y!O5}hAckqN1iePm@h "1_i|6Rf .7}sS}2 /ZC|~ACAJ()ĉOsngb]5.z wWcO &@BI^%wc - +z\e6֍yȋ]Tpy@RH_W  :kK䊜 *(:AA"7 :ڳj35)$ٔ>ׇSB?=uc^pIS{{E) lszz@d&V|}2׮MkkDCz>ُyEQ]+A I}x<Ed!Q[ I?ۋkw#މ\iSFhAl˙_>EdWviͬʻr%" .!r?SF I B2T8/t?&}`a_!. );wTn!e+kDDž|\+A9(Xq>fKX?ɣG՗@>& !|ɑRΕ5*hC\sx QA9$FTQwi}-g1ﰐ"όpniM6Wd6?jeE\lz? ~|Hpyg/z8h(T5OW V|OB'00͘;LnʣU}Yd#^]%"l}zmrqF4KTb{N87r# *}~UJD} aܟ )ЋIzX|HEۮ,|x?!iMFbsrEulYhYQ(*FyRAgP?FEXwÈitSL/Z}vVYcA|bLtw.{mϫ ʰ' eC6f lAVAo ?ai|遜W ~\AA1T}O,S|/}"˽AQО(dw5lZj^q?G- [aJ%<&- Nm>;u}^\>!%Q{G[MNs)~6}f&lC>CTe\%w!׈f (I>A>(g%^sN q"A-& +S\/ƽbS)9C|D0 (:/w!k-"/|zEF.>ħY¼WAT;(}RA WQb/ nTqE^}Uhaa(U!RܭyIO{C DT~i6iO7<^!T/fLnt2/P+Gݖ#8Gt%zBU޺%'.mwKNW(;Avd _!jF"'[ ATw1 Jk**V6ՇRS֥盞u]ԖFaޔ'VBw$%ȶLEiyM|1 _)u%j@]'i(0aNf~k< /e|Vȇ_$=ר{TԣXӡÇ{aY6~-c6-bxZ;#{zUQﴖѾC(T _6L$,pqG\W$FCKYpmg`h;-"xz:Vٯv22qECCjb\T'T[A{DüBT .=jdU"̸-)鉩|xA}Ye˹@H/L[h$/"p5WOwk"Cz[QYhwHbG b\IaӇL Qp,e>K*\P#̻ uk.s*ݶ} ܹ_Z`< ??AC]Mzg9κ)1O64eS!HL5m&5 rH@e_%(QRB)ֈ'B ~ٌlހ#zcLj l j14PIG01UoŽĈ: XUZ&+:XQIwwz?bνBݣ]W]ba{_z\hO-WهD[+{En ĀV3M"ǐb!ɖ(R돿ΣFE+aIk\,%F{$V{(uhڀEn(4c5a]p~KZ>P6. aMPPs?u ǃ4߈ђiLFQwݛpY@]eQ FY]AĶr?h`J+x?RE&^dc_UK̻-M8FGı܏  q@myF+[O h~((eO'hX(`=cB8+L *<6EA> WdGu o. $jҌhw ψp.PK"{,{6UDc##e*yL!w}ihn}y ;m, læsCD0\…!'{ Wp<ёxО0Cm/GRj9X^*I_Bx6*>> oR1FMxuPaoxR`O"դ渓2߲3Ms=oy~zr[ \e)_[k@eݘGT$98k. \s.Ψ;ƌMOԘylmrM! <1 /{ww>ΰ9vX$|yB4PU101R ^ΔqD9gm:ϊp.\^O;rČ#1gatGCWpSIayT!b8L+1L{Núw;OO6="c5"C?iwc. Tdx F>z9,}YP}wcn^J5|p &9|hVƾ̻ADM}bX@ydH!|I W_梴|[r=ΪGdz17OC\W@Bo&1XhIc4^=m~Pha NDJZ~+QӚ^`6Cw[GGD謈DMPiӦ$D-ņL6Sb&]=P.x340!!bB(aUNeXv="TZ\#FVH@*tKMx&LZ̎:-l[K5)if{CJs%1#_"g226 dV Kr:˔iRHH:2w36dH̨6=3դܑ+!%}1h L4- hm*əxzfC¦mjJHK1e=oaψ mމtc;*dmz`AcE6l`̙GKXsE;93(D"1|dzRϷƕ2|rIFV$B֐@<Uxl~OHb@D(|"1'1U)EHnT3'>Ge.WTH{1n&[];ɖ3qO Ʊw}ޓ!(ֻ6zC@7vwl:YbS )MiJmA=3q͛7SO=xWzGٽ{3gV-є4wiSҔ詧zQΟatI;ҥK߿ם\<ٔ4 hV2{b\<0c6咴m[9H. 3 V+LԮpY @m23SSսjloq@(Dvdߡ7d;Ԯ@pvb gͻ[KC}.^< "[t[Szvwl֤&W1^_ܬyxePEy/ ȶ%}CMc'Bɣ+.)$zg:$m*W&9yߍ]F8v\Mbl!_3YNM t:!d'C1AOB123"XC\. Ows"{OZ|b%8Ir B. 7_'p. E _]Ǻ=uk/ V^bxg=!Nj+`*xdP8)m1mZI=;oxH`49A9bݔOex9ijM%8j*^h=f%Hp^i \!O¦FMwZX[Tu¦.ftsT!w}\bWDйN4-UD$u sJXQ>LB1"\Q?KoCy6 f^xI_ :q{Cq/tPzBv#Pڮ9ס0;I99Gs# @߇!sYG^yܹ̞=ERҥK'(7}K<8>+kXnɡ҈G8۩ $J~ǺcPp^ڙv!$b {g>gpH3dE̻r{#~/#^{us6`;xsDNhV#_r7OhDb[G{zEPwhJ#3%*u̟ 8C ,ڴJ$yK˦:`O47;8L'|$Wº)Mxg !`sk9vQ(n7j$xf%GKsӇbI/eW1nݚ؇le|u[}ktv!t `q/CܜOe4 V'QVumyu̯qQMPcBO@ ʣ֝'bjWW0OڒکbnQ[e瀚PD|B%1NLqIu(]v@,8Ҩ,ں5fB^l…hQɦpmµ%~|P,<<G5&@'z>Vb߈eWƸÚͶ%~P !` +X?Q[ǼJs0MU$'V"8𢤺u]n*{T6nΑX1kzqϳouId|jWUQwj3I~CIKW*ƸMCVC h Wº+q_D#t¦M nQWnS@ۥ⾈ _ߧjhO͌́c b|)v)6.ɓзXg[mµNCob櫽t]gʕz{9٩0 9n,4;\PX0r7AE8+~m9C VZ=EW%=lF9e,;#SBͥ>%zǾƍʰ*k|W"'żâxm#!7>thF$oH  xkcW%r7STrMk+%5JM*|R\vyd ߮pqsAµ=Aɘj2!~1%8?kQȴEi9 yO ]]I' Q28\>K>>Fʇ(|Dpbv܇5!5ֻ[Z>{M8>_16!cob}nNv!82 GӇMp7ےlciOqIWvjWWDw[ErYXbnu)G 5Ls05EʘLv[>d"%VB#ֺ+h@*<`ϡ*тnM:!!&!8Χ䐆t["l*5E5TizJr$r7Kl~k\\%I9O >ajN67[nE?9|J8Rwi]eby)b[~l+ɓ'=.amE|\򘜩1Wظ;lmمE9 r\ wNUVa/R֒%G}?ۏ4r4XS{sT> Z/-9~z^l&wExp^mA=*%k}Nnf{-Q70ooM_[%X)J7>Nc7,r]_̟Tp/أNR> y5;/7Q=aQv 53Ut"%X5̟:vƿϾøytY&R*.3Ua]2Ç ݸ- !U%_Xu!Z|kKOzQ'wX)O::%I“O>IL*0{l̙s7֜9sضm3qE./+3"r)N)//=5dG&m*P>LBFJ#{fNs5D2]ش>/އ!JOw{Qj)ū=ZI6mJapAj"\Cf=ӛ\,0'ȎDC({&-ܑ/ ֓[Zť5uWR=C}\k(%ZTVb$H1H(> P({v@tPDgSoSDp`5@{?ڏS#Q@BGyV cNoz{K])={m✿?=*=ԯjiB}h]:qaS I| yDLoܗ;Hϴ. D~~aD<жk|\@6+c'q4!&jBpO:lzST)tฬMbʰB%xgSn>;7Xwm:lO>lSs I8 I~q*9dW&iuhO?ߓXŹF!ӌ{cFx8k]=it,HYAᒀ p)V"I.Q…AʾLߟO&[Rrɕ 0oA|I&+M8vB;v옴sXq?|gc kدkh*|9@'8g< %y0#Qji9̹i ş#ʨB_ ْm}(|$沑;$=S'y3xN˦a_iShs qoW;lՠMkWWpw@NH̤9g>+bU0zg2GzW)KPSIT_* /`$\\?͌RдMnwWaB_ u+'CV{qu }܇0evڇ,JvG!Vgz<֍GCtes\jNwNk[[#_8,m8SҔ^|{a̞=^zzzNu8x 'o'|2 ,q7r̟?3f4wUU}0DQwfΝl۶BqINiJSzhҔtL$ٳgdfϞ0rK\fll q0Mi&3gdƌ̞=0$;w裏NeviX%K( r;v}v~a.\ҥKYr%DQĎ;(Sl)M鿹)MiJG$I,[+V I۷og֭).2xh tO~j458~| i].~zC!ꎭYk_M8ViiD+<{| )6vif}JY/ܹs7o$9Ylvb˖-&IXz5+W'`͸K$;Is1:::+*Уa6mڄ8}݌0Z735׈V+`rʌ2:k}g?;/܎4 f=8\9ioy HH$Y87$32YΕu޶y.<. prmuMֳ/X+.c1˺9r(kU5R~+P (`lR mkfL%@4;Ĺ$=7.}j9`i deu-F\l Ѷk]_[iҰAVx-t ܮ(C؄ ڟ$kj͌2!k,`r@ A̻-YlBՁ)*BқP]hah'8ўթXS}vyDASE(\6ݝWՉkcs? )ͬST|hedocԽj wT4a`Sz I)I9nkjDs" &OZOQ1|Hўn [+`$Ds"<{G)ѕ!r ו?Ya'ij"xu%!ny_7WP ʾFVP^ tI2LUA#\B0Dݯ")K7utwȜksEh^8nH;+ڳ\h=%lZ(rO 7Th- U5uj\.n&T?8B<+~uhIDB¹Np|O79(~G''1V[w * ᒀ[jhZv8Ľqjrp.- A;lûF}ͦ/sOOڙec(BE]Ĝmŏ~#vAD "1u14M.],HDڵ|MӸKeϞ=GnX/x;wod׮]DQwڥiÓ[y;صkr};'bn*yHi>ǐ{J|DDs=wf$+xg:k=Z A){)UU)o-#$u}Meb~PR C su\ċ+d+NE+?I1y@$J1f+J[ţs;HE٣f 9`qG xۻޝŚl!*(ej. R5}HOF\u9/;-=BYVeh(G۠6PdN#2Jh 'eq&ب2n:bM/t`cWWC$;%3^s/bO?ǜ%HU56='1g8mNCL5aȝA[R3no\t<"Ń ck|([!rgP6mD~ak\CbaBmC]A= D!EMch x|WPriAr_+ǢU >j.)-(O NA$·E)[]$ P\xD-8qG8 ð!Pk@8R^&LuFs۰m- L3A_ 6H_ |MۚbڕHՂRPx >&|vL!x&5aխa'4tWmJb"3AeÄMI&vhmw*}v0O@QU?'BZ]`z%Q0jHKЖi "B)qY*^!`}q&ƩtpOla=L+G^9b<&Pe2obr <#ċG8`9DtUt_Cah <-gcNؽ$a̮>B]**tg?WjVj=BY 9df]~ I&Mjзvt}]nVVZ 'EwW~>7km   o xd֮]wnn|cg7s/inŋq!T%O f38pO}~ډ͋bV#$rV"H{e &Q1$m.ocO>?EBW;D,QZPD]R^$'&!摲5ĉ;gٿ)l5Rp"Tu}y -.w8]5a!KwM ly@ynaTd\Vy%j`UB^'=唌C٦Qі=šX37S$jU955HPhyQ;Y6Y$aM4DS] Pi!2vE_ݞ`S >wCp6mNձe2C(lPtRګ/tP !B .W}g6y )' dS_3HDȍ}IA!{#8co;N͐OFBbIa/W$v]B8# ^Yҁܰͽ$g5 H]3@VA}"Lwon#?3C;eh͵o&H6W`,aWY^NOb^W&QWh8-9pc^R %rk)GTG:1sW$.kAoXlUpdȽJS]aE{Ç1sLJ'M įI.(r/5UΒ%Kr[~cWd]&6k>}HDD.]mpo{O~3f²,ࠧmIhii!ˡW7iI&1|~ӳQoc Wt| .|8NL](RlƘ/z;{wI~ _!@s%]&D$2\MRQ9vXmojt\ Ru*fY֋i$5I_:DVAۻ|mɟ%~OwKAEJ{]΢YזmauYDG$v7m3nG6w$}6ݳϲr345CRx2@ 0PsNe-1|S<5>DXgsXMb?Nz\-}ٴnq3]6C< y@h-b$USqbG1Hk7*N2R^Br$>{]#88P+Yg$(ŵ;"{a-!^N)俔bqws$]>/u nUla81.nz}iPFg=nZ:nuNHaGs*mNcN3h9 0[LAZ7ڭ6V\rdTͣM7|rAa 6T),̢'DŦRZrbl3:D]\m`sT*<_uzzզxr>x$S(~7^NR^*H8WƶW}XB<4e¦N\&oVɒDGL~OVQR:mzv;rN6\ٍ9@v6L (a[/mZ? lw'Ӝw؎$1 Ax6TUU6zNSh6<'O$}P?{8ŋo3f9^y9{8eܹtvvEh?*4M 44q]S]0/.rOXSq,< ǠEʼn~޵aLeQdfTjoRt +ɬt%LD>ʜoRADok$$[BʖǼ rOz۵Bw嶲%i& [E[-6@m6 n9.J±S rh3|į*?6rNF͞ii2rb?Wl< 8q cYEya"Voզ|jSצasVd$W6=5$y^M !qxs+9>! d}l= ZQUܰ7G'd$C17ցlC(*>&zwj>J'$}vfz'!e֕+^`4чw)`Ne9W7N}[!\9w3rőưTJ= | AduFqwJ)L?{<^WuI}1ET]]l}/b diBNN͈} \\?JVv*{ |ss|cԫ;mrmcHH8N_t6䐥/ĉ6bY,QgӛohGN+Ŀ[cӣofQa8I%s|/*-`f1^>Y,`-C# ɺ뒞e7携Φ/'d7+o6<Oͯ8.y<^|E$UBR$_c<`Oq,l+5щ'Z$7Y,_"=0:(Tn7x;?R{{~O?PϚu%okq vq-U8) (VJ_..jJ<^AiN 2pT?Eubb K["^0 NI { E R؝[?Pi*MmK]9)zLfAv][xsek?N_&]BjG00Wx:l657UimPXSMg!}J.k8#\bn^?#kzMwP85չt@bq}mO֠3V)vqZ{|S ,Mz$S&TDbۻo+[ڵst'x׻/G" NчJ[HO}S\ytww7ubH*t# 3mڴrF:Xp!O>$===[d> V:u*W^y%=sO[ozh>yG5smfҥokkCk^O~l[|Z[e[[IPCXO~\r%Rs-q̆ IDATmJըr6v4MnN>dzQS؎Ś5kH&\}xZ̝;qxFL{PeD݊l]o3cIf@ /&Tac$,mQF[[`salSh&{/'t{降{{{7o/2 rL$nE-rۙ8q"/=D2dpppT6sU"~̙37xcV|u7*9PA;cj߲rC>3&{,7%nnOo 9ݤP޻^$o,]5pքw[;-6oz|/>֛?Cq &[,_K 6L{DiWCd,oP< #K÷:I^0Bk#GR]IhfmuFqjlTn2N ۫ )TDĮk|V.q&@0gwK:F$ٖ<[:L /Dc^=6攖J%}Q̕꫃7=V(57~55 q~[GHDa"? Ph4 i1'i\0w .LbPoM2ɜ"v}3 TA _al<(csDn/: &ګ*=&+bY"|{uqϝ!{VkI6q'J౲MdšdJ=O{7؉:'zc)/cN,-g 5wJ Q(GV̅y[@TDCOqڝ*,zu$?QOl8E G 5!*;5]2w0ȝ!(NYD2`J^B¤Ut@;NOː;#ám߹ 6 2vlH%PWaC̥ۜ)g0vX u&Pۿu100ԩSy?tt]G$cĉ])STqG]~? y:{#L&G~6 *cF/ ɕ8F~A wB(s"s*.g!BN^':Smpp 0qjqUzVz;tpcg3_OZyn+ 2 Kx {v kIUҞEe`Q/R៼pAgT?\E-eo'xIguI yԓ1< ;r1jeJj71kNR849K'CWU 2?mP=H7ƹR]2xE8f[H}cCRF‘bqA}ٳRdœf:/·ćRÜhgX,c8EÞ<j<<ê5BjO0g|{=7#r #~I .c4+$<$𧠇{ 7;3-Q)ިF7b ;clV!}aK4JuD?G{ƾ91;Qn+,B`2ګ՜d )`1h=;{<#8*,ɋZ=N2B3lx2X&H=}7lXdp?mRN6C8ROWs& 7;5]$5o$/0.4@rBU?>JzKs'9qzl~[zW_$˃2$?U?y3 B$՗慍;mSNa8{ibb={6< G}t)v!7_|&s*z,Pϩ5D[U1gds]#*ȴ!(6E'JMt7$m_daE#YB$veA-_|y9iff 4M_:m㏳d@h}G#rwto B!"pؓ`xMDb8RJ<_,͌f ԇ}__Ӫ5FB1qI:~U'ZM=h2q{ym9Y(]OPWkt~oV[2 dK9xH:}c/G]$Wjm,*/bU d"ya;rZn4\9O'wrLjY߫pAUbSW9VrT6צUp~#]l~cEmC 4;# @8acN4m'w~xݑ;JyiP֪^@ ̪T,FYz$[]t\p.+{,V%ёJh1xs%pUюfm[-N { W~dAu};)dm eLӱ_m˷ďP{T$[f~W`N-?67+2_Ox0B!=o0*: ] \\D&92D被qr;1 Z\o^k֗nжW~ r((hKi@&@Ԉ1xs-a(+bkwdqz+nRܶTc-rQϤ&8S&*[f!#,:b]˜/z yY_ zք֯wc$Ij" G ;P7`M2畐3rED{G, V׺WeixK]1HtaSlT0g8!kY;_#YXwɞ&rSr\J dU :M}k=;HHHuZլ܊`^Y㧸uJZ~cr[akb٦Y٣nM56RorJlˤ vUs H 6&NԩɔPרsK"(YJFiANږt!/ۼ d Ak~Xs nnҲ-My a+T4AfDGBbi'4śt=O<gʔ)̚59sNʂ ;w.$a6_J<'H$% M:ݘ9$4\A],P*;`峿L߾},{zouxދ>D|Ju(Qa|k'-6C !H*7DΕGP!JL/Z!1)~1<D*|U邛ǗK(o4(P ,.ֲ Ȏ!&3 |gԣ$r`@he7Ixg/zbۢ5äxPԇpX09)Lj[};zqOa- W cwj_E*E8lbPplS՚C+6QZ/Q=5vpAu]x)`#;O|z_~yDGCU*(7~(:,$놆uG~,9s&ԧ9s&V"JǑGc=ơ/L,C$8FlW$j+4zdM>go躾>n ,W(p]Zp|pTw+=(H?`{]7b+6<[;v,/҇-G<hs,b``lfgٲe#:r10o<}y/_رc yܹs{9蠃XbEexڵw}{e]/׿5|0>Xmz{{%^]އH|Q?q*.\.G.CӴV:oVl[؊`lٲ>ة(J[T^4sTUUU6m{OGGGC!N;ė%{1?<^z)DT*ŪUO?o}[8Sp\qgz &pYg!I%y6lO?Ͳeˈbtttl5\:7(l ?8saţVlVki9ӚhB$;'/nEY8I ϓ[Ԉ_M.~*O*-hK'uXc, #0C@Drnro#8>ȝц*7Suvxa@j ,  ȁmorVFZϫWs>h>'QZ%-oJkZ;R"n\ <8לlZiEBN9Y_Xi%J0~n#{v}&cpoU_ZA,qA -cc u /{xKgIw>m ;=s0¿4dH]·T@߫H>Ӹ`0f %p|xEw> |qg_Qxv4w:l+kBDfF;4!Ba`2-Q<@ߣP+%ޔ;sA cv b?6{[Om'vv7p[z[q:KNKӶUjzvb'guEsA+/ii;YKjbCp $Џsu]9/u!10xK/I8CV*nyf+i܀KlQHvb'2.K,v䌌fĖl^\崗v{a;vzvOL@1HY1xhSSOӷ'v]$}l=$VaI>1NK}__}^p;XL:scf5{0PxGc'5ϵǖ׻/z?= 9ebgN̈yNZюiY2xԕ#0v9}9^m6* )k Ga^/is|)|Zwj}b_ uJ!_:?# ^B:J_‰$F}=ϕ+S7CښdQ:\I\qcs$_ze$SrmNYTôIϽ}Wzj1`{h])]6^E6Ys<^]UWͷE0 IDAT!ojq1ffu&xBW1Xۙ.W]$s^cp0fvR'x'"9%o83vo‹Y tYO]Ϊ^, )8"F,k¡^3F 4===(\ `4M: ƍǒ%K㴵UC=@ ΢E*mUƍaF{ZLJę1_=q%Lc#E}8oM4)#݊$Hn%?)Dz.%pv/A!ҜKٰyAۉ>$E/ѧ47H޸pN>Tv}6D̙e6R4=ϚbR<4G rg{aNsM{O?@^GF߭ԇЖj@iᨍ;_i 5iy^N_FAo&KM/ǚָaM,bHe:4R\5\\:%xFcW'~U9t lrzRt k>/A,p,yy c ->qFPZPĜmx:הΤ( n8Qc%AR*. ۙÕmnim۬_ w]w1|dY_rlq?W^oB7tLǂ)2ob;OAA$~x!!ßWQp{A[j[b1Fvqp=sH)S0|>?[oUqe ut:M>gر#:CqEQD"d ¦.t5W= ć[k tzI^*|%kI 0%&-!+ρ|K|1W6R?ZU]] &8U|Cb̻Aǩ!{E{?Qث$vb3g(B2i'_< !B86FDp8<" 0ifŢ(ʕ+Yb|svnF:ӇpʞۤKQf hϦ6(ǞDR5NLx>Y"#bNg6 0{=yGlka f@<>]8 m md4 uĚ9)2*@M(>㺮ض2 SM*( GW"^J1=K'lFF31QY&:fm;WuX}6s芎2"(a`{$x,# tS<퀇xHwWcGD?f0@xYɖU}_*һRxgs{= #3>RuN @Qe.@'T;sg׏X:$}P̀P)~&O 4aS\C]UiƮ:8b.T >QWv(-()PTÏ ]tsgcGƍ.? F|(}b픐}9o'mmI2v)a1HT٦vɟ\{9 hj(-(\.4!K,(TMF:]6VWh‡PQ_ bP.//~*_F'rG mS.$׽ e$ 'eٖLZfΒS2e=o+d!^ʋWk%gQWiXoqBc ¿͇VZF8<өhDxU$j"?&.y%{eWF-Œ_D*H$ު˿5rG ug6%V)}|0,jHDc-q=5@U"&;w #>\c" _avDqx\qErS' /U -U!Z{χrJbmށ꘰˒Fp$9\xs(Jh5<ݣ"nki+wg-d tK۷{y?@+/K{UWlXNw|+G|t2===ldtu7]p%$AY "3uzϞnzXKww7Dd29\@χUQwg ɖ9 \$>2ERRߡɱ^-UܼL>"E ?z{ ɐ\_qV{oeJ.Hy @V+1uA;PPPQ%4Kc_NE)"wѕ\F~Yլ+4N3Ubm[A{˻/{GkP(!d|/Ty-+*s_! gemws|_!rG {ţ(bM2jGցVWDnV%}V^BDPWjcQ Wkm{Տ^kSsB ϷAΧFHV\kLaQζ]:?/TYaΔGYHD9?---hѢM*lEϭ-&vd2 zf1v欣c\5.1px'=D"^ou /᭷K?V mp`u qPM\;Vf@7F6ٰhlM7QMR|8Æ 622tvoMQ4D.sfy'3vݭD[Qc+ ^J:?:z,Uؿ2:)cWbm5e'~LP*FcǖоJ4*-!vNGesݷ݆ >?3ƍQPdN`:nE\3v xEMm߿W,Ӿ=~pCQ3.7_•$c=`񛸀5n,ܷv^Erx?L.`(BQET-j)lvh4JOOOdY{_n.} /xk'nVQq]+mh-\I P8 '(+=WqqBY*M.koQmV&#-]W٢-JBпT+bSS1:r%;(jv|+G2LE`t>"f 󷜐 T6. Ὠ98Z쀢+Mǩ9%#YűE"$IMiX$ܸD9H$60N300"q.9@-\He:{ W" wqGq'IZ6-wdW}rU===9HH M0&ư8y6g%`akMf $F(H3ӡ:T7[]zf41򛧧=u{,2~ D#!c`O|,!T;fցBT7 ^pUJKKKtu5;_ ʲL$`288d1WqA*RIj=ׇ=2͙A(RiXQ(}Ϳ V"pCJY#ZVl/s~wvc}Bx..Ȯ&BU]:hJCUqTMEJ ZqrԖm ydO@\ݫ"{} )~]]պZ)+Er]uK*H^ |Syޒ+x~]~k_#0`5Tԣ@0G ~35?'a"* 1 TaKސE ya)Wy{#&ezI8}r@%% {r ?*~BCMc 0o5: ^/q2{odupw1t=H cd r?QA/nqBى 8Iqۯcz>XPGB~ZTcs4; "R~V~?a[8*NF\Jh)sm2d#bVDxI^D(s ύz XV\%DBЫ7GB:6.Ixq W=3~8(Q@\JܷȆ;G.V@=dz+K~59eʿVn12yO_Cx&RtrOPR[߿l6K*Z|+yyϭ7%,V6umՏNz*ɐXͷpԁ\)"S/Y [&n((|)5lb,.ٹukB B"h`۵U,I$[" 6r{=poj&/1G qrenEs:O^x-o.`ɑM 0ݸgKxqkIkDGUm|-[n}}o'N6!pz߼{Eܢzka^nrO[zV$5"{k^&9_]`aR >9OԱM}ek-' ߐc%|:~hvpA@i F|!! +(!]i$MtA߈""~2G~]$F*7N+Y$~VaG~;͖XZ⊻} +d<7-#~@zup8]e 3pۘ YM>{=gݣxa?M{FO (g(KlGwMBtKyG8)F8hS$Ȓ\+nu\iZɥ K !_ [te $ˈxQoBcy%!V&U~TgU:m D q/!m۪f6]y`U_x'져YZOuQLWv M£>!h/!$ȖmC*l}e0H@zE,K\EiuZY̶S\gX޼rmG/}'nxmg.̐I7B<x_K,g~b"{9e[|AAhT ~!nm9Vރ{M;+`-NJ mI~!^W˦0:>wX[b}'{[fKH5覺W8}~'ۑaidk8#j9Mu- ͓X'$+XŮǯ=qBj9vk׵<$3؊槷V˞| F P 0:L}2"71B Wה1o19Iq:,AS)G3z+Ug14}TEMHzFg]m@Z,6u?s[A*bh#}*@|GܠBŚ!;KI)H;Lq'R03z%!;524e c`$+RD4Xպ2?@2%la ><<&[3ls+LA)- V?](k|Bibsv}7γ/In CgڳQK\UkC{^L@azh Mc; 6~Ih߂@_|a< Ċ1ƦaM I+yGt.v10bk؉qf/@l^ < c O+cѭt,t"E[@oۜ2rc?}9e܎v6o}1@0Xzz.HfSKBM h"ߏ :a@$}. o{HATu|K<Gaa?w%cnJi QS1/emOv`-Gh zU+p ^J@$3xc!ZCJcXGY+ec `S%'UBG k M0C9ӡ^<j" IDATf_=?%4&{eTi~e>' zQD0ǰo1Oلęr޴;z(/cO4%2s&D9Gg\H .dM0% H3" ߒ: jO!u<$? d _z`nӧ6̪IXGr%FR)zzz|-h]?]H0L2xGKz ^5!)%ct'b{_Zp¢<ҌDDO-QoxsQEZog)%SO0>?֙#`YhȽ 7-"u]Y+)ڨp9<{Άhϵ>ׁPw(unTI ]}@ۉ5j?DuCW'"27Aj >6L(";Cb: ҉5SO1rjBcC.d9=~ X7L4 3=~QvvIݒLLLHdYE23DUyJ;,mi9%*L\LP4sAEhS޼L-yk aX2 N8{Ȍ1{ 8S L<^J,2C~#N+7.bm2)w(?la}tggo kU~)$e(t fHoN5abZX$ݞ y,mMtm6%c6S>Z-c0F BO+uFd8dq$#$P\֐+V&7)\'0'C5GSdOan1g=n8o%P[01+{Y2O$v&^ >1 Z!u`Gn_A%I-yM@u#{XZZ¶Kx98?~ tdE((VUD )dARd 8$$j]VU5W-A-hd\Ռh 0qnxB zxPxB ئ@O_9pR]Ŷ/P B Bq{n.et|dTBb+{,{#F?#U|.Xfw 2y& gQ= wNH\sY; 4N2y?5W 4ti[ Cv ҮE~Ŀ7ו,?XA]Yz a%s|A_r٘"!Iہ*E[pQYKm}y2]i2,I7q6 <Fdm.ΠWe%eͭFr6ju^EIU )UΩ@)@pQax3x2԰̜M'\F_ų=J?cqr|f ]vk󵶕e̠᫽e x՚TՅZWjϨ^[`Q%*NաЕ{ROd5.+ ʊ]~$|`Pnv(GW|>=-Yj_L Ipq 5U`o}\\?p?L=9h`mՄb[Ct Tv~CFq@`0xY|l֧:vhZj"66QQ<[?rn_-SNi+xqű]?;guq]e2|qec. hY9/UdYF4͍g(ZKG&GS3a``QuOxh :z 2iE|ܢ:FK9V ~{Jw,EJ* =h(eu1VĒ|W/iubx5 8z5^R$Ԡ25Z ]ܠ0D61IT#E)Ҩ: #fεg}@1D((ii66յR'N\<ݎ>pJDh 5֖B.^%ة8Q^ jug|HP𶹋i"Th *-7M$@¾GҶOlE)43q/.I~b`6H`b(~I^)D]#zmWrkzd|UuٴW2l\W"xrٷ)cO A Vh$jC٫0?|_lW5A_n6e/j- Sp+YG44M, *%$+h`V~m4a;ϱj˦L왋ϧꔆ5 @*HGW]|z>D{ƟVxbīC\![2_" UK*ɚr@_ D/7t/Z>h d/."zʴ-_H>BdUQM]xZI<ݺOKR~!EÇy[R=v9J|w|n>F[;Kô}>sRTȾB!rx,"9(h+kI+ᶹ{հAmRoIŸi ATǗŠX8CH 4BqK!R_ڔ%eE!k >OFm>p5F`&Hpy|8=ngxN7݋x ż:m.rQA=wR؟l i!-GjLcssAvwpRWXtd&8XFt}35UlfDJN4)-PV+A.5Տ9x\DY$ҔQƑ+)Ҿ]E7e}8ua1* %r)B7B_Q ؼ q?֛7b ,j3rF6v Lh ٗd(a΀{k}Q ,YN }&@Pf@Z\)vx}d0kSeeڧ:֌}eRV:?Mbdi5U 9G@S=k)w&:c!пЈ9)v>?M}tO)x]gLf[b>sgvejjcاQΫ-}N 6v> '7rQj9Wq)K|t7:?X - ,7::l>n=Rk߶~ʯ/a-BwKmZKJ$6gݻ;켶ofJ$E&~5͟Erf3-P/Z_G%ӇI ޽r?gmV^^T[nBzRk%};#LܙzJ9I؎0 RPEe0kT)B`pM]%辫  ;NB1|(L#W$N쵹f?Vb҉Rhα{7tH?^Fp醲pȣ1ދ 'cdvx"O3.h#3͔f9N6_{Y-na lobO&Kl Ȧ1~:)w{ "qg;Hk#T=*rbl†FtIyI3qfxZI5GP {#sq?b>f:ZVȺf,*gљ-)tَ P3*OlF_+>;dqT3:,-17z`9ıH4T169F3A/2a?eLܿ@!> ү_F*J}7xD.>-2ʖe'h89 xiW ׁzHOǏwYňR(\0˗Dn#AdM-N#f#Eߊڱhz2>.؅O6ڦs5P(np -6U NX|t]hZ qqoPN-ATȏ(J%^(ioUs7`듨s F,vJKe/9--$+$O:加hاLz;hw'Xҏ<ϼ0!ڟhR@,yZ x<δK#}\4]&]fL6c#M'kTͼe{"6f-ݳ4W#`f#9Nm+sW]ʕp4[W- T %BX~"(jnbRzuI\/"L}guƫ0G2~D?F /W xҢ%FQ6Ӥ_Lףt}ɖPu (p&<^'cYg4'IwȘ<WQ ޻,k)&ъ̾p} }Rwh׳`5;GJiRȑ[T7?"0`IǷm^_Ph`"IuNmB #FƻWHYx X+D#OTb+0Iw֞[V/8ݯzUp*$G}? 0:EP+zÝwG?QH&+D&J%azpHaBc>(ZH@y_ es@qmkAS~tNw1Ԑ`O& ڪ{Stf!ʘYo*gX۬dSrn, rFզ>ᩛ+u4|o„Ydϡi!|.◫o_Ͱs^S}%ͤɫO/A^"d! IDATk 㸔kOĿn zRdql{u=ER|b`8ӎ- DG'δ^;/?]^ЉmZהV{BA 'N뵆"Q(S 2|/Z]˿$Zb~a(JN= > BAԠBL 8 >KeDAL|@GvNl8JM8=ZX;ΰy d`,B0jFa׷$p.w1(ܖ#w<陴E |?H컉#)8w! A~6Y;Ǚ7" 2(sj۶maO(E<|!nɕrcDrPV O2P}aC[$|r@`vHsէ`t9S,Y$  橡m raY[諏QB,%y v]j߿;0xx[]72ě܁i>矪iCZUj.ToM0F~\ɥ4PdS:Q]쟺>4F$we+Oz2z: ta3z09pMx*/iJ]E\R6d~g휎L3H05RP7BrY"q K^#7!1߆dʄ~&{&+A%J3m> P'yRs$;OXT_@/!R6J.e&Q[F܆Ir*h^O4EG$V@J[P}?pٖYN]Z˾!-V, 3.YrCk2ej@*-d~>C$'xI >u9 i$OT6隈5R5`8!VS Ro.$QPZ-' Hf, _tU8uAuqZ9JOEx>.G MfJqn4 [Ȱ0ǎ6ݻy>1fgg(BGGdz"{^=fL悄Ϻp-j5UQЂ!Pܹ*j~_X:ȠZ@5q=:·^a{q\up]p<ڮKvd>{bh!r t,: $Io'HO|;P$z.FWK\d՟4*I%+gdUnQRUu@.[TUj3F@d8Jpl!z/^kP\?U, .ORpYW].yR3h<|t-Ph%-%hA5-\+I9rKjB;SF yRKrz\8EPh^;1jլktrGo G_rFb_Us 1:t̞A].`C I^!_H«E֫+Px@[ԕ7Wm\۲z|wܹ_ӧ k̄2s+׬`%\|_СCM,]dlll(BM&HBB1jμ#2>QuZx:b".&c''G6 ru.VKjm6Жuխ:4" t Rtzd\xq*]h|+Dп1ORK?t|>|>ϟɟL&?޽{dYfs)D+߿To~=[&>8J>[]\5jj؆4p]ppD#X<^F{ tev_{ n#ߩ|eV^O5f 4zbb;|%[4ƿhҕ$]| _t;~)h<َ ]'ZՀXڙz͞<:ilv0c# qS u]m 8Ah2Fb =l=߰kq.[Vתj{w/#'BT.ux -@~Tst( 5ӂ8Q'W4($ ]!cLy^ JD(⪫/9ϟ%q6J%NpPp)LGLWHL27qM;g#2m6xp.B-H;V0+8 mmD31nj,֦- CLF tMӓo0sڲ8Ǟ?@#n0o)&@K߹#lAL'戆e^$xMV(O8SK`C6bC09Vm,Oĉ<C5p{ɑ罿 L䌌0&pwQdEE[W)(^ѤH4%S-_"{e1Iw%b$.r&ԓ{:WTttDzy>]U}ԩ|VݚV{,Xu[Z,`29"Fē~Fed{--W'DWwb?z~ƜYS6YzleU!:ŵR0:+4/V3R+)bWq檯O [EFHiU"B"L:U+fMnYeagI92Dh*l#KcQzj/.8-|ms{ ]$u'zL#y عL>EG-ڑB ;SE3 _X&DUP*sg6V䚳wd`z* <hH ӯbQ6?+$jLe9ުU}KwgqJ ?:%rI6%욘BwS?")zgX_\,T]貎CvgF*6ڞoSn~ C,'eDT_f׷گ,kO3=$qI&Z)L_/lKf4:Qv--ۙEH,ylm@U|wB*3-h}*-nLwTY|.$h09}-IB 2iN*{(8rrbsfNC6'wItD,쎓}zzt* 8&1kBF5Ic,2^HR$ig-˖|9O֊}p֢x~+@˵+:mCJtBpޯcy"F++טcYھA緻\P1Jp<ܡٚ.h:Lt=BD ,/tb7 r +݂(2;Fn:_F.71u.\t|%Dc՚xOʼn<%lyƅ늇Ɋ}-=sMng)Lxb`2]Cݘ/nhƅ 7< ԧЇ>c=Fss ]iVeY-|ӟ$~IvO|$qekn3LIL]>*Y븛+5 YF6| u_+:U6~G?|#gUz?4׉,b1z{{yO|}cqYp&4Q ﰯ\E7kU*mGTr'ˇlm.<|x_G[VBݩPΟvl/v!-Դh5`|  t]Ɨ!2[Noc|X9K:jnUP>FNw›ҖjgQhxH^ { [oaY)oZTIfRWhL7.Hɞ r9/8o{B Ǥxr~RV&5^C_]ľԈ\rv澲fPqmPev U_x*dFjBM-n, ."? "6"Z7!{O YRN!WFh6q&b Y@BBZڄX\ڟp->w3"OŜeS@6u~eVs IDATIxYɴYIX:q\4k\;?rq)ϯmtwO+m'5$Z%#M4~pٍaH'bf{ꦢBm_#ob~< V[W{_pJȹL䤄Mt}.apWP?SLH~ z?݇~U.%c$u:OwWŽM1v! B-4`ܶf lɡYI6!61J!NѬ|$?*7gvuuQ__6GsMKwBQn;pO=yM}F7@- `F+bwbh-W(}$`,믥]ԕM*ܷx?;%\repͤnblo4R:W[v[o{I8^ZG?by{=*x[餵Zkvp+BA;A2DVcKv-w+d3ײtu [;@*]\ْL v V>s# A+ jdcnč/kA;&qKKKwDd׍R%wM$jׁR_d#^z誀'jB&f%ULd"۫&!, TwU}U6QuUU%JҾE$t~Q)k|x^"cnG3z㤷\pJgymWXz"Y%iS&ܼz-\m#("u47V}9O{ŊTzo'0~g\}71ij=C؊͝6UfL[KQee2 =%R0-l].' }l |9kFsh }/)RܱB`yIf[ܮ877wG8hLRo9XKd=XĵDLjX!5+Y`4+aU2IIHm]Zι,ߞN@kk+D`&UE^+)qӈ^!p܁,>W;ob]|ߋ~3P,vJuT{YCޏ~'o:aY;8~Rݝ!ۛA\v)$oeنALW߳E$szoL{!dFadzLIm$gS_XcF)Vdʪ۫<Ʒ.Ra9]9D6av4j*S48tsNJktg%m<^TCpV>5 wO=6quK=:nэb(Jp4Ka< =w!VmWwT+?5ԃ;WݧïDkЬgTq7]{/9i7$=~)ԿGW ^3Jt'G\|9BcaWcDU#\zy[yfO 1;e_r\ DbDbD/0:o;N&Y/UTWn6&}r|_Sjfߤ˭񍨯}+YpMMH!,?U=N3{L{ .8Cv{:8G_'{,O̗|I >Xx}8rWPGkrOg^mҕs&zQ~;O]3%BroǾ}ߑG:(y-ۀt@z@{Έ$2uQZP; 1 Ǩ?#x=X8HYVQ~7O@dwUKX]xhugItwdէo GŘFu>4ŷ#0$z>Fd YxdyqodQ̈AH`:Q+Ĵ]YY$2YGt8F6MvË::NvQ7_[$Y,?Dtz&:Wp/x^}vl# aZ/Alȹ:RO l5jvA|0ڢ#x.x~%<!ݾAr+?`ծ:Tl Q:֯oi!ہ  '׶8ׄBF$:óa!ˇ(:ۈk@6Q wBy(C#Jr}“uE} k11FƑ{B_ nfݡԸHt"l``˩mjK N,{6\Tq)$;ز@0eD]_j3m:&ClFX]]%rͰV,O~ILJhډY-"XxQI&pME}pSw-km&]Kz屖o3o7Ÿ(--;nbhs7ix_&ITυ,/8$rݓxyc} E~XG!QHZ&t%L\ᝃX/ ,9lm;wIה :̱EqsKW|ƇG"o?Bߊ>E\kr}8N94~.Px{o1uHy2S 3)#;{CJ (GާFq}كH8Zo\j\$ T ^E | [;lK#D12 Sgץ޲w ::,U[ݭҰDt!Ft!F"{d 2H6'TxΤ]߅*RZL +t.8D=pt}jf233ΤwݟÊZ`w-iJS^z ɹEK14F:}fa],$tBt,FY6O%݋#DcBY4Ʈa6:S9;j%Jg圌-E6՗ӝJdk"lkJ B(DA$м}:He \e{5T+$J>{XGgY^{2%3fڸ3ź">z=c)ԨW/g6&`PؒoD̉U7S MtuR2W! j0wx<}"WYt/eIT&2:CO >d@[%^/Ş"uv Ձ3gJ~JMָt͓-45lHXkͲY">~?*#+TSlRpg$Mq&V,YfMܙR=_;+]6l]uK+$YkWn\kSi U.es8Z-1KpZ͛;n<,T6ZYJU{AHA+ăLTy[](:k:cP HĪ@+MAǓ. 컎Cq|/\=Ȫ5+lץ D2ul\ڻZsC¡sOto`a32`ROI~7>Ҏu ,y4Nv}xi혺3u7OߙHE(I1<"R]#$vc `OoeW4@p.Ķ ߔdߔ>Ͷo x.ڤ N[>|`@.PrfoωȮo( gWAQD]+Ƒu )9//|yAnr,D h )ETB-dOʃT12bYdoFTE"C;bY lY;' wG&`i I{҈/k=K&Q'ձ{)˵YL$ۜtN1R$בERd\w_ ړr-~ Ӎ9sܵ*2's|lmۅ*V?/4Y×wL[Kr~~ ϳ~i/9ů-ϴۏ΃9z}II2?{:n\ .x3i&yLٴ^O۷:mOB;K \]Nڿޅup+O&hYm_4Cjg3!G̉lN\3օ6f~b| Mf5x@V'Hj%X|S|S!!EVt.ȃ=fbryB"9vN^VQ*Wdk-)Ka)㋢s8Aԣ=) L,BB&^GmͰq"T&їtp {˅!DKB`x-,\ԁ9wɗ S/En?|r`m..98 @!itj [-ԟNE ŊiZA~eiUD_~P̟;>{T}4 DAU A3)y-H& ՂksjWusGfv :Ƣsȅcd ̂B,ae[",!zE A }T_؁dHSVyWȉw΍lȒߞ_=]&ThJckD=>2YҼbJz9f9lȐfN'tٚVz}Q}Q>#46Vol6W$s-Vͺlį2ֿ*^7$XۈwpU VI"~ֿ ݔidd2r`0apKK ~~cq:::Xٵ^%WccGiT7,.'?_Eݡ`j*gXڒb CN^5%Ba}Z\wwCVa_xY4P2fbUP2P*x&(6,g։bȥ-۪zZ^ ^ndOAm={u_}<;''}߮UeBV%\|9T|OC̵2i/wnj+.winuZ[' Ö,iЋ\~+nx(mn4ҙk(:HBM%8?Q]9 d`v"jq*YG:e!HyO+,ݗTK]|< oKﹽN6FUW8',>/܇`8CItzOu5~n#6ׄ$$9 !)ޢ_;h 9 4?/6S B?d9{Țύ)&Y pֿ7onqfƠX@YQіr) `j5QU"Zņl 8Vnv\4+J\OLK}'EfŻVm &`_\G {$ʁ]+0޳}[q-'snu$;j_"cF?4J9:ClbJ&șw&"L$Oco߾ۮ r+frup+j eK`Euٿ7a럹X["!vU[0L.0BO~y&&&(,,,(BHY/}>`Z!8#yLŒeVpy"ӏMӽ%0xޙ@L߳~o VNGE _qZb|ȾK*u)P&y34Lx8B)]uF)V^ܿ8_@s=t)pc'9'?0蛆Yٺƒ;0zZBwOxg!` @ QI߷Ȗ!~䁡 4tp}9WD$`z=˶s'E#I냜2ZZS=O"sH/q ,X qN'80\PQU8A䢕gV,{}בUc'8^Nˤ5'^d>ƶ5x#^q~MAL?+0x?{o]nna$}s8Iwê1N,H߱ HR\yԞWЖ/kj7ޤABs7s>r[Y /0kx0d;/S*' j!DGA&fW=H)&NxxSYt9k"z@Ȋ,w.3k:w?kمb5iUB,,g=Q&S@JHHD?&ht3r S0VASs#ۇ}g֣Kի,š"B`.)ӚX+<+O01 9mB+Ơ,nպ6aTD+.~ rΤ0jw݇{؃/ԣDf#B ^A0/ǙOW^|u}tC'O[GLqzEoS2)x,ͥkD]Ux^x>9.]ķ->;* rӸ|u U"WLֿr[sML(%WP+x~65HYiN:C7qO~'N /GpXsaR)&''tì,wDH$-gTYCuq~mKtdU&JT̛S(=u>Ԙ&Xj+m/[3!{ҁvKȊ\s4 Ĕ Q7SoybuI*2qT?9L1P @14CCI(Vr]]ˑv) h. S2) пWcWMrn+j(ҸQ_lbl&λõWWo@$[XY_S0S! VfzNܼ]$D ח}}16|9`L PB9k=3+]y1ֳ鮪:Uus~d^&suM)Xbb8V_{?RIAS$eA9/'鋉EN4 -ĖcLmeL.4AeL[Gqh;ɑt>|_/>PUj|TGctoIZJGMGR~sU*#x}wpc+mH%};c7; XB$ȲL" L~nG%Q)B{mp=SYdlv) l`V,{( ;eWٰ # UͮVu#sg9Xy^kSQKYf/VZjc+ K+ѓ<Kp\wCvQ|Lͣtb+.^DqM \ 3? ^7:l]O l9&7Mʇn̐ gܺ - K,rj5(LwOiKs}W|+t-YKV$)D7DZ6FĊ0e*on~ӷv׆ "M`n*#v{fC 1ATZ^9J״:,iR;(CCC(Gqk 䢂N+NC%}eNH:]$EB]GgiM-h[JhFj\P3® KD2QɖPs*aLw.a Y0U! p.ƼϜ>&MLR 'VDEz<9TI۸K` oSʗ <g"1~wwzi++QΖ÷,8Bxprnu s>1؁Ρ<ķѻ 2·ՒWrR+Gt +n΄]Owte}4ڌo2,N%sHf)IǕdC%9Hl8Nhj꛽l|wm܈$rq0_9G9Vm'ߕG @SmQå,qlQ'pU|401 frL>^ Oc|M,so+F<ccc~݅&пՓz^!n7 su$ԭCN^#"5qYD&.,}CwyfzJ `||rH6Ų .{Fs LvHRI" =Dnj\%4Fyٟso]r^))|,j81\2xdbH5pGT,-عH)^BT-yB0BH_2Owɧ[Kt&F$AH3n2|1Rϵ4myZI#upɧb9q/ *@\{| <ŋ{8 M H: :V"c, ɌXJL@*HyM>lK 9<s覎pUwT%.w) (e֕RD?M!g}+zڔeN Mu

* Incoming data: 0x024281 * Timestamp format : Terrestrial Trunked Radio * * @throws Exception for any error */ @Test public void testParseTimeStampFromTETRASavedInMSB0() throws Exception { final byte[] TEST_DATA = new byte[] {0x2, 0x42, (byte) 0x81}; class TetraTimestamp { @Bin(type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_2, order = 1, bitOrder = JBBPBitOrder.MSB0) byte timezone; @Bin(type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_2, order = 2, bitOrder = JBBPBitOrder.MSB0) byte reserved; @Bin(type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_4, order = 3, bitOrder = JBBPBitOrder.MSB0) byte month; @Bin(type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_5, order = 4, bitOrder = JBBPBitOrder.MSB0) byte day; @Bin(type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_5, order = 5, bitOrder = JBBPBitOrder.MSB0) byte hour; @Bin(type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_6, order = 6, bitOrder = JBBPBitOrder.MSB0) byte minute; } TetraTimestamp parsed = JBBPParser.prepare( "bit:2 timezone; bit:2 reserved; bit:4 month; bit:5 day; bit:5 hour; bit:6 minute;", JBBPBitOrder.MSB0).parse(TEST_DATA).mapTo(new TetraTimestamp()); assertEquals(2, parsed.month); assertEquals(8, parsed.day); assertEquals(10, parsed.hour); assertEquals(1, parsed.minute); assertArrayEquals(TEST_DATA, JBBPOut.BeginBin(JBBPBitOrder.MSB0).Bin(parsed).End().toByteArray()); } /** * Case 28-jul-2016 * Simultaneous usage of expression evaluator from multiple threads. * * Issue #10, assertArrayLength throws exception in multi-thread * * @throws Exception for any error */ @Test public void testMultiThreadUsageOfParser() throws Exception { final JBBPParser parserIP = JBBPParser.prepare("skip:14; // skip bytes till the frame\n" + "bit:4 InternetHeaderLength;" + "bit:4 Version;" + "bit:2 ECN;" + "bit:6 DSCP;" + "ushort TotalPacketLength;" + "ushort Identification;" + "bit:8 IPFlagsAndFragmentOffset_low;" + "bit:5 IPFlagsAndFragmentOffset_high;" + "bit:1 MoreFragment;" + "bit:1 DonotFragment;" + "bit:1 ReservedBit;" + "ubyte TTL;" + "ubyte Protocol;" + "ushort HeaderChecksum;" + "int SourceAddress;" + "int DestinationAddress;" + "byte [(InternetHeaderLength-5)*4] Options;"); final JBBPParser parserTCP = JBBPParser.prepare("skip:34; // skip bytes till the frame\n" + "ushort SourcePort;" + "ushort DestinationPort;" + "int SequenceNumber;" + "int AcknowledgementNumber;" + "bit:1 NONCE;" + "bit:3 RESERVED;" + "bit:4 HLEN;" + "bit:1 FIN;" + "bit:1 SYN;" + "bit:1 RST;" + "bit:1 PSH;" + "bit:1 ACK;" + "bit:1 URG;" + "bit:1 ECNECHO;" + "bit:1 CWR;" + "ushort WindowSize;" + "ushort TCPCheckSum;" + "ushort UrgentPointer;" + "byte [HLEN*4-20] Option;" + "byte [_] Data;"); byte[] testArray; try (InputStream inStream = getResourceAsInputStream("tcppacket.bin")) { testArray = new JBBPBitInputStream(inStream).readByteArray(-1); assertEquals(173, testArray.length); } final byte[] theData = testArray; final AtomicInteger errorCounter = new AtomicInteger(); final AtomicLong parsingCounter = new AtomicLong(); final int ITERATIONS = 1000; final Runnable test = () -> { for (int i = 0; i < ITERATIONS; i++) { try { Thread.sleep(System.nanoTime() & 0xF); final byte[] ippacket = parserTCP.parse(theData).findFieldForNameAndType("Data", JBBPFieldArrayByte.class) .getArray(); assertEquals(119, ippacket.length); final byte[] optionsip = parserIP.parse(ippacket).findFieldForNameAndType("Options", JBBPFieldArrayByte.class) .getArray(); assertEquals(4, optionsip.length); parsingCounter.incrementAndGet(); } catch (Exception ex) { ex.printStackTrace(); errorCounter.incrementAndGet(); } } }; final Thread[] threads = new Thread[15]; for (int i = 0; i < threads.length; i++) { final Thread testThread = new Thread(test, "jbbp_test_thread" + i); testThread.setDaemon(true); threads[i] = testThread; testThread.start(); } for (final Thread t : threads) { t.join(); } assertEquals(threads.length * ITERATIONS, parsingCounter.get()); assertEquals(0, errorCounter.get()); } @Test public void testParseBitsThroughDslBasedScriptAndMapping() throws Exception { class Bits { @Bin(name = "a", type = BinType.BIT_ARRAY, bitNumber = JBBPBitNumber.BITS_1, arraySizeExpr = "_") byte[] bit; } JBBPParser parser = JBBPParser.prepare(JBBPDslBuilder.Begin().AnnotatedClassFields(Bits.class).End()); Bits parsed = parser.parse(new byte[] {73}).mapTo(new Bits()); System.out.println(JBBPTextWriter.makeStrWriter().Bin(parsed).Close().toString()); assertArrayEquals(new byte[] {1, 0, 0, 1, 0, 0, 1, 0}, parsed.bit); } /** * Case 03-feb-2020 * Issue #26, Bug in parsing of stringj written in MSB0 * * @throws Exception for any error */ @Test public void testStringMsb0() throws Exception { JBBPOut joparam = JBBPOut.BeginBin(JBBPByteOrder.BIG_ENDIAN, JBBPBitOrder.MSB0).String("zzzz").Int(12345); final byte[] array = joparam.End().toByteArray(); assertArrayEquals(new byte[] {32, 94, 94, 94, 94, 0, 0, 0x0C, (byte) 0x9C}, array); final JBBPFieldStruct bitflds = JBBPParser.prepare("stringj fin; int i;", JBBPBitOrder.MSB0).parse(array); assertEquals("zzzz", bitflds.findFieldForNameAndType("fin", JBBPFieldString.class).getAsString()); assertEquals(12345, bitflds.findFieldForNameAndType("i", JBBPFieldInt.class).getAsInt()); } /** * Case 18-feb-2020, #27 Strings in other codecs * Example how to implement custom ASCII string format * * @throws Exception for any error */ @Test public void testAscIIPascalString() throws Exception { final class AscIIPascalString implements JBBPCustomFieldTypeProcessor { private final String[] TYPES = new String[] {"asciistr"}; @Override public String[] getCustomFieldTypes() { return TYPES; } @Override public boolean isAllowed( final JBBPFieldTypeParameterContainer fieldType, final String fieldName, final int extraData, final boolean isArray ) { return extraData == 0; } @Override public JBBPAbstractField readCustomFieldType( final JBBPBitInputStream in, final JBBPBitOrder bitOrder, final int parserFlags, final JBBPFieldTypeParameterContainer customTypeFieldInfo, final JBBPNamedFieldInfo fieldName, final int extraData, final boolean readWholeStream, final int arrayLength ) throws IOException { if (arrayLength < 0) { return new JBBPFieldString(fieldName, readPascalAscIIString(in)); } else { final String[] loadedStrings; if (readWholeStream) { final List strings = new ArrayList<>(); while (in.hasAvailableData()) { strings.add(readPascalAscIIString(in)); } loadedStrings = strings.toArray(new String[0]); } else { loadedStrings = new String[arrayLength]; for (int i = 0; i < arrayLength; i++) { loadedStrings[i] = readPascalAscIIString(in); } } return new JBBPFieldArrayString(fieldName, loadedStrings); } } private String readPascalAscIIString(final JBBPBitInputStream in) throws IOException { final byte[] charArray = in.readByteArray(in.readByte()); return new String(charArray, StandardCharsets.US_ASCII); } } final JBBPParser parserSingle = JBBPParser.prepare("asciistr str1; asciistr str2;", new AscIIPascalString()); final JBBPFieldStruct parsedSingle = parserSingle.parse(new byte[] {5, 65, 66, 67, 68, 69, 0}); assertEquals("ABCDE", parsedSingle.findFieldForNameAndType("str1", JBBPFieldString.class).getAsString()); assertEquals("", parsedSingle.findFieldForNameAndType("str2", JBBPFieldString.class).getAsString()); final JBBPParser parserArray = JBBPParser.prepare("asciistr [2] str1; asciistr [_] str2;", new AscIIPascalString()); final JBBPFieldStruct parsedArrays = parserArray.parse(new byte[] {2, 65, 66, 1, 67, 3, 68, 69, 70, 2, 71, 72, 1, 73}); assertArrayEquals(new String[] {"AB", "C"}, parsedArrays.findFieldForNameAndType("str1", JBBPFieldArrayString.class).getArray()); assertArrayEquals(new String[] {"DEF", "GH", "I"}, parsedArrays.findFieldForNameAndType("str2", JBBPFieldArrayString.class).getArray()); } /** * Case 10-aug-2017 * NullPointer exception when referencing a JBBPCustomFieldTypeProcessor parsed field. * * Issue #16, NullPointer exception when referencing a JBBPCustomFieldTypeProcessor parsed field * * @throws Exception for any error */ @Test public void testNPEWhenReferencingCustomFieldProcessorAsArrayLength() throws Exception { final class Uint32 implements JBBPCustomFieldTypeProcessor { private final String[] TYPES = new String[] {"uint32"}; private long uint32_read(final JBBPBitInputStream in, final JBBPByteOrder byteOrder, final JBBPBitOrder bitOrder) throws IOException { final int signedInt = in.readInt(byteOrder); return signedInt & 0xffffffffL; } @Override public String[] getCustomFieldTypes() { return TYPES; } @Override public boolean isAllowed(final JBBPFieldTypeParameterContainer fieldType, final String fieldName, final int extraData, final boolean isArray) { return extraData == 0; } private long[] convertLongs(List longs) { long[] ret = new long[longs.size()]; Iterator iterator = longs.iterator(); for (int i = 0; i < ret.length; i++) { ret[i] = iterator.next(); } return ret; } @Override public JBBPAbstractField readCustomFieldType(final JBBPBitInputStream in, final JBBPBitOrder bitOrder, final int parserFlags, final JBBPFieldTypeParameterContainer customTypeFieldInfo, final JBBPNamedFieldInfo fieldName, final int extraData, final boolean readWholeStream, final int arrayLength) throws IOException { if (arrayLength < 0) { final long uint32_val = uint32_read(in, customTypeFieldInfo.getByteOrder(), bitOrder); return new JBBPFieldLong(fieldName, uint32_val); } else { if (readWholeStream) { ArrayList laLaLaLaLong = new ArrayList<>(); try { while (!Thread.currentThread().isInterrupted()) { laLaLaLaLong.add(uint32_read(in, customTypeFieldInfo.getByteOrder(), bitOrder)); } } catch (EOFException e) { } long[] longs = convertLongs(laLaLaLaLong); return new JBBPFieldArrayLong(fieldName, longs); } else { final long[] array = new long[arrayLength]; for (int i = 0; i < arrayLength; i++) { array[i] = uint32_read(in, customTypeFieldInfo.getByteOrder(), bitOrder); } return new JBBPFieldArrayLong(fieldName, array); } } } } final JBBPParser sasParser = JBBPParser.prepare( ">uint32 keycount;" + "key [keycount] {" + "byte[1] contentId; " + "byte[1] keyData; " + "}" , new Uint32() ); JBBPFieldStruct result = sasParser.parse(new byte[] {0, 0, 0, 0, 0x01, (byte) 0xFC, 0x05}); assertEquals(0, ((JBBPNumericField) result.findFieldForName("keycount")).getAsInt()); result = sasParser.parse(new byte[] {0, 0, 0, 2, 1, 2, 3, 4}); assertEquals(2, ((JBBPNumericField) result.findFieldForName("keycount")).getAsInt()); } /** * Case 09-jun-2020 * Example how to use external variable value provider. * * @throws Exception for any error */ @Test public void testByteArrayWhichLengthProvidedExternally() throws Exception { class BKlazz { @Bin(order = 1, type = BinType.BYTE_ARRAY) byte[] a; @Bin(order = 2, type = BinType.BYTE_ARRAY) byte[] b; @Bin(order = 3, type = BinType.BYTE_ARRAY) byte[] c; } JBBPParser parser = JBBPParser.prepare("byte [$alen] a; byte [$blen] b; byte [$clen] c;"); BKlazz parsed = parser.parse(new byte[] {1, 2, 3}, null, new JBBPExternalValueProvider() { @Override public int provideArraySize(String fieldName, JBBPNamedNumericFieldMap numericFieldMap, JBBPCompiledBlock compiledBlock) { if ("alen".equals(fieldName)) { return 0; } else if ("blen".equals(fieldName)) { return 3; } else if ("clen".equals(fieldName)) { return 0; } else { throw new IllegalArgumentException("Unknown name: " + fieldName); } } }).mapTo(new BKlazz()); assertArrayEquals(new byte[0], parsed.a); assertArrayEquals(new byte[] {1, 2, 3}, parsed.b); assertArrayEquals(new byte[0], parsed.c); } /** * Case 09-jun-2020 * Example how to write custom field type read-write-mapping processor for nullable byte array. * * @throws Exception for any error */ @Test public void testNullableByteArrayField() throws Exception { class NullableByteArrayProcessor implements JBBPCustomFieldWriter, JBBPMapperCustomFieldProcessor, JBBPCustomFieldTypeProcessor { private final String TYPE = "nullableByteArray"; private final String[] CUSTOM_TYPE = new String[] {TYPE.toLowerCase(Locale.ENGLISH)}; @Override public String[] getCustomFieldTypes() { return CUSTOM_TYPE; } @Override public boolean isAllowed(JBBPFieldTypeParameterContainer fieldType, String fieldName, int extraData, boolean isArray) { return !isArray; } private byte[] readFromStream(JBBPByteOrder byteOrder, JBBPBitInputStream in) throws IOException { final int len = in.readInt(byteOrder); return len < 0 ? null : in.readByteArray(len); } @Override public JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, int parserFlags, JBBPFieldTypeParameterContainer customTypeFieldInfo, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException { if (arrayLength < 0) { return toStruct(fieldName, readFromStream(customTypeFieldInfo.getByteOrder(), in)); } else { throw new IllegalArgumentException("Array of nullable byte arrays is unsupported"); } } private void writeTo(final JBBPBitOutputStream outStream, final JBBPByteOrder order, final byte[] data) throws IOException { if (data == null) { outStream.writeInt(-1, order); } else { outStream.writeInt(data.length, order); outStream.write(data); } } @Override public void writeCustomField(JBBPOut context, JBBPBitOutputStream outStream, Object instanceToSave, Field instanceCustomField, Bin fieldAnnotation, Object value) throws IOException { if (fieldAnnotation.customType().equals(TYPE)) { writeTo(outStream, fieldAnnotation.byteOrder(), (byte[]) value); } else { throw new IllegalArgumentException( "Unsupported custom type: " + fieldAnnotation.customType()); } } private JBBPFieldStruct toStruct(JBBPNamedFieldInfo fieldName, final byte[] array) { if (array == null) { return new JBBPFieldStruct(fieldName, new JBBPAbstractField[0]); } else { return new JBBPFieldStruct(fieldName, new JBBPAbstractField[] {new JBBPFieldArrayByte(null, array)}); } } private byte[] fromStruct(final JBBPFieldStruct struct) { final JBBPAbstractField[] fields = struct.getArray(); return fields.length == 0 ? null : ((JBBPFieldArrayByte) struct.getArray()[0]).getArray(); } @Override public Object prepareObjectForMapping(JBBPFieldStruct parsedBlock, Bin annotation, Field field) { if (annotation.customType().equals(TYPE)) { if (field.getType() == byte[][].class) { final JBBPFieldArrayStruct structs = parsedBlock.findFieldForNameAndType(field.getName(), JBBPFieldArrayStruct.class); final byte[][] result = new byte[structs.size()][]; for (int i = 0; i < structs.size(); i++) { result[i] = fromStruct(structs.getElementAt(i)); } return result; } else { return fromStruct( parsedBlock.findFieldForNameAndType(field.getName(), JBBPFieldStruct.class)); } } else { throw new IllegalArgumentException("Unexpected custom type: " + annotation.customType()); } } } ; final NullableByteArrayProcessor nullableByteArrayProcessor = new NullableByteArrayProcessor(); class Klazz { @Bin int a; @Bin(custom = true, customType = "nullableByteArray") byte[] b; @Bin int c; } ; Klazz object = new Klazz(); object.a = 12345; object.b = null; object.c = 7890; final byte[] withNullField = JBBPOut.BeginBin().Bin(object, nullableByteArrayProcessor).End().toByteArray(); assertArrayEquals( new byte[] {0, 0, 48, 57, (byte) -1, (byte) -1, (byte) -1, (byte) -1, 0, 0, 30, (byte) -46}, withNullField); object = new Klazz(); object.a = 12345; object.b = new byte[] {1, 2, 3}; object.c = 7890; final byte[] withContent = JBBPOut.BeginBin().Bin(object, nullableByteArrayProcessor).End().toByteArray(); assertArrayEquals(new byte[] {0, 0, 48, 57, 0, 0, 0, 3, 1, 2, 3, 0, 0, 30, (byte) -46}, withContent); object = new Klazz(); object.a = 12345; object.b = new byte[0]; object.c = 7890; final byte[] withZeroLength = JBBPOut.BeginBin().Bin(object, nullableByteArrayProcessor).End().toByteArray(); assertArrayEquals(new byte[] {0, 0, 48, 57, 0, 0, 0, 0, 0, 0, 30, (byte) -46}, withZeroLength); JBBPParser parser = JBBPParser.prepare("int a; nullableByteArray b; int c;", nullableByteArrayProcessor); Klazz parsed = parser.parse(withNullField).mapTo(new Klazz(), nullableByteArrayProcessor); assertEquals(12345, parsed.a); assertNull(parsed.b); assertEquals(7890, parsed.c); parsed = parser.parse(withZeroLength).mapTo(new Klazz(), nullableByteArrayProcessor); assertEquals(12345, parsed.a); assertArrayEquals(new byte[0], parsed.b); assertEquals(7890, parsed.c); parsed = parser.parse(withContent).mapTo(new Klazz(), nullableByteArrayProcessor); assertEquals(12345, parsed.a); assertArrayEquals(new byte[] {1, 2, 3}, parsed.b); assertEquals(7890, parsed.c); } @Bin public class Parent { @Bin(order = 1, type = BinType.USHORT, comment = "Size of package header") public int headerSize; } @Bin public class Child extends Parent { @Bin(order = 2, type = BinType.BYTE_ARRAY, arraySizeExpr = "2") public String body; } /* * Test for reported issue #42 https://github.com/raydac/java-binary-block-parser/issues/42 */ @Test public void testCase_github_bug42_ThereIsNotAnyOpenedStruct() throws Exception { byte[] data = new byte[] {0x12, 0x34, 0x20, 0x20}; final JBBPParser parser = JBBPParser.prepare(JBBPDslBuilder.Begin().AnnotatedClass(Child.class).End()); JBBPFieldStruct parsed = parser.parse(data); final Child parsedPackage = parsed.findFieldForNameAndType("Child", JBBPFieldStruct.class).mapTo(new Child()); assertEquals(0x1234, parsedPackage.headerSize); assertEquals(" ", parsedPackage.body); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/ClassParsingTest.java000066400000000000000000000311371445000440600326240ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPNamedNumericFieldMap; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.JBBPVarFieldProcessor; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.model.JBBPAbstractArrayField; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; public class ClassParsingTest extends AbstractParserIntegrationTest { // public static final int FORMAT_J2SE8 = 0x34; public static final int FORMAT_J2SE7 = 0x33; // public static final int FORMAT_J2SE6 = 0x32; public static final int FORMAT_J2SE5 = 0x31; // public static final int FORMAT_JDK14 = 0x30; // public static final int FORMAT_JDK13 = 0x2F; // public static final int FORMAT_JDK12 = 0x2E; // public static final int FORMAT_JDK11 = 0x2D; private static final int CONSTANT_Class = 7; private static final int CONSTANT_Fieldref = 9; private static final int CONSTANT_Methodref = 10; private static final int CONSTANT_InterfaceMethodref = 11; private static final int CONSTANT_String = 8; private static final int CONSTANT_Integer = 3; private static final int CONSTANT_Float = 4; private static final int CONSTANT_Long = 5; private static final int CONSTANT_Double = 6; private static final int CONSTANT_NameAndType = 12; private static final int CONSTANT_Utf8 = 1; private static final int CONSTANT_MethodHandle = 15; private static final int CONSTANT_MethodType = 16; private static final int CONSTANT_InvokeDynamic = 18; private static final String[] AllowedAttributes = new String[] { "ConstantValue", "Code", "StackMapTable", "Exceptions", "InnerClasses", "EnclosingMethod", "Synthetic", "Signature", "SourceFile", "SourceDebugExtension", "LineNumberTable", "LocalVariableTable", "LocalVariableTypeTable", "Deprecated", "RuntimeVisibleAnnotations", "RuntimeInvisibleAnnotations", "RuntimeVisibleParameterAnnotations", "RuntimeInvisibleParameterAnnotations", "AnnotationDefault", "BootstrapMethods" }; private static final JBBPParser classParser = JBBPParser.prepare( " int magic;" + "ushort minor_version;" + "ushort major_version;" + "ushort constant_pool_count;" + "constant_pool_item [constant_pool_count - 1] { var [1] cp_item; //we can make any array size because the field will be processed by a custom processor\n }" + "ushort access_flags;" + "ushort this_class;" + "ushort super_class;" + "ushort interfaces_count;" + "interfaces[interfaces_count]{" + " ushort index;" + "}" + "ushort fields_count;" + "fields[fields_count]{" + " ushort access_flags;" + " ushort name_index;" + " ushort descriptor_index;" + " ushort attributes_count;" + " attribute_info [attributes_count] {" + " ushort name_index;" + " int length;" + " byte [length] info;" + " }" + "}" + "ushort methods_count;" + "methods[methods_count]{" + " ushort access_flags;" + " ushort name_index;" + " ushort descriptor_index;" + " ushort attributes_count;" + " attribute_info[attributes_count]{" + " ushort name_index;" + " int length;" + " byte [length] info;" + " }" + "}" + "ushort attributes_count;" + "attribute_info[attributes_count]{" + " ushort name_index;" + " int length;" + " byte [length] info;" + "}"); private JBBPVarFieldProcessor getVarFieldProcessor() { return new JBBPVarFieldProcessor() { @Override public JBBPAbstractArrayField readVarArray( final JBBPBitInputStream inStream, final int arraySize, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { if ("cp_item".equals(fieldName.getFieldName())) { final int tagItem = inStream.readByte(); final JBBPFieldArrayByte result; switch (tagItem) { case CONSTANT_Class: case CONSTANT_String: case CONSTANT_MethodType: { result = new JBBPFieldArrayByte(fieldName, new byte[] {(byte) tagItem, (byte) inStream.readByte(), (byte) inStream.readByte()}); } break; case CONSTANT_InterfaceMethodref: case CONSTANT_Methodref: case CONSTANT_Fieldref: case CONSTANT_Float: case CONSTANT_Integer: case CONSTANT_NameAndType: { result = new JBBPFieldArrayByte(fieldName, new byte[] {(byte) tagItem, (byte) inStream.readByte(), (byte) inStream.readByte(), (byte) inStream.readByte(), (byte) inStream.readByte()}); } break; case CONSTANT_Double: case CONSTANT_Long: { result = new JBBPFieldArrayByte(fieldName, new byte[] {(byte) tagItem, (byte) inStream.readByte(), (byte) inStream.readByte(), (byte) inStream.readByte(), (byte) inStream.readByte(), (byte) inStream.readByte(), (byte) inStream.readByte(), (byte) inStream.readByte(), (byte) inStream.readByte()}); } break; case CONSTANT_Utf8: { final int len = inStream.readUnsignedShort(byteOrder); final byte[] array = inStream.readByteArray(len); final byte[] res = new byte[array.length + 3]; res[0] = (byte) tagItem; res[1] = (byte) (len >>> 8); res[2] = (byte) len; System.arraycopy(array, 0, res, 3, array.length); result = new JBBPFieldArrayByte(fieldName, res); } break; case CONSTANT_MethodHandle: case CONSTANT_InvokeDynamic: { result = new JBBPFieldArrayByte(fieldName, new byte[] {(byte) tagItem, (byte) inStream.readByte(), (byte) inStream.readByte(), (byte) inStream.readByte()}); } break; default: { fail("Can't process constant pool tag [" + tagItem + ']'); throw new Error(); } } return result; } fail("Unsupported var field [" + fieldName + ']'); return null; } @Override public JBBPAbstractField readVarField(final JBBPBitInputStream inStream, final JBBPNamedFieldInfo fieldName, final int extraValue, final JBBPByteOrder byteOrder, final JBBPNamedNumericFieldMap numericFieldMap) throws IOException { fail("Must not be called"); return null; } }; } private String extractClassNameFromConstantPool(final ClassFile klazz, final int classInfoIndex) throws Exception { final byte[] constantClassInfo = klazz.constant_pool_item[classInfoIndex - 1].cp_item; final int utf8Index = (constantClassInfo[1] << 8) | (constantClassInfo[2] & 0xFF); return extractUtf8FromConstantPool(klazz, utf8Index); } private String extractUtf8FromConstantPool(final ClassFile klazz, final int utf8Index) throws Exception { final byte[] utf8data = klazz.constant_pool_item[utf8Index - 1].cp_item; return new String(utf8data, 3, utf8data.length - 3, StandardCharsets.UTF_8); } private void assertAttribute(final ClassFile klass, final AttributeInfo attr) throws Exception { final String attrName = extractUtf8FromConstantPool(klass, attr.name_index); for (final String s : AllowedAttributes) { if (s.equals(attrName)) { return; } } fail("Disallowed attribute '" + attrName + '\''); } private void assertClass(final ClassFile klazz, final int majorVersion, final String className, final String superclass, final int interfaces, final int fields, final int methods) throws Exception { assertEquals(0xCAFEBABE, klazz.magic); assertEquals(0, klazz.minor_version); assertEquals(majorVersion, klazz.major_version); assertEquals(className, extractClassNameFromConstantPool(klazz, klazz.this_class)); assertEquals(superclass, extractClassNameFromConstantPool(klazz, klazz.super_class)); assertEquals(interfaces, klazz.interfaces.length); assertEquals(fields, klazz.fields.length); assertEquals(methods, klazz.methods.length); if (fields > 0) { for (final FieldMethodInfo info : klazz.fields) { assertTrue(klazz.attribute_info.length > 0); for (final AttributeInfo ainfo : info.attribute_info) { assertAttribute(klazz, ainfo); } } } if (methods > 0) { for (final FieldMethodInfo info : klazz.methods) { assertTrue(klazz.attribute_info.length > 0); for (final AttributeInfo ainfo : info.attribute_info) { assertAttribute(klazz, ainfo); } } } assertTrue(klazz.attribute_info.length > 0); for (final AttributeInfo ainfo : klazz.attribute_info) { assertAttribute(klazz, ainfo); } } @Test public void testParseClassFile_TestClass() throws Exception { final InputStream in = getResourceAsInputStream("test.clazz"); try { final ClassFile klazz = classParser.parse(in, getVarFieldProcessor(), null).mapTo(new ClassFile()); assertClass(klazz, FORMAT_J2SE7, "Test", "java/lang/Object", 0, 2, 4); assertEquals(831, classParser.getFinalStreamByteCounter()); } finally { JBBPUtils.closeQuietly(in); } } @Test public void testParseClassFile_HexEngineClass() throws Exception { final InputStream in = getResourceAsInputStream("hexengine.clazz"); try { final ClassFile klazz = classParser.parse(in, getVarFieldProcessor(), null).mapTo(new ClassFile()); assertClass(klazz, FORMAT_J2SE5, "com/igormaznitsa/jhexed/engine/HexEngine", "java/lang/Object", 0, 22, 44); assertEquals(21364, classParser.getFinalStreamByteCounter()); } finally { JBBPUtils.closeQuietly(in); } } @Bin public static class ConstantPoolItem { byte[] cp_item; } @Bin public static class Interface { char index; } @Bin public static class FieldMethodInfo { char access_flags; char name_index; char descriptor_index; char attributes_count; AttributeInfo[] attribute_info; } @Bin public static class AttributeInfo { char name_index; int length; byte[] info; } @Bin public static class ClassFile { int magic; char minor_version; char major_version; char constant_pool_count; ConstantPoolItem[] constant_pool_item; char access_flags; char this_class; char super_class; char interfaces_count; Interface[] interfaces; char fields_count; FieldMethodInfo[] fields; char methods_count; FieldMethodInfo[] methods; char attributes_count; AttributeInfo[] attribute_info; } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/ConvertToJSONTest.java000066400000000000000000000146411445000440600326510ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.model.JBBPAbstractArrayField; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBit; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayInt; import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong; import com.igormaznitsa.jbbp.model.JBBPFieldArrayShort; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayUShort; import com.igormaznitsa.jbbp.model.JBBPFieldBit; import com.igormaznitsa.jbbp.model.JBBPFieldBoolean; import com.igormaznitsa.jbbp.model.JBBPFieldByte; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldShort; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.model.JBBPFieldUByte; import com.igormaznitsa.jbbp.model.JBBPFieldUShort; import java.io.InputStream; import net.minidev.json.JSONArray; import net.minidev.json.JSONObject; import net.minidev.json.JSONStyle; import org.junit.jupiter.api.Test; public class ConvertToJSONTest extends AbstractParserIntegrationTest { public static JSONObject convertToJSon(final JSONObject jsn, final JBBPAbstractField field) { final JSONObject json = jsn == null ? new JSONObject() : jsn; final String fieldName = field.getFieldName() == null ? "unnamed" : field.getFieldName(); if (field instanceof JBBPAbstractArrayField) { final JSONArray jsonArray = new JSONArray(); if (field instanceof JBBPFieldArrayBit) { for (final byte b : ((JBBPFieldArrayBit) field).getArray()) { jsonArray.add(b); } } else if (field instanceof JBBPFieldArrayBoolean) { for (final boolean b : ((JBBPFieldArrayBoolean) field).getArray()) { jsonArray.add(b); } } else if (field instanceof JBBPFieldArrayByte) { for (final byte b : ((JBBPFieldArrayByte) field).getArray()) { jsonArray.add(b); } } else if (field instanceof JBBPFieldArrayInt) { for (final int b : ((JBBPFieldArrayInt) field).getArray()) { jsonArray.add(b); } } else if (field instanceof JBBPFieldArrayLong) { for (final long b : ((JBBPFieldArrayLong) field).getArray()) { jsonArray.add(b); } } else if (field instanceof JBBPFieldArrayShort) { for (final short b : ((JBBPFieldArrayShort) field).getArray()) { jsonArray.add(b); } } else if (field instanceof JBBPFieldArrayStruct) { final JBBPFieldArrayStruct array = (JBBPFieldArrayStruct) field; for (int i = 0; i < array.size(); i++) { jsonArray.add(convertToJSon(new JSONObject(), array.getElementAt(i))); } } else if (field instanceof JBBPFieldArrayUByte) { for (final byte b : ((JBBPFieldArrayUByte) field).getArray()) { jsonArray.add(b & 0xFF); } } else if (field instanceof JBBPFieldArrayUShort) { for (final short b : ((JBBPFieldArrayUShort) field).getArray()) { jsonArray.add(b & 0xFFFF); } } else { throw new Error("Unexpected field type"); } json.put(fieldName, jsonArray); } else { if (field instanceof JBBPFieldBit) { json.put(fieldName, ((JBBPFieldBit) field).getAsInt()); } else if (field instanceof JBBPFieldBoolean) { json.put(fieldName, ((JBBPFieldBoolean) field).getAsBool()); } else if (field instanceof JBBPFieldByte) { json.put(fieldName, ((JBBPFieldByte) field).getAsInt()); } else if (field instanceof JBBPFieldInt) { json.put(fieldName, ((JBBPFieldInt) field).getAsInt()); } else if (field instanceof JBBPFieldLong) { json.put(fieldName, ((JBBPFieldLong) field).getAsLong()); } else if (field instanceof JBBPFieldShort) { json.put(fieldName, ((JBBPFieldShort) field).getAsInt()); } else if (field instanceof JBBPFieldStruct) { final JBBPFieldStruct struct = (JBBPFieldStruct) field; final JSONObject obj = new JSONObject(); for (final JBBPAbstractField f : struct.getArray()) { convertToJSon(obj, f); } if (jsn == null) { json.putAll(obj); } else { json.put(fieldName, obj); } } else if (field instanceof JBBPFieldUByte) { json.put(fieldName, ((JBBPFieldUByte) field).getAsInt()); } else if (field instanceof JBBPFieldUShort) { json.put(fieldName, ((JBBPFieldUShort) field).getAsInt()); } else { throw new Error("Unexpected field"); } } return json; } @Test public void testConvertToJSON() throws Exception { try (InputStream pngStream = getResourceAsInputStream("picture.png")) { final JBBPParser pngParser = JBBPParser.prepare( "long header;" + "// chunks\n" + "chunk [_]{" + " int length; " + " int type; " + " byte[length] data; " + " int crc;" + "}" ); final JSONObject json = convertToJSon(null, pngParser.parse(pngStream)); final String jsonText = json.toJSONString(JSONStyle.MAX_COMPRESS); assertEquals(13917, jsonText.length()); assertTrue(jsonText.contains("header:")); assertTrue(jsonText.contains("chunk:{")); assertTrue(jsonText.contains("length:")); assertTrue(jsonText.contains("type:")); assertTrue(jsonText.contains("data:")); assertTrue(jsonText.contains("crc:")); } } } CustomThreeByteIntegerTypeTest.java000066400000000000000000000213651445000440600354240ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.exceptions.JBBPParsingException; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayInt; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Arrays; import org.junit.jupiter.api.Test; /** * Example of three byte integer custom type processor to parse unsigned integer values represented by three bytes in data stream. */ public class CustomThreeByteIntegerTypeTest extends AbstractParserIntegrationTest { @Test public void testCustomFieldAsAnonymousSingleField() throws Exception { final JBBPParser parser = JBBPParser.prepare("int24;", new Int24CustomTypeProcessor()); assertEquals(5, parser.parse(new byte[] {0, 0, 5}).findFieldForType(JBBPFieldInt.class).getAsInt()); } @Test public void testReadThreeByteInteger_AnonymousArray() throws Exception { final JBBPParser parser = JBBPParser.prepare("int24 [_];", new Int24CustomTypeProcessor()); assertArrayEquals(new int[] {0x010203, 0x040506, 0x070809}, parser.parse(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}) .findFieldForType(JBBPFieldArrayInt.class).getArray()); } @Test public void testReadThreeByte_NamedCustomFieldAsArrayLength() throws Exception { final JBBPParser parser = JBBPParser.prepare("int24 value; byte [value];", new Int24CustomTypeProcessor()); assertEquals(5, parser.parse(new byte[] {0, 0, 5, 1, 2, 3, 4, 5}).findFieldForType(JBBPFieldArrayByte.class) .size()); } @Test public void testReadThreeByteInteger_NamedCustomFieldInExpression() throws Exception { final JBBPParser parser = JBBPParser .prepare("int24 value1; int24 value2; byte [value1+value2];", new Int24CustomTypeProcessor()); final JBBPFieldStruct struct = parser.parse(new byte[] {0, 0, 2, 0, 0, 3, 1, 2, 3, 4, 5}); assertEquals(5, struct.findFieldForType(JBBPFieldArrayByte.class).size()); assertEquals(2, struct.findFieldForNameAndType("value1", JBBPFieldInt.class).getAsInt()); assertEquals(3, struct.findFieldForNameAndType("value2", JBBPFieldInt.class).getAsInt()); } @Test public void testReadThreeByteInteger_OneValue() throws Exception { final JBBPParser parser = JBBPParser.prepare("int24 value;", new Int24CustomTypeProcessor()); final JBBPParser inverseParser = JBBPParser.prepare(" parser.parse(new byte[] {0x01, 0x02})); } @Test public void testReadThreeByteInteger_WholeArray() throws Exception { final JBBPParser parser = JBBPParser.prepare("int24 [_] array;", new Int24CustomTypeProcessor()); assertArrayEquals(new int[] {0x010203, 0x040506, 0x070809}, parser.parse(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}) .findFieldForType(JBBPFieldArrayInt.class).getArray()); } @Test public void testReadThreeByteInteger_ArrayFirstThreeElements() throws Exception { final JBBPParser parser = JBBPParser.prepare("int24 [3] array;", new Int24CustomTypeProcessor()); assertArrayEquals(new int[] {0x010203, 0x040506, 0x070809}, parser .parse(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C}) .findFieldForType(JBBPFieldArrayInt.class).getArray()); } /** * Buffer to accumulate integer values. */ private static final class IntBuffer { private int[] buffer; private int freePos; public IntBuffer(final int initialCapacity) { this.buffer = new int[Math.max(3, initialCapacity)]; } public IntBuffer put(final int value) { if (this.freePos == this.buffer.length) { final int[] newBuffer = new int[(this.buffer.length * 3) / 2]; System.arraycopy(this.buffer, 0, newBuffer, 0, this.buffer.length); this.buffer = newBuffer; } this.buffer[this.freePos++] = value; return this; } public int[] toArray() { return Arrays.copyOf(this.buffer, this.freePos); } } /** * Class implements custom type processor for three byte unsigned integer values. */ private static final class Int24CustomTypeProcessor implements JBBPCustomFieldTypeProcessor { private static final String[] TYPES = new String[] {"int24"}; private static int readThreeBytesAsInt(final JBBPBitInputStream in, final JBBPByteOrder byteOrder, final JBBPBitOrder bitOrder) throws IOException { final int b0 = in.readByte(); final int b1 = in.readByte(); final int b2 = in.readByte(); final int value = byteOrder == JBBPByteOrder.BIG_ENDIAN ? (b0 << 16) | (b1 << 8) | b2 : (b2 << 16) | (b1 << 8) | b0; return bitOrder == JBBPBitOrder.LSB0 ? value : ((int) JBBPFieldInt.reverseBits(value) >>> 8); } @Override public String[] getCustomFieldTypes() { return TYPES; } @Override public boolean isAllowed(final JBBPFieldTypeParameterContainer fieldType, final String fieldName, final int extraData, final boolean isArray) { return extraData == 0; } @Override public JBBPAbstractField readCustomFieldType(final JBBPBitInputStream in, final JBBPBitOrder bitOrder, final int parserFlags, final JBBPFieldTypeParameterContainer customTypeFieldInfo, final JBBPNamedFieldInfo fieldName, final int extraData, final boolean readWholeStream, final int arrayLength) throws IOException { if (arrayLength < 0) { return new JBBPFieldInt(fieldName, readThreeBytesAsInt(in, customTypeFieldInfo.getByteOrder(), bitOrder)); } else { if (readWholeStream) { final IntBuffer intBuffer = new IntBuffer(1024); while (in.hasAvailableData()) { intBuffer.put(readThreeBytesAsInt(in, customTypeFieldInfo.getByteOrder(), bitOrder)); } return new JBBPFieldArrayInt(fieldName, intBuffer.toArray()); } else { final int[] array = new int[arrayLength]; for (int i = 0; i < arrayLength; i++) { array[i] = readThreeBytesAsInt(in, customTypeFieldInfo.getByteOrder(), bitOrder); } return new JBBPFieldArrayInt(fieldName, array); } } } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/NetPacketParsingTest.java000066400000000000000000000305521445000440600334350ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldBit; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.model.JBBPFieldUByte; import com.igormaznitsa.jbbp.model.JBBPFieldUShort; import com.igormaznitsa.jbbp.utils.JBBPTextWriter; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.StringWriter; import org.junit.jupiter.api.Test; public class NetPacketParsingTest extends AbstractParserIntegrationTest { @Test public void testParsingTCPFrameInsideNetworkFrame() throws Exception { final JBBPBitInputStream netPacketStream = new JBBPBitInputStream(getResourceAsInputStream("tcppacket.bin")); try { // Ethernet header Ethernet II final JBBPParser ethernetParserHeaderWithout802_1QTag = JBBPParser.prepare( "byte[6] MacDestination;" + "byte[6] MacSource;" + "ushort EtherTypeOrLength;" ); // IPv4 header final JBBPParser ipParserHeaderWithoutOptions = JBBPParser.prepare( "bit:4 InternetHeaderLength;" + "bit:4 Version;" + "bit:2 ECN;" + "bit:6 DSCP;" + "ushort TotalPacketLength;" + "ushort Identification;" + "ushort IPFlagsAndFragmentOffset;" + "ubyte TTL;" + "ubyte Protocol;" + "ushort HeaderChecksum;" + "int SourceAddress;" + "int DestinationAddress;" + "byte [((InternetHeaderLength-5)*4)*((((InternetHeaderLength-5)&16)^16)>>4)] Options;" ); // TCP header final JBBPParser tcpHeader = JBBPParser.prepare( "ushort SourcePort;" + "ushort DestinationPort;" + "int SequenceNumber;" + "int AcknowledgementNumber;" + "bit:1 NONCE;" + "bit:3 RESERVED;" + "bit:4 HLEN;" + "bit:1 FIN;" + "bit:1 SYN;" + "bit:1 RST;" + "bit:1 PSH;" + "bit:1 ACK;" + "bit:1 URG;" + "bit:1 ECNECHO;" + "bit:1 CWR;" + "ushort WindowSize;" + "ushort TCPCheckSum;" + "ushort UrgentPointer;" + "byte [$$-HLEN*4] Option;" ); // Check Ethernet header final JBBPFieldStruct parsedEthernetHeader = ethernetParserHeaderWithout802_1QTag.parse(netPacketStream); assertArrayEquals( new byte[] {(byte) 0x60, (byte) 0x67, (byte) 0x20, (byte) 0xE1, (byte) 0xF9, (byte) 0xF8}, parsedEthernetHeader.findFieldForNameAndType("MacDestination", JBBPFieldArrayByte.class) .getArray(), "Destination MAC"); assertArrayEquals( new byte[] {(byte) 0x00, (byte) 0x26, (byte) 0x44, (byte) 0x74, (byte) 0xFE, (byte) 0x66}, parsedEthernetHeader.findFieldForNameAndType("MacSource", JBBPFieldArrayByte.class) .getArray(), "Source MAC"); final int etherTypeOrLength = parsedEthernetHeader.findFieldForNameAndType("EtherTypeOrLength", JBBPFieldUShort.class) .getAsInt(); assertEquals(0x800, etherTypeOrLength, "Ethernet type or length"); if (etherTypeOrLength > 1500) { // list of protocols http://standards-oui.ieee.org/ethertype/eth.txt System.out.println( "Ethernet type is : 0x" + Integer.toHexString(etherTypeOrLength).toUpperCase()); } else { System.out.println("Payload length : " + etherTypeOrLength); } // Check IP header netPacketStream.resetCounter(); final JBBPFieldStruct parsedIPHeader = ipParserHeaderWithoutOptions.parse(netPacketStream); assertEquals(4, parsedIPHeader.findFieldForNameAndType("Version", JBBPFieldBit.class).getAsInt(), "IP Version"); final int internetHeaderLength = parsedIPHeader.findFieldForNameAndType("InternetHeaderLength", JBBPFieldBit.class) .getAsInt(); assertEquals(5, internetHeaderLength, "Length of the IP header (in 4 byte items)"); assertEquals(0, parsedIPHeader.findFieldForNameAndType("DSCP", JBBPFieldBit.class).getAsInt(), "Differentiated Services Code Point"); assertEquals(0, parsedIPHeader.findFieldForNameAndType("ECN", JBBPFieldBit.class).getAsInt(), "Explicit Congestion Notification"); final int ipTotalPacketLength = parsedIPHeader.findFieldForNameAndType("TotalPacketLength", JBBPFieldUShort.class) .getAsInt(); assertEquals(159, ipTotalPacketLength, "Entire IP packet size, including header and data, in bytes"); assertEquals(30810, parsedIPHeader.findFieldForNameAndType("Identification", JBBPFieldUShort.class) .getAsInt(), "Identification"); final int ipFlagsAndFragmentOffset = parsedIPHeader.findFieldForNameAndType("IPFlagsAndFragmentOffset", JBBPFieldUShort.class) .getAsInt(); assertEquals(0x2, ipFlagsAndFragmentOffset >>> 13, "Extracted IP flags"); assertEquals(0x00, ipFlagsAndFragmentOffset & 0x1FFF, "Extracted Fragment offset"); assertEquals(0x39, parsedIPHeader.findFieldForNameAndType("TTL", JBBPFieldUByte.class).getAsInt(), "Time To Live"); assertEquals(0x06, parsedIPHeader.findFieldForNameAndType("Protocol", JBBPFieldUByte.class).getAsInt(), "Protocol (RFC-790)"); assertEquals(0x7DB6, parsedIPHeader.findFieldForNameAndType("HeaderChecksum", JBBPFieldUShort.class) .getAsInt(), "IPv4 Header Checksum"); assertEquals(0xD5C7B393, parsedIPHeader.findFieldForNameAndType("SourceAddress", JBBPFieldInt.class).getAsInt(), "Source IP address"); assertEquals(0xC0A80145, parsedIPHeader.findFieldForNameAndType("DestinationAddress", JBBPFieldInt.class) .getAsInt(), "Destination IP address"); assertEquals(0, parsedIPHeader.findFieldForNameAndType("Options", JBBPFieldArrayByte.class) .getArray().length); // Check TCP header netPacketStream.resetCounter(); final JBBPFieldStruct parsedTcpHeader = tcpHeader.parse(netPacketStream); assertEquals(40018, parsedTcpHeader.findFieldForNameAndType("SourcePort", JBBPFieldUShort.class).getAsInt()); assertEquals(56344, parsedTcpHeader.findFieldForNameAndType("DestinationPort", JBBPFieldUShort.class) .getAsInt()); assertEquals(0xE0084171, parsedTcpHeader.findFieldForNameAndType("SequenceNumber", JBBPFieldInt.class).getAsInt()); assertEquals(0xAB616F71, parsedTcpHeader.findFieldForNameAndType("AcknowledgementNumber", JBBPFieldInt.class) .getAsInt()); assertFalse(parsedTcpHeader.findFieldForNameAndType("FIN", JBBPFieldBit.class).getAsBool()); assertFalse(parsedTcpHeader.findFieldForNameAndType("SYN", JBBPFieldBit.class).getAsBool()); assertFalse(parsedTcpHeader.findFieldForNameAndType("RST", JBBPFieldBit.class).getAsBool()); assertTrue(parsedTcpHeader.findFieldForNameAndType("PSH", JBBPFieldBit.class).getAsBool()); assertTrue(parsedTcpHeader.findFieldForNameAndType("ACK", JBBPFieldBit.class).getAsBool()); assertFalse(parsedTcpHeader.findFieldForNameAndType("URG", JBBPFieldBit.class).getAsBool()); assertFalse( parsedTcpHeader.findFieldForNameAndType("ECNECHO", JBBPFieldBit.class).getAsBool()); assertFalse(parsedTcpHeader.findFieldForNameAndType("CWR", JBBPFieldBit.class).getAsBool()); assertFalse(parsedTcpHeader.findFieldForNameAndType("NONCE", JBBPFieldBit.class).getAsBool()); assertFalse( parsedTcpHeader.findFieldForNameAndType("RESERVED", JBBPFieldBit.class).getAsBool()); assertEquals(5, parsedTcpHeader.findFieldForNameAndType("HLEN", JBBPFieldBit.class).getAsInt()); assertEquals(40880, parsedTcpHeader.findFieldForNameAndType("WindowSize", JBBPFieldUShort.class).getAsInt()); assertEquals(0x8BB6, parsedTcpHeader.findFieldForNameAndType("TCPCheckSum", JBBPFieldUShort.class).getAsInt()); assertEquals(0, parsedTcpHeader.findFieldForNameAndType("UrgentPointer", JBBPFieldUShort.class) .getAsInt()); assertEquals(0, parsedTcpHeader.findFieldForNameAndType("Option", JBBPFieldArrayByte.class).size()); // extract data final int payloadDataLength = ipTotalPacketLength - (internetHeaderLength * 4) - (int) netPacketStream.getCounter(); final byte[] data = netPacketStream.readByteArray(payloadDataLength); assertEquals(119, data.length); System.out.println( new JBBPTextWriter(new StringWriter()).Comment("Payload data extracted from the TCP part") .Byte(data).BR().toString()); final byte[] restOfFrame = netPacketStream.readByteArray(-1); assertEquals(0, restOfFrame.length); } finally { JBBPUtils.closeQuietly(netPacketStream); } } @Test public void testParseSomePacketGettedOverTCP_ExampleFromStackOverflow() throws Exception { final class Parsed { @Bin(order = 1) byte begin; @Bin(order = 2, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_4) int version; @Bin(order = 3, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_4) int returnType; @Bin(order = 4) byte[] productCode; @Bin(order = 5, type = BinType.USHORT) int dataLength; } final byte[] testArray = new byte[] {0x23, 0x21, (byte) 0x90, 0x23, 0x21, 0x22, 0x12, 0x00, (byte) 0xAA}; final Parsed parsed = JBBPParser.prepare( "byte begin; bit:4 version; bit:4 returnType; byte [5] productCode; ushort dataLength;") .parse(testArray) .mapTo(new Parsed()); assertEquals(0x23, parsed.begin); assertEquals(0x01, parsed.version); assertEquals(0x02, parsed.returnType); assertArrayEquals(new byte[] {(byte) 0x90, 0x23, 0x21, 0x22, 0x12}, parsed.productCode); assertEquals(0x00AA, parsed.dataLength); assertArrayEquals(testArray, JBBPOut.BeginBin().Bin(parsed).End().toByteArray()); } @Test public void testParseUDP() throws Exception { final class Parsed { @Bin(order = 1) char source; @Bin(order = 2) char destination; @Bin(order = 3) char length; @Bin(order = 4) char checksum; @Bin(order = 5) byte[] data; } final byte[] testArray = new byte[] {0x04, (byte) 0x89, 0x00, 0x35, 0x00, 0x2C, (byte) 0xAB, (byte) 0xB4, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x70, 0x6F, 0x70, 0x64, 0x02, 0x69, 0x78, 0x06, 0x6E, 0x65, 0x74, 0x63, 0x6F, 0x6D, 0x03, 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x01, 0x00, 0x01}; final Parsed parsed = JBBPParser.prepare( "ushort source; ushort destination; ushort length; ushort checksum; byte [length-8] data;") .parse(testArray).mapTo(new Parsed()); assertEquals(0x0489, parsed.source); assertEquals(0x0035, parsed.destination); assertEquals(0xABB4, parsed.checksum); assertEquals(0x002C - 8, parsed.data.length); assertArrayEquals(testArray, JBBPOut.BeginBin().Bin(parsed).End().toByteArray()); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/PNGParsingTest.java000066400000000000000000000205111445000440600321750ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static com.igormaznitsa.jbbp.TestUtils.assertPngChunk; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.InputStream; import org.apache.commons.codec.digest.PureJavaCrc32; import org.junit.jupiter.api.Test; public class PNGParsingTest extends AbstractParserIntegrationTest { private static void assertChunk(final String name, final int length, final JBBPFieldStruct chunk) { final int chunkName = (name.charAt(0) << 24) | (name.charAt(1) << 16) | (name.charAt(2) << 8) | name.charAt(3); assertEquals(chunkName, chunk.findFieldForNameAndType("type", JBBPFieldInt.class).getAsInt(), "Chunk must be " + name); assertEquals(length, chunk.findFieldForNameAndType("length", JBBPFieldInt.class).getAsInt(), "Chunk length must be " + length); final PureJavaCrc32 crc32 = new PureJavaCrc32(); crc32.update(name.charAt(0)); crc32.update(name.charAt(1)); crc32.update(name.charAt(2)); crc32.update(name.charAt(3)); if (length != 0) { final byte[] array = chunk.findFieldForType(JBBPFieldArrayByte.class).getArray(); assertEquals(length, array.length, "Data array " + name + " must be " + length); for (final byte b : array) { crc32.update(b & 0xFF); } } final int crc = (int) crc32.getValue(); assertEquals(crc, chunk.findLastFieldForType(JBBPFieldInt.class).getAsInt(), "CRC32 for " + name + " must be " + crc); } @Test public void testPngParsing_Mapping() throws Exception { final InputStream pngStream = getResourceAsInputStream("picture.png"); try { final JBBPParser pngParser = JBBPParser.prepare( "long header;" + "// chunks\n" + "chunk [_]{" + " int length; " + " int type; " + " byte[length] data; " + " int crc;" + "}" ); @Bin class Chunk { int length; int type; byte[] data; int crc; } @Bin class Png { long hEAder; Chunk[] chuNK; Chunk makeChunk() { return new Chunk(); } } final Png result = new Png(); final Png png = pngParser.parse(pngStream).mapTo(result, aClass -> { if (aClass == Chunk.class) { return result.makeChunk(); } return null; }); assertEquals(0x89504E470D0A1A0AL, png.hEAder); final String[] chunkNames = new String[] {"IHDR", "gAMA", "bKGD", "pHYs", "tIME", "tEXt", "IDAT", "IEND"}; final int[] chunkSizes = new int[] {0x0D, 0x04, 0x06, 0x09, 0x07, 0x19, 0x0E5F, 0x00}; assertEquals(chunkNames.length, png.chuNK.length); for (int i = 0; i < png.chuNK.length; i++) { assertPngChunk(chunkNames[i], chunkSizes[i], png.chuNK[i].type, png.chuNK[i].length, png.chuNK[i].crc, png.chuNK[i].data); } assertEquals(3847, pngParser.getFinalStreamByteCounter()); } finally { JBBPUtils.closeQuietly(pngStream); } } @Test public void testPngParsing() throws Exception { final InputStream pngStream = getResourceAsInputStream("picture.png"); try { final JBBPParser pngParser = JBBPParser.prepare( "long header;" + "// chunks\n" + "chunk [_]{" + " int length; " + " int type; " + " byte[length] data; " + " int crc;" + "}" ); final JBBPFieldStruct result = pngParser.parse(pngStream); assertEquals(0x89504E470D0A1A0AL, result.findFieldForNameAndType("header", JBBPFieldLong.class).getAsLong()); final JBBPFieldArrayStruct chunks = result.findFieldForNameAndType("chunk", JBBPFieldArrayStruct.class); final String[] chunkNames = new String[] {"IHDR", "gAMA", "bKGD", "pHYs", "tIME", "tEXt", "IDAT", "IEND"}; final int[] chunkSizes = new int[] {0x0D, 0x04, 0x06, 0x09, 0x07, 0x19, 0x0E5F, 0x00}; assertEquals(chunkNames.length, chunks.size()); for (int i = 0; i < chunks.size(); i++) { assertChunk(chunkNames[i], chunkSizes[i], chunks.getElementAt(i)); } assertEquals(3847, pngParser.getFinalStreamByteCounter()); } finally { JBBPUtils.closeQuietly(pngStream); } } @Test public void testPngParsing_WithExternalValue() throws Exception { final InputStream pngStream = getResourceAsInputStream("picture.png"); try { final JBBPParser pngParser = JBBPParser.prepare( "long header;" + "// chunks\n" + "chunk [_]{" + " int length; " + " int type; " + " byte[$value] data; " + " int crc;" + "}" ); final JBBPFieldStruct result = pngParser.parse(pngStream, null, (fieldName, numericFieldMap, compiledBlock) -> { if ("value".equals(fieldName)) { return numericFieldMap.findFieldForPathAndType("chunk.length", JBBPFieldInt.class) .getAsInt(); } fail("Unexpected variable '" + fieldName + '\''); return -1; }); assertEquals(0x89504E470D0A1A0AL, result.findFieldForNameAndType("header", JBBPFieldLong.class).getAsLong()); final JBBPFieldArrayStruct chunks = result.findFieldForNameAndType("chunk", JBBPFieldArrayStruct.class); final String[] chunkNames = new String[] {"IHDR", "gAMA", "bKGD", "pHYs", "tIME", "tEXt", "IDAT", "IEND"}; final int[] chunkSizes = new int[] {0x0D, 0x04, 0x06, 0x09, 0x07, 0x19, 0x0E5F, 0x00}; assertEquals(chunkNames.length, chunks.size()); for (int i = 0; i < chunks.size(); i++) { assertChunk(chunkNames[i], chunkSizes[i], chunks.getElementAt(i)); } assertEquals(3847, pngParser.getFinalStreamByteCounter()); } finally { JBBPUtils.closeQuietly(pngStream); } } @Test public void testPngParsingAndSynthesisThroughMapping() throws Exception { final InputStream pngStream = getResourceAsInputStream("picture.png"); try { final JBBPParser pngParser = JBBPParser.prepare( "long header;" + "// chunks\n" + "chunk [_]{" + " int length; " + " int type; " + " byte[length] data; " + " int crc;" + "}" ); class Chunk { @Bin(order = 1) int length; @Bin(order = 2) int type; @Bin(order = 3) byte[] data; @Bin(order = 4) int crc; } class Png { @Bin(order = 1) long hEAder; @Bin(order = 2) Chunk[] chuNK; Chunk makeNewChunk() { return new Chunk(); } } final Png result = new Png(); final Png parsedAndMapped = pngParser.parse(pngStream).mapTo(result, aClass -> { if (aClass == Chunk.class) { return result.makeNewChunk(); } return null; }); final byte[] saved = JBBPOut.BeginBin().Bin(parsedAndMapped).End().toByteArray(); assertResource("picture.png", saved); } finally { JBBPUtils.closeQuietly(pngStream); } } } PackedBCDCustomFieldTest.java000066400000000000000000000115341445000440600340120ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.exceptions.JBBPCompilationException; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldArrayLong; import com.igormaznitsa.jbbp.model.JBBPFieldLong; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import java.io.IOException; import org.junit.jupiter.api.Test; public class PackedBCDCustomFieldTest implements JBBPCustomFieldTypeProcessor { private static final String[] types = new String[] {"bcd", "sbcd"}; public static long readValueFromPackedDecimal(final JBBPBitInputStream in, final int len, final boolean signed) throws IOException { final byte[] data = in.readByteArray(len); StringBuilder digitStr = new StringBuilder(); for (int i = 0; i < len * 2; i++) { byte currentByte = data[i / 2]; byte digit = (i % 2 == 0) ? (byte) ((currentByte & 0xff) >>> 4) : (byte) (currentByte & 0x0f); if (digit < 10) { digitStr.append(digit); } } if (signed) { byte sign = (byte) (data[len - 1] & 0x0f); if (sign == 0x0b || sign == 0x0d) { digitStr.insert(0, '-'); } } return Long.parseLong(digitStr.toString()); } @Override public String[] getCustomFieldTypes() { return types; } @Override public boolean isAllowed(final JBBPFieldTypeParameterContainer fieldType, final String fieldName, final int extraData, final boolean isArray) { if (fieldType.getByteOrder() == JBBPByteOrder.LITTLE_ENDIAN) { System.err .println("Packed Decimal does not support little endian...using big endian instead"); return false; } return extraData > 0 && extraData < 15; } @Override public JBBPAbstractField readCustomFieldType(final JBBPBitInputStream in, final JBBPBitOrder bitOrder, final int parserFlags, final JBBPFieldTypeParameterContainer customTypeFieldInfo, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException { final boolean signed = "sbcd".equals(customTypeFieldInfo.getTypeName()); if (readWholeStream) { throw new UnsupportedOperationException("Whole stream reading unsupported"); } else { if (arrayLength <= 0) { return new JBBPFieldLong(fieldName, readValueFromPackedDecimal(in, extraData, signed)); } else { final long[] result = new long[arrayLength]; for (int i = 0; i < arrayLength; i++) { result[i] = readValueFromPackedDecimal(in, extraData, signed); } return new JBBPFieldArrayLong(fieldName, result); } } } @Test public void testParse_SingleDefaultNonamedPackedDecimal_Default() throws Exception { final JBBPParser parser = JBBPParser.prepare("bcd:4;", this); final JBBPFieldStruct result = parser.parse(new byte[] {0x12, 0x34, 0x56, 0x7F}); assertEquals(1234567L, result.findFieldForType(JBBPFieldLong.class).getAsLong()); } @Test public void testParse_SingleDefaultNonamedPackedDecimal_BigEndian() throws Exception { final JBBPParser parser = JBBPParser.prepare(">bcd:4;", this); final JBBPFieldStruct result = parser.parse(new byte[] {0x12, 0x34, 0x56, 0x7F}); assertEquals(1234567L, result.findFieldForType(JBBPFieldLong.class).getAsLong()); } @Test public void testParse_SingleDefaultNonamedPackedDecimal_LittleEndian_Exception() throws Exception { final PackedBCDCustomFieldTest theInstance = this; assertThrows(JBBPCompilationException.class, () -> JBBPParser.prepare("Remarkablepaper device * Information about the format was found here */ public class RemarkableLinesParsingTest extends AbstractParserIntegrationTest { private static final String REMARKABLE_V5_LINES = "byte [43] header;" + "", maxX, maxY)); writer.println(); writer.print(format("", maxX, maxY)); writer.println(); for (final Layer layer : layers) { layer.printSvg(writer); } writer.print(""); } public static class Layer { @Bin(order = 1, byteOrder = LITTLE_ENDIAN) public int nstrokes; @Bin(order = 2, arraySizeExpr = "nstrokes") public Stroke[] strokes; public void printSvg(final PrintWriter writer) { for (final Stroke stroke : strokes) { stroke.printSvg(writer); } writer.println(); } public float findMaxX() { float result = Float.MIN_VALUE; for (final Stroke s : strokes) { result = Math.max(result, s.findMaxX()); } return result; } public float findMaxY() { float result = Float.MIN_VALUE; for (final Stroke s : strokes) { result = Math.max(result, s.findMaxY()); } return result; } public static class Stroke { @Bin(order = 1, byteOrder = LITTLE_ENDIAN) public int pen; @Bin(order = 2, byteOrder = LITTLE_ENDIAN) public int color; @Bin(order = 3, byteOrder = LITTLE_ENDIAN) public int unknown1; @Bin(order = 4, byteOrder = LITTLE_ENDIAN) public float width; @Bin(order = 5, byteOrder = LITTLE_ENDIAN) public int unknown2; @Bin(order = 6, byteOrder = LITTLE_ENDIAN) public int nsegments; @Bin(order = 7, arraySizeExpr = "nsegments") public Segment[] segments; private static String color2svg(final int index) { switch (index) { case 1: return "grey"; case 2: return "white"; default: return "black"; } } private static float pen2opacity(final int index) { switch (index) { case 3: case 7: case 13: case 16: return 0.9f; case 5: case 18: return 0.2f; case 8: return 0.0f; default: return 1.0f; } } public float findMaxX() { float result = Float.MIN_VALUE; for (final Segment s : segments) { result = Math.max(result, s.x); } return result; } public float findMaxY() { float result = Float.MIN_VALUE; for (final Segment s : segments) { result = Math.max(result, s.y); } return result; } public void printSvg(final PrintWriter writer) { writer.print(format("", UUID.randomUUID().toString(), color2svg(this.color), pen2opacity(this.pen))); writer.println(); writer.print(format(""); writer.println(); writer.print(""); writer.println(); } public static class Segment { @Bin(order = 1, byteOrder = LITTLE_ENDIAN) public float x; @Bin(order = 2, byteOrder = LITTLE_ENDIAN) public float y; @Bin(order = 3, byteOrder = LITTLE_ENDIAN) public float pressure; @Bin(order = 4, byteOrder = LITTLE_ENDIAN) public float tilt; @Bin(order = 5, byteOrder = LITTLE_ENDIAN) public float unknown1; @Bin(order = 6, byteOrder = LITTLE_ENDIAN) public float unknown2; public void printSvg(final PrintWriter writer) { writer.print(format("%f,%f", x, y)); } } } } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/SNAParsingTest.java000066400000000000000000000130101445000440600321660ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static com.igormaznitsa.jbbp.io.JBBPByteOrder.LITTLE_ENDIAN; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.utils.JBBPDslBuilder; import com.igormaznitsa.jbbp.utils.JBBPTextWriter; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.InputStream; import org.junit.jupiter.api.Test; /** * Test for parsing of SNA snapshots for ZX-Spectrum emulator. */ public class SNAParsingTest extends AbstractParserIntegrationTest { private static final JBBPParser PARSER_SNA_48 = JBBPParser.prepare( "ubyte regI;" + " 10000); System.out.println(text); } @Test public void testParseAndSave_ThroughDslBuilder() throws Exception { final JBBPParser parser = JBBPParser.prepare(JBBPDslBuilder.Begin().AnnotatedClass(SNA.class).End()); final InputStream in = getResourceAsInputStream("zexall.sna"); JBBPFieldStruct parsed; try { parsed = parser.parse(in); } finally { JBBPUtils.closeQuietly(in); } final SNA mapped = parsed.findFieldForNameAndType("SNA", JBBPFieldStruct.class).mapTo(new SNA()); assertResource("zexall.sna", JBBPOut.BeginBin().Bin(mapped).End().toByteArray()); } @Bin(comment = "Parsed SNA snapshot") private class SNA { @Bin(type = BinType.UBYTE, order = 1, comment = "Register I") int regI; @Bin(type = BinType.USHORT, order = 2, name = "altHL", comment = "Register pair HL'", byteOrder = LITTLE_ENDIAN) int altRegHL; @Bin(type = BinType.USHORT, order = 3, name = "altDE", comment = "Register pair DE'", byteOrder = LITTLE_ENDIAN) int altRegDE; @Bin(type = BinType.USHORT, order = 4, name = "altBC", comment = "Registe pair BC'", byteOrder = LITTLE_ENDIAN) int altRegBC; @Bin(type = BinType.USHORT, order = 5, name = "altAF", comment = "Register pair AF'", byteOrder = LITTLE_ENDIAN) int altRegAF; @Bin(type = BinType.USHORT, order = 6, comment = "Register pair HL", byteOrder = LITTLE_ENDIAN) int regHL; @Bin(type = BinType.USHORT, order = 7, comment = "Register pair DE", byteOrder = LITTLE_ENDIAN) int regDE; @Bin(type = BinType.USHORT, order = 8, comment = "Register pair BC", byteOrder = LITTLE_ENDIAN) int regBC; @Bin(type = BinType.USHORT, order = 9, comment = "Register IY", byteOrder = LITTLE_ENDIAN) int regIY; @Bin(type = BinType.USHORT, order = 10, comment = "Register IX", byteOrder = LITTLE_ENDIAN) int regIX; @Bin(type = BinType.UBYTE, order = 11, comment = "IFF1 and IFF2 values") int iff; @Bin(type = BinType.UBYTE, order = 12, comment = "Register R") int regR; @Bin(type = BinType.USHORT, order = 13, comment = "Register pair AF", byteOrder = LITTLE_ENDIAN) int regAF; @Bin(type = BinType.USHORT, order = 14, comment = "Register SP", byteOrder = LITTLE_ENDIAN) int regSP; @Bin(type = BinType.UBYTE, order = 15, comment = "Interruption mode (0-IM0, 1-IM1, 2-IM2") int im; @Bin(type = BinType.UBYTE, order = 16, comment = "Border color") int borderColor; @Bin(order = 17, comment = "Dump of memory since 16384 address", arraySizeExpr = "49152") byte[] ramDump; } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/TAP_ParsingTest.java000066400000000000000000000113051445000440600323350ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.IOException; import java.io.InputStream; import org.junit.jupiter.api.Test; public class TAP_ParsingTest extends AbstractParserIntegrationTest { public static final JBBPParser HEADER_PARSER = JBBPParser .prepare("byte type; byte [10] name; { if (aClass == Tap.class) { return new Tap(); } throw new Error("Unexpected class: " + aClass); }); assertEquals(89410, TAP_FILE_PARSER.getFinalStreamByteCounter()); assertEquals(6, tap.tapblocks.length); for (Tap t : tap.tapblocks) { assertTapChecksum(t.checksum, t.flag, t.data); } parsedBlocks = new TapData[tap.tapblocks.length]; for (int i = 0; i < tap.tapblocks.length; i++) { final Tap t = tap.tapblocks[i]; final TapData td; switch (t.flag & 0xFF) { case 0: { // header td = HEADER_PARSER.parse(t.data).mapTo(new Header()); ((Header) td).check = t.checksum; } break; case 0xFF: { // data td = DATA_PARSER.parse(t.data).mapTo(new Data()); } break; default: { fail("Unexpected block type [0x" + Integer.toHexString(t.flag & 0xFF) + ']'); td = null; } break; } parsedBlocks[i] = td; } } finally { JBBPUtils.closeQuietly(in); } final JBBPOut ctx = JBBPOut.BeginBin(JBBPByteOrder.LITTLE_ENDIAN); for (final TapData td : parsedBlocks) { td.save(ctx); } assertResource("test.tap", ctx.End().toByteArray()); } @Bin static abstract class TapData { abstract void save(JBBPOut ctx) throws IOException; } static class Header extends TapData { byte type; @Bin(type = BinType.BYTE_ARRAY) String name; @Bin(type = BinType.USHORT) int length; @Bin(type = BinType.USHORT) int param1; @Bin(type = BinType.USHORT) int param2; transient byte check; @Override public String toString() { return "HEADER: " + name + " (length=" + length + ", param1=" + param1 + ", param2=" + param2 + ')'; } @Override void save(final JBBPOut ctx) throws IOException { ctx.Short(19).Byte(0, type).ResetCounter().Byte(name).Align(10).Short(length).Short(param1) .Short(param2).Byte(check); } } static class Data extends TapData { byte[] data; @Override public String toString() { return "DATA: length=" + (data.length - 1); } private byte calculateCheckSum() { byte a = (byte) 0xFF; for (byte b : data) { a ^= b; } return a; } @Override void save(final JBBPOut ctx) throws IOException { ctx.Short(data.length + 2).Byte(0xFF).Byte(data).Byte(calculateCheckSum()); } } @Bin static class Tap { byte flag; byte[] data; byte checksum; } @Bin static class TapContainer { Tap[] tapblocks; } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/TGAParsingTest.java000066400000000000000000000120731445000440600321700ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldShort; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.model.JBBPFieldUByte; import com.igormaznitsa.jbbp.model.JBBPFieldUShort; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.InputStream; import org.junit.jupiter.api.Test; public class TGAParsingTest extends AbstractParserIntegrationTest { private static final JBBPParser TGAParser = JBBPParser.prepare( "Header {" + " ubyte IDLength;" + " ubyte ColorMapType;" + " ubyte ImageType;" + " >> 3] ColorMapItem; " + " }" + "byte [_] ImageData;" ); private void assertTgaFile(final JBBPFieldStruct parsedTga, final String imageId, final int width, final int height, final int pixelDepth, final int colorTableItems, final int imageDataSize) { final JBBPFieldArrayByte imageIdArray = parsedTga.findFieldForNameAndType("ImageID", JBBPFieldArrayByte.class); if (imageId == null || imageId.length() == 0) { assertEquals(0, imageIdArray.size()); } else { assertEquals(imageId.length(), imageIdArray.size()); for (int i = 0; i < imageId.length(); i++) { assertEquals(imageId.charAt(i) & 0xFF, imageIdArray.getArray()[i] & 0xFF); } } assertEquals(width, parsedTga.findFieldForPathAndType("header.Width", JBBPFieldUShort.class).getAsInt()); assertEquals(height, parsedTga.findFieldForPathAndType("header.Height", JBBPFieldUShort.class).getAsInt()); assertEquals(pixelDepth, parsedTga.findFieldForPathAndType("header.PixelDepth", JBBPFieldUByte.class).getAsInt()); assertEquals(colorTableItems, parsedTga.findFieldForNameAndType("ColorMap", JBBPFieldArrayStruct.class).size()); assertEquals(imageDataSize, parsedTga.findFieldForNameAndType("ImageData", JBBPFieldArrayByte.class).size()); } @Test public void testTgaParsing_Cbw8() throws Exception { final InputStream tgaStream = getResourceAsInputStream("cbw8.tga"); try { final JBBPFieldStruct result = TGAParser.parse(tgaStream); assertTgaFile(result, "Truevision(R) Sample Image", 128, 128, 8, 0, 8715); } finally { JBBPUtils.closeQuietly(tgaStream); } } @Test public void testTgaParsing_Xingt32() throws Exception { final InputStream tgaStream = getResourceAsInputStream("xing_t32.tga"); try { final JBBPFieldStruct result = TGAParser.parse(tgaStream); assertTgaFile(result, "", 240, 164, 32, 0, 240 * 164 * 4); } finally { JBBPUtils.closeQuietly(tgaStream); } } @Test public void testTgaParsing_Logo() throws Exception { final InputStream tgaStream = getResourceAsInputStream("logo.tga"); try { final JBBPFieldStruct result = TGAParser.parse(tgaStream); assertTgaFile(result, "", 319, 165, 32, 0, 116944); assertEquals(0, result.findFieldForPathAndType("Header.XOffset", JBBPFieldShort.class).getAsInt()); assertEquals(165, result.findFieldForPathAndType("Header.YOffset", JBBPFieldShort.class).getAsInt()); } finally { JBBPUtils.closeQuietly(tgaStream); } } @Test public void testTgaParsing_IndexedColorMap() throws Exception { final InputStream tgaStream = getResourceAsInputStream("indexedcolor.tga"); try { final JBBPFieldStruct result = TGAParser.parse(tgaStream); assertTgaFile(result, "", 640, 480, 8, 256, 155403); } finally { JBBPUtils.closeQuietly(tgaStream); } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/WAVParsingTest.java000066400000000000000000000111331445000440600322060ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static org.junit.jupiter.api.Assertions.assertEquals; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldArrayStruct; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.InputStream; import org.junit.jupiter.api.Test; public class WAVParsingTest extends AbstractParserIntegrationTest { private static final JBBPParser wavParser = JBBPParser.prepare( ">> 8) & 0xFF), (char) ((value >>> 16) & 0xFF), (char) (value >>> 24)}); } private static void assertWavChunks(final JBBPFieldStruct parsedWav, final String... chunks) { assertEquals(0x46464952, parsedWav.findFieldForNameAndType("ChunkID", JBBPFieldInt.class).getAsInt()); assertEquals(0x45564157, parsedWav.findFieldForNameAndType("Format", JBBPFieldInt.class).getAsInt()); int calculatedSize = 4; int index = 0; assertEquals(chunks.length, parsedWav.findFieldForNameAndType("SubChunks", JBBPFieldArrayStruct.class).size(), "Number of parsed subchunks must be [" + chunks.length + ']'); for (final JBBPFieldStruct subchunk : parsedWav .findFieldForNameAndType("SubChunks", JBBPFieldArrayStruct.class)) { final String strChunkId = chunks[index++]; assertEquals(4, strChunkId.length(), "WAV subchunk must have 4 char length [" + strChunkId + ']'); assertEquals(strChunkId, wavInt2Str( subchunk.findFieldForNameAndType("SubChunkID", JBBPFieldInt.class).getAsInt())); final int subChunkSize = subchunk.findFieldForNameAndType("SubChunkSize", JBBPFieldInt.class).getAsInt(); assertEquals(subChunkSize, subchunk.findFieldForNameAndType("data", JBBPFieldArrayByte.class).size()); calculatedSize += subChunkSize + 8 + (subChunkSize & 1); } assertEquals(calculatedSize, parsedWav.findFieldForNameAndType("ChunkSize", JBBPFieldInt.class).getAsInt()); } @Test public void testParsingWAVFile_TruSpeech() throws Exception { final InputStream wavFileStream = getResourceAsInputStream("truspech.wav"); try { assertWavChunks(wavParser.parse(wavFileStream), "fmt ", "fact", "data"); } finally { JBBPUtils.closeQuietly(wavFileStream); } } @Test public void testParsingWAVFile_M1F1float64WEAFsp() throws Exception { final InputStream wavFileStream = getResourceAsInputStream("M1F1-float64WE-AFsp.wav"); try { assertWavChunks(wavParser.parse(wavFileStream), "fmt ", "fact", "data", "afsp", "LIST"); } finally { JBBPUtils.closeQuietly(wavFileStream); } } @Test public void testParsingWAVFile_Drmapan() throws Exception { final InputStream wavFileStream = getResourceAsInputStream("drmapan.wav"); try { assertWavChunks(wavParser.parse(wavFileStream), "fmt ", "PEAK", "data"); } finally { JBBPUtils.closeQuietly(wavFileStream); } } @Test public void testParsingWAVFile_11kgsm() throws Exception { final InputStream wavFileStream = getResourceAsInputStream("11kgsm.wav"); try { assertWavChunks(wavParser.parse(wavFileStream), "fmt ", "fact", "data"); } finally { JBBPUtils.closeQuietly(wavFileStream); } } @Test public void testParsingWAVFile_8ksbc12() throws Exception { final InputStream wavFileStream = getResourceAsInputStream("8ksbc12.wav"); try { assertWavChunks(wavParser.parse(wavFileStream), "fmt ", "fact", "data"); } finally { JBBPUtils.closeQuietly(wavFileStream); } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/it/Z80_v1_ParsingTest.java000066400000000000000000000366211445000440600327100ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.it; import static com.igormaznitsa.jbbp.io.JBBPByteOrder.LITTLE_ENDIAN; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.jbbp.io.JBBPCustomFieldWriter; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.io.JBBPOutVarProcessor; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.mapper.JBBPMapperCustomFieldProcessor; import com.igormaznitsa.jbbp.model.JBBPFieldArrayByte; import com.igormaznitsa.jbbp.model.JBBPFieldBit; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import com.igormaznitsa.jbbp.utils.Function; import com.igormaznitsa.jbbp.utils.JBBPTextWriter; import com.igormaznitsa.jbbp.utils.JBBPTextWriterExtraAdapter; import com.igormaznitsa.jbbp.utils.JBBPUtils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import org.junit.jupiter.api.Test; /** * Test to parse RLE encoded snapshots in well-known Z80 format (v.1) for * ZX-Spectrum emulators. */ public class Z80_v1_ParsingTest extends AbstractParserIntegrationTest { private static final JBBPParser z80Parser = JBBPParser.prepare( "byte reg_a; byte reg_f; , Object> INSTANTIATOR = aClass -> { if (aClass == Flags.class) { return new Flags(); } if (aClass == EmulFlags.class) { return new EmulFlags(); } return null; }; @Test public void testRLEEncoding() throws Exception { assertArrayEquals(new byte[] {(byte) 0xED, (byte) 0xED, 1, 2, 3}, JBBPOut.BeginBin() .Var(new RLEDataEncoder(), 0, new byte[] {(byte) 0xED, (byte) 0xED, 1, 2, 3}).End() .toByteArray()); assertArrayEquals( new byte[] {(byte) 0xED, (byte) 0xED, 2, (byte) 0xED, 1, 2, 3, 0x00, (byte) 0xED, (byte) 0xED, 0x00}, JBBPOut.BeginBin() .Var(new RLEDataEncoder(), 1, new byte[] {(byte) 0xED, (byte) 0xED, 1, 2, 3}).End() .toByteArray()); assertArrayEquals( new byte[] {(byte) 0xED, 0x00, (byte) 0xED, (byte) 0xED, 0x05, 0x00, 0x00, (byte) 0xED, (byte) 0xED, 0x00}, JBBPOut.BeginBin().Var(new RLEDataEncoder(), 1, new byte[] {(byte) 0xED, 0, 0, 0, 0, 0, 0}) .End().toByteArray()); assertArrayEquals( new byte[] {(byte) 0xED, (byte) 0xED, 8, 5, 1, 2, 3, 0x00, (byte) 0xED, (byte) 0xED, 0x00}, JBBPOut.BeginBin() .Var(new RLEDataEncoder(), 1, new byte[] {5, 5, 5, 5, 5, 5, 5, 5, 1, 2, 3}).End() .toByteArray()); } @Test public void testParseAndWriteTestZ80WithoutCheckOfFields() throws Exception { assertParseAndPackBack("test1.z80", 16059); assertParseAndPackBack("test2.z80", 29330); assertParseAndPackBack("test3.z80", 5711); assertParseAndPackBack("test4.z80", 9946); } private Z80Snapshot assertParseAndPackBack(final String name, final long etalonLen) throws Exception { final Z80Snapshot z80sn; final InputStream resource = getResourceAsInputStream(name); try { z80sn = z80Parser.parse(resource).mapTo(new Z80Snapshot(), new DataProcessor(), INSTANTIATOR); assertEquals(etalonLen, z80Parser.getFinalStreamByteCounter()); } finally { JBBPUtils.closeQuietly(resource); } // form the same from parsed final JBBPOut out = JBBPOut.BeginBin(LITTLE_ENDIAN, JBBPBitOrder.LSB0); assertEquals(0, out.getByteCounter()); final byte[] packed = out. Byte(z80sn.reg_a, z80sn.reg_f). Short(z80sn.reg_bc, z80sn.reg_hl, z80sn.reg_pc, z80sn.reg_sp). Byte(z80sn.reg_ir, z80sn.reg_r). Bit(z80sn.flags.reg_r_bit7). Bits(JBBPBitNumber.BITS_3, z80sn.flags.bordercolor). Bit(z80sn.flags.basic_samrom, z80sn.flags.compressed). Bits(JBBPBitNumber.BITS_2, z80sn.flags.nomeaning). Short(z80sn.reg_de, z80sn.reg_bc_alt, z80sn.reg_de_alt, z80sn.reg_hl_alt). Byte(z80sn.reg_a_alt, z80sn.reg_f_alt). Short(z80sn.reg_iy, z80sn.reg_ix). Byte(z80sn.iff, z80sn.iff2). Bits(JBBPBitNumber.BITS_2, z80sn.emulFlags.interruptmode). Bit(z80sn.emulFlags.issue2emulation, z80sn.emulFlags.doubleintfreq). Bits(JBBPBitNumber.BITS_2, z80sn.emulFlags.videosync, z80sn.emulFlags.inputdevice). Var(new RLEDataEncoder(), z80sn.flags.compressed, z80sn.data). End().toByteArray(); assertEquals(etalonLen, out.getByteCounter()); assertResource(name, packed); return z80sn; } @Test public void testParseAndWriteTestZ80WithCheckOfFields() throws Exception { final Z80Snapshot z80sn = assertParseAndPackBack("test.z80", 12429); final String text = new JBBPTextWriter().ByteOrder(LITTLE_ENDIAN).SetMaxValuesPerLine(32) .AddExtras(new JBBPTextWriterExtraAdapter() { @Override public String doConvertCustomField(JBBPTextWriter context, Object obj, Field field, Bin annotation) throws IOException { try { final byte[] data = (byte[]) field.get(obj); return "byte array length [" + data.length + ']'; } catch (Exception ex) { throw new RuntimeException(ex); } } }).Bin(z80sn).Close().toString(); assertTrue(text.contains("byte array length [49152]")); System.out.println(text); assertEquals(0x7E, z80sn.reg_a & 0xFF); assertEquals(0x86, z80sn.reg_f & 0xFF); assertEquals(0x7A74, z80sn.reg_bc & 0xFFFF); assertEquals(0x7430, z80sn.reg_hl & 0xFFFF); assertEquals(12198, z80sn.reg_pc & 0xFFFF); assertEquals(65330, z80sn.reg_sp & 0xFFFF); assertEquals(0x3F, z80sn.reg_ir & 0xFF); assertEquals(0x1A, z80sn.reg_r & 0xFF); assertEquals(0, z80sn.flags.reg_r_bit7); assertEquals(2, z80sn.flags.bordercolor); assertEquals(0, z80sn.flags.basic_samrom); assertEquals(1, z80sn.flags.compressed); assertEquals(0, z80sn.flags.nomeaning); assertEquals(0x742B, z80sn.reg_de & 0xFFFF); assertEquals(0x67C6, z80sn.reg_bc_alt & 0xFFFF); assertEquals(0x3014, z80sn.reg_de_alt & 0xFFFF); assertEquals(0x3461, z80sn.reg_hl_alt & 0xFFFF); assertEquals(0x00, z80sn.reg_a_alt & 0xFF); assertEquals(0x46, z80sn.reg_f_alt & 0xFF); assertEquals(0x5C3A, z80sn.reg_iy & 0xFFFF); assertEquals(0x03D4, z80sn.reg_ix & 0xFFFF); assertEquals(0xFF, z80sn.iff & 0xFF); assertEquals(0xFF, z80sn.iff2 & 0xFF); assertEquals(1, z80sn.emulFlags.interruptmode); assertEquals(0, z80sn.emulFlags.issue2emulation); assertEquals(0, z80sn.emulFlags.doubleintfreq); assertEquals(0, z80sn.emulFlags.videosync); assertEquals(0, z80sn.emulFlags.inputdevice); assertEquals(49152, z80sn.data.length); int summ = 0; for (final byte b : z80sn.data) { summ += b & 0xFF; } assertTrue(summ > 0); } @Test public void testParseAndPackThrowMapping() throws Exception { final InputStream in = getResourceAsInputStream("test.z80"); Z80Snapshot parsed; try { parsed = z80Parser.parse(in).mapTo(new Z80Snapshot(), new DataProcessor(), INSTANTIATOR); } finally { JBBPUtils.closeQuietly(in); } final byte[] saved = JBBPOut.BeginBin(LITTLE_ENDIAN).Bin(parsed, new DataProcessor()).End().toByteArray(); assertResource("test.z80", saved); } private static class RLEDataEncoder implements JBBPOutVarProcessor { @Override public boolean processVarOut(final JBBPOut context, final JBBPBitOutputStream outStream, final Object... args) throws IOException { final byte[] unpackedData = (byte[]) args[1]; if (((Number) args[0]).intValue() == 0) { context.Byte(unpackedData); } else { int value = -1; int counter = 0; for (final byte anUnpackedData : unpackedData) { final int cur = anUnpackedData & 0xFF; if (value < 0) { value = cur; counter = 1; } else { if (value == cur) { counter++; if (counter == 0xFF) { context.Byte(0xED, 0xED, counter, cur); value = -1; counter = 0; } } else if (counter >= 5 || (value == 0xED && counter > 1)) { context.Byte(0xED, 0xED, counter, value); counter = 1; value = cur; } else { while (counter != 0) { context.Byte(value); counter--; } if (value == 0xED) { context.Byte(cur); value = -1; counter = 0; } else { counter = 1; value = cur; } } } } if (counter < 5) { while (counter != 0) { context.Byte(value); counter--; } } else { context.Byte(0xED, 0xED, counter, value); } context.Byte(0x00, 0xED, 0xED, 0x00); } return true; } } private static class DataProcessor implements JBBPMapperCustomFieldProcessor, JBBPCustomFieldWriter { @Override public Object prepareObjectForMapping(JBBPFieldStruct parsedBlock, Bin annotation, Field field) { if (field.getName().equals("data")) { final byte[] data = parsedBlock.findFieldForNameAndType("data", JBBPFieldArrayByte.class).getArray(); if (parsedBlock.findFieldForPathAndType("flags.compressed", JBBPFieldBit.class) .getAsBool()) { // RLE compressed final ByteArrayOutputStream baos = new ByteArrayOutputStream(data.length << 1); int i = 0; // check end marker assertEquals(0x00, data[data.length - 1] & 0xFF); assertEquals(0xED, data[data.length - 2] & 0xFF); assertEquals(0xED, data[data.length - 3] & 0xFF); assertEquals(0x00, data[data.length - 4] & 0xFF); final int len = data.length - 4; while (i < len) { final int a = data[i++] & 0xFF; if (a == 0xED) { final int b = data[i++] & 0xFF; if (b == 0xED) { int num = data[i++] & 0xFF; final int val = data[i++] & 0xFF; while (num > 0) { baos.write(val); num--; } } else { baos.write(a); baos.write(b); } } else { baos.write(a); } } return baos.toByteArray(); } else { // uncompressed return data; } } else { fail("Unexpected field"); return null; } } @Override public void writeCustomField(final JBBPOut context, final JBBPBitOutputStream out, final Object instanceForSaving, final Field instanceCustomField, final Bin fieldAnnotation, final Object value) throws IOException { try { final byte[] array = (byte[]) instanceCustomField.get(instanceForSaving); new RLEDataEncoder().processVarOut(context, out, 1, array); } catch (Exception ex) { ex.printStackTrace(); fail("Can't get field data"); } } } static class EmulFlags { @Bin(order = 1, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_2, byteOrder = LITTLE_ENDIAN) byte interruptmode; @Bin(order = 2, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_1, byteOrder = LITTLE_ENDIAN) byte issue2emulation; @Bin(order = 3, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_1, byteOrder = LITTLE_ENDIAN) byte doubleintfreq; @Bin(order = 4, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_2, byteOrder = LITTLE_ENDIAN) byte videosync; @Bin(order = 5, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_2, byteOrder = LITTLE_ENDIAN) byte inputdevice; } static class Flags { @Bin(order = 1, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_1, byteOrder = LITTLE_ENDIAN) byte reg_r_bit7; @Bin(order = 2, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_3, byteOrder = LITTLE_ENDIAN) byte bordercolor; @Bin(order = 3, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_1, byteOrder = LITTLE_ENDIAN) byte basic_samrom; @Bin(order = 4, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_1, byteOrder = LITTLE_ENDIAN) byte compressed; @Bin(order = 5, type = BinType.BIT, bitNumber = JBBPBitNumber.BITS_2, byteOrder = LITTLE_ENDIAN) byte nomeaning; } static class Z80Snapshot { @Bin(order = 1, byteOrder = LITTLE_ENDIAN) byte reg_a; @Bin(order = 2, byteOrder = LITTLE_ENDIAN) byte reg_f; @Bin(order = 3, byteOrder = LITTLE_ENDIAN) short reg_bc; @Bin(order = 4, byteOrder = LITTLE_ENDIAN) short reg_hl; @Bin(order = 5, byteOrder = LITTLE_ENDIAN) short reg_pc; @Bin(order = 6, byteOrder = LITTLE_ENDIAN) short reg_sp; @Bin(order = 7, byteOrder = LITTLE_ENDIAN) byte reg_ir; @Bin(order = 8, byteOrder = LITTLE_ENDIAN) byte reg_r; @Bin(order = 9, byteOrder = LITTLE_ENDIAN) Flags flags; @Bin(order = 10, byteOrder = LITTLE_ENDIAN) short reg_de; @Bin(order = 11, byteOrder = LITTLE_ENDIAN) short reg_bc_alt; @Bin(order = 12, byteOrder = LITTLE_ENDIAN) short reg_de_alt; @Bin(order = 13, byteOrder = LITTLE_ENDIAN) short reg_hl_alt; @Bin(order = 14, byteOrder = LITTLE_ENDIAN) byte reg_a_alt; @Bin(order = 15, byteOrder = LITTLE_ENDIAN) byte reg_f_alt; @Bin(order = 16, byteOrder = LITTLE_ENDIAN) short reg_iy; @Bin(order = 17, byteOrder = LITTLE_ENDIAN) short reg_ix; @Bin(order = 18, byteOrder = LITTLE_ENDIAN) byte iff; @Bin(order = 19, byteOrder = LITTLE_ENDIAN) byte iff2; @Bin(order = 20) EmulFlags emulFlags; @Bin(order = 21, custom = true) byte[] data; } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/000077500000000000000000000000001445000440600273735ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/BinTypeTest.java000066400000000000000000000050621445000440600324530ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.mapper; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import java.math.BigInteger; import java.util.List; import org.junit.jupiter.api.Test; public class BinTypeTest { @Test public void testNPEInFindCompatibleForNull() { assertThrows(NullPointerException.class, () -> BinType.findCompatible(null)); } @Test public void testFindCompatibleArray() { assertEquals(BinType.BYTE_ARRAY, BinType.findCompatible(byte[].class)); assertEquals(BinType.USHORT_ARRAY, BinType.findCompatible(char[].class)); assertEquals(BinType.SHORT_ARRAY, BinType.findCompatible(short[].class)); assertEquals(BinType.BOOL_ARRAY, BinType.findCompatible(boolean[].class)); assertEquals(BinType.INT_ARRAY, BinType.findCompatible(int[].class)); assertEquals(BinType.LONG_ARRAY, BinType.findCompatible(long[].class)); assertEquals(BinType.STRUCT_ARRAY, BinType.findCompatible(List[].class)); assertEquals(BinType.FLOAT_ARRAY, BinType.findCompatible(float[].class)); assertEquals(BinType.DOUBLE_ARRAY, BinType.findCompatible(double[].class)); assertEquals(BinType.STRING_ARRAY, BinType.findCompatible(String[].class)); } @Test public void testFindCompatible() { assertEquals(BinType.BYTE, BinType.findCompatible(byte.class)); assertEquals(BinType.USHORT, BinType.findCompatible(char.class)); assertEquals(BinType.SHORT, BinType.findCompatible(short.class)); assertEquals(BinType.BOOL, BinType.findCompatible(boolean.class)); assertEquals(BinType.INT, BinType.findCompatible(int.class)); assertEquals(BinType.LONG, BinType.findCompatible(long.class)); assertEquals(BinType.STRUCT, BinType.findCompatible(BigInteger.class)); assertEquals(BinType.FLOAT, BinType.findCompatible(float.class)); assertEquals(BinType.DOUBLE, BinType.findCompatible(double.class)); assertEquals(BinType.STRING, BinType.findCompatible(String.class)); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/ClassWithPFGSG.java000066400000000000000000000022101445000440600327210ustar00rootroot00000000000000package com.igormaznitsa.jbbp.mapper; @Bin public class ClassWithPFGSG { @Bin(order = 1) private byte a; @Bin(order = 2) private byte b; @Bin(order = 3) private Internal i; public Internal getI() { return this.i; } public byte getA() { return this.a; } public void setA(final byte a) { this.a = a; } public byte getB() { return this.b; } public void setB(final byte b) { this.b = b; } public Internal makeI() { this.i = new Internal(); return this.i; } @Bin public class Internal { @Bin(order = 1) private byte c; @Bin(order = 2) private InternalInternal ii; public byte getC() { return this.c; } public void setC(byte c) { this.c = c; } public InternalInternal getIi() { return this.ii; } public InternalInternal makeIi() { this.ii = new InternalInternal(); return ii; } @Bin public class InternalInternal { @Bin(order = 1) private byte d; public byte getD() { return this.d; } public void setD(final byte d) { this.d = d; } } } } ClassWithPrivateFields.java000066400000000000000000000012741445000440600345460ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.mapper; class ClassWithPrivateFields { @Bin private int field; } ClassWithPrivateFieldsAndSetterGetter.java000066400000000000000000000015021445000440600375250ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.mapper; class ClassWithPrivateFieldsAndSetterGetter { @Bin private int field; public int getField() { return this.field; } public void setField(int value) { this.field = value; } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/JBBPMapperTest.java000066400000000000000000001177551445000440600330000ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.mapper; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.TestUtils; import com.igormaznitsa.jbbp.exceptions.JBBPMapperException; import com.igormaznitsa.jbbp.exceptions.JBBPNumericFieldValueConversionException; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPOut; import com.igormaznitsa.jbbp.model.JBBPFieldInt; import java.io.ByteArrayInputStream; import java.util.Random; import org.junit.jupiter.api.Test; public class JBBPMapperTest { @Test void testMakeNewInstanceInLocalThroughDefaultConstructors() throws Exception { final StaticTopNoInstanceMethod result = JBBPParser.prepare("levelOne { levelTwos[_]{byte a;}}").parse(new byte[] {1, 2, 3}) .mapTo(new StaticTopNoInstanceMethod()); assertNotNull(result.levelOne); assertNotNull(result.levelOne.levelTwos); assertEquals(3, result.levelOne.levelTwos.length); } @Test void testMakeNewInstanceInLocalThroughDefaultConstructors_WithFieldFilter() throws Exception { final StaticTopNoInstanceMethod result = JBBPParser.prepare("levelOne { levelTwos[_]{byte a;}}").parse(new byte[] {1, 2, 3, 4, 5, 6}) .mapTo(new StaticTopNoInstanceMethod(), (b, f) -> !f.getName().equals("levelTwos")); assertNotNull(result.levelOne); assertNull(result.levelOne.levelTwos); } @Test void testMakeNewInstanceInLocalStaticClasses() throws Exception { final StaticTop result = JBBPParser.prepare("levelOne { levelTwos[_]{byte a;}}").parse(new byte[] {1, 2, 3}) .mapTo(new StaticTop()); assertNotNull(result.levelOne); assertNotNull(result.levelOne.levelTwos); assertEquals(3, result.levelOne.levelTwos.length); } @Test void testMakeNewInstanceInLocalNonStaticClasses() throws Exception { class Top { @Bin LevelOne levelOne; public Object newInstance(final Class klazz) { if (klazz == LevelOne.class) { return new LevelOne(); } return null; } class LevelOne { @Bin LevelTwo[] levelTwos; public Object newInstance(final Class klazz) { if (klazz == LevelTwo.class) { return new LevelTwo(); } return null; } class LevelTwo { @Bin byte a; } } } final Top result = JBBPParser.prepare("levelOne { levelTwos[_]{byte a;}}").parse(new byte[] {1, 2, 3}) .mapTo(new Top()); assertNotNull(result.levelOne); assertNotNull(result.levelOne.levelTwos); assertEquals(3, result.levelOne.levelTwos.length); } @Test void testMap_InsideStructAndClass() throws Exception { class Mapped { @Bin byte a; } assertEquals(3, JBBPParser.prepare("byte a; some{struc {byte a;}}").parse(new byte[] {1, 3}) .mapTo("some.struc", new Mapped()).a); } @Test void testMap_RootStructToClassWithNullCustomProcessor() throws Exception { class Mapped { @Bin byte a; } assertEquals(3, JBBPMapper.map(JBBPParser.prepare("byte a;").parse(new byte[] {3}), new Mapped()).a); } @Test void testMap_Byte() throws Exception { class Mapped { @Bin byte a; } assertEquals(3, JBBPParser.prepare("byte a;").parse(new byte[] {3}).mapTo(new Mapped()).a); } @Test void testMap_Short() throws Exception { class Mapped { @Bin short a; } assertEquals(0x0304, JBBPParser.prepare("short a;").parse(new byte[] {3, 4}).mapTo(new Mapped()).a); } @Test void testMap_Boolean() throws Exception { class Mapped { @Bin boolean a; @Bin boolean b; @Bin boolean c; } final Mapped mapped = JBBPParser.prepare("bool a; bool b; bool c;").parse(new byte[] {23, 0, 12}) .mapTo(new Mapped()); assertTrue(mapped.a); assertFalse(mapped.b); assertTrue(mapped.c); } @Test void testMap_String() throws Exception { class Mapped { @Bin String a; @Bin String b; } final Mapped mapped = JBBPParser.prepare("stringj a; stringj b;").parse(new byte[] {3, 65, 66, 67, 2, 68, 69}) .mapTo(new Mapped()); assertEquals("ABC", mapped.a); assertEquals("DE", mapped.b); } @Test void testMap_String_WithFilter() throws Exception { class Mapped { @Bin String a; @Bin String b; } final Mapped mapped = JBBPParser.prepare("stringj a; stringj b;").parse(new byte[] {3, 65, 66, 67, 2, 68, 69}) .mapTo(new Mapped(), (b, f) -> !"b".equals(f.getName())); assertEquals("ABC", mapped.a); assertNull(mapped.b); } @Test void testMap_StringArrayToStringArray() throws Exception { class Mapped { @Bin String[] a; } final Mapped mapped = JBBPParser.prepare("stringj [_] a;").parse(new byte[] {3, 65, 66, 67, 2, 68, 69}) .mapTo(new Mapped()); assertArrayEquals(new String[] {"ABC", "DE"}, mapped.a); } @Test void testMap_IgnoreStaticField() throws Exception { final MappedWithStaticField mapped = JBBPParser.prepare("int a;").parse(new byte[] {1, 2, 3, 4}) .mapTo(new MappedWithStaticField()); assertEquals(0x01020304, mapped.a); assertEquals(111, MappedWithStaticField.ignored); } @Test void testMap_Bit() throws Exception { class Mapped { @Bin(type = BinType.BIT) byte a; @Bin(type = BinType.BIT) byte b; @Bin(type = BinType.BIT) byte c; } final Mapped mapped = JBBPParser.prepare("bit:3 a; bit:2 b; bit:3 c; ").parse(new byte[] {(byte) 0xDD}) .mapTo(new Mapped()); assertEquals(5, mapped.a); assertEquals(3, mapped.b); assertEquals(6, mapped.c); } @Test void testMap_Bit_WithFilter() throws Exception { class Mapped { @Bin(type = BinType.BIT) byte a; @Bin(type = BinType.BIT) byte b; @Bin(type = BinType.BIT) byte c; } final Mapped mapped = JBBPParser.prepare("bit:3 a; bit:2 b; bit:3 c; ").parse(new byte[] {(byte) 0xDD}) .mapTo(new Mapped(), (b, f) -> "c".equals(f.getName())); assertEquals(0, mapped.a); assertEquals(0, mapped.b); assertEquals(6, mapped.c); } @Test void testMap_Int() throws Exception { class Mapped { @Bin int a; } assertEquals(0x01020304, JBBPParser.prepare("int a;").parse(new byte[] {1, 2, 3, 4}).mapTo(new Mapped()).a); } @Test void testMap_UInt() throws Exception { class Mapped { @Bin(type = BinType.UINT) long a; } assertEquals(0xFFA0B0C0L, JBBPParser.prepare("uint a;") .parse(new byte[] {(byte) 0xFF, (byte) 0xA0, (byte) 0xB0, (byte) 0xC0}) .mapTo(new Mapped()).a); } @Test void testMap_UIntToInt() throws Exception { class Mapped { @Bin(type = BinType.UINT) int a; } assertEquals(0x1FA0B0C0, JBBPParser.prepare("uint a;") .parse(new byte[] {(byte) 0x1F, (byte) 0xA0, (byte) 0xB0, (byte) 0xC0}) .mapTo(new Mapped()).a); } @Test void testMap_UIntToInt_ErrorForTooBigValue() { class Mapped { @Bin(type = BinType.UINT) int a; } assertThrows(JBBPNumericFieldValueConversionException.class, () -> { JBBPParser.prepare("uint a;") .parse(new byte[] {(byte) 0xFF, (byte) 0xA0, (byte) 0xB0, (byte) 0xC0}) .mapTo(new Mapped()); }); } @Test void testMap_MapIntToFloat() throws Exception { class Mapped { @Bin(type = BinType.INT) float a; } final byte[] max = JBBPOut.BeginBin().Int(Float.floatToIntBits(Float.MAX_VALUE)).End().toByteArray(); assertEquals(Float.MAX_VALUE, JBBPParser.prepare("int a;").parse(max).mapTo(new Mapped()).a, 0.005d); final byte[] min = JBBPOut.BeginBin().Int(Float.floatToIntBits(Float.MIN_VALUE)).End().toByteArray(); assertEquals(Float.MIN_VALUE, JBBPParser.prepare("int a;").parse(min).mapTo(new Mapped()).a, 0.005d); } @Test void testMap_MapIntArrayToFloatArray() throws Exception { class Mapped { @Bin(type = BinType.INT_ARRAY) float[] a; } final byte[] max = JBBPOut.BeginBin().Float(Float.MAX_VALUE, Float.MIN_VALUE).End().toByteArray(); final Mapped result = JBBPParser.prepare("int [_] a;").parse(max).mapTo(new Mapped()); assertEquals(2, result.a.length); assertEquals(Float.MAX_VALUE, result.a[0], TestUtils.FLOAT_DELTA); assertEquals(Float.MIN_VALUE, result.a[1], TestUtils.FLOAT_DELTA); } @Test void testMap_MapUIntArrayToLongArray() throws Exception { class Mapped { @Bin(type = BinType.UINT_ARRAY) long[] a; } final byte[] max = JBBPOut.BeginBin().Byte(0xFF, 1, 2, 3, 0xFE, 4, 5, 6).End().toByteArray(); final Mapped result = JBBPParser.prepare("uint [_] a;").parse(max).mapTo(new Mapped()); assertEquals(2, result.a.length); assertEquals(0xFF010203L, result.a[0]); assertEquals(0xFE040506L, result.a[1]); } @Test void testMap_MapUIntArrayToIntArray() throws Exception { class Mapped { @Bin(type = BinType.UINT_ARRAY) int[] a; } final byte[] max = JBBPOut.BeginBin().Byte(0xFF, 1, 2, 3, 0xFE, 4, 5, 6).End().toByteArray(); final Mapped result = JBBPParser.prepare("uint [_] a;").parse(max).mapTo(new Mapped()); assertEquals(2, result.a.length); assertEquals(0xFF010203, result.a[0]); assertEquals(0xFE040506, result.a[1]); } @Test void testMap_MapUIntArrayToFloatArray() throws Exception { class Mapped { @Bin(type = BinType.UINT_ARRAY) float[] a; } final byte[] max = JBBPOut.BeginBin() .Int(Float.floatToIntBits(Float.MIN_VALUE), Float.floatToIntBits(Float.MAX_VALUE)).End() .toByteArray(); final Mapped result = JBBPParser.prepare("uint [_] a;").parse(max).mapTo(new Mapped()); assertEquals(2, result.a.length); assertEquals(Float.MIN_VALUE, result.a[0], TestUtils.FLOAT_DELTA); assertEquals(Float.MAX_VALUE, result.a[1], TestUtils.FLOAT_DELTA); } @Test void testMap_MapUIntArrayToDoubleArray() throws Exception { class Mapped { @Bin(type = BinType.UINT_ARRAY) double[] a; } final byte[] max = JBBPOut.BeginBin().Int(0xFF0A0B0C, 0x01020304).End().toByteArray(); final Mapped result = JBBPParser.prepare("uint [_] a;").parse(max).mapTo(new Mapped()); assertEquals(2, result.a.length); assertEquals(2.114031933E-314d, result.a[0], TestUtils.FLOAT_DELTA); assertEquals(8.3541856E-317d, result.a[1], TestUtils.FLOAT_DELTA); } @Test void testMap_MapLongArrayToDoubleArray() throws Exception { class Mapped { @Bin(type = BinType.LONG_ARRAY) double[] a; } final byte[] max = JBBPOut.BeginBin().Double(Double.MAX_VALUE, Double.MIN_VALUE).End().toByteArray(); final Mapped result = JBBPParser.prepare("long [_] a;").parse(max).mapTo(new Mapped()); assertEquals(2, result.a.length); assertEquals(Double.MAX_VALUE, result.a[0], TestUtils.FLOAT_DELTA); assertEquals(Double.MIN_VALUE, result.a[1], TestUtils.FLOAT_DELTA); } @Test void testMap_MapFloatToFloat() throws Exception { class Mapped { @Bin float a; } final byte[] max = JBBPOut.BeginBin().Float(-1.234567f).End().toByteArray(); assertEquals(-1.234567f, JBBPParser.prepare("floatj a;").parse(max).mapTo(new Mapped()).a, TestUtils.FLOAT_DELTA); } @Test void testMap_MapFloatArrayToFloatArray() throws Exception { class Mapped { @Bin float[] a; } final byte[] max = JBBPOut.BeginBin().Float(-1.234567f, 1.234567f).End().toByteArray(); assertArrayEquals(new float[] {-1.234567f, 1.234567f}, JBBPParser.prepare("floatj [_] a;").parse(max).mapTo(new Mapped()).a, TestUtils.FLOAT_DELTA); } @Test void testMap_MapFloatArrayToFloatArray_WithFilter() throws Exception { class Mapped { @Bin float[] a; } final byte[] max = JBBPOut.BeginBin().Float(-1.234567f, 1.234567f).End().toByteArray(); assertNull( JBBPParser.prepare("floatj [_] a;").parse(max).mapTo(new Mapped(), (b, f) -> false).a); } @Test void testMap_MapLongToDouble() throws Exception { class Mapped { @Bin(type = BinType.LONG) double a; } final byte[] max = JBBPOut.BeginBin().Long(Double.doubleToLongBits(Double.MAX_VALUE)).End().toByteArray(); assertEquals(Double.MAX_VALUE, JBBPParser.prepare("long a;").parse(max).mapTo(new Mapped()).a, TestUtils.FLOAT_DELTA); final byte[] min = JBBPOut.BeginBin().Long(Double.doubleToLongBits(Double.MIN_VALUE)).End().toByteArray(); assertEquals(Double.MIN_VALUE, JBBPParser.prepare("long a;").parse(min).mapTo(new Mapped()).a, TestUtils.FLOAT_DELTA); } @Test void testMap_MapDoubleToDouble() throws Exception { class Mapped { @Bin double a; } final byte[] max = JBBPOut.BeginBin().Double(-1.2345678912345d).End().toByteArray(); assertEquals(-1.2345678912345d, JBBPParser.prepare("doublej a;").parse(max).mapTo(new Mapped()).a, TestUtils.FLOAT_DELTA); } @Test void testMap_MapDoubleArrayToDoubleArray() throws Exception { class Mapped { @Bin double[] a; } final byte[] max = JBBPOut.BeginBin().Double(-1.2345678912345d, 45.3334d).End().toByteArray(); assertArrayEquals(new double[] {-1.2345678912345d, 45.3334d}, JBBPParser.prepare("doublej [_] a;").parse(max).mapTo(new Mapped()).a, TestUtils.FLOAT_DELTA); } @Test void testMap_Long() throws Exception { class Mapped { @Bin long a; } assertEquals(0x0102030405060708L, JBBPParser.prepare("long a;").parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}) .mapTo(new Mapped()).a); } @Test void testMap_UByte() throws Exception { class Mapped { @Bin(type = BinType.UBYTE) int a; } assertEquals(0xFE, JBBPParser.prepare("ubyte a;").parse(new byte[] {(byte) 0xFE}).mapTo(new Mapped()).a); } @Test void testMap_UShort() throws Exception { class Mapped { @Bin char a; } assertEquals(0x0102, JBBPParser.prepare("ushort a;").parse(new byte[] {1, 2}).mapTo(new Mapped()).a); } @Test void testMap_ByteArray() throws Exception { class Mapped { @Bin byte[] a; } assertArrayEquals(new byte[] {1, 2, 3, 4}, JBBPParser.prepare("byte [_] a;").parse(new byte[] {1, 2, 3, 4}).mapTo(new Mapped()).a); } @Test void testMap_UByteArrayToString() throws Exception { class Mapped { @Bin(type = BinType.UBYTE_ARRAY) String a; } assertEquals("JFIF", JBBPParser.prepare("ubyte [_] a;") .parse(new byte[] {(byte) 0x4A, (byte) 0x46, (byte) 0x49, (byte) 0x46}) .mapTo(new Mapped()).a); } @Test void testMap_BitArrayToString() throws Exception { class Mapped { @Bin(type = BinType.BIT_ARRAY) String a; } assertEquals(new String(new char[] {0xA, 0x4, 0x6, 0x4, 0x9, 0x4, 0x6, 0x4}), JBBPParser.prepare("bit:4 [_] a;") .parse(new byte[] {(byte) 0x4A, (byte) 0x46, (byte) 0x49, (byte) 0x46}) .mapTo(new Mapped()).a); } @Test void testMap_BitArrayToStringWhenWholeByte() throws Exception { class Mapped { @Bin(type = BinType.BIT_ARRAY) String a; } assertEquals(new String(new char[] {0xFF, 0xED, 0x01, 0x36}), JBBPParser.prepare("bit:8 [_] a;") .parse(new byte[] {(byte) 0xFF, (byte) 0xED, (byte) 0x01, (byte) 0x36}) .mapTo(new Mapped()).a); } @Test void testMap_ByteArrayToString() throws Exception { class Mapped { @Bin(type = BinType.BYTE_ARRAY) String a; } assertEquals("JFIF", JBBPParser.prepare("byte [_] a;") .parse(new byte[] {(byte) 0x4A, (byte) 0x46, (byte) 0x49, (byte) 0x46}) .mapTo(new Mapped()).a); } @Test void testMap_ShortArrayToString() throws Exception { class Mapped { @Bin(type = BinType.SHORT_ARRAY) String a; } assertEquals("JFIF", JBBPParser.prepare("short [_] a;") .parse(new byte[] {0, (byte) 0x4A, 0, (byte) 0x46, 0, (byte) 0x49, 0, (byte) 0x46}) .mapTo(new Mapped()).a); } @Test void testMap_IntArrayToString_Error() throws Exception { class Mapped { @Bin(type = BinType.INT_ARRAY) String a; } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("int [_] a;") .parse(new byte[] {0, (byte) 0x4A, 0, (byte) 0x46, 0, (byte) 0x49, 0, (byte) 0x46}) .mapTo(new Mapped())); } @Test void testMap_UShortArrayToString() throws Exception { class Mapped { @Bin(type = BinType.USHORT_ARRAY) String a; } assertEquals("JFIF", JBBPParser.prepare("ushort [_] a;") .parse(new byte[] {0, (byte) 0x4A, 0, (byte) 0x46, 0, (byte) 0x49, 0, (byte) 0x46}) .mapTo(new Mapped()).a); } @Test void testMap_BitArray() throws Exception { class Mapped { @Bin(type = BinType.BIT_ARRAY) byte[] a; } assertArrayEquals(new byte[] {2, 0, 3, 2}, JBBPParser.prepare("bit:2 [_] a;").parse(new byte[] {(byte) 0xB2}).mapTo(new Mapped()).a); } @Test void testMap_ShortArray() throws Exception { class Mapped { @Bin short[] a; } assertArrayEquals(new short[] {0x0102, 0x0304}, JBBPParser.prepare("short [_] a;").parse(new byte[] {1, 2, 3, 4}).mapTo(new Mapped()).a); } @Test void testMap_BoolArray() throws Exception { class Mapped { @Bin boolean[] a; } final Mapped mapped = JBBPParser.prepare("bool [_] a;").parse(new byte[] {1, 0, 0, 4, 8, 0}).mapTo(new Mapped()); assertEquals(6, mapped.a.length); assertTrue(mapped.a[0]); assertFalse(mapped.a[1]); assertFalse(mapped.a[2]); assertTrue(mapped.a[3]); assertTrue(mapped.a[4]); assertFalse(mapped.a[5]); } @Test void testMap_UShortArray() throws Exception { class Mapped { @Bin char[] a; } assertArrayEquals(new char[] {0x0102, 0x0304}, JBBPParser.prepare("ushort [_] a;").parse(new byte[] {1, 2, 3, 4}).mapTo(new Mapped()).a); } @Test void testMap_IntArray() throws Exception { class Mapped { @Bin int[] a; } assertArrayEquals(new int[] {0x01020304, 0x05060708}, JBBPParser.prepare("int [_] a;").parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}) .mapTo(new Mapped()).a); } @Test public void testMap_Struct() throws Exception { class Inside { @Bin int a; } class Mapped { @Bin byte b; @Bin Inside a; } final Mapped mapped = JBBPParser.prepare("byte b; a{ int a; }").parse(new byte[] {1, 2, 3, 4, 5}) .mapTo(new Mapped(), aClass -> { if (aClass == Inside.class) { return new Inside(); } return null; }); assertEquals(0x02030405, mapped.a.a); } @Test public void testMap_Struct_WithFilter() throws Exception { class Inside { @Bin int a; } class Mapped { @Bin byte b; @Bin Inside a; } final Mapped mapped = JBBPParser.prepare("byte b; a{ int a; }").parse(new byte[] {1, 2, 3, 4, 5}) .mapTo(new Mapped(), (b, f) -> !(f.getDeclaringClass().getSimpleName().equals("Inside") && f.getName().equals("a")), aClass -> { if (aClass == Inside.class) { return new Inside(); } return null; }); assertEquals(0, mapped.a.a); } @Test void testMap_StructArray() throws Exception { class Inside { @Bin int a; } class Mapped { @Bin byte b; @Bin Inside[] a; } final Mapped mapped = JBBPParser.prepare("byte b; a [_]{ int a; }").parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}) .mapTo(new Mapped(), aClass -> { if (aClass == Inside.class) { return new Inside(); } return null; }); assertEquals(2, mapped.a.length); assertEquals(0x02030405, mapped.a[0].a); assertEquals(0x06070809, mapped.a[1].a); } @Test void testMap_StructArray_WithFilter() throws Exception { class Inside { @Bin int a; } class Mapped { @Bin byte b; @Bin Inside[] a; } final Mapped mapped = JBBPParser.prepare("byte b; a [_]{ int a; }").parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}) .mapTo(new Mapped(), (b, f) -> !f.getName().equals("a"), aClass -> { if (aClass == Inside.class) { return new Inside(); } return null; }); assertEquals(1, mapped.b); assertNull(mapped.a); } @Test void testMap_LongArray() throws Exception { class Mapped { @Bin long[] a; } assertArrayEquals(new long[] {0x0102030405060708L, 0x1112131415161718L}, JBBPParser.prepare("long [_] a;").parse( new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}) .mapTo(new Mapped()).a); } @Test void testMap_LongArray_WithFilter() throws Exception { class Mapped { @Bin long[] a; } assertNull(JBBPParser.prepare("long [_] a;").parse( new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18}) .mapTo(new Mapped(), (b, f) -> !f.getName().equals("a")).a); } @Test void testMap_ErrorForMappingStructureToPrimitiveField() { class Mapped { @Bin(name = "test", type = BinType.STRUCT) long a; } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("test { byte [_] a;}").parse(new byte[] {1, 2, 3, 4}) .mapTo(new Mapped())); } @Test void testMap_ErrorForMappingStructureToPrimitiveField_IgnoredByFilter() { class Mapped { @Bin(name = "test", type = BinType.STRUCT) long a; } assertDoesNotThrow( () -> JBBPParser.prepare("test { byte [_] a;}").parse(new byte[] {1, 2, 3, 4}) .mapTo(new Mapped(), (b, f) -> !f.getName().equals("a"))); } @Test void testMap_mapInsideStructureDefinedByItsPath() throws Exception { class Mapped { @Bin long a; } final Mapped mapped = JBBPParser.prepare("byte f; test { inside {long a;} }") .parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}).mapTo("test.inside", new Mapped()); assertEquals(0x0203040506070809L, mapped.a); } @Test void testMap_mapInsideStructureDefinedByItsPath_ErrorForNonStructure() throws Exception { class Mapped { @Bin long a; } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("byte f; test { inside {long a;} }") .parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9}).mapTo("f", new Mapped())); } @Test void testMap_privateFieldInPackagelevelClass() { assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("int field;").parse(new byte[] {1, 2, 3, 4}) .mapTo(new ClassWithPrivateFields())); } @Test void testMap_privateFieldWithSetterInPackagelevelClass() throws Exception { final ClassWithPrivateFieldsAndSetterGetter instance = JBBPParser.prepare("int field;").parse(new byte[] {1, 2, 3, 4}) .mapTo(new ClassWithPrivateFieldsAndSetterGetter()); assertEquals(0x1020304, instance.getField()); } @Test public void testMap_classWithGettersSettersAndGenerator() throws Exception { final ClassWithPFGSG instance = JBBPParser.prepare("byte a; byte b; i { byte c; ii { byte d;}}") .parse(new byte[] {1, 2, 3, 4}) .mapTo(new ClassWithPFGSG()); assertEquals(1, instance.getA()); assertEquals(2, instance.getB()); assertEquals(3, instance.getI().getC()); assertEquals(4, instance.getI().getIi().getD()); assertArrayEquals(new byte[] {1, 2, 3, 4}, JBBPOut.BeginBin().Bin(instance).End().toByteArray()); } @Test void testMap_customMappingFields_Class() throws Exception { final class Mapped { @Bin int a; @Bin(custom = true, paramExpr = "TEST_TEXT") String b; @Bin int c; } final Mapped mapped = JBBPParser.prepare("int a; int b; int c;") .parse(new byte[] {1, 2, 3, 4, 0x4A, 0x46, 0x49, 0x46, 5, 6, 7, 8}) .mapTo(new Mapped(), (parsedBlock, annotation, field) -> { if ("b".equals(field.getName()) && "TEST_TEXT".equals(annotation.paramExpr())) { final int bvalue = parsedBlock.findFieldForNameAndType("b", JBBPFieldInt.class).getAsInt(); return String.valueOf((char) ((bvalue >>> 24) & 0xFF)) + (char) ((bvalue >>> 16) & 0xFF) + (char) ((bvalue >>> 8) & 0xFF) + (char) (bvalue & 0xFF); } else { fail("Unexpected state" + field); return null; } }); assertEquals(0x01020304, mapped.a); assertEquals("JFIF", mapped.b); assertEquals(0x05060708, mapped.c); } @Test void testMap_customMappingFields_ClassInstance() throws Exception { final class Mapped { @Bin int a; @Bin(custom = true, paramExpr = "TEST_TEXT") String b; @Bin int c; } final Mapped mapped = new Mapped(); final Mapped result = JBBPParser.prepare("int a; int b; int c;") .parse(new byte[] {1, 2, 3, 4, 0x4A, 0x46, 0x49, 0x46, 5, 6, 7, 8}) .mapTo(mapped, (parsedBlock, annotation, field) -> { if ("b".equals(field.getName()) && "TEST_TEXT".equals(annotation.paramExpr())) { final int bvalue = parsedBlock.findFieldForNameAndType("b", JBBPFieldInt.class).getAsInt(); return String.valueOf((char) ((bvalue >>> 24) & 0xFF)) + (char) ((bvalue >>> 16) & 0xFF) + (char) ((bvalue >>> 8) & 0xFF) + (char) (bvalue & 0xFF); } else { fail("Unexpected state" + field); return null; } }); assertSame(mapped, result); assertEquals(0x01020304, mapped.a); assertEquals("JFIF", mapped.b); assertEquals(0x05060708, mapped.c); } @Test void testMap_AnnotationForWholeClass() throws Exception { @Bin final class Parsed { int a; int b; @Bin(type = BinType.BYTE_ARRAY) String c; } final Parsed parsed = JBBPParser.prepare("int a; int b; byte [_] c;") .parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd'}) .mapTo(new Parsed()); assertEquals(0x01020304, parsed.a); assertEquals(0x05060708, parsed.b); assertEquals("abcd", parsed.c); } @Test void testMap_InstanceOfInnerClass() throws Exception { final class Outer { @Bin int value; @Bin Inner inner; public Outer() { inner = new Outer.Inner(); } final class Inner { @Bin byte a; @Bin byte b; } } final Outer oldouter = new Outer(); final Outer.Inner inner = oldouter.inner; final Outer newouter = JBBPParser.prepare("int value; inner{ byte a; byte b;}") .parse(new byte[] {1, 2, 3, 4, 5, 6}).mapTo(oldouter); assertSame(oldouter, newouter); assertSame(inner, newouter.inner); assertEquals(0x01020304, oldouter.value); assertEquals(5, inner.a); assertEquals(6, inner.b); } @Test void testMap_InstanceOfInnerClassPreparedArray() throws Exception { final class Outer { @Bin int value; @Bin Inner[] inner; public Outer() { inner = new Outer.Inner[2]; inner[0] = new Outer.Inner(); inner[1] = new Outer.Inner(); } final class Inner { @Bin byte a; @Bin byte b; } } final Outer oldouter = new Outer(); final Outer.Inner[] inner = oldouter.inner; final Outer.Inner inner0 = oldouter.inner[0]; final Outer.Inner inner1 = oldouter.inner[1]; final Outer newouter = JBBPParser.prepare("int value; inner [2] { byte a; byte b;}") .parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).mapTo(oldouter); assertSame(oldouter, newouter); assertSame(inner, newouter.inner); assertSame(inner0, newouter.inner[0]); assertSame(inner1, newouter.inner[1]); assertEquals(0x01020304, oldouter.value); assertEquals(5, inner[0].a); assertEquals(6, inner[0].b); assertEquals(7, inner[1].a); assertEquals(8, inner[1].b); } @Test void testMap_InstanceOfInnerClassNonPreparedArray() throws Exception { final class Outer { @Bin int value; @Bin Inner[] inner; public Outer() { inner = new Outer.Inner[2]; } Inner makeInner() { return new Inner(); } final class Inner { @Bin byte a; @Bin byte b; } } final Outer oldouter = new Outer(); final Outer.Inner[] inner = oldouter.inner; assertNull(inner[0]); assertNull(inner[1]); final Outer newouter = JBBPParser.prepare("int value; inner [2] { byte a; byte b;}") .parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).mapTo(oldouter, aClass -> { if (aClass == Outer.Inner.class) { return oldouter.makeInner(); } return null; }); assertSame(oldouter, newouter); assertSame(inner, newouter.inner); assertEquals(0x01020304, oldouter.value); assertEquals(5, inner[0].a); assertEquals(6, inner[0].b); assertEquals(7, inner[1].a); assertEquals(8, inner[1].b); } @Test void testMap_InstanceOfInnerClassNonPreparedArray_ErrorForDifferentSize() { final class Outer { @Bin int val; @Bin Inner[] inner; public Outer() { inner = new Outer.Inner[3]; } final class Inner { @Bin byte a; @Bin byte b; } } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("int value; inner [2] { byte a; byte b;}") .parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}).mapTo(new Outer())); } @Test void testMap_MapToClassHierarchyWithAnnotationInheritance() throws Exception { @Bin class Ancestor { int a; } class Successor extends Ancestor { int b; } final Successor successor = JBBPParser.prepare("int a; int b;").parse(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}) .mapTo(new Successor()); assertEquals(0x01020304, successor.a); assertEquals(0x05060708, successor.b); } @Test void testMap_MapElementsByTheirPaths() throws Exception { class Parsed { @Bin(path = "struct.a") byte num; @Bin(path = "struct.b", type = BinType.BYTE_ARRAY) String str; } final Parsed parsed = JBBPParser.prepare("int start; struct { byte a; byte [3] b; } int end;") .parse(new byte[] {1, 2, 3, 4, 5, (byte) 'a', (byte) 'b', (byte) 'c', 6, 7, 8, 9}) .mapTo(new Parsed()); assertEquals(0x05, parsed.num); assertEquals("abc", parsed.str); } @Test void testMap_MapElementsByTheirPaths_ErrorForUnknownField() { class Parsed { @Bin(path = "struct.a") byte num; @Bin(path = "struct.c", type = BinType.BYTE_ARRAY) String str; } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("int start; struct { byte a; byte [3] b; } int end;") .parse(new byte[] {1, 2, 3, 4, 5, (byte) 'a', (byte) 'b', (byte) 'c', 6, 7, 8, 9}) .mapTo(new Parsed())); } @Test void testMap_MapElementsByTheirPaths_ErrorForFieldIncompatibleType() { class Parsed { @Bin(path = "struct.a") byte num; @Bin(path = "struct.b", type = BinType.UBYTE_ARRAY) String str; } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("int start; struct { byte a; byte [3] b; } int end;") .parse(new byte[] {1, 2, 3, 4, 5, (byte) 'a', (byte) 'b', (byte) 'c', 6, 7, 8, 9}) .mapTo(new Parsed())); } @Test void testMap_MapElementsByTheirPaths_ErrorForFieldIncompatibleType_ArrayMappingField() { class Parsed { @Bin(path = "struct.a", type = BinType.BYTE) byte[] num; @Bin(path = "struct.b", type = BinType.BYTE_ARRAY) String str; } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("int start; struct { byte a; byte [3] b; } int end;") .parse(new byte[] {1, 2, 3, 4, 5, (byte) 'a', (byte) 'b', (byte) 'c', 6, 7, 8, 9}) .mapTo(new Parsed())); } @Test void testMap_MapElementsByTheirPaths_ErrorForFieldIncompatibleType_ArrayBinField() { class Parsed { @Bin(path = "struct.a") byte num; @Bin(path = "struct.b", type = BinType.BYTE_ARRAY) byte str; } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("int start; struct { byte a; byte [3] b; } int end;") .parse(new byte[] {1, 2, 3, 4, 5, (byte) 'a', (byte) 'b', (byte) 'c', 6, 7, 8, 9}) .mapTo(new Parsed())); } @Test void testMap_IgnoreMarkedFieldByDefaultIfTransient() throws Exception { @Bin class Parsed { @Bin(path = "struct.a") byte num; @Bin(path = "struct.b", type = BinType.BYTE_ARRAY) String str; transient String ignored; } final Parsed parsed = JBBPParser.prepare("int start; struct { byte a; byte [3] b; } int end;") .parse(new byte[] {1, 2, 3, 4, 5, (byte) 'a', (byte) 'b', (byte) 'c', 6, 7, 8, 9}) .mapTo(new Parsed()); assertEquals(0x05, parsed.num); assertEquals("abc", parsed.str); } @Test void testMap_ParsedMarkedTransientField() throws Exception { @Bin class Parsed { @Bin(path = "struct.a") byte num; @Bin(path = "struct.b", type = BinType.BYTE_ARRAY) String str; @Bin(path = "struct.c", type = BinType.BYTE_ARRAY) transient String trans; } final Parsed parsed = JBBPParser.prepare("int start; struct { byte a; byte [3] b; byte [3] c; } byte end;").parse( new byte[] {1, 2, 3, 4, 5, (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', 9}).mapTo(new Parsed()); assertEquals(0x05, parsed.num); assertEquals("abc", parsed.str); assertEquals("def", parsed.trans); } @Test void testMap_IgnoreNonMarkedField() throws Exception { class Parsed { @Bin(path = "struct.a") byte num; @Bin(path = "struct.b", type = BinType.BYTE_ARRAY) String str; String ignored; } final Parsed parsed = JBBPParser.prepare("int start; struct { byte a; byte [3] b; } int end;") .parse(new byte[] {1, 2, 3, 4, 5, (byte) 'a', (byte) 'b', (byte) 'c', 6, 7, 8, 9}) .mapTo(new Parsed()); assertEquals(0x05, parsed.num); assertEquals("abc", parsed.str); } @Test void testMap_Structure_IgnoringFinalFieldsInMapping() throws Exception { @Bin class Struct { final byte a; final byte b; Struct(byte a, byte b) { this.a = a; this.b = b; } } @Bin class Parsed { final Struct[] struct; Parsed(Struct[] s) { this.struct = s; } } final Random rnd = new Random(1234); final byte[] array = new byte[1024]; rnd.nextBytes(array); final Parsed parsed = JBBPParser.prepare("struct [_] { byte a; byte b; }").parse(new ByteArrayInputStream(array)) .mapTo(new Parsed(null), aClass -> { fail("Must not be called"); return null; }); assertNull(parsed.struct); } @Test void testMap_FieldWithDefinedBitNumberToBitField_FieldPresented() throws Exception { class Parsed { @Bin(bitNumber = JBBPBitNumber.BITS_5) byte field; } final Parsed parsed = JBBPParser.prepare("int fieldint; bit:5 field;").parse(new byte[] {1, 2, 3, 4, 0x35}) .mapTo(new Parsed()); assertEquals(0x15, parsed.field); } @Test void testMap_FieldWithDefinedBitNumberToBitField_FieldPresentedWithDifferentBitNumber() throws Exception { class Parsed { @Bin(bitNumber = JBBPBitNumber.BITS_5) byte field; } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("int fieldint; bit:6 field;").parse(new byte[] {1, 2, 3, 4, 0x35}) .mapTo(new Parsed())); } @Test void testMap_ArrayFieldWithDefinedBitNumberToArrayBitField_FieldPresented() throws Exception { class Parsed { @Bin(bitNumber = JBBPBitNumber.BITS_4) byte[] field; } final Parsed parsed = JBBPParser.prepare("int fieldint; bit:4 [2] field;").parse(new byte[] {1, 2, 3, 4, 0x35}) .mapTo(new Parsed()); assertArrayEquals(new byte[] {5, 3}, parsed.field); } @Test void testMap_ArrayFieldIgnoredBitNumberFieldForDefinedType() throws Exception { class Parsed { @Bin(type = BinType.INT_ARRAY, bitNumber = JBBPBitNumber.BITS_4) int[] field; } final Parsed parsed = JBBPParser.prepare("int fieldint; int [2] field;") .parse(new byte[] {1, 2, 3, 4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x0A, 0x0B, 0x0C}) .mapTo(new Parsed()); assertArrayEquals(new int[] {0x05060708, 0x090A0B0C}, parsed.field); } @Test void testMap_ArrayFieldWithDefinedBitNumberToArrayBitField_FieldPresentedWithDifferentBitNumber() throws Exception { class Parsed { @Bin(bitNumber = JBBPBitNumber.BITS_4) byte field; } assertThrows(JBBPMapperException.class, () -> JBBPParser.prepare("int fieldint; bit:3 [2] field;") .parse(new byte[] {1, 2, 3, 4, 0x35}).mapTo(new Parsed())); } @Test void testMap_IgnoreNotFoundFields() throws Exception { class Parsed { @Bin int a; @Bin int b; } final Parsed parsed = JBBPParser.prepare("int a; int b;", JBBPParser.FLAG_SKIP_REMAINING_FIELDS_IF_EOF) .parse(new byte[] {1, 2, 3, 4}) .mapTo(new Parsed(), JBBPMapper.FLAG_IGNORE_MISSING_VALUES); assertEquals(0x01020304, parsed.a); assertEquals(0, parsed.b); } public static class StaticTop { @Bin public StaticLevelOne levelOne; public Object newInstance(final Class klazz) { if (klazz == StaticTop.StaticLevelOne.class) { return new StaticTop.StaticLevelOne(); } if (klazz == StaticTop.StaticLevelOne.StaticLevelTwo.class) { return new StaticTop.StaticLevelOne.StaticLevelTwo(); } return null; } public static class StaticLevelOne { @Bin public StaticLevelTwo[] levelTwos; public static class StaticLevelTwo { @Bin byte a; } } } public static class StaticTopNoInstanceMethod { @Bin public StaticLevelOne levelOne; public static class StaticLevelOne { @Bin public StaticLevelTwo[] levelTwos; public static class StaticLevelTwo { @Bin byte a; } } } @Bin private static class MappedWithStaticField { static int ignored = 111; int a; } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/000077500000000000000000000000001445000440600272075ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/FakePayload.java000066400000000000000000000014271445000440600322360ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import java.io.Serializable; public class FakePayload implements Serializable { private static final long serialVersionUID = 9192692187614580755L; } JBBPFieldArrayBitTest.java000066400000000000000000000076731445000440600337670ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayBitTest { private final byte[] array = new byte[] {(byte) -1, 0, 1, 2, 3}; private final JBBPFieldArrayBit test = new JBBPFieldArrayBit(new JBBPNamedFieldInfo("test.field", "field", 999), array, JBBPBitNumber.BITS_1); @Test public void testConstructor_NPEForNullBitNumber() { assertThrows(NullPointerException.class, () -> new JBBPFieldArrayBit(new JBBPNamedFieldInfo("test.field", "field", 999), new byte[] {(byte) -1, 0, 1, 2, 3}, null)); } @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); assertEquals(JBBPBitNumber.BITS_1, test.getBitWidth()); } @Test public void testSize() { assertEquals(5, test.size()); } @Test public void testGetArray() { assertArrayEquals(new byte[] {(byte) -1, 0, 1, 2, 3}, test.getArray()); } @Test public void testGetAsBool() { final boolean[] etalon = new boolean[] {true, false, true, true, true}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsBool(i)); } } @Test public void testGetAsInt() { final int[] etalon = new int[] {255, 0, 1, 2, 3}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsInt(i)); } } @Test public void testGetElementAt() { final int[] etalon = new int[] {255, 0, 1, 2, 3}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldBit f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], f.getAsInt()); } } @Test public void testGetAsLong() { final long[] etalon = new long[] {255L, 0L, 1L, 2L, 3L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsLong(i)); } } @Test public void testIterable() { final int[] etalon = new int[] {255, 0, 1, 2, 3}; int index = 0; for (final JBBPFieldBit f : test) { assertEquals(etalon[index++], f.getAsInt()); } } @Test public void testGetValueArrayAsObject() { final byte[] array = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; final JBBPFieldArrayBit test = new JBBPFieldArrayBit(new JBBPNamedFieldInfo("test.field", "field", 999), array, JBBPBitNumber.BITS_4); assertArrayEquals(array, (byte[]) test.getValueArrayAsObject(false)); final byte[] inverted = (byte[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldBit.reverseBits(array[i], JBBPBitNumber.BITS_4), inverted[i]); } } } JBBPFieldArrayBooleanTest.java000066400000000000000000000072201445000440600346140ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayBooleanTest { private final boolean[] array = new boolean[] {true, false, true, true, false}; private final JBBPFieldArrayBoolean test = new JBBPFieldArrayBoolean(new JBBPNamedFieldInfo("test.field", "field", 999), array); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(5, test.size()); } @Test public void testGetArray() { final boolean[] etalon = new boolean[] {true, false, true, true, false}; final boolean[] array = test.getArray(); assertEquals(etalon.length, array.length); for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], array[i]); } } @Test public void testGetAsBool() { final boolean[] etalon = new boolean[] {true, false, true, true, false}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsBool(i)); } } @Test public void testGetAsInt() { final int[] etalon = new int[] {1, 0, 1, 1, 0}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsInt(i)); } } @Test public void testGetAsLong() { final long[] etalon = new long[] {1L, 0L, 1L, 1L, 0L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsLong(i)); } } @Test public void testGetElementAt() { final boolean[] etalon = new boolean[] {true, false, true, true, false}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldBoolean f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], f.getAsBool()); } } @Test public void testIterable() { final boolean[] etalon = new boolean[] {true, false, true, true, false}; int index = 0; for (final JBBPFieldBoolean f : test) { assertEquals(etalon[index++], f.getAsBool()); } } @Test public void testGetValueArrayAsObject() { final boolean[] noninverted = (boolean[]) test.getValueArrayAsObject(false); assertEquals(array.length, noninverted.length); for (int i = 0; i < array.length; i++) { assertEquals(array[i], noninverted[i]); } final boolean[] inverted = (boolean[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldBoolean.reverseBits(array[i]) != 0L, inverted[i]); } } } JBBPFieldArrayByteTest.java000066400000000000000000000063421445000440600341440ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayByteTest { private final byte[] array = new byte[] {(byte) -1, 0, 1, 2, 3}; private final JBBPFieldArrayByte test = new JBBPFieldArrayByte(new JBBPNamedFieldInfo("test.field", "field", 999), array); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(5, test.size()); } @Test public void testGetArray() { assertArrayEquals(new byte[] {(byte) -1, 0, 1, 2, 3}, test.getArray()); } @Test public void testGetAsBool() { final boolean[] etalon = new boolean[] {true, false, true, true, true}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsBool(i)); } } @Test public void testGetAsInt() { final int[] etalon = new int[] {-1, 0, 1, 2, 3}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsInt(i)); } } @Test public void testGetAsLong() { final long[] etalon = new long[] {-1L, 0L, 1L, 2L, 3L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsLong(i)); } } @Test public void testIterable() { final int[] etalon = new int[] {-1, 0, 1, 2, 3}; int index = 0; for (final JBBPFieldByte f : test) { assertEquals(etalon[index++], f.getAsInt()); } } @Test public void testGetElementAt() { final int[] etalon = new int[] {-1, 0, 1, 2, 3}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldByte f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], f.getAsInt()); } } @Test public void testGetValueArrayAsObject() { assertArrayEquals(array, (byte[]) test.getValueArrayAsObject(false)); final byte[] inverted = (byte[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldByte.reverseBits(array[i]), inverted[i]); } } } JBBPFieldArrayIntTest.java000066400000000000000000000064301445000440600337710ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayIntTest { private final int[] array = new int[] {-278349, 12223423, 0, -2, 3}; private final JBBPFieldArrayInt test = new JBBPFieldArrayInt(new JBBPNamedFieldInfo("test.field", "field", 999), array); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(5, test.size()); } @Test public void testGetArray() { assertArrayEquals(new int[] {-278349, 12223423, 0, -2, 3}, test.getArray()); } @Test public void testGetAsBool() { final boolean[] etalon = new boolean[] {true, true, false, true, true}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsBool(i)); } } @Test public void testGetAsInt() { final int[] etalon = new int[] {-278349, 12223423, 0, -2, 3}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsInt(i)); } } @Test public void testGetAsLong() { final long[] etalon = new long[] {-278349L, 12223423L, 0L, -2L, 3L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsLong(i)); } } @Test public void testGetElementAt() { final int[] etalon = new int[] {-278349, 12223423, 0, -2, 3}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldInt f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], f.getAsInt()); } } @Test public void testIterable() { final int[] etalon = new int[] {-278349, 12223423, 0, -2, 3}; int index = 0; for (final JBBPFieldInt f : test) { assertEquals(etalon[index++], f.getAsInt()); } } @Test public void testGetValueArrayAsObject() { assertArrayEquals(array, (int[]) test.getValueArrayAsObject(false)); final int[] inverted = (int[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldInt.reverseBits(array[i]), inverted[i]); } } } JBBPFieldArrayLongTest.java000066400000000000000000000072231445000440600341370ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayLongTest { private final long[] array = new long[] {-278349872364L, 12223423987439324L, 0L, -2782346872343L, 37238468273412L}; private final JBBPFieldArrayLong test = new JBBPFieldArrayLong(new JBBPNamedFieldInfo("test.field", "field", 999), array); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(5, test.size()); } @Test public void testGetArray() { assertArrayEquals( new long[] {-278349872364L, 12223423987439324L, 0L, -2782346872343L, 37238468273412L}, test.getArray()); } @Test public void testGetAsBool() { final boolean[] etalon = new boolean[] {true, true, false, true, true}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsBool(i)); } } @Test public void testGetAsInt() { final int[] etalon = new int[] {(int) -278349872364L, (int) 12223423987439324L, (int) 0L, (int) -2782346872343L, (int) 37238468273412L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsInt(i)); } } @Test public void testGetAsLong() { final long[] etalon = new long[] {-278349872364L, 12223423987439324L, 0L, -2782346872343L, 37238468273412L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsLong(i)); } } @Test public void testGetElementAt() { final long[] etalon = new long[] {-278349872364L, 12223423987439324L, 0L, -2782346872343L, 37238468273412L}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldLong f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], f.getAsLong()); } } @Test public void testIterable() { final long[] etalon = new long[] {-278349872364L, 12223423987439324L, 0L, -2782346872343L, 37238468273412L}; int index = 0; for (final JBBPFieldLong f : test) { assertEquals(etalon[index++], f.getAsLong()); } } @Test public void testGetValueArrayAsObject() { assertArrayEquals(array, (long[]) test.getValueArrayAsObject(false)); final long[] inverted = (long[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldLong.reverseBits(array[i]), inverted[i]); } } } JBBPFieldArrayShortTest.java000066400000000000000000000064461445000440600343450ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayShortTest { private final short[] array = new short[] {(short) -27834, 23423, 0, -2, 3}; private final JBBPFieldArrayShort test = new JBBPFieldArrayShort(new JBBPNamedFieldInfo("test.field", "field", 999), array); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(5, test.size()); } @Test public void testGetArray() { assertArrayEquals(new short[] {(short) -27834, 23423, 0, -2, 3}, test.getArray()); } @Test public void testGetAsBool() { final boolean[] etalon = new boolean[] {true, true, false, true, true}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsBool(i)); } } @Test public void testGetAsInt() { final int[] etalon = new int[] {-27834, 23423, 0, -2, 3}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsInt(i)); } } @Test public void testGetAsLong() { final long[] etalon = new long[] {-27834L, 23423L, 0L, -2L, 3L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsLong(i)); } } @Test public void testGetElementAt() { final int[] etalon = new int[] {-27834, 23423, 0, -2, 3}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldShort f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], f.getAsInt()); } } @Test public void testIterable() { final int[] etalon = new int[] {-27834, 23423, 0, -2, 3}; int index = 0; for (final JBBPFieldShort f : test) { assertEquals(etalon[index++], f.getAsInt()); } } @Test public void testGetValueArrayAsObject() { assertArrayEquals(array, (short[]) test.getValueArrayAsObject(false)); final short[] inverted = (short[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldShort.reverseBits(array[i]), inverted[i]); } } } JBBPFieldArrayStringTest.java000066400000000000000000000041011445000440600344760ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/modelpackage com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayStringTest { private final String[] array = new String[] {"012", null, "ABC"}; private final JBBPFieldArrayString test = new JBBPFieldArrayString(new JBBPNamedFieldInfo("test.field", "field", 999), array); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(3, test.size()); } @Test public void testGetArray() { assertArrayEquals(new String[] {"012", null, "ABC"}, test.getArray()); } @Test public void testGetElementAt() { final String[] etalon = new String[] {"012", null, "ABC"}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldString f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], f.getAsString()); } } @Test public void testIterable() { final String[] etalon = new String[] {"012", null, "ABC"}; int index = 0; for (final JBBPFieldString f : test) { assertEquals(etalon[index++], f.getAsString()); } } @Test public void testGetValueArrayAsObject() { assertArrayEquals(array, (String[]) test.getValueArrayAsObject(false)); final String[] inverted = (String[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldString.reverseBits(array[i]), inverted[i]); } } }JBBPFieldArrayStructTest.java000066400000000000000000000047071445000440600345300ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import org.junit.jupiter.api.Test; public class JBBPFieldArrayStructTest { private final JBBPFieldArrayStruct test = new JBBPFieldArrayStruct( new JBBPNamedFieldInfo("test.field", "field", 999), new JBBPFieldStruct[] { new JBBPFieldStruct(null, new JBBPAbstractField[0]), new JBBPFieldStruct(null, new JBBPAbstractField[0]) } ); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(2, test.size()); } @Test public void testGetArray() { assertEquals(2, test.getArray().length); assertNotNull(test.getArray()[0]); assertNotNull(test.getArray()[1]); } @Test public void testIterable() { int counter = 0; for (final JBBPFieldStruct f : test) { assertNotNull(f); counter++; } assertEquals(2, counter); } @Test public void testGetValueArrayAsObject() throws Exception { final Object resultForFalse = test.getValueArrayAsObject(false); final Object resultForTrue = test.getValueArrayAsObject(true); assertNotSame(test.getArray(), resultForFalse); assertNotSame(test.getArray(), resultForTrue); assertNotSame(resultForTrue, resultForFalse); assertArrayEquals((Object[]) resultForTrue, test.getArray()); assertArrayEquals((Object[]) resultForTrue, (Object[]) resultForFalse); } } JBBPFieldArrayUByteTest.java000066400000000000000000000063521445000440600342720ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayUByteTest { private final byte[] array = new byte[] {(byte) -1, 0, 1, 2, 3}; private final JBBPFieldArrayUByte test = new JBBPFieldArrayUByte(new JBBPNamedFieldInfo("test.field", "field", 999), array); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(5, test.size()); } @Test public void testGetArray() { assertArrayEquals(new byte[] {(byte) -1, 0, 1, 2, 3}, test.getArray()); } @Test public void testGetAsBool() { final boolean[] etalon = new boolean[] {true, false, true, true, true}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsBool(i)); } } @Test public void testGetAsInt() { final int[] etalon = new int[] {255, 0, 1, 2, 3}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsInt(i)); } } @Test public void testGetAsLong() { final long[] etalon = new long[] {255L, 0L, 1L, 2L, 3L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsLong(i)); } } @Test public void testGetElementAt() { final int[] etalon = new int[] {255, 0, 1, 2, 3}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldUByte f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], f.getAsInt()); } } @Test public void testIterable() { final int[] etalon = new int[] {255, 0, 1, 2, 3}; int index = 0; for (final JBBPFieldUByte f : test) { assertEquals(etalon[index++], f.getAsInt()); } } @Test public void testGetValueArrayAsObject() { assertArrayEquals(array, (byte[]) test.getValueArrayAsObject(false)); final byte[] inverted = (byte[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldByte.reverseBits(array[i]), inverted[i]); } } } JBBPFieldArrayUIntTest.java000066400000000000000000000067711445000440600341260ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayUIntTest { private final int[] array = new int[] {-278349, 12223423, 0, -2, 3}; private final long[] arrayLong = new long[] {-278349 & 0xFFFFFFFFL, 12223423, 0, -2 & 0xFFFFFFFFL, 3}; private final JBBPFieldArrayUInt test = new JBBPFieldArrayUInt(new JBBPNamedFieldInfo("test.field", "field", 999), array); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(5, test.size()); } @Test public void testGetArray() { assertArrayEquals(new long[] {4294688947L, 12223423L, 0L, 4294967294L, 3L}, test.getArray()); } @Test public void testGetAsBool() { final boolean[] etalon = new boolean[] {true, true, false, true, true}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsBool(i)); } } @Test public void testGetAsInt() { final int[] etalon = new int[] {-278349, 12223423, 0, -2, 3}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsInt(i)); } } @Test public void testGetAsLong() { final long[] etalon = new long[] {4294688947L, 12223423L, 0L, 4294967294L, 3L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsLong(i)); } } @Test public void testGetElementAt() { final int[] etalon = new int[] {-278349, 12223423, 0, -2, 3}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldUInt f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], (int) f.getAsLong()); assertEquals(etalon[i] & 0xFFFFFFFFL, f.getAsLong()); } } @Test public void testIterable() { final int[] etalon = new int[] {-278349, 12223423, 0, -2, 3}; int index = 0; for (final JBBPFieldUInt f : test) { assertEquals(etalon[index++], (int) f.getAsLong()); } } @Test public void testGetValueArrayAsObject() { assertArrayEquals(arrayLong, (long[]) test.getValueArrayAsObject(false)); final long[] inverted = (long[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldUInt.reverseBits(array[i]), inverted[i]); } } } JBBPFieldArrayUShortTest.java000066400000000000000000000057421445000440600344700ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import java.io.Serializable; import org.junit.jupiter.api.Test; public class JBBPFieldArrayUShortTest { private final JBBPFieldArrayUShort test = new JBBPFieldArrayUShort(new JBBPNamedFieldInfo("test.field", "field", 999), new short[] {(short) -27834, 23423, 0, -2, 3}); @Test public void testNameAndOffset() { assertEquals("test.field", test.getFieldPath()); assertEquals("field", test.getFieldName()); assertNotNull(test.getNameInfo()); assertEquals(999, test.getNameInfo().getFieldOffsetInCompiledBlock()); } @Test public void testSize() { assertEquals(5, test.size()); } @Test public void testGetArray() { assertArrayEquals(new short[] {(short) -27834, 23423, 0, -2, 3}, test.getArray()); } @Test public void testGetAsBool() { final boolean[] etalon = new boolean[] {true, true, false, true, true}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsBool(i)); } } @Test public void testGetAsInt() { final int[] etalon = new int[] {-27834 & 0xFFFF, 23423, 0, -2 & 0xFFFF, 3}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsInt(i)); } } @Test public void testGetAsLong() { final long[] etalon = new long[] {-27834L & 0xFFFFL, 23423L, 0L, -2L & 0xFFFFL, 3L}; for (int i = 0; i < etalon.length; i++) { assertEquals(etalon[i], test.getAsLong(i)); } } @Test public void testGetElementAt() { final int[] etalon = new int[] {-27834 & 0xFFFF, 23423, 0, -2 & 0xFFFF, 3}; final Serializable payload = new FakePayload(); test.setPayload(payload); for (int i = 0; i < etalon.length; i++) { final JBBPFieldUShort f = test.getElementAt(i); assertSame(payload, f.getPayload()); assertEquals(etalon[i], f.getAsInt()); } } @Test public void testIterable() { final int[] etalon = new int[] {-27834 & 0xFFFF, 23423, 0, -2 & 0xFFFF, 3}; int index = 0; for (final JBBPFieldUShort f : test) { assertEquals(etalon[index++], f.getAsInt()); } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/JBBPFieldBitTest.java000066400000000000000000000070601445000440600330350ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import org.junit.jupiter.api.Test; public class JBBPFieldBitTest { @Test public void testConstructor_NPEForNullBitNumber() { assertThrows(NullPointerException.class, () -> new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 123, null)); } @Test public void testNameField() { final JBBPFieldBit field = new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 123, JBBPBitNumber.BITS_4); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); assertEquals(JBBPBitNumber.BITS_4, field.getBitWidth()); } @Test public void testgetAsBool_True() { assertTrue(new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 123, JBBPBitNumber.BITS_1).getAsBool()); } @Test public void testgetAsBool_False() { assertFalse(new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 0, JBBPBitNumber.BITS_1).getAsBool()); } @Test public void testgetAsInt() { assertEquals(12, new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 12, JBBPBitNumber.BITS_3).getAsInt()); assertEquals(-12 & 0xFF, new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), -12, JBBPBitNumber.BITS_4).getAsInt()); } @Test public void testgetAsLong() { assertEquals(12L, new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 12, JBBPBitNumber.BITS_6).getAsLong()); assertEquals(-12L & 0xFFL, new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), -12, JBBPBitNumber.BITS_5).getAsLong()); } @Test public void testGetAsInvertedBitOrder() { assertEquals(1L, new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 7, JBBPBitNumber.BITS_1).getAsInvertedBitOrder()); assertEquals(4L, new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 1, JBBPBitNumber.BITS_3).getAsInvertedBitOrder()); assertEquals(0xF0L, new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 0xF, JBBPBitNumber.BITS_8).getAsInvertedBitOrder()); assertEquals(0x8FL, new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 0xFF1, JBBPBitNumber.BITS_8).getAsInvertedBitOrder()); assertEquals(0x8L, new JBBPFieldBit(new JBBPNamedFieldInfo("test.field", "field", 123), 0xFF1, JBBPBitNumber.BITS_4).getAsInvertedBitOrder()); } } JBBPFieldBooleanTest.java000066400000000000000000000050551445000440600336210ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import org.junit.jupiter.api.Test; public class JBBPFieldBooleanTest { @Test public void testNameField() { final JBBPFieldBoolean field = new JBBPFieldBoolean(new JBBPNamedFieldInfo("test.field", "field", 123), true); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); } @Test public void testgetAsBool_True() { assertTrue( new JBBPFieldBoolean(new JBBPNamedFieldInfo("test.field", "field", 123), true).getAsBool()); } @Test public void testgetAsBool_False() { assertFalse(new JBBPFieldBoolean(new JBBPNamedFieldInfo("test.field", "field", 123), false) .getAsBool()); } @Test public void testgetAsInt() { assertEquals(1, new JBBPFieldBoolean(new JBBPNamedFieldInfo("test.field", "field", 123), true).getAsInt()); assertEquals(0, new JBBPFieldBoolean(new JBBPNamedFieldInfo("test.field", "field", 123), false).getAsInt()); } @Test public void testgetAsLong() { assertEquals(1L, new JBBPFieldBoolean(new JBBPNamedFieldInfo("test.field", "field", 123), true).getAsLong()); assertEquals(0L, new JBBPFieldBoolean(new JBBPNamedFieldInfo("test.field", "field", 123), false) .getAsLong()); } @Test public void testGetAsInvertedBitOrder() { assertEquals(0L, new JBBPFieldBoolean(new JBBPNamedFieldInfo("test.field", "field", 123), false) .getAsInvertedBitOrder()); assertEquals(1L, new JBBPFieldBoolean(new JBBPNamedFieldInfo("test.field", "field", 123), true) .getAsInvertedBitOrder()); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/JBBPFieldByteTest.java000066400000000000000000000055031445000440600332220ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import org.junit.jupiter.api.Test; public class JBBPFieldByteTest { @Test public void testNameField() { final JBBPFieldByte field = new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 123); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); } @Test public void testgetAsBool_True() { assertTrue(new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 123) .getAsBool()); } @Test public void testgetAsBool_False() { assertFalse(new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 0) .getAsBool()); } @Test public void testgetAsInt() { assertEquals(12, new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 12) .getAsInt()); assertEquals(-12, new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) -12) .getAsInt()); } @Test public void testgetAsLong() { assertEquals(12L, new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 12) .getAsLong()); assertEquals(-12L, new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) -12) .getAsLong()); } @Test public void testGetAsInvertedBitOrder() { assertEquals(0xFFFFFFFFFFFFFF80L, new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 1) .getAsInvertedBitOrder()); assertEquals(0xFFFFFFFFFFFFFFE0L, new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 7) .getAsInvertedBitOrder()); assertEquals(0x0FL, new JBBPFieldByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 0xF0) .getAsInvertedBitOrder()); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/JBBPFieldIntTest.java000066400000000000000000000054121445000440600330500ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import org.junit.jupiter.api.Test; public class JBBPFieldIntTest { @Test public void testNameField() { final JBBPFieldInt field = new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), 123456); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); } @Test public void testgetAsBool_True() { assertTrue( new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), 32423).getAsBool()); } @Test public void testgetAsBool_False() { assertFalse( new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), 0).getAsBool()); } @Test public void testgetAsInt() { assertEquals(234324, new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), 234324).getAsInt()); assertEquals(-234324, new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), -234324).getAsInt()); } @Test public void testgetAsLong() { assertEquals(234324L, new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), 234324).getAsLong()); assertEquals(-234324L, new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), -234324).getAsLong()); } @Test public void testGetAsInvertedBitOrder() { assertEquals(0x0000000020C04080L, new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), 0x01020304) .getAsInvertedBitOrder()); assertEquals(0x000000007FFFFFFFL, new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), 0xFFFFFFFE) .getAsInvertedBitOrder()); assertEquals(0xFFFFFFFF80000000L, new JBBPFieldInt(new JBBPNamedFieldInfo("test.field", "field", 123), 0x00000001) .getAsInvertedBitOrder()); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/JBBPFieldLongTest.java000066400000000000000000000051251445000440600332160ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import org.junit.jupiter.api.Test; public class JBBPFieldLongTest { @Test public void testNameField() { final JBBPFieldLong field = new JBBPFieldLong(new JBBPNamedFieldInfo("test.field", "field", 123), 123456L); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); } @Test public void testgetAsBool_True() { assertTrue( new JBBPFieldLong(new JBBPNamedFieldInfo("test.field", "field", 123), 32423L).getAsBool()); } @Test public void testgetAsBool_False() { assertFalse( new JBBPFieldLong(new JBBPNamedFieldInfo("test.field", "field", 123), 0L).getAsBool()); } @Test public void testgetAsInt() { assertEquals((int) 23432498237439L, new JBBPFieldLong(new JBBPNamedFieldInfo("test.field", "field", 123), 23432498237439L) .getAsInt()); assertEquals((int) -2343249987234L, new JBBPFieldLong(new JBBPNamedFieldInfo("test.field", "field", 123), -2343249987234L) .getAsInt()); } @Test public void testgetAsLong() { assertEquals(23432498237439L, new JBBPFieldLong(new JBBPNamedFieldInfo("test.field", "field", 123), 23432498237439L) .getAsLong()); assertEquals(-2343249987234L, new JBBPFieldLong(new JBBPNamedFieldInfo("test.field", "field", 123), -2343249987234L) .getAsLong()); } @Test public void testGetAsInvertedBitOrder() { assertEquals(0x10E060A020C04080L, new JBBPFieldLong(new JBBPNamedFieldInfo("test.field", "field", 123), 0x0102030405060708L) .getAsInvertedBitOrder()); } } JBBPFieldShortTest.java000066400000000000000000000055721445000440600333450ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import org.junit.jupiter.api.Test; public class JBBPFieldShortTest { @Test public void testNameField() { final JBBPFieldShort field = new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 23456); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); } @Test public void testgetAsBool_True() { assertTrue(new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 32423) .getAsBool()); } @Test public void testgetAsBool_False() { assertFalse(new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 0) .getAsBool()); } @Test public void testgetAsInt() { assertEquals(23432, new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 23432) .getAsInt()); assertEquals(-23432, new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) -23432) .getAsInt()); } @Test public void testgetAsLong() { assertEquals(23432L, new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 23432) .getAsLong()); assertEquals(-23432L, new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) -23432) .getAsLong()); } @Test public void testGetAsInvertedBitOrder() { assertEquals(0xFFFFFFFFFFFF8000L, new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 0x1) .getAsInvertedBitOrder()); assertEquals(0x0L, new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 0x0) .getAsInvertedBitOrder()); assertEquals(0x0000000000004080L, new JBBPFieldShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 0x0102) .getAsInvertedBitOrder()); } } JBBPFieldStringTest.java000066400000000000000000000026621445000440600335110ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/modelpackage com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import org.junit.jupiter.api.Test; public class JBBPFieldStringTest { @Test public void testNameField() { final JBBPFieldString field = new JBBPFieldString(new JBBPNamedFieldInfo("test.field", "field", 123), "Huzzaa"); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); } @Test public void testReverseBits_Null() { assertNull(JBBPFieldString.reverseBits(null)); } @Test public void testReverseBits_Text() { assertEquals("\u0C00谀䰀", JBBPFieldString.reverseBits("012")); assertEquals("012", JBBPFieldString.reverseBits(JBBPFieldString.reverseBits("012"))); } @Test public void testGetAsString_NotNull() { final JBBPFieldString field = new JBBPFieldString(new JBBPNamedFieldInfo("test.field", "field", 123), "Huzzaa"); assertEquals("Huzzaa", field.getAsString()); } @Test public void testGetAsString_Null() { final JBBPFieldString field = new JBBPFieldString(new JBBPNamedFieldInfo("test.field", "field", 123), null); assertNull(field.getAsString()); } }JBBPFieldStructTest.java000066400000000000000000000301531445000440600335230ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.exceptions.JBBPFinderException; import com.igormaznitsa.jbbp.exceptions.JBBPTooManyFieldsFoundException; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.utils.JBBPUtils; import org.junit.jupiter.api.Test; public class JBBPFieldStructTest { @Test public void testConstructor_Fields() { final JBBPFieldStruct struct = new JBBPFieldStruct(null, new JBBPAbstractField[] {new JBBPFieldByte(null, (byte) 123), new JBBPFieldByte(null, (byte) -123)}); assertNull(struct.getNameInfo()); assertEquals(2, struct.getArray().length); } @Test public void testConstructor_Name_Fields() { final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), new JBBPAbstractField[] {new JBBPFieldByte(null, (byte) 123), new JBBPFieldByte(null, (byte) -123)}); assertNotNull(struct.getNameInfo()); assertEquals("test.struct", struct.getNameInfo().getFieldPath()); assertEquals("struct", struct.getNameInfo().getFieldName()); assertEquals(999, struct.getNameInfo().getFieldOffsetInCompiledBlock()); assertEquals(2, struct.getArray().length); } @Test public void testConstructor_Name_ListOfFields() { final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), JBBPUtils .fieldsAsList(new JBBPFieldByte(null, (byte) 123), new JBBPFieldByte(null, (byte) -123))); assertNotNull(struct.getNameInfo()); assertEquals("test.struct", struct.getNameInfo().getFieldPath()); assertEquals("struct", struct.getNameInfo().getFieldName()); assertEquals(999, struct.getNameInfo().getFieldOffsetInCompiledBlock()); assertEquals(2, struct.getArray().length); } @Test public void testFindFieldForName() { final JBBPFieldByte field1 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field1", "field1", 1024), (byte) 23); final JBBPFieldInt field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("struct.field2", "field2", 1960), 23432); final JBBPFieldByte field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field3", "field3", 2048), (byte) 78); final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), new JBBPAbstractField[] {field1, field2, field3}); assertSame(field1, struct.findFieldForName("field1")); assertSame(field2, struct.findFieldForName("field2")); assertSame(field3, struct.findFieldForName("field3")); assertNull(struct.findFieldForName("field4")); } @Test public void testFindFieldForNameAndType() { final JBBPFieldByte field1 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field1", "field1", 1024), (byte) 23); final JBBPFieldInt field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("struct.field2", "field2", 1960), 23432); final JBBPFieldByte field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field3", "field3", 2048), (byte) 78); final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), new JBBPAbstractField[] {field1, field2, field3}); assertSame(field2, struct.findFieldForNameAndType("field2", JBBPFieldInt.class)); assertNull(struct.findFieldForNameAndType("field2", JBBPFieldByte.class)); assertNull(struct.findFieldForNameAndType("field1", JBBPFieldInt.class)); } @Test public void testFindFieldForPathAndType() { final JBBPFieldByte field1 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field1", "field1", 1024), (byte) 23); final JBBPFieldInt field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("struct.field2", "field2", 1960), 23432); final JBBPFieldByte field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field3", "field3", 2048), (byte) 78); final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), new JBBPAbstractField[] {field1, field2, field3}); assertSame(field2, struct.findFieldForPathAndType("struct.field2", JBBPFieldInt.class)); assertNull(struct.findFieldForPathAndType("field2", JBBPFieldByte.class)); assertNull(struct.findFieldForPathAndType("field1", JBBPFieldInt.class)); } @Test public void testFindFieldForPath() { final JBBPFieldByte field = new JBBPFieldByte(new JBBPNamedFieldInfo("struct1.struct2.field3", "field3", 2048), (byte) 78); final JBBPFieldStruct struct2 = new JBBPFieldStruct(new JBBPNamedFieldInfo("struct1.struct2", "struct2", 1024), new JBBPAbstractField[] {field}); final JBBPFieldStruct struct1 = new JBBPFieldStruct(new JBBPNamedFieldInfo("struct1", "struct1", 1024), new JBBPAbstractField[] {struct2}); try { struct1.findFieldForPath(null); fail("Must throw NPE"); } catch (NullPointerException ex) { } try { struct1.findFieldForPath("struct1.struct2.field3.unknown"); fail("Must throw finder exception for attempt to find inside a field"); } catch (JBBPFinderException ex) { } assertNull(struct1.findFieldForPath("struct1.struct2.field0")); assertNull(struct1.findFieldForPath("struct")); assertSame(field, struct1.findFieldForPath("struct1.struct2.field3")); } @Test public void testFindFieldForType() { final JBBPFieldByte field1 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field1", "field1", 1024), (byte) 23); final JBBPFieldInt field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("struct.field2", "field2", 1960), 23432); final JBBPFieldByte field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field3", "field3", 2048), (byte) 78); final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), new JBBPAbstractField[] {field1, field2, field3}); try { struct.findFieldForType(JBBPFieldByte.class); fail("Must throw JBBPTooManyFieldsFoundException"); } catch (JBBPTooManyFieldsFoundException ex) { assertEquals(2, ex.getNumberOfFoundInstances()); } assertSame(field2, struct.findFieldForType(JBBPFieldInt.class)); assertNull(struct.findFieldForType(JBBPFieldArrayByte.class)); } @Test public void testFindFirstFieldForType() { final JBBPFieldByte field1 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field1", "field1", 1024), (byte) 23); final JBBPFieldInt field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("struct.field2", "field2", 1960), 23432); final JBBPFieldByte field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field3", "field3", 2048), (byte) 78); final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), new JBBPAbstractField[] {field1, field2, field3}); assertSame(field1, struct.findFirstFieldForType(JBBPFieldByte.class)); assertSame(field2, struct.findFirstFieldForType(JBBPFieldInt.class)); assertNull(struct.findFirstFieldForType(JBBPFieldArrayByte.class)); } @Test public void testFindLastFieldForType() { final JBBPFieldByte field1 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field1", "field1", 1024), (byte) 23); final JBBPFieldInt field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("struct.field2", "field2", 1960), 23432); final JBBPFieldByte field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field3", "field3", 2048), (byte) 78); final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), new JBBPAbstractField[] {field1, field2, field3}); assertSame(field3, struct.findLastFieldForType(JBBPFieldByte.class)); assertSame(field2, struct.findLastFieldForType(JBBPFieldInt.class)); assertNull(struct.findLastFieldForType(JBBPFieldArrayByte.class)); } @Test public void testPathExists() { final JBBPFieldByte field1 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field1", "field1", 1024), (byte) 23); final JBBPFieldInt field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("struct.field2", "field2", 1960), 23432); final JBBPFieldByte field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field3", "field3", 2048), (byte) 78); final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), new JBBPAbstractField[] {field1, field2, field3}); assertTrue(struct.pathExists("struct.field1")); assertFalse(struct.pathExists("field1")); assertFalse(struct.pathExists("struct.field4")); } @Test public void testNameExists() { final JBBPFieldByte field1 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field1", "field1", 1024), (byte) 23); final JBBPFieldInt field2 = new JBBPFieldInt(new JBBPNamedFieldInfo("struct.field2", "field2", 1960), 23432); final JBBPFieldByte field3 = new JBBPFieldByte(new JBBPNamedFieldInfo("struct.field3", "field3", 2048), (byte) 78); final JBBPFieldStruct struct = new JBBPFieldStruct(new JBBPNamedFieldInfo("test.struct", "struct", 999), new JBBPAbstractField[] {field1, field2, field3}); assertFalse(struct.nameExists("struct.field1")); assertTrue(struct.nameExists("field1")); assertFalse(struct.nameExists("struct.field4")); assertTrue(struct.nameExists("field3")); } @Test public void testMapTo_Class() throws Exception { final ClassTestMapToClass mapped = JBBPParser.prepare("byte a; byte b; byte c;").parse(new byte[] {1, 2, 3}) .mapTo(new ClassTestMapToClass()); assertEquals(1, mapped.a); assertEquals(2, mapped.b); assertEquals(3, mapped.c); } @Test public void testMapTo_Object() throws Exception { final ClassTestMapToClass mapped = new ClassTestMapToClass(); assertSame(mapped, JBBPParser.prepare("byte a; byte b; byte c;").parse(new byte[] {1, 2, 3}).mapTo(mapped)); assertEquals(1, mapped.a); assertEquals(2, mapped.b); assertEquals(3, mapped.c); } @Test public void testInterStructFieldReferences() throws Exception { final JBBPParser parser = JBBPParser.prepare( "header {ubyte sections; ubyte datalen;} sections [header.sections]{byte[header.datalen] data;}"); final JBBPFieldArrayStruct sections = parser.parse(new byte[] {3, 2, 1, 2, 3, 4, 5, 6}) .findFieldForNameAndType("sections", JBBPFieldArrayStruct.class); assertEquals(3, sections.size()); for (int i = 0; i < 3; i++) { JBBPFieldArrayByte data = sections.getElementAt(i).findFieldForNameAndType("data", JBBPFieldArrayByte.class); final int base = i * 2; assertArrayEquals(new byte[] {(byte) (base + 1), (byte) (base + 2)}, data.getArray()); } } private class ClassTestMapToClass { @Bin byte b; @Bin byte a; @Bin byte c; } } JBBPFieldUByteTest.java000066400000000000000000000055331445000440600332730ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import org.junit.jupiter.api.Test; public class JBBPFieldUByteTest { @Test public void testNameField() { final JBBPFieldUByte field = new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 10); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); } @Test public void testgetAsBool_True() { assertTrue(new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 123) .getAsBool()); } @Test public void testgetAsBool_False() { assertFalse(new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 0) .getAsBool()); } @Test public void testgetAsInt() { assertEquals(12, new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 12) .getAsInt()); assertEquals(-12 & 0xFF, new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) -12) .getAsInt()); } @Test public void testgetAsLong() { assertEquals(12L, new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 12) .getAsLong()); assertEquals(-12L & 0xFFL, new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) -12) .getAsLong()); } @Test public void testGetAsInvertedBitOrder() { assertEquals(0x0000000000000080L, new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 1) .getAsInvertedBitOrder()); assertEquals(0x00000000000000E0L, new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 7) .getAsInvertedBitOrder()); assertEquals(0x0FL, new JBBPFieldUByte(new JBBPNamedFieldInfo("test.field", "field", 123), (byte) 0xF0) .getAsInvertedBitOrder()); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/JBBPFieldUIntTest.java000066400000000000000000000057451445000440600332060ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.exceptions.JBBPNumericFieldValueConversionException; import org.junit.jupiter.api.Test; public class JBBPFieldUIntTest { @Test public void testNameField() { final JBBPFieldUInt field = new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), 123456); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); } @Test public void testGetAsBool_True() { assertTrue( new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), 32423).getAsBool()); } @Test public void testGetAsBool_False() { assertFalse( new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), 0).getAsBool()); } @Test public void testGetAsInt() { assertEquals(234324, new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), 234324).getAsInt()); assertThrows(JBBPNumericFieldValueConversionException.class, () -> new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), -234324).getAsInt()); } @Test public void testGetAsLong() { assertEquals(234324L, new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), 234324).getAsLong()); assertEquals(4294732972L, new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), 4294732972L).getAsLong()); } @Test public void testGetAsInvertedBitOrder() { assertEquals(0x0000000020C04080L, new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), 0x01020304) .getAsInvertedBitOrder()); assertEquals(0x000000007FFFFFFFL, new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), 0xFFFFFFFE) .getAsInvertedBitOrder()); assertEquals(0x0000000080000000L, new JBBPFieldUInt(new JBBPNamedFieldInfo("test.field", "field", 123), 0x00000001) .getAsInvertedBitOrder()); } } JBBPFieldUShortTest.java000066400000000000000000000070561445000440600334710ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/model/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.model; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import org.junit.jupiter.api.Test; public class JBBPFieldUShortTest { @Test public void testNameField() { final JBBPFieldUShort field = new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 23456); final JBBPNamedFieldInfo namedField = field.getNameInfo(); assertEquals("test.field", namedField.getFieldPath()); assertEquals("field", namedField.getFieldName()); assertEquals(123, namedField.getFieldOffsetInCompiledBlock()); } @Test public void testgetAsBool_True() { assertTrue( new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 32423) .getAsBool()); } @Test public void testgetAsBool_False() { assertFalse(new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 0) .getAsBool()); } @Test public void testgetAsInt() { assertEquals(23432, new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 23432) .getAsInt()); assertEquals(-23432 & 0xFFFF, new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) -23432) .getAsInt()); } @Test public void testgetAsLong() { assertEquals(23432L, new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 23432) .getAsLong()); assertEquals(-23432L & 0xFFFFL, new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) -23432) .getAsLong()); } @Test public void testGetAsInvertedBitOrder() { assertEquals(0x0000000000008000L, new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 0x1) .getAsInvertedBitOrder()); assertEquals(0x0L, new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 0x0) .getAsInvertedBitOrder()); assertEquals(0x0000000000004080L, new JBBPFieldUShort(new JBBPNamedFieldInfo("test.field", "field", 123), (short) 0x0102) .getAsInvertedBitOrder()); } @Test public void testGetValueArrayAsObject() { final short[] array = new short[] {(short) -27834, 23423, 0, -2, 3}; final JBBPFieldArrayUShort test = new JBBPFieldArrayUShort(new JBBPNamedFieldInfo("test.field", "field", 999), array); assertArrayEquals(array, (short[]) test.getValueArrayAsObject(false)); final short[] inverted = (short[]) test.getValueArrayAsObject(true); assertEquals(array.length, inverted.length); for (int i = 0; i < array.length; i++) { assertEquals(JBBPFieldShort.reverseBits(array[i]), inverted[i]); } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/testaux/000077500000000000000000000000001445000440600276045ustar00rootroot00000000000000AbstractJBBPToJavaConverterTest.java000066400000000000000000000256731445000440600364430ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/testaux/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.testaux; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.conversion.JBBPToJavaConverter; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.io.JBBPBitOutputStream; import com.igormaznitsa.jbbp.utils.ReflectUtils; import com.igormaznitsa.jbbp.utils.TargetSources; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import javax.tools.Diagnostic; import javax.tools.DiagnosticCollector; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; public abstract class AbstractJBBPToJavaConverterTest { protected static final String PACKAGE_NAME = "com.igormaznitsa.test"; protected static final String CLASS_NAME = "TestClass"; protected static TemporaryFolder tempFolder = new TemporaryFolder(); protected final Random testRandomGen = new Random(123456); protected boolean printGeneratedClassText = false; @BeforeAll public static void beforeAll() { tempFolder = new TemporaryFolder(); } @AfterAll public static void afterAll() { if (tempFolder != null) { tempFolder.dispose(); } } protected static Map makeMap(final String... values) { final Map result = new HashMap<>(); int i = 0; while (i < values.length) { result.put(values[i++], values[i++]); } return result; } protected Object callRead(final Object instance, final byte[] array) throws Exception { try { return this.callRead(instance, new JBBPBitInputStream(new ByteArrayInputStream(array))); } catch (InvocationTargetException ex) { if (ex.getCause() != null) { throw (Exception) ex.getCause(); } else { throw ex; } } } protected Object callRead(final Object instance, final JBBPBitInputStream inStream) throws Exception { try { instance.getClass().getMethod("read", JBBPBitInputStream.class).invoke(instance, inStream); return instance; } catch (InvocationTargetException ex) { if (ex.getTargetException() != null) { throw (Exception) ex.getTargetException(); } else { throw ex; } } } protected byte[] callWrite(final Object instance) throws Exception { try { final ByteArrayOutputStream bout = new ByteArrayOutputStream(); final JBBPBitOutputStream bitOut = new JBBPBitOutputStream(bout); instance.getClass().getMethod("write", JBBPBitOutputStream.class).invoke(instance, bitOut); bitOut.close(); return bout.toByteArray(); } catch (InvocationTargetException ex) { if (ex.getCause() != null) { throw (Exception) ex.getCause(); } else { throw ex; } } } protected void callWrite(final Object instance, final JBBPBitOutputStream outStream) throws Exception { instance.getClass().getMethod("write", JBBPBitOutputStream.class).invoke(instance, outStream); } protected Object compileAndMakeInstanceSrc(final String script, final String classCustomText, final StringBuilder srcBuffer) throws Exception { final String classBody = JBBPToJavaConverter.makeBuilder(JBBPParser.prepare(script)).setMainClassName(CLASS_NAME) .setMainClassPackage(PACKAGE_NAME).setMainClassCustomText(classCustomText).build() .convert(); if (srcBuffer != null) { srcBuffer.append(classBody); } final ClassLoader classLoader = saveAndCompile(new JavaClassContent(PACKAGE_NAME + '.' + CLASS_NAME, classBody)); return ReflectUtils.newInstance(classLoader.loadClass(PACKAGE_NAME + '.' + CLASS_NAME)); } protected Object compileAndMakeInstance(final String script) throws Exception { return this.compileAndMakeInstance(PACKAGE_NAME + '.' + CLASS_NAME, script, null); } protected Object compileAndMakeInstance(final String script, final int parserFlags) throws Exception { return this.compileAndMakeInstance(PACKAGE_NAME + '.' + CLASS_NAME, script, parserFlags, null); } protected Object compileAndMakeInstance(final String instanceClassName, final String script, final JBBPCustomFieldTypeProcessor customFieldProcessor, final JavaClassContent... extraClasses) throws Exception { return this .compileAndMakeInstance(instanceClassName, script, 0, customFieldProcessor, extraClasses); } public ClassLoader saveAndCompile(final JavaClassContent... javaClassContents) throws IOException { return this.saveAndCompile(null, javaClassContents); } public ClassLoader saveAndCompile(final ClassLoader classLoader, final JavaClassContent... javaClassContents) throws IOException { final File folder = tempFolder.newFolder(); final List classFiles = new ArrayList<>(); for (final JavaClassContent c : javaClassContents) { final File classFile = c.makeFile(folder); final File pack = classFile.getParentFile(); if (!pack.isDirectory() && !pack.mkdirs()) { throw new IOException("Can't create folder : " + pack); } FileUtils.writeStringToFile(classFile, c.getText(), "UTF-8"); classFiles.add(classFile); } final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); final DiagnosticCollector diagnostics = new DiagnosticCollector<>(); final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null); final Iterable compilationUnits = fileManager.getJavaFileObjectsFromFiles(classFiles); if (!compiler.getTask(null, fileManager, null, null, null, compilationUnits).call()) { for (final Diagnostic diagnostic : diagnostics.getDiagnostics()) { System.err .format("Error on line %d in %s%n", diagnostic.getLineNumber(), diagnostic.getSource()); } for (final File f : classFiles) { System.err.println("File '" + f.getName() + '\''); System.err.println("-------------------------------------------"); System.err.println(FileUtils.readFileToString(f, StandardCharsets.UTF_8)); } throw new IOException("Error during compilation"); } return classLoader == null ? new URLClassLoader(new URL[] {folder.toURI().toURL()}) : classLoader; } protected Object compileAndMakeInstance(final String instanceClassName, final String script, final int parserFlags, final JBBPCustomFieldTypeProcessor customFieldProcessor, final JavaClassContent... extraClasses) throws Exception { final List javaClassContents = new ArrayList<>(Arrays.asList(extraClasses)); final JavaClassContent javaClassContent = new JavaClassContent(PACKAGE_NAME + '.' + CLASS_NAME, JBBPParser.prepare(script, JBBPBitOrder.LSB0, customFieldProcessor, parserFlags) .convertToSrc(TargetSources.JAVA, PACKAGE_NAME + "." + CLASS_NAME).get(0).getResult() .values().iterator().next()); if (this.printGeneratedClassText) { System.out.println(javaClassContent.classText); } javaClassContents.add(0, javaClassContent); final ClassLoader classLoader = saveAndCompile(javaClassContents.toArray(new JavaClassContent[0])); return ReflectUtils.newInstance(classLoader.loadClass(instanceClassName)); } protected static class TemporaryFolder { private final File folder; private final AtomicBoolean disposed = new AtomicBoolean(false); public TemporaryFolder() { final String localTmpFolderPath = System.getProperty("jbbp.target.folder", null); if (localTmpFolderPath == null) { throw new Error("Temp folder is not defined among system properties"); } final File localTmpFolderAsFile = new File(localTmpFolderPath); if (!localTmpFolderAsFile.isDirectory() && !localTmpFolderAsFile.mkdirs()) { throw new Error("Can't create main temp folder : " + localTmpFolderAsFile); } try { final Path path = Files.createTempDirectory(localTmpFolderAsFile.toPath(), "jbbp2j6"); this.folder = path.toFile(); this.folder.deleteOnExit(); } catch (IOException ex) { throw new Error("Can't create tem directory", ex); } } public File newFolder() { if (this.disposed.get()) { throw new IllegalStateException("Already disposed"); } try { final File result = Files.createTempDirectory(this.folder.toPath(), "_jbbp").toFile(); result.deleteOnExit(); return result; } catch (IOException ex) { throw new Error( "Can't make new sub-folder in temp folder : " + this.folder.getAbsolutePath(), ex); } } public void dispose() { if (this.disposed.compareAndSet(false, true)) { try { FileUtils.deleteDirectory(this.folder); } catch (IOException ex) { throw new Error("Can't delete emp directory : " + this.folder.getAbsolutePath(), ex); } } else { throw new Error("Already disposed"); } } } public final static class JavaClassContent { private final String className; private final String classText; public JavaClassContent(final String className, final String classText) { this.className = className; this.classText = classText; } public File makeFile(final File folder) { return new File(folder, this.className.replace('.', '/') + ".java"); } public String getText() { return this.classText; } } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/000077500000000000000000000000001445000440600272475ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/DynamicIntBuffer.java000066400000000000000000000054511445000440600333100ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; import com.igormaznitsa.jbbp.compiler.conversion.JBBPToJavaConverterReadWriteTest; import java.util.Arrays; /** * Buffer to accumulate integer values. NB! It is being used in tests. * * @see JBBPToJavaConverterReadWriteTest */ public class DynamicIntBuffer { protected static final int INITIAL_CAPACITY_INCREASE = 128; protected int counter; protected int[] array; protected int capacityIncreaseStep; /** * Constructor with default buffer capacity. */ public DynamicIntBuffer() { this(INITIAL_CAPACITY_INCREASE); } /** * Constructor with capacity. * * @param capacity capacity of the inside buffer, must be >=0 */ public DynamicIntBuffer(final int capacity) { this.array = new int[capacity]; this.capacityIncreaseStep = INITIAL_CAPACITY_INCREASE; } /** * Write an integer value into the inside buffer. * * @param value value to be written */ public void write(final int value) { if (this.array.length == this.counter) { if (this.capacityIncreaseStep < 0x1000) { this.capacityIncreaseStep <<= 2; } else if (this.capacityIncreaseStep > 0x10000) { this.capacityIncreaseStep += this.capacityIncreaseStep / 3; } else { this.capacityIncreaseStep <<= 1; } int newCapacity = this.array.length + this.capacityIncreaseStep; if (newCapacity < 0) { newCapacity = Integer.MAX_VALUE; } this.array = Arrays.copyOf(this.array, newCapacity); } this.array[this.counter++] = value; } /** * Get current length of written data. * * @return the length of currently written data. */ public int length() { return this.counter; } /** * Reset the buffer data. * * @param capacity the capacity of the buffer */ public void reset(final int capacity) { this.counter = 0; this.capacityIncreaseStep = INITIAL_CAPACITY_INCREASE; this.array = this.array.length == capacity ? this.array : new int[capacity]; } /** * Convert current saved data into int array. * * @return int array from saved data, must not be null */ public int[] toIntArray() { return Arrays.copyOf(this.array, this.counter); } } JBBPCustomFieldTypeProcessorAggregatorTest.java000066400000000000000000000257551445000440600403320ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.igormaznitsa.jbbp.JBBPCustomFieldTypeProcessor; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.compiler.tokenizer.JBBPFieldTypeParameterContainer; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import com.igormaznitsa.jbbp.model.JBBPAbstractField; import com.igormaznitsa.jbbp.model.JBBPFieldByte; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; public class JBBPCustomFieldTypeProcessorAggregatorTest { @Test public void testConstructor_ErrorForDuplicatedType() throws Exception { assertThrows(IllegalArgumentException.class, () -> { final JBBPCustomFieldTypeProcessor proc1 = new JBBPCustomFieldTypeProcessor() { @Override public String[] getCustomFieldTypes() { return new String[] {"type1", "type2", "type3"}; } @Override public boolean isAllowed(JBBPFieldTypeParameterContainer fieldType, String fieldName, int extraData, boolean isArray) { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, int parserFlags, JBBPFieldTypeParameterContainer customTypeFieldInfo, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }; final JBBPCustomFieldTypeProcessor proc2 = new JBBPCustomFieldTypeProcessor() { @Override public String[] getCustomFieldTypes() { return new String[] {"type5", "type6", "type3"}; } @Override public boolean isAllowed(JBBPFieldTypeParameterContainer fieldType, String fieldName, int extraData, boolean isArray) { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, int parserFlags, JBBPFieldTypeParameterContainer customTypeFieldInfo, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }; new JBBPCustomFieldTypeProcessorAggregator(proc1, proc2); }); } @Test public void testJoiningTypes() throws Exception { final JBBPCustomFieldTypeProcessor proc1 = new JBBPCustomFieldTypeProcessor() { @Override public String[] getCustomFieldTypes() { return new String[] {"type1", "type2", "type3"}; } @Override public boolean isAllowed(JBBPFieldTypeParameterContainer fieldType, String fieldName, int extraData, boolean isArray) { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, int parserFlags, JBBPFieldTypeParameterContainer customTypeFieldInfo, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }; final JBBPCustomFieldTypeProcessor proc2 = new JBBPCustomFieldTypeProcessor() { @Override public String[] getCustomFieldTypes() { return new String[] {"type4", "type5", "type6"}; } @Override public boolean isAllowed(JBBPFieldTypeParameterContainer fieldType, String fieldName, int extraData, boolean isArray) { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, int parserFlags, JBBPFieldTypeParameterContainer customTypeFieldInfo, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException { throw new UnsupportedOperationException( "Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }; final List types = Arrays .asList(new JBBPCustomFieldTypeProcessorAggregator(proc1, proc2).getCustomFieldTypes()); assertEquals(6, types.size()); assertTrue(types.contains("type1")); assertTrue(types.contains("type2")); assertTrue(types.contains("type3")); assertTrue(types.contains("type4")); assertTrue(types.contains("type5")); assertTrue(types.contains("type6")); } @Test public void testAllowedAndRead() throws Exception { final List allowed = new ArrayList<>(); final List read = new ArrayList<>(); final JBBPCustomFieldTypeProcessor proc1 = new JBBPCustomFieldTypeProcessor() { @Override public String[] getCustomFieldTypes() { return new String[] {"type1", "type2", "type3"}; } @Override public boolean isAllowed(JBBPFieldTypeParameterContainer fieldType, String fieldName, int extraData, boolean isArray) { allowed.add(new Record(fieldType.getTypeName(), this)); return true; } @Override public JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, int parserFlags, JBBPFieldTypeParameterContainer customTypeFieldInfo, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException { read.add(new Record(customTypeFieldInfo.getTypeName(), this)); return new JBBPFieldByte(fieldName, (byte) in.readByte()); } }; final JBBPCustomFieldTypeProcessor proc2 = new JBBPCustomFieldTypeProcessor() { @Override public String[] getCustomFieldTypes() { return new String[] {"type4", "type5", "type6"}; } @Override public boolean isAllowed(JBBPFieldTypeParameterContainer fieldType, String fieldName, int extraData, boolean isArray) { allowed.add(new Record(fieldType.getTypeName(), this)); return true; } @Override public JBBPAbstractField readCustomFieldType(JBBPBitInputStream in, JBBPBitOrder bitOrder, int parserFlags, JBBPFieldTypeParameterContainer customTypeFieldInfo, JBBPNamedFieldInfo fieldName, int extraData, boolean readWholeStream, int arrayLength) throws IOException { read.add(new Record(customTypeFieldInfo.getTypeName(), this)); return new JBBPFieldByte(fieldName, (byte) in.readByte()); } }; final JBBPParser parser = JBBPParser .prepare("type1; type2; type3; type4; type5; type6;", JBBPBitOrder.LSB0, new JBBPCustomFieldTypeProcessorAggregator(proc1, proc2), 0); assertEquals(6, allowed.size()); assertEquals("type1", allowed.get(0).type); assertSame(proc1, allowed.get(0).proc); assertEquals("type2", allowed.get(1).type); assertSame(proc1, allowed.get(1).proc); assertEquals("type3", allowed.get(2).type); assertSame(proc1, allowed.get(2).proc); assertEquals("type4", allowed.get(3).type); assertSame(proc2, allowed.get(3).proc); assertEquals("type5", allowed.get(4).type); assertSame(proc2, allowed.get(4).proc); assertEquals("type6", allowed.get(5).type); assertSame(proc2, allowed.get(5).proc); parser.parse(new byte[] {0, 0, 0, 0, 0, 0}); assertEquals(6, read.size()); assertEquals("type1", read.get(0).type); assertSame(proc1, read.get(0).proc); assertEquals("type2", read.get(1).type); assertSame(proc1, read.get(1).proc); assertEquals("type3", read.get(2).type); assertSame(proc1, read.get(2).proc); assertEquals("type4", read.get(3).type); assertSame(proc2, read.get(3).proc); assertEquals("type5", read.get(4).type); assertSame(proc2, read.get(4).proc); assertEquals("type6", read.get(5).type); assertSame(proc2, read.get(5).proc); } static class Record { final String type; final JBBPCustomFieldTypeProcessor proc; public Record(final String type, final JBBPCustomFieldTypeProcessor proc) { this.type = type; this.proc = proc; } } } JBBPDslBuilderTest.java000066400000000000000000000545021445000440600333700ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/utilspackage com.igormaznitsa.jbbp.utils; import static com.igormaznitsa.jbbp.utils.JBBPDslBuilder.Begin; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.model.JBBPFieldArrayBit; import com.igormaznitsa.jbbp.model.JBBPFieldStruct; import org.junit.jupiter.api.Test; class JBBPDslBuilderTest { @Test public void testEmptyStringForEmptyContent() { assertEquals("", Begin().End()); assertEquals("", Begin().End(false)); assertEquals("", Begin().End(true)); } @Test public void testAllowedVarName() { Begin().Int("a").End(); Begin().Int("A").End(); Begin().Int("a1").End(); Begin().Int("a2").End(); Begin().Int("a1_1").End(); Begin().Int("a2_2").End(); } @Test public void testWrongName() { assertThrows(IllegalArgumentException.class, () -> Begin().Int("").End()); assertThrows(IllegalArgumentException.class, () -> Begin().Int(" ").End()); assertThrows(IllegalArgumentException.class, () -> Begin().Int("3a").End()); assertThrows(IllegalArgumentException.class, () -> Begin().Int("ab\n").End()); assertThrows(IllegalArgumentException.class, () -> Begin().Int("a$\n").End()); } @Test public void testCheckForDuplicatedNameInSameStructure() { final JBBPDslBuilder builder = JBBPDslBuilder.Begin().Int("test"); assertThrows(IllegalArgumentException.class, () -> builder.Bool("test")); assertThrows(IllegalArgumentException.class, () -> builder.Bool("Test")); final JBBPDslBuilder builder1 = JBBPDslBuilder.Begin().Int("test").Struct().CloseStruct(); assertThrows(IllegalArgumentException.class, () -> builder1.Bool("test")); final JBBPDslBuilder builder2 = JBBPDslBuilder.Begin().Int("test"); assertThrows(IllegalArgumentException.class, () -> builder2.Struct("test")); final JBBPDslBuilder builder3 = JBBPDslBuilder.Begin().Struct("test").Int("a").CloseStruct().Bool("b"); assertThrows(IllegalArgumentException.class, () -> builder3.Struct("test")); } @Test public void testCheckForDuplicatedNameInDifferentStructures() { JBBPDslBuilder.Begin().Int("test").Struct().Bool("test").CloseStruct().End(); JBBPDslBuilder.Begin().Int("test").Struct().Int("test").Struct().Bool("test").CloseStruct() .CloseStruct().End(); JBBPDslBuilder.Begin().Struct().Int("test").Struct().Bool("test").CloseStruct().CloseStruct() .Int("test").End(); JBBPDslBuilder.Begin().Struct("test").Int("test").CloseStruct().End(); } @Test public void testNonFormatted() { assertEquals("bool test;{int field1;}", Begin().Bool("test").Struct().Int("field1").CloseStruct().End(false)); } @Test public void testFormatted() { assertEquals("bool test;\n{\n\tint field1;\n}\n", Begin().Bool("test").Struct().Int("field1").CloseStruct().End(true)); } @Test public void testType_Custom() { assertEquals("some;", Begin().Custom("some").End()); assertEquals("some huzzaa;", Begin().Custom("some", "huzzaa").End()); assertEquals("some:(a+b);", Begin().Custom("some", null, "a+b").End()); assertEquals("some:(a+b) huzzaa;", Begin().Custom("some", "huzzaa", "a+b").End()); } @Test public void testType_Var() { assertEquals("var;", Begin().Var().End()); assertEquals("var huzzaa;", Begin().Var("huzzaa").End()); assertEquals("var:(a+b);", Begin().Var(null, "a+b").End()); assertEquals("var:(a+b) huzzaa;", Begin().Var("huzzaa", "a+b").End()); } @Test public void testComment() { assertEquals("// Test\n", Begin().Comment("Test").End()); assertEquals("// //\n// Test\n", Begin().Comment("//").Comment("Test").End()); assertEquals("// Test\n// Test2\n", Begin().Comment("Test").Comment("Test2").End()); assertEquals("int a;// Test\n// Test2\n", Begin().Int("a").Comment("Test").Comment("Test2").End()); assertEquals("int a;\n// Test\n// Test2\n", Begin().Int("a").NewLineComment("Test").NewLineComment("Test2").End()); assertEquals("int a;hello{// hello\n}// end hello\n", Begin().Int("a").Struct("hello").Comment("hello").CloseStruct().Comment("end hello").End()); } @Test public void testType_VarArray() { assertEquals("var[1234];", Begin().VarArray(1234).End()); assertEquals("var[q+b];", Begin().VarArray("q+b").End()); assertEquals("var[1234] lupus;", Begin().VarArray("lupus", 1234).End()); assertEquals("var[a+1234] lupus;", Begin().VarArray("lupus", "a+1234").End()); assertEquals("var:(c/2)[_] huzzaa;", Begin().VarArray("huzzaa", -1, "c/2").End()); assertEquals("var:(c/2)[a+b] huzzaa;", Begin().VarArray("huzzaa", "a+b", "c/2").End()); } @Test public void testType_CustomArray() { assertEquals("some[1234];", Begin().CustomArray("some", 1234).End()); assertEquals("some[q+b];", Begin().CustomArray("some", "q+b").End()); assertEquals("some[1234] lupus;", Begin().CustomArray("some", "lupus", 1234).End()); assertEquals("some[a+1234] lupus;", Begin().CustomArray("some", "lupus", "a+1234").End()); assertEquals("some:(c/2)[_] huzzaa;", Begin().CustomArray("some", "huzzaa", -1, "c/2").End()); assertEquals("some:(c/2)[a+b] huzzaa;", Begin().CustomArray("some", "huzzaa", "a+b", "c/2").End()); } @Test public void testType_Bit() { assertEquals("bit:1;", Begin().Bit().End()); assertEquals("bit:1 abc;", Begin().Bit("abc").End()); } @Test public void testType_Bits() { assertEquals("bit:4;", Begin().Bits(4).End()); assertEquals("bit:3;", Begin().Bits(JBBPBitNumber.BITS_3).End()); assertEquals("bit:3 huzzaa;", Begin().Bits("huzzaa", JBBPBitNumber.BITS_3).End()); assertEquals("bit:3;", Begin().Bits(null, JBBPBitNumber.BITS_3).End()); assertEquals("bit:(a+b);", Begin().Bits(null, "a+b").End()); assertEquals("bit:(a+b) abc;", Begin().Bits("abc", "a+b").End()); } @Test public void testType_Bool() { assertEquals("bool;", Begin().Bool().End()); assertEquals("bool abc;", Begin().Bool("abc").End()); } @Test public void testType_Byte() { assertEquals("byte;", Begin().Byte().End()); assertEquals("byte abc;", Begin().Byte("abc").End()); } @Test public void testType_UByte() { assertEquals("ubyte;", Begin().UByte().End()); assertEquals("ubyte abc;", Begin().UByte("abc").End()); } @Test public void testType_Short() { assertEquals("short;", Begin().Short().End()); assertEquals("short abc;", Begin().Short("abc").End()); } @Test public void testType_UShort() { assertEquals("ushort;", Begin().UShort().End()); assertEquals("ushort abc;", Begin().UShort("abc").End()); } @Test public void testType_Int() { assertEquals("int;", Begin().Int().End()); assertEquals("int abc;", Begin().Int("abc").End()); } @Test public void testType_Long() { assertEquals("long;", Begin().Long().End()); assertEquals("long abc;", Begin().Long("abc").End()); } @Test public void testType_Float() { assertEquals("floatj;", Begin().Float().End()); assertEquals("floatj abc;", Begin().Float("abc").End()); } @Test public void testType_Double() { assertEquals("doublej;", Begin().Double().End()); assertEquals("doublej abc;", Begin().Double("abc").End()); } @Test public void testType_String() { assertEquals("stringj;", Begin().String().End()); assertEquals("stringj abc;", Begin().String("abc").End()); } @Test public void testType_Skip() { assertEquals("skip:1;", Begin().Skip().End()); assertEquals("skip:3;", Begin().Skip(3).End()); assertEquals("skip:(a+b);", Begin().Skip("a+b").End()); assertThrows(IllegalArgumentException.class, () -> Begin().Skip(-3)); } @Test public void testResetCounter() { assertEquals("reset$$;", Begin().ResetCounter().End()); } @Test public void testVal() { assertThrows(NullPointerException.class, () -> Begin().Val(null, "a+b").End()); assertThrows(NullPointerException.class, () -> Begin().Val("hello", null).End()); assertThrows(IllegalArgumentException.class, () -> Begin().Val("", "a+b").End()); assertThrows(IllegalArgumentException.class, () -> Begin().Val("hello", "").End()); assertEquals("val:(a+b) hello;", Begin().Val("hello", "a+b").End()); } @Test public void testType_Align() { assertEquals("align:1;", Begin().Align().End()); assertEquals("align:3;", Begin().Align(3).End()); assertEquals("align:(a+b);", Begin().Align("a+b").End()); assertThrows(IllegalArgumentException.class, () -> Begin().Align(-3)); } @Test public void testType_BitArray() { assertEquals("bit:3[_];", Begin().BitArray(JBBPBitNumber.BITS_3, -1).End()); assertEquals("bit:3[23];", Begin().BitArray(JBBPBitNumber.BITS_3, 23).End()); assertEquals("bit:3[23] some;", Begin().BitArray("some", JBBPBitNumber.BITS_3, 23).End()); assertEquals("bit:3[a+b] some;", Begin().BitArray("some", JBBPBitNumber.BITS_3, "a+b").End()); assertEquals("bit:(a+b)[a+b] some;", Begin().BitArray("some", "a+b", "a+b").End()); assertEquals("bit:3[a+b];", Begin().BitArray(JBBPBitNumber.BITS_3, "a+b").End()); } @Test public void testType_BoolArray() { assertEquals("bool[a+b];", Begin().BoolArray("a+b").End()); assertEquals("bool[2334] abc;", Begin().BoolArray("abc", 2334).End()); assertEquals("bool[_];", Begin().BoolArray(-5).End()); assertEquals("bool[_] abc;", Begin().BoolArray("abc", -5).End()); assertEquals("bool[a+b] abc;", Begin().BoolArray("abc", "a+b").End()); } @Test public void testType_ByteArray() { assertEquals("byte[a+b];", Begin().ByteArray("a+b").End()); assertEquals("byte[2334] abc;", Begin().ByteArray("abc", 2334).End()); assertEquals("byte[_];", Begin().ByteArray(-5).End()); assertEquals("byte[_] abc;", Begin().ByteArray("abc", -5).End()); assertEquals("byte[a+b] abc;", Begin().ByteArray("abc", "a+b").End()); } @Test public void testType_UByteArray() { assertEquals("ubyte[a+b];", Begin().UByteArray("a+b").End()); assertEquals("ubyte[2334] abc;", Begin().UByteArray("abc", 2334).End()); assertEquals("ubyte[_];", Begin().UByteArray(-5).End()); assertEquals("ubyte[_] abc;", Begin().UByteArray("abc", -5).End()); assertEquals("ubyte[a+b] abc;", Begin().UByteArray("abc", "a+b").End()); } @Test public void testType_ShortArray() { assertEquals("short[a+b];", Begin().ShortArray("a+b").End()); assertEquals("short[2334] abc;", Begin().ShortArray("abc", 2334).End()); assertEquals("short[_];", Begin().ShortArray(-5).End()); assertEquals("short[_] abc;", Begin().ShortArray("abc", -5).End()); assertEquals("short[a+b] abc;", Begin().ShortArray("abc", "a+b").End()); } @Test public void testType_UShortArray() { assertEquals("ushort[a+b];", Begin().UShortArray("a+b").End()); assertEquals("ushort[2334] abc;", Begin().UShortArray("abc", 2334).End()); assertEquals("ushort[_];", Begin().UShortArray(-5).End()); assertEquals("ushort[_] abc;", Begin().UShortArray("abc", -5).End()); assertEquals("ushort[a+b] abc;", Begin().UShortArray("abc", "a+b").End()); } @Test public void testType_IntArray() { assertEquals("int[a+b];", Begin().IntArray("a+b").End()); assertEquals("int[2334] abc;", Begin().IntArray("abc", 2334).End()); assertEquals("int[_];", Begin().IntArray(-5).End()); assertEquals("int[_] abc;", Begin().IntArray("abc", -5).End()); assertEquals("int[a+b] abc;", Begin().IntArray("abc", "a+b").End()); } @Test public void testType_LongArray() { assertEquals("long[a+b];", Begin().LongArray("a+b").End()); assertEquals("long[2334] abc;", Begin().LongArray("abc", 2334).End()); assertEquals("long[_];", Begin().LongArray(-5).End()); assertEquals("long[_] abc;", Begin().LongArray("abc", -5).End()); assertEquals("long[a+b] abc;", Begin().LongArray("abc", "a+b").End()); } @Test public void testType_FloatArray() { assertEquals("floatj[a+b];", Begin().FloatArray("a+b").End()); assertEquals("floatj[2334] abc;", Begin().FloatArray("abc", 2334).End()); assertEquals("floatj[_];", Begin().FloatArray(-5).End()); assertEquals("floatj[_] abc;", Begin().FloatArray("abc", -5).End()); assertEquals("floatj[a+b] abc;", Begin().FloatArray("abc", "a+b").End()); } @Test public void testType_DoubleArray() { assertEquals("doublej[a+b];", Begin().DoubleArray("a+b").End()); assertEquals("doublej[2334] abc;", Begin().DoubleArray("abc", 2334).End()); assertEquals("doublej[_];", Begin().DoubleArray(-5).End()); assertEquals("doublej[_] abc;", Begin().DoubleArray("abc", -5).End()); assertEquals("doublej[a+b] abc;", Begin().DoubleArray("abc", "a+b").End()); } @Test public void testStruct_CloseStruct_All() { assertEquals("{{{}}}", Begin().Struct().Struct().Struct().CloseStruct(true).End()); assertEquals("{\n" + "\t{\n" + "\t\t{\n" + "\t\t}\n" + "\t}\n" + "}\n", Begin().Struct().Struct().Struct().CloseStruct(true).End(true)); assertThrows(IllegalStateException.class, () -> Begin().CloseStruct(true)); } @Test public void testSize() { final JBBPDslBuilder dsl = Begin(); assertEquals(0, dsl.size()); dsl.Int(); assertEquals(1, dsl.size()); } @Test public void testHasOpenedStructs() { final JBBPDslBuilder dsl = Begin(); assertFalse(dsl.hasOpenedStructs()); dsl.Struct(); assertTrue(dsl.hasOpenedStructs()); dsl.CloseStruct(); assertFalse(dsl.hasOpenedStructs()); } @Test public void testStructArray() { assertEquals("[123]{}", Begin().StructArray(123).CloseStruct().End()); assertEquals("[_]{}", Begin().StructArray("_").CloseStruct().End()); assertEquals("alloha[123]{}", Begin().StructArray("alloha", 123).CloseStruct().End()); assertEquals("alloha[_]{}", Begin().StructArray("alloha", "_").CloseStruct().End()); } @Test public void testStruct_CloseStruct() { assertEquals("{{{}}}", Begin().Struct().Struct().Struct().CloseStruct().CloseStruct().CloseStruct().End()); assertEquals("{\n" + "\t{\n" + "\t\t{\n" + "\t\t}\n" + "\t}\n" + "}\n", Begin().Struct().Struct().Struct().CloseStruct().CloseStruct().CloseStruct().End(true)); assertThrows(IllegalStateException.class, () -> Begin().CloseStruct()); } @Test public void testType_StringArray() { assertEquals("stringj[a+b];", Begin().StringArray("a+b").End()); assertEquals("stringj[2334] abc;", Begin().StringArray("abc", 2334).End()); assertEquals("stringj[_];", Begin().StringArray(-5).End()); assertEquals("stringj[_] abc;", Begin().StringArray("abc", -5).End()); assertEquals("stringj[a+b] abc;", Begin().StringArray("abc", "a+b").End()); } @Test public void testAnotatedClass_AllClassAnnotated() { @Bin class Test { @Bin(type = BinType.UBYTE) int a; } assertEquals("Test{ubyte a;}", Begin().AnnotatedClass(Test.class).End()); } @Test public void testAnotatedClass_AnnottatedButWithoutType() { class Test { @Bin(order = 1) int a; @Bin(order = 3) int c; @Bin(order = 2, byteOrder = JBBPByteOrder.LITTLE_ENDIAN) int b; @Bin(order = 4, arraySizeExpr = "a+b") Internal[] d; class Internal { @Bin(order = 1) short a; @Bin(order = 2, arraySizeExpr = "8") short[] b; } } assertEquals("Test{int a; { JBBPSystemProperty.PROPERTY_INSTANTIATOR_CLASS.remove(); System.setProperty(JBBPSystemProperty.PROPERTY_INSTANTIATOR_CLASS.getPropertyName(), "abcd"); JBBPSystemProperty.PROPERTY_INSTANTIATOR_CLASS.getAsInteger(12345); }); } } JBBPTextWriterTest.java000066400000000000000000001026071445000440600334600ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPByteOrder; import com.igormaznitsa.jbbp.it.AbstractParserIntegrationTest; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import com.igormaznitsa.jbbp.utils.JBBPTextWriter.Extra; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Field; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; public class JBBPTextWriterTest extends AbstractParserIntegrationTest { private static JBBPTextWriter makeWriter() { return new JBBPTextWriter(new StringWriter(), JBBPByteOrder.BIG_ENDIAN, "\n", 16, "0x", ".", ";", "~", ","); } @Test public void testMakeStrWriter() throws Exception { final String generated = JBBPTextWriter.makeStrWriter().Int(12).Byte(34).BR().Comment("Huzzaaa").Close().toString(); assertEquals(String.format(".0x0000000C,0x22%n;Huzzaaa"), generated); } @Test public void testConstructor_Default() { final JBBPTextWriter writer = new JBBPTextWriter(); assertTrue(writer.getWrappedWriter() instanceof StringWriter); assertEquals(JBBPByteOrder.BIG_ENDIAN, writer.getByteOrder()); } @Test public void testConstructor_OnlyWriter() { final Writer someWriter = new StringWriter(); final JBBPTextWriter writer = new JBBPTextWriter(someWriter); assertSame(someWriter, writer.getWrappedWriter()); assertEquals(JBBPByteOrder.BIG_ENDIAN, writer.getByteOrder()); } @Test public void testConstructor_WriterAndByteOrder() { final Writer someWriter = new StringWriter(); final JBBPTextWriter writer = new JBBPTextWriter(someWriter, JBBPByteOrder.LITTLE_ENDIAN); assertSame(someWriter, writer.getWrappedWriter()); assertEquals(JBBPByteOrder.LITTLE_ENDIAN, writer.getByteOrder()); } @Test public void testCommentHelloWorld() throws Exception { assertEquals(";Hello World", makeWriter().Comment("Hello World").Close().toString()); } @Test public void testMultilineCommentHelloWorld() throws Exception { assertEquals(";Hello\n;World", makeWriter().Comment("Hello\nWorld").Close().toString()); } @Test public void testMultilineCommentAfterValue() throws Exception { assertEquals(".0x12345678;Hello\n ;World", makeWriter().Int(0x12345678).Comment("Hello\nWorld").Close().toString()); } @Test public void testCommentAndValue() throws Exception { assertEquals(";Hello World\n.0x01,0x00000001,0x0000000000000001", makeWriter().Comment("Hello World").Byte(1).Int(1).Long(1).Close().toString()); } @Test public void testComment_DisableEnable() throws Exception { assertEquals(";Hrum\n.0x01,0x00000001,0x0000000000000001", makeWriter().DisableComments().Comment("Hello World").EnableComments().Comment("Hrum") .Byte(1).Int(1).Long(1).Close().toString()); } @Test public void testDouble_Max_radix10() throws Exception { assertEquals(".1.7976931348623157E308", makeWriter().SetValuePrefix("").Radix(10).Double(Double.MAX_VALUE).Close().toString()); } @Test public void testDouble_Max_radix16() throws Exception { assertEquals(".1.FFFFFFFFFFFFFP1023", makeWriter().SetValuePrefix("").Radix(16).Double(Double.MAX_VALUE).Close().toString()); } @Test public void testFloat_Max_radix10() throws Exception { assertEquals(".3.4028234663852886E38", makeWriter().SetValuePrefix("").Radix(10).Float(Float.MAX_VALUE).Close().toString()); } @Test public void testFloat_Min_radix10() throws Exception { assertEquals(".-1.401298464324817E-45", makeWriter().SetValuePrefix("").Radix(10).Float(-Float.MIN_VALUE).Close().toString()); } @Test public void testFloat_Max_radix16() throws Exception { assertEquals(".1.FFFFFEP127", makeWriter().SetValuePrefix("").Radix(16).Float(Float.MAX_VALUE).Close().toString()); } @Test public void testFloat_Min_radix16() throws Exception { assertEquals(".-1.0P-149", makeWriter().SetValuePrefix("").Radix(16).Float(-Float.MIN_VALUE).Close().toString()); } @Test public void testValueAndMultilineComment() throws Exception { final String text = makeWriter() .Comment("Comment1", "Comment2") .IndentInc() .Int(1) .Comment("It's header") .HR() .IndentDec() .Comment("It's body") .IndentInc() .Byte(new byte[] {1, 2, 3, 4}) .Comment("Body", "Next comment", "One more comment") .BR().BR() .Byte(new byte[] {0x0A, 0x0B}) .Comment("Part", "Part line2", "Part line3") .HR() .IndentInc() .Comment("End") .IndentDec(1) .Comment("The End") .IndentDec() .Long(-1L) .Close().toString(); System.out.println(text); assertFileContent("testwriter.txt", text); } @Test public void testExtras_ErrorForNull() throws Exception { assertThrows(NullPointerException.class, () -> makeWriter().AddExtras((Extra[]) null)); } @Test public void testExtras_ErrorForEmptyExtras() throws Exception { assertThrows(IllegalStateException.class, () -> makeWriter().Obj(0, new Object())); } @Test public void testExtras_NotPrintedForNull() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.AddExtras(new JBBPTextWriterExtraAdapter() { @Override public String doConvertObjToStr(JBBPTextWriter context, int id, Object obj) throws IOException { return null; } }).Obj(1, new Object()); assertEquals("", writer.Close().toString()); } @Test public void testMultilineComments() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Byte(1).Comment("Hello", "World"); assertEquals(".0x01;Hello\n ;World", writer.Close().toString()); } @Test public void testHorizontalRule() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.SetHR("~", 10, '>').HR().Byte(1); assertEquals("~>>>>>>>>>>\n.0x01", writer.Close().toString()); } @Test public void testHorizontalRule_DisableEnable() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.SetHR("~", 10, '>').DisableComments().HR().Byte(1).EnableComments().HR(); assertEquals("\n.0x01\n~>>>>>>>>>>\n", writer.Close().toString()); } @Test public void testLineBreak() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Byte(1).BR().Byte(2); assertEquals(".0x01\n.0x02", writer.Close().toString()); } @Test public void testExtras_PrintInfoAboutComplexObjectIntoWriter() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.SetMaxValuesPerLine(16).AddExtras(new JBBPTextWriterExtraAdapter() { @Override public String doConvertObjToStr(final JBBPTextWriter context, final int id, final Object obj) throws IOException { context .BR() .Comment("Complex object") .HR() .Byte(1).Comment("Header") .Int(0x1234).Comment("Another header") .IndentInc() .Comment("Body") .HR() .Byte(new byte[128]) .HR() .IndentDec() .Long(0x1234567890L).Comment("End of data") .HR(); return null; } }); final String text = writer.Byte(0xFF).Obj(111, "Hello").Int(0xCAFEBABE).Close().toString(); System.out.println(text); assertFileContent("testwriter3.txt", text); } @Test public void testStringNumerationWithExtras() throws Exception { final JBBPTextWriter writer = makeWriter(); final AtomicInteger newLineCounter = new AtomicInteger(0); final AtomicInteger bytePrintCounter = new AtomicInteger(0); final AtomicInteger closeCounter = new AtomicInteger(0); writer.SetMaxValuesPerLine(32).SetCommentPrefix(" // ") .AddExtras(new JBBPTextWriterExtraAdapter() { @Override public void onClose(final JBBPTextWriter context) throws IOException { context.Comment("The Last Line"); closeCounter.incrementAndGet(); } @Override public void onNewLine(final JBBPTextWriter context, final int lineNumber) throws IOException { newLineCounter.incrementAndGet(); } @Override public void onBeforeFirstValue(final JBBPTextWriter context) throws IOException { context.write( JBBPUtils.ensureMinTextLength(Integer.toString(context.getLine()), 8, '0', 0) + ' '); } @Override public String doConvertByteToStr(final JBBPTextWriter context, final int value) throws IOException { bytePrintCounter.incrementAndGet(); return null; } @Override public void onReachedMaxValueNumberForLine(final JBBPTextWriter context) throws IOException { context.Comment("End of line"); } }); for (int i = 0; i < 130; i++) { writer.Byte(i); } assertFileContent("testwriter2.txt", writer.Close().toString()); assertEquals(4, newLineCounter.get()); assertEquals(130, bytePrintCounter.get()); assertEquals(1, closeCounter.get()); } @Test public void testByteOrder() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.ByteOrder(JBBPByteOrder.LITTLE_ENDIAN); assertEquals(JBBPByteOrder.LITTLE_ENDIAN, writer.getByteOrder()); writer.ByteOrder(JBBPByteOrder.BIG_ENDIAN); assertEquals(JBBPByteOrder.BIG_ENDIAN, writer.getByteOrder()); } @Test public void testObj_NoExtras() throws Exception { assertThrows(IllegalStateException.class, () -> makeWriter().Obj(123, "Str1", "Str2", "Str3")); } @Test public void testObj_ExtrasReturnNull() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.SetValuePrefix("").AddExtras(new JBBPTextWriterExtraAdapter() { }); writer.Obj(123, "Str1", "Str2", "Str3"); assertEquals("", writer.Close().toString()); } @Test public void testObj_PrintIntervalFromArray() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.AddExtras(new JBBPTextWriterExtraAdapter() { @Override public String doConvertObjToStr(JBBPTextWriter context, int id, Object obj) throws IOException { assertEquals(1234, id); assertNotNull(obj); assertSame(writer, context); return obj.toString(); } }); assertEquals(".0xHello,0xWorld,0xHurraaa", writer.Obj(1234, new Object[] {1, 2, "Hello", "World", "Hurraaa", 3}, 2, 3).Close() .toString()); } @Test public void testObj_ExtrasReturnValue() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.SetValuePrefix("").AddExtras(new JBBPTextWriterExtraAdapter() { @Override public String doConvertObjToStr(JBBPTextWriter context, int id, Object obj) throws IOException { return obj.toString(); } }); writer.Obj(123, "Str1", "Str2", "Str3"); assertEquals(".Str1,Str2,Str3", writer.Close().toString()); } @Test public void testByte_OneValue() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Byte(10); writer.Byte(-1); assertEquals(".0x0A,0xFF", writer.Close().toString()); } @Test public void testByte_OneValueAfterComment() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Comment("Hello"); writer.Byte(-1); assertEquals(";Hello\n.0xFF", writer.Close().toString()); } @Test public void testByte_Array() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Byte(new byte[] {(byte) 10, (byte) -1}); assertEquals(".0x0A,0xFF", writer.Close().toString()); } @Test public void testByte_PartOfArray() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Byte(new byte[] {0, (byte) 10, (byte) -1, 0}, 1, 2); assertEquals(".0x0A,0xFF", writer.Close().toString()); } @Test public void testByte_String() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Byte("012345"); assertEquals(".0x30,0x31,0x32,0x33,0x34,0x35", writer.Close().toString()); } @Test public void testShort_OneValue() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Short(10); writer.Short(-1); assertEquals(".0x000A,0xFFFF", writer.Close().toString()); } @Test public void testShort_Array() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Short(new short[] {(short) 0x1234, (short) -1}); assertEquals(".0x1234,0xFFFF", writer.Close().toString()); } @Test public void testShort_Array_InversedByteOrder() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Short(new short[] {(short) 0x1234, (short) -1}); assertEquals(".0x3412,0xFFFF", writer.Close().toString()); } @Test public void testShort_PartOfArray() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Short(new short[] {0, (short) 0x1234, (short) -1, 0}, 1, 2); assertEquals(".0x1234,0xFFFF", writer.Close().toString()); } @Test public void testShort_String() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Short("012345"); assertEquals(".0x0030,0x0031,0x0032,0x0033,0x0034,0x0035", writer.Close().toString()); } @Test public void testInt_OneValue() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Int(0x12345678); writer.Int(-1); assertEquals(".0x12345678,0xFFFFFFFF", writer.Close().toString()); } @Test public void testUInt_OneValue() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.UInt(0x12345678); writer.UInt(-1); assertEquals(".0x12345678,0xFFFFFFFF", writer.Close().toString()); } @Test public void testInt_Array() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Int(new int[] {0x12345678, -1}); assertEquals(".0x12345678,0xFFFFFFFF", writer.Close().toString()); } @Test public void testUInt_Array() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.UInt(new int[] {0x12345678, -1}); assertEquals(".0x12345678,0xFFFFFFFF", writer.Close().toString()); } @Test public void testInt_Array_InversedByteOrder() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Int(new int[] {0x12345678, -1}); assertEquals(".0x78563412,0xFFFFFFFF", writer.Close().toString()); } @Test public void testUInt_Array_InversedByteOrder() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).UInt(new int[] {0x12345678, -1}); assertEquals(".0x78563412,0xFFFFFFFF", writer.Close().toString()); } @Test public void testInt_PartOfArray() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Int(new int[] {0, 0x12345678, -1, 0}, 1, 2); assertEquals(".0x12345678,0xFFFFFFFF", writer.Close().toString()); } @Test public void testUInt_PartOfArray() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.UInt(new int[] {0, 0x12345678, -1, 0}, 1, 2); assertEquals(".0x12345678,0xFFFFFFFF", writer.Close().toString()); } @Test public void testLong_OneValue() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Long(0x123456789ABCDEFFL); writer.Long(-1L); assertEquals(".0x123456789ABCDEFF,0xFFFFFFFFFFFFFFFF", writer.Close().toString()); } @Test public void testLong_Array() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Long(new long[] {0x123456789ABCDEFFL, -1L}); assertEquals(".0x123456789ABCDEFF,0xFFFFFFFFFFFFFFFF", writer.Close().toString()); } @Test public void testLong_Array_InversedByteOrder() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Long(new long[] {0x123456789ABCDEFFL, -1L}); assertEquals(".0xFFDEBC9A78563412,0xFFFFFFFFFFFFFFFF", writer.Close().toString()); } @Test public void testLong_PartOfArray() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Long(new long[] {0L, 0x123456789ABCDEFFL, -1L, 0L}, 1, 2); assertEquals(".0x123456789ABCDEFF,0xFFFFFFFFFFFFFFFF", writer.Close().toString()); } @Test public void testRadix_ErrorForLessThan2() { assertThrows(IllegalArgumentException.class, () -> makeWriter().Radix(1)); } @Test public void testRadix_ErrorForGreaterThan36() { assertThrows(IllegalArgumentException.class, () -> makeWriter().Radix(37)); } @Test public void testRadix() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Radix(2); assertEquals(2, writer.getRadix()); writer.Radix(12); assertEquals(12, writer.getRadix()); writer.SetValuePrefix("").Radix(16).Int(0x12345).Radix(2).Int(0x12345).Radix(10).Int(0x12345); assertEquals(".00012345,00000000000000010010001101000101,0000074565", writer.Close().toString()); } @Test public void testGetLineSeparator() throws Exception { assertEquals("hello", new JBBPTextWriter(makeWriter(), JBBPByteOrder.BIG_ENDIAN, "hello", 11, "", "", "", "", "") .getLineSeparator()); } @Test public void testAddDellExtras() throws Exception { final JBBPTextWriter writer = makeWriter(); final JBBPTextWriterExtraAdapter extras1 = new JBBPTextWriterExtraAdapter() { @Override public String doConvertByteToStr(JBBPTextWriter context, int value) throws IOException { return "bbb" + value; } }; final JBBPTextWriterExtraAdapter extras2 = new JBBPTextWriterExtraAdapter() { @Override public String doConvertByteToStr(JBBPTextWriter context, int value) throws IOException { return "aaa" + value; } }; writer.AddExtras(extras1, extras2).SetValuePrefix(""); writer.Byte(1); writer.DelExtras(extras2); writer.Byte(2); assertEquals(".aaa1,bbb2", writer.Close().toString()); } @Test public void testStr_ErrorForNull() throws Exception { assertThrows(NullPointerException.class, () -> makeWriter().Str((String[]) null)); } @Test public void testStr() throws Exception { assertEquals(".0x01,Hello,World,,0x02", makeWriter().Byte(1).Str("Hello", "World", null).Byte(2).Close().toString()); } @Test public void testPrintSpecialChars() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.SetTabSpaces(4).write('\t'); writer.write('\r'); writer.Byte(1); writer.write('\n'); assertEquals(" .0x01\n", writer.Close().toString()); } @Test public void testStates() throws Exception { final JBBPTextWriter writer = makeWriter(); assertTrue(writer.isLineStart()); assertFalse(writer.isComments()); assertFalse(writer.isValues()); writer.Byte(-1); assertFalse(writer.isLineStart()); assertFalse(writer.isComments()); assertTrue(writer.isValues()); writer.Comment("Hello", "World"); assertFalse(writer.isLineStart()); assertTrue(writer.isComments()); assertFalse(writer.isValues()); writer.write(" state not changed"); assertFalse(writer.isLineStart()); assertTrue(writer.isComments()); assertFalse(writer.isValues()); writer.BR(); assertTrue(writer.isLineStart()); assertFalse(writer.isComments()); assertFalse(writer.isValues()); } @Test public void testFlushAndClose() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.write(new char[] {'a', 'b'}); writer.flush(); writer.close(); assertEquals("ab", writer.toString()); } @Test public void testGetLinePosition() throws Exception { final JBBPTextWriter writer = makeWriter(); assertEquals(0, writer.getLinePosition()); writer.write("123"); assertEquals(3, writer.getLinePosition()); writer.write("111\n"); assertEquals(0, writer.getLinePosition()); } @Test public void testAppend() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.append("123"); writer.append('4'); writer.append("a56b", 1, 3); writer.append(null); assertEquals("123456null", writer.Close().toString()); } @Test public void testSetValuePrefixPostfix() throws Exception { assertEquals(".0x01,$02^", makeWriter().Byte(1).SetValuePrefix("$").SetValuePostfix("^").Byte(2).Close().toString()); } @Test public void testSetTabSpaces() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.SetTabSpaces(3).Tab().BR().IndentInc(3).Byte(1).IndentDec(2).BR().Comment("Hello"); assertEquals(" \n .0x01\n ;Hello", writer.Close().toString()); } @Test public void testSetTabSpaces_ErrorForNegative() throws Exception { assertThrows(IllegalArgumentException.class, () -> makeWriter().SetTabSpaces(-1)); } @Test public void testSetTabSpaces_ErrorForZero() throws Exception { assertThrows(IllegalArgumentException.class, () -> makeWriter().SetTabSpaces(0)); } @Test public void testPrintNumericValueByExtras() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.AddExtras(new JBBPTextWriterExtraAdapter() { @Override public String doConvertObjToStr(JBBPTextWriter context, int id, Object obj) throws IOException { assertEquals(234, id); return "obj" + obj; } @Override public String doConvertLongToStr(JBBPTextWriter context, long value) throws IOException { return "long" + value; } @Override public String doConvertIntToStr(JBBPTextWriter context, int value) throws IOException { return "int" + value; } @Override public String doConvertShortToStr(JBBPTextWriter context, int value) throws IOException { return "short" + value; } @Override public String doConvertFloatToStr(JBBPTextWriter context, float value) throws IOException { return "float" + value; } @Override public String doConvertDoubleToStr(JBBPTextWriter context, double value) throws IOException { return "double" + value; } @Override public String doConvertByteToStr(JBBPTextWriter context, int value) throws IOException { return "byte" + value; } }); writer.SetValuePrefix("").Byte(1).Short(2).Int(3).Long(4).Obj(234, "Str").Float(Float.MIN_VALUE) .Double(Double.MAX_VALUE); assertEquals(".byte1,short2,int3,long4,objStr,float1.4E-45,double1.7976931348623157E308", writer.Close().toString()); } @Test public void testBin_EasyCase() throws Exception { @Bin(name = "some class") class SomeClass { @Bin(order = 1) byte a; @Bin(order = 2, comment = "Short field") short b; @Bin(order = 3) int c; @Bin(order = 4, comment = "Long field") long d; @Bin(order = 5, comment = "some array") byte[] arr = new byte[128]; @Bin(order = 6, comment = "some string") String str = "Hello String"; @Bin(order = 7, comment = "some string array") String[] strs = new String[] {"Hello", null, "World"}; } final SomeClass cl = new SomeClass(); cl.a = 1; cl.b = 2; cl.c = 3; cl.d = 4; final JBBPTextWriter writer = makeWriter(); writer.SetMaxValuesPerLine(16); final String text = writer.SetCommentPrefix("; ").Bin(cl).Close().toString(); System.out.println(text); assertFileContent("testwriterbin1.txt", text); } @Test public void testBin_ParsedPng() throws Exception { final JBBPTextWriter writer = makeWriter(); final InputStream pngStream = getResourceAsInputStream("picture.png"); try { final JBBPParser pngParser = JBBPParser.prepare( "long header;" + "// chunks\n" + "chunks [_]{" + " int length; " + " int type; " + " byte[length] data; " + " int crc;" + "}" ); class Chunk { @Bin(order = 1) int Length; @Bin(order = 2) int Type; @Bin(order = 3) byte[] Data; @Bin(order = 4) int CRC; } class Png { @Bin(order = 1) long Header; @Bin(order = 2) Chunk[] Chunks; } final Png png = pngParser.parse(pngStream).mapTo(new Png(), aClass -> { if (aClass == Chunk.class) { return new Chunk(); } return null; }); final String text = writer.SetMaxValuesPerLine(16).Bin(png).Close().toString(); System.out.println(text); assertFileContent("testwriterbin2.txt", text); } finally { JBBPUtils.closeQuietly(pngStream); } } @Test public void testBin_ParsedDoubleFloat() throws Exception { final JBBPTextWriter writer = makeWriter(); final InputStream pngStream = getResourceAsInputStream("picture.png"); final JBBPParser parser = JBBPParser.prepare("floatj f; doublej d; floatj [2] fa; doublej [2] da;"); class Klazz { @Bin float f; @Bin float[] fa; @Bin double d; @Bin double[] da; } final byte[] data = new byte[4 + 8 + 4 * 2 + 8 * 2]; new Random(111222).nextBytes(data); final Klazz parsed = parser.parse(data).mapTo(new Klazz()); final String text = writer.SetMaxValuesPerLine(16).Bin(parsed).Close().toString(); System.out.println(text); assertFileContent("testwriterbinfloatdouble.txt", text); } @Test public void testBin_ParsedPng_NonMappedRawStruct() throws Exception { final InputStream pngStream = getResourceAsInputStream("picture.png"); try { final JBBPParser pngParser = JBBPParser.prepare( "long header;" + "// chunks\n" + "chunks [_]{" + " int length; " + " int type; " + " byte[length] data; " + " int crc;" + "}" ); final String text = makeWriter().SetMaxValuesPerLine(16).Bin(pngParser.parse(pngStream)).Close().toString(); System.out.println(text); assertFileContent("testwriterbin2b.txt", text); } finally { JBBPUtils.closeQuietly(pngStream); } } @Test public void testBin_AllEasyTypes_NonMappedRawStruct() throws Exception { final JBBPParser parser = JBBPParser .prepare("bit:2 a1; bit:6 a2; byte a; ubyte b; short c; ushort d; int e; long f; bool g;"); final byte[] testArray = new byte[] {(byte) 0xDE, (byte) 0x12, (byte) 0xFE, (byte) 0x23, (byte) 0x11, (byte) 0x45, (byte) 0xDA, (byte) 0x82, (byte) 0xA0, (byte) 0x33, (byte) 0x7F, (byte) 0x99, (byte) 0x04, (byte) 0x10, (byte) 0x45, (byte) 0xBD, (byte) 0xCA, (byte) 0xFE, (byte) 0x12, (byte) 0x11, (byte) 0xBA, (byte) 0xBE}; final String text = makeWriter().SetMaxValuesPerLine(16).Bin(parser.parse(testArray)).Close().toString(); System.out.println(text); assertFileContent("txtwrtrjbbpobj1.txt", text); } @Test public void testBin_ValField() throws Exception { final JBBPParser parser = JBBPParser.prepare("val:123 a;"); final String text = makeWriter().SetMaxValuesPerLine(16).Bin(parser.parse(new byte[0])).Close().toString(); assertEquals( "~--------------------------------------------------------------------------------\n" + "; Start {} \n" + "~--------------------------------------------------------------------------------\n" + " .0x0000007B; int a\n" + "~--------------------------------------------------------------------------------\n" + "; End {} \n" + "~--------------------------------------------------------------------------------\n", text); } @Test public void testBin_AllEasyTypes_Anonymous_NonMappedRawStruct() throws Exception { final JBBPParser parser = JBBPParser.prepare("bit:2; bit:6; byte; ubyte; short; ushort; int; long; bool; stringj;"); final byte[] testArray = new byte[] { (byte) 0xDE, (byte) 0x12, (byte) 0xFE, (byte) 0x23, (byte) 0x11, (byte) 0x45, (byte) 0xDA, (byte) 0x82, (byte) 0xA0, (byte) 0x33, (byte) 0x7F, (byte) 0x99, (byte) 0x04, (byte) 0x10, (byte) 0x45, (byte) 0xBD, (byte) 0xCA, (byte) 0xFE, (byte) 0x12, (byte) 0x11, 3, 65, 66, 67 }; final String text = makeWriter().SetMaxValuesPerLine(16).Bin(parser.parse(testArray)).Close().toString(); System.out.println(text); assertFileContent("txtwrtrjbbpobj2.txt", text); } @Test public void testBin_StringFieldAndStringArray() throws Exception { final JBBPParser parser = JBBPParser.prepare("stringj[2];stringj;"); final byte[] testArray = new byte[] { 3, 65, 66, 67, 3, 68, 69, 70, 3, 71, 72, 73 }; final String text = makeWriter().SetMaxValuesPerLine(16).Bin(parser.parse(testArray)).Close().toString(); System.out.println(text); assertFileContent("txtwrtrjbbpobj3.txt", text); } @Test public void testBin_BooleanArray_NonMappedRawStruct() throws Exception { final JBBPParser parser = JBBPParser.prepare("bool [_] array;"); final byte[] testArray = new byte[] {(byte) 0xDE, (byte) 0x00, (byte) 0xFE, (byte) 0x00, (byte) 0x11, (byte) 0x45, (byte) 0xDA, (byte) 0x82, (byte) 0xA0, (byte) 0x33, (byte) 0x7F, (byte) 0x99, (byte) 0x04, (byte) 0x10, (byte) 0x45, (byte) 0xBD, (byte) 0xCA, (byte) 0xFE, (byte) 0x12, (byte) 0x11, (byte) 0x00, (byte) 0xBE}; final String text = makeWriter().SetMaxValuesPerLine(16).Bin(parser.parse(testArray)).Close().toString(); System.out.println(text); assertFileContent("boolarrayraw.txt", text); } @Test public void testBin_ByteArrayMappedToString() throws Exception { class Parsed { @Bin(type = BinType.BYTE_ARRAY) String str1; @Bin(type = BinType.UBYTE_ARRAY) String str2; } final Parsed parsed = JBBPParser.prepare("byte [5] str1; ubyte [4] str2;") .parse(new byte[] {49, 50, 51, 52, 53, 54, 55, 56, 57}).mapTo(new Parsed()); final String text = makeWriter().Bin(parsed).Close().toString(); System.out.println(text); assertFileContent("testwriterbin5.txt", text); } @Test public void testCustomFieldInMappedClass() throws Exception { class TestClass { @Bin(order = 1) int a; @Bin(order = 2, custom = true) int b; } final JBBPTextWriter writer = makeWriter(); writer.AddExtras(new JBBPTextWriterExtraAdapter() { @Override public String doConvertObjToStr(JBBPTextWriter context, int id, Object obj) throws IOException { fail("Must not be called"); return null; } @Override public String doConvertCustomField(final JBBPTextWriter context, final Object obj, final Field field, final Bin annotation) throws IOException { return "test" + field.getName(); } }); final String text = writer.SetHR("~", 3, '-').SetValuePrefix("").Bin(new TestClass()).Close().toString(); System.out.println(text); assertFileContent("testwriterbin3.txt", text); } @Test public void testCustomArrayFieldInMappedClass() throws Exception { class TestClass { @Bin(order = 1) int a; @Bin(order = 2, custom = true) int[] b = new int[3]; } final JBBPTextWriter writer = makeWriter(); writer.AddExtras(new JBBPTextWriterExtraAdapter() { @Override public String doConvertObjToStr(JBBPTextWriter context, int id, Object obj) throws IOException { fail("Must not be called"); return null; } @Override public String doConvertCustomField(JBBPTextWriter context, Object obj, Field field, Bin annotation) throws IOException { context.HR().Str(field.getType().isArray() ? "See on array" : "Error") .Comment("Line one", "Line two").HR(); return null; } }); final String text = writer.SetHR("~", 3, '-').SetValuePrefix("").Bin(new TestClass()).Close().toString(); System.out.println(text); assertFileContent("testwriterbin4.txt", text); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/JBBPUtilsTest.java000066400000000000000000000642341445000440600325210ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import com.igormaznitsa.jbbp.io.JBBPBitNumber; import com.igormaznitsa.jbbp.io.JBBPBitOrder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.Random; import org.junit.jupiter.api.Test; public class JBBPUtilsTest { @Test public void testUtf8EncdeDecode() { assertEquals("78634двлфодйукйДЛОД wdf", JBBPUtils.utf8ToStr(JBBPUtils.strToUtf8("78634двлфодйукйДЛОД wdf"))); } @Test public void testRemoveTrailingZeros() throws Exception { assertNull(JBBPUtils.removeTrailingZeros(null)); assertEquals("", JBBPUtils.removeTrailingZeros("")); assertEquals("abcdf", JBBPUtils.removeTrailingZeros("abcdf")); assertEquals("000000abcdf", JBBPUtils.removeTrailingZeros("000000abcdf")); assertEquals("000000a00000bcdf", JBBPUtils.removeTrailingZeros("000000a00000bcdf0000000")); assertEquals("0", JBBPUtils.removeTrailingZeros("0000000")); assertEquals("0", JBBPUtils.removeTrailingZeros("0")); assertEquals("0 ", JBBPUtils.removeTrailingZeros("0 ")); assertEquals(" 000000 ", JBBPUtils.removeTrailingZeros(" 000000 ")); assertEquals("10.2", JBBPUtils.removeTrailingZeros("10.20000")); assertEquals("1", JBBPUtils.removeTrailingZeros("100")); } @Test public void testRemoveLeadingZeros() throws Exception { assertNull(JBBPUtils.removeLeadingZeros(null)); assertEquals("", JBBPUtils.removeLeadingZeros("")); assertEquals("abcdf", JBBPUtils.removeLeadingZeros("abcdf")); assertEquals("abcdf", JBBPUtils.removeLeadingZeros("000000abcdf")); assertEquals("a00000bcdf0000000", JBBPUtils.removeLeadingZeros("000000a00000bcdf0000000")); assertEquals("0", JBBPUtils.removeLeadingZeros("0000000")); assertEquals("0", JBBPUtils.removeLeadingZeros("0")); assertEquals(" ", JBBPUtils.removeLeadingZeros("0 ")); assertEquals(" 000000 ", JBBPUtils.removeLeadingZeros(" 000000 ")); } @Test public void testULong2Str_UnsignedHex() throws Exception { assertEquals("12345678ABCD", JBBPUtils.ulong2str(0x12345678ABCDL, 16, null)); assertEquals("7FFFFFFFFFFFFFFF", JBBPUtils.ulong2str(0x7FFFFFFFFFFFFFFFL, 16, null)); assertEquals("8000000000000000", JBBPUtils.ulong2str(0x8000000000000000L, 16, null)); assertEquals("8FFFFFFFFFFFFFFF", JBBPUtils.ulong2str(0x8FFFFFFFFFFFFFFFL, 16, null)); assertEquals("8100000000000000", JBBPUtils.ulong2str(0x8100000000000000L, 16, null)); assertEquals("F23418824AB12342", JBBPUtils.ulong2str(0xF23418824AB12342L, 16, null)); } @Test public void testULong2Str_UnsignedDec() throws Exception { assertEquals("20015998348237", JBBPUtils.ulong2str(0x12345678ABCDL, 10, null)); assertEquals("9223372036854775807", JBBPUtils.ulong2str(9223372036854775807L, 10, null)); assertEquals("9223372036853827875", JBBPUtils.ulong2str(0x7FFFFFFFFFF18923L, 10, null)); assertEquals("9223372036854775808", JBBPUtils.ulong2str(0x8000000000000000L, 10, null)); assertEquals("10376293541461622783", JBBPUtils.ulong2str(0x8FFFFFFFFFFFFFFFL, 10, null)); assertEquals("9295429630892703744", JBBPUtils.ulong2str(0x8100000000000000L, 10, null)); assertEquals("17452601403845452610", JBBPUtils.ulong2str(0xF23418824AB12342L, 10, null)); } @Test public void testPackIntToByteArray() { for (int i = 0; i < 0x80; i++) { final byte[] array = JBBPUtils.packInt(i); assertEquals(1, array.length); assertEquals(i, array[0] & 0xFF); } final Random rnd = new Random(1234); for (int i = 0; i < 1000; i++) { final int generated = rnd.nextInt(0x7FFFFFFF); final byte[] array = JBBPUtils.packInt(generated); if ((generated & 0xFFFFFF80) == 0) { assertEquals(1, array.length); assertEquals(i, array[0] & 0xFF); } else if ((generated & 0xFFFF0000) == 0) { assertEquals(3, array.length); assertEquals(0x80, array[0] & 0xFF); assertEquals(generated >>> 8, array[1] & 0xFF); assertEquals(generated & 0xFF, array[2] & 0xFF); } else { assertEquals(5, array.length); assertEquals(0x81, array[0] & 0xFF); assertEquals((generated >>> 24) & 0xFF, array[1] & 0xFF); assertEquals((generated >>> 16) & 0xFF, array[2] & 0xFF); assertEquals((generated >>> 8) & 0xFF, array[3] & 0xFF); assertEquals(generated & 0xFF, array[4] & 0xFF); } } } @Test public void testCloseQuetly() { try { JBBPUtils.closeQuietly(null); JBBPUtils.closeQuietly(new ByteArrayInputStream(new byte[10])); final InputStream closed = new ByteArrayInputStream(new byte[10]); closed.close(); JBBPUtils.closeQuietly(closed); } catch (Exception ex) { fail("Must not throw any exception"); } } @Test public void testIsNumber() { assertFalse(JBBPUtils.isNumber(null)); assertFalse(JBBPUtils.isNumber("")); assertFalse(JBBPUtils.isNumber("12837921739821739203928103802198383742984732a")); assertFalse(JBBPUtils.isNumber("a12837921739821739203928103802198383742984732")); assertTrue(JBBPUtils.isNumber("12837921739821739203928103802198383742984732")); assertTrue(JBBPUtils.isNumber("-12837921739821739203928103802198383742984732")); } @Test public void testUnpackInt_NPEForArrayIsNull() { assertThrows(NullPointerException.class, () -> JBBPUtils.unpackInt(null, new JBBPIntCounter())); } @Test public void testUnpackInt_IAEForWrongPrefix() { assertThrows(IllegalArgumentException.class, () -> JBBPUtils.unpackInt(new byte[] {(byte) 0xAA, 0, 0, 0, 0, 0}, new JBBPIntCounter())); } @Test public void testPackUnpackIntFromByteArray() { final byte[] array = new byte[5]; final JBBPIntCounter pos = new JBBPIntCounter(); int counter1 = 0; int counter2 = 0; int counter3 = 0; final int[] etalons = new int[] {0, -1, -89, 234, 123124, 1223112, 34323, Integer.MIN_VALUE, Integer.MAX_VALUE}; for (final int generated : etalons) { pos.set(0); final int len = JBBPUtils.packInt(array, pos, generated); if ((generated & 0xFFFFFF80) == 0) { assertEquals(1, len); counter1++; } else if ((generated & 0xFFFF0000) == 0) { assertEquals(3, len); counter2++; } else { assertEquals(5, len); counter3++; } assertEquals(pos.get(), len); pos.set(0); assertEquals(generated, JBBPUtils.unpackInt(array, pos)); assertEquals(pos.get(), len); } assertTrue(counter1 > 0); assertTrue(counter2 > 0); assertTrue(counter3 > 0); } @Test public void testArray2Hex() { assertNull(JBBPUtils.array2hex(null)); assertEquals("[0x01, 0x02, 0x03, 0xFF]", JBBPUtils.array2hex(new byte[] {1, 2, 3, (byte) 0xFF})); } @Test public void testArray2Oct() { assertNull(JBBPUtils.array2hex(null)); assertEquals("[0o001, 0o002, 0o003, 0o377]", JBBPUtils.array2oct(new byte[] {1, 2, 3, (byte) 0xFF})); } @Test public void testArray2Bin() { assertNull(JBBPUtils.array2bin(null)); assertEquals("[0b00000001, 0b00000010, 0b00000011, 0b11111111]", JBBPUtils.array2bin(new byte[] {1, 2, 3, (byte) 0xFF})); } @Test public void testReverseBitsInByte() { for (int i = 0; i < 256; i++) { int etalon = 0; int a = i; for (int y = 0; y < 8; y++) { etalon = (etalon << 1) | (a & 0x1); a >>= 1; } assertEquals((byte) etalon, JBBPUtils.reverseBitsInByte((byte) i), "Value is " + i); } } @Test public void testReverseBitsInByte_DefinedNumber() { assertEquals((byte) 1, JBBPUtils.reverseBitsInByte(JBBPBitNumber.BITS_1, (byte) 0xFF)); assertEquals((byte) 0, JBBPUtils.reverseBitsInByte(JBBPBitNumber.BITS_1, (byte) 0x00)); assertEquals((byte) 6, JBBPUtils.reverseBitsInByte(JBBPBitNumber.BITS_3, (byte) 0x63)); assertEquals((byte) 0x31, JBBPUtils.reverseBitsInByte(JBBPBitNumber.BITS_6, (byte) 0x63)); } @Test public void testBin2Str() { assertNull(JBBPUtils.bin2str(null, true)); assertNull(JBBPUtils.bin2str(null, false)); assertEquals("01010101 10101010", JBBPUtils.bin2str(new byte[] {0x55, (byte) 0xAA}, true)); assertEquals("0101010110101010", JBBPUtils.bin2str(new byte[] {0x55, (byte) 0xAA}, false)); assertEquals("00001001", JBBPUtils.bin2str(new byte[] {0x9}, false)); assertEquals("1010101001010101", JBBPUtils.bin2str(new byte[] {0x55, (byte) 0xAA}, JBBPBitOrder.MSB0, false)); assertEquals("0101010110101010", JBBPUtils.bin2str(new byte[] {0x55, (byte) 0xAA})); } @Test public void testStr2Bin_Default() { assertEquals(0, JBBPUtils.str2bin(null).length); assertArrayEquals(new byte[] {(byte) 0x80}, JBBPUtils.str2bin("10000000")); assertArrayEquals(new byte[] {(byte) 0x01}, JBBPUtils.str2bin("1")); assertArrayEquals(new byte[] {(byte) 0x80, 0x01}, JBBPUtils.str2bin("10000000X00x0Zz1")); assertArrayEquals(new byte[] {(byte) 0x80, 0x01, 0x07}, JBBPUtils.str2bin("10000000000000010111")); assertArrayEquals(new byte[] {(byte) 0x80, 0x01, 0x07}, JBBPUtils.str2bin("10000000_00000001_0111")); try { JBBPUtils.str2bin("10001021"); fail("Must throw IAE"); } catch (IllegalArgumentException ex) { } } @Test public void testStr2Bin_LSB0() { assertEquals(0, JBBPUtils.str2bin(null, JBBPBitOrder.LSB0).length); assertArrayEquals(new byte[] {(byte) 0x80}, JBBPUtils.str2bin("10000000", JBBPBitOrder.LSB0)); assertArrayEquals(new byte[] {(byte) 0x01}, JBBPUtils.str2bin("1", JBBPBitOrder.LSB0)); assertArrayEquals(new byte[] {(byte) 0x01}, JBBPUtils.str2bin("00000001", JBBPBitOrder.LSB0)); assertArrayEquals(new byte[] {(byte) 0x80, (byte) 0x01}, JBBPUtils.str2bin("10000000X00x0Zz1", JBBPBitOrder.LSB0)); assertArrayEquals(new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x07}, JBBPUtils.str2bin("10000000000000010111", JBBPBitOrder.LSB0)); assertArrayEquals(new byte[] {(byte) 0x80, (byte) 0x01, (byte) 0x07}, JBBPUtils.str2bin("10000000_00000001_0111", JBBPBitOrder.LSB0)); try { JBBPUtils.str2bin("10001021", JBBPBitOrder.MSB0); fail("Must throw IAE"); } catch (IllegalArgumentException ex) { } } @Test public void testStr2Bin_LSB0_1bitShift() { final byte[] array = JBBPUtils.str2bin("0 11111111 01010101 00011000 00000001", JBBPBitOrder.LSB0); assertArrayEquals(new byte[] {(byte) 0x7F, (byte) 0xAA, (byte) 0x8C, (byte) 0x0, (byte) 0x01}, array); } @Test public void testStr2Bin_MSB() { assertEquals(0, JBBPUtils.str2bin(null, JBBPBitOrder.MSB0).length); assertArrayEquals(new byte[] {(byte) 0x01}, JBBPUtils.str2bin("1", JBBPBitOrder.MSB0)); assertArrayEquals(new byte[] {(byte) 0x80}, JBBPUtils.str2bin("00000001", JBBPBitOrder.MSB0)); assertArrayEquals(new byte[] {(byte) 0x01}, JBBPUtils.str2bin("10000000", JBBPBitOrder.MSB0)); assertArrayEquals(new byte[] {(byte) 0xA9}, JBBPUtils.str2bin("10010101", JBBPBitOrder.MSB0)); assertArrayEquals(new byte[] {(byte) 0x01, (byte) 0x80}, JBBPUtils.str2bin("10000000X00x0Zz1", JBBPBitOrder.MSB0)); assertArrayEquals(new byte[] {(byte) 0x01, (byte) 0x80, (byte) 0x0E}, JBBPUtils.str2bin("1000000000000001 0111", JBBPBitOrder.MSB0)); assertArrayEquals(new byte[] {(byte) 0x01, (byte) 0x80, (byte) 0x0E}, JBBPUtils.str2bin("10000000_00000001_0111", JBBPBitOrder.MSB0)); assertArrayEquals(new byte[] {(byte) 0x03, (byte) 0x00, (byte) 0x01, (byte) 0x01}, JBBPUtils.str2bin("1_10000000_00000001_00000001", JBBPBitOrder.MSB0)); try { JBBPUtils.str2bin("10001021", JBBPBitOrder.MSB0); fail("Must throw IAE"); } catch (IllegalArgumentException ex) { } } @Test public void testSplitString() { assertArrayEquals(new String[] {""}, JBBPUtils.splitString("", '.')); assertArrayEquals(new String[] {"aaa"}, JBBPUtils.splitString("aaa", '.')); assertArrayEquals(new String[] {"aaa", "bbb"}, JBBPUtils.splitString("aaa.bbb", '.')); assertArrayEquals(new String[] {"aaa", "bbb", ""}, JBBPUtils.splitString("aaa.bbb.", '.')); assertArrayEquals(new String[] {"", ""}, JBBPUtils.splitString(".", '.')); } @Test public void testAssertNotNull() { JBBPUtils.assertNotNull(new Object(), "test"); final String message = "Test message"; try { JBBPUtils.assertNotNull(null, message); fail("Must throw NPE"); } catch (NullPointerException ex) { assertSame(message, ex.getMessage()); } try { JBBPUtils.assertNotNull(null, null); fail("Must throw NPE"); } catch (NullPointerException ex) { assertNotNull(message, ex.getMessage()); } } @Test public void testReverseArray() { assertNull(JBBPUtils.reverseArray(null)); final byte[] empty = new byte[0]; assertSame(empty, JBBPUtils.reverseArray(empty)); assertArrayEquals(new byte[] {1}, JBBPUtils.reverseArray(new byte[] {1})); assertArrayEquals(new byte[] {2, 1}, JBBPUtils.reverseArray(new byte[] {1, 2})); assertArrayEquals(new byte[] {5, 4, 3, 2, 1}, JBBPUtils.reverseArray(new byte[] {1, 2, 3, 4, 5})); assertArrayEquals(new byte[] {6, 5, 4, 3, 2, 1}, JBBPUtils.reverseArray(new byte[] {1, 2, 3, 4, 5, 6})); } @Test public void testSplitInteger() { byte[] buff = null; assertArrayEquals(new byte[] {1, 2, 3, 4}, JBBPUtils.splitInteger(0x01020304, false, buff)); buff = new byte[2]; assertArrayEquals(new byte[] {1, 2, 3, 4}, JBBPUtils.splitInteger(0x01020304, false, buff)); buff = new byte[4]; assertArrayEquals(new byte[] {1, 2, 3, 4}, JBBPUtils.splitInteger(0x01020304, false, buff)); buff = new byte[8]; assertArrayEquals(new byte[] {1, 2, 3, 4, 0, 0, 0, 0}, JBBPUtils.splitInteger(0x01020304, false, buff)); buff = null; assertArrayEquals(new byte[] {4, 3, 2, 1}, JBBPUtils.splitInteger(0x01020304, true, buff)); buff = new byte[2]; assertArrayEquals(new byte[] {4, 3, 2, 1}, JBBPUtils.splitInteger(0x01020304, true, buff)); buff = new byte[4]; assertArrayEquals(new byte[] {4, 3, 2, 1}, JBBPUtils.splitInteger(0x01020304, true, buff)); buff = new byte[8]; assertArrayEquals(new byte[] {4, 3, 2, 1, 0, 0, 0, 0}, JBBPUtils.splitInteger(0x01020304, true, buff)); } @Test public void testSplitLong() { byte[] buff = null; assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}, JBBPUtils.splitLong(0x0102030405060708L, false, buff)); buff = new byte[2]; assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}, JBBPUtils.splitLong(0x0102030405060708L, false, buff)); buff = new byte[8]; assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8}, JBBPUtils.splitLong(0x0102030405060708L, false, buff)); buff = new byte[10]; assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 0, 0}, JBBPUtils.splitLong(0x0102030405060708L, false, buff)); buff = null; assertArrayEquals(new byte[] {8, 7, 6, 5, 4, 3, 2, 1}, JBBPUtils.splitLong(0x0102030405060708L, true, buff)); buff = new byte[2]; assertArrayEquals(new byte[] {8, 7, 6, 5, 4, 3, 2, 1}, JBBPUtils.splitLong(0x0102030405060708L, true, buff)); buff = new byte[8]; assertArrayEquals(new byte[] {8, 7, 6, 5, 4, 3, 2, 1}, JBBPUtils.splitLong(0x0102030405060708L, true, buff)); buff = new byte[10]; assertArrayEquals(new byte[] {8, 7, 6, 5, 4, 3, 2, 1, 0, 0}, JBBPUtils.splitLong(0x0102030405060708L, true, buff)); } @Test public void testConcat() { assertArrayEquals(new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, JBBPUtils .concat(new byte[] {1, 2, 3, 4}, new byte[] {5}, new byte[] {6, 7, 8, 9}, new byte[0], new byte[] {10})); } @Test public void testReverdeByteOrder_ErrorForZeroByteNumber() { assertThrows(IllegalArgumentException.class, () -> JBBPUtils.reverseByteOrder(1234, 0)); } @Test public void testReverdeByteOrder_ErrorForTooBigByteNumber() { assertThrows(IllegalArgumentException.class, () -> JBBPUtils.reverseByteOrder(1234, 9)); } @Test public void testReverdeByteOrder() { assertEquals(0x0000000000000012L, JBBPUtils.reverseByteOrder(0x0000000000000012L, 1)); assertEquals(0x0000000000003412L, JBBPUtils.reverseByteOrder(0x0000000000001234L, 2)); assertEquals(0x0000000000563412L, JBBPUtils.reverseByteOrder(0x0000000000123456L, 3)); assertEquals(0x0000000078563412L, JBBPUtils.reverseByteOrder(0x0000000012345678L, 4)); assertEquals(0x0000009A78563412L, JBBPUtils.reverseByteOrder(0x000000123456789AL, 5)); assertEquals(0x0000BC9A78563412L, JBBPUtils.reverseByteOrder(0x0000123456789ABCL, 6)); assertEquals(0x00DEBC9A78563412L, JBBPUtils.reverseByteOrder(0x00123456789ABCDEL, 7)); assertEquals(0xF1DEBC9A78563412L, JBBPUtils.reverseByteOrder(0x123456789ABCDEF1L, 8)); } @Test public void testEnsureMinTextLength() { assertEquals("..........text1", JBBPUtils.ensureMinTextLength("text1", 15, '.', 0)); assertEquals("text1..........", JBBPUtils.ensureMinTextLength("text1", 15, '.', 1)); assertEquals("..text1...", JBBPUtils.ensureMinTextLength("text1", 10, '.', -1)); } @Test public void testArrayStartsWith_NPE() { try { JBBPUtils.arrayStartsWith(null, new byte[11]); fail("Must be NPE"); } catch (NullPointerException ex) { } try { JBBPUtils.arrayStartsWith(new byte[11], null); fail("Must be NPE"); } catch (NullPointerException ex) { } try { JBBPUtils.arrayStartsWith(null, null); fail("Must be NPE"); } catch (NullPointerException ex) { } } @Test public void testArrayStartsWith_EmptyStart() { assertTrue(JBBPUtils.arrayStartsWith(new byte[0], new byte[0])); assertTrue(JBBPUtils.arrayStartsWith(new byte[111], new byte[0])); } @Test public void testArrayStartsWith_TooLongSubstring() { assertFalse(JBBPUtils.arrayStartsWith(new byte[] {1, 2}, new byte[] {1, 2, 3})); } @Test public void testArrayStartsWith_TheSameLength() { assertTrue(JBBPUtils.arrayStartsWith(new byte[] {1, 2}, new byte[] {1, 2})); } @Test public void testArrayStartsWith_Found() { assertTrue(JBBPUtils.arrayStartsWith(new byte[] {1, 2, 3, 4}, new byte[] {1, 2})); } @Test public void testArrayStartsWith_NotFound() { assertFalse(JBBPUtils.arrayStartsWith(new byte[] {1, 2, 3, 4}, new byte[] {1, 2, 4})); } @Test public void testArrayEndsWith_NPE() { try { JBBPUtils.arrayEndsWith(null, new byte[11]); fail("Must be NPE"); } catch (NullPointerException ex) { } try { JBBPUtils.arrayEndsWith(new byte[11], null); fail("Must be NPE"); } catch (NullPointerException ex) { } try { JBBPUtils.arrayEndsWith(null, null); fail("Must be NPE"); } catch (NullPointerException ex) { } } @Test public void testArrayEndsWith_EmptyEnd() { assertTrue(JBBPUtils.arrayEndsWith(new byte[0], new byte[0])); assertTrue(JBBPUtils.arrayEndsWith(new byte[111], new byte[0])); } @Test public void testArrayEndsWith_TooLongSubstring() { assertFalse(JBBPUtils.arrayEndsWith(new byte[] {1, 2}, new byte[] {1, 2, 3})); } @Test public void testArrayEndsWith_TheSameLength() { assertTrue(JBBPUtils.arrayEndsWith(new byte[] {1, 2}, new byte[] {1, 2})); } @Test public void testArrayEndsWith_Found() { assertTrue(JBBPUtils.arrayEndsWith(new byte[] {1, 2, 3, 4}, new byte[] {2, 3, 4})); } @Test public void testArrayEndsWith_NotFound() { assertFalse(JBBPUtils.arrayEndsWith(new byte[] {1, 2, 3, 4}, new byte[] {2, 4})); } @Test public void testGenerateMask() { assertEquals(0, JBBPUtils.makeMask(0)); assertEquals(1, JBBPUtils.makeMask(1)); assertEquals(3, JBBPUtils.makeMask(2)); assertEquals(0x7F, JBBPUtils.makeMask(100)); assertEquals(0xFFFF, JBBPUtils.makeMask(65535)); } @Test public void testTraceData_Defaults() throws Exception { final byte[] testData = new byte[211]; for (int i = 0; i < 111; i++) { testData[i] = (byte) i; } final InputStream stream = new ByteArrayInputStream(testData); final ByteArrayOutputStream outArray = new ByteArrayOutputStream(); final PrintStream out = new PrintStream(outArray, true, "UTF-8"); JBBPUtils.traceData(stream, out); out.close(); final String asString = new String(outArray.toByteArray(), StandardCharsets.UTF_8).replace("\r\n", "\n") .replace("\r", "\n"); assertEquals( "00000000 00 01 02 03 | 04 05 06 07 | 08 09 0A 0B | 0C 0D 0E 0F | 10 11 12 13 | 14 15 16 17 | 18 19 1A 1B | 1C 1D 1E 1F ................................\n" + "00000020 20 21 22 23 | 24 25 26 27 | 28 29 2A 2B | 2C 2D 2E 2F | 30 31 32 33 | 34 35 36 37 | 38 39 3A 3B | 3C 3D 3E 3F !\"#$%&'()*+,-./0123456789:;<=>?\n" + "00000040 40 41 42 43 | 44 45 46 47 | 48 49 4A 4B | 4C 4D 4E 4F | 50 51 52 53 | 54 55 56 57 | 58 59 5A 5B | 5C 5D 5E 5F @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\n" + "00000060 60 61 62 63 | 64 65 66 67 | 68 69 6A 6B | 6C 6D 6E 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 `abcdefghijklmn.................\n" + "00000080 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 ................................\n" + "000000A0 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 ................................\n" + "000000C0 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 -- | -- -- -- -- | -- -- -- -- | -- -- -- -- ................................\n", asString); } @Test public void testTraceData_CustomWithChars() throws Exception { final byte[] testData = new byte[211]; for (int i = 0; i < 111; i++) { testData[i] = (byte) i; } final InputStream stream = new ByteArrayInputStream(testData); final ByteArrayOutputStream outArray = new ByteArrayOutputStream(); final PrintStream out = new PrintStream(outArray, true, "UTF-8"); JBBPUtils.traceData(stream, 8, 4, "#", "_", "$", "Z", '&', true, out); out.close(); final String asString = new String(outArray.toByteArray(), StandardCharsets.UTF_8).replace("\r\n", "\n") .replace("\r", "\n"); assertEquals( "00000000#00_01_02_03_04_05_06_07$08_09_0A_0B_0C_0D_0E_0F$10_11_12_13_14_15_16_17$18_19_1A_1B_1C_1D_1E_1FZ&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\n" + "00000020#20_21_22_23_24_25_26_27$28_29_2A_2B_2C_2D_2E_2F$30_31_32_33_34_35_36_37$38_39_3A_3B_3C_3D_3E_3FZ !\"#$%&'()*+,-./0123456789:;<=>?\n" + "00000040#40_41_42_43_44_45_46_47$48_49_4A_4B_4C_4D_4E_4F$50_51_52_53_54_55_56_57$58_59_5A_5B_5C_5D_5E_5FZ@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\n" + "00000060#60_61_62_63_64_65_66_67$68_69_6A_6B_6C_6D_6E_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00Z`abcdefghijklmn&&&&&&&&&&&&&&&&&\n" + "00000080#00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00Z&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\n" + "000000A0#00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00Z&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\n" + "000000C0#00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_--_--_--_--_--$--_--_--_--_--_--_--_--Z&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\n", asString); } @Test public void testTraceData_CustomNoChars() throws Exception { final byte[] testData = new byte[211]; for (int i = 0; i < 111; i++) { testData[i] = (byte) i; } final InputStream stream = new ByteArrayInputStream(testData); final ByteArrayOutputStream outArray = new ByteArrayOutputStream(); final PrintStream out = new PrintStream(outArray, true, "UTF-8"); JBBPUtils.traceData(stream, 8, 4, "#", "_", "$", "Z", '&', false, out); out.close(); final String asString = new String(outArray.toByteArray(), StandardCharsets.UTF_8).replace("\r\n", "\n") .replace("\r", "\n"); assertEquals( "00000000#00_01_02_03_04_05_06_07$08_09_0A_0B_0C_0D_0E_0F$10_11_12_13_14_15_16_17$18_19_1A_1B_1C_1D_1E_1F\n" + "00000020#20_21_22_23_24_25_26_27$28_29_2A_2B_2C_2D_2E_2F$30_31_32_33_34_35_36_37$38_39_3A_3B_3C_3D_3E_3F\n" + "00000040#40_41_42_43_44_45_46_47$48_49_4A_4B_4C_4D_4E_4F$50_51_52_53_54_55_56_57$58_59_5A_5B_5C_5D_5E_5F\n" + "00000060#60_61_62_63_64_65_66_67$68_69_6A_6B_6C_6D_6E_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00\n" + "00000080#00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00\n" + "000000A0#00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00\n" + "000000C0#00_00_00_00_00_00_00_00$00_00_00_00_00_00_00_00$00_00_00_--_--_--_--_--$--_--_--_--_--_--_--_--\n", asString); } } java-binary-block-parser-2.0.6/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/SpecialTestUtils.java000066400000000000000000000020011445000440600333440ustar00rootroot00000000000000/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.utils; public final class SpecialTestUtils { private SpecialTestUtils() { } public static byte[] copyOfRange(final byte[] array, final int startIndex, final int endIndex) { int len = endIndex - startIndex; if (len <= 0) { return new byte[0]; } final byte[] result = new byte[len]; System.arraycopy(array, startIndex, result, 0, len); return result; } } java-binary-block-parser-2.0.6/jbbp/src/test/jbbp/000077500000000000000000000000001445000440600217015ustar00rootroot00000000000000com.igormaznitsa.jbbp.benchmarks.JBBPBenchmarkParser.jbbp000066400000000000000000000001401445000440600344740ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/jbbpubyte value; data [(value>>1)*(value+3)]{ bit:3 a; bit:3 b; bit:2 c; skip:1; }java-binary-block-parser-2.0.6/jbbp/src/test/jmh/000077500000000000000000000000001445000440600215425ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/jmh/com/000077500000000000000000000000001445000440600223205ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/jmh/com/igormaznitsa/000077500000000000000000000000001445000440600250275ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/jmh/com/igormaznitsa/jbbp/000077500000000000000000000000001445000440600257445ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/jmh/com/igormaznitsa/jbbp/benchmarks/000077500000000000000000000000001445000440600300615ustar00rootroot00000000000000JBBP_Benchmark.java000066400000000000000000000045021445000440600333350ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/jmh/com/igormaznitsa/jbbp/benchmarks/* * Copyright 2017 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.jbbp.benchmarks; import com.igormaznitsa.jbbp.JBBPParser; import com.igormaznitsa.jbbp.io.JBBPBitInputStream; import com.igormaznitsa.jbbp.mapper.Bin; import com.igormaznitsa.jbbp.mapper.BinType; import org.openjdk.jmh.annotations.Benchmark; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.Random; /** * Test set to check productivity of different work modes with parsing data of the same data set. */ public class JBBP_Benchmark { private static final JBBPParser parser = JBBPParser .prepare("ubyte value; data [(value>>1)*(value+3)]{ bit:3 a; bit:3 b; bit:2 c; skip:1; }"); private static final Random RND = new Random(12345); private static final byte[] DATA; static { final int val = 201; DATA = new byte[1 + ((val >> 1) * (val + 3)) * 2]; RND.nextBytes(DATA); DATA[0] = (byte) val; } @Benchmark public void measureParse_DynamicAndMapping() throws IOException { parser.parse(DATA).mapTo(new Data()); } @Benchmark public void measureParse_Dynamic() throws IOException { parser.parse(DATA); } @Benchmark public void measureParse_Static() throws IOException { new JBBPBenchmarkParser().read(new JBBPBitInputStream(new ByteArrayInputStream(DATA))); } public static class InData { @Bin(name = "a", type = BinType.BIT) public byte a; @Bin(name = "b", type = BinType.BIT) public byte b; @Bin(name = "c", type = BinType.BIT) public byte c; } public static class Data { @Bin(name = "value", type = BinType.UBYTE) public int value; @Bin(name = "data") public InData[] data; public Object newInstance(Class klazz) { return klazz == InData.class ? new InData() : null; } } } java-binary-block-parser-2.0.6/jbbp/src/test/resources/000077500000000000000000000000001445000440600227765ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/000077500000000000000000000000001445000440600235545ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/000077500000000000000000000000001445000440600262635ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/000077500000000000000000000000001445000440600272005ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/000077500000000000000000000000001445000440600276145ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/11kgsm.wav000066400000000000000000000744301445000440600314460ustar00rootroot00000000000000RIFFyWAVEfmt 1+A@factRdataxjc`۶mۖb`۶mۖ$Ir6ZhZ^0.(cnê+6jd+)rS (Zsm #%k\8͊,ltT ʔzƶmF[9$6m|R9+sիfTǫj{`rw!r9$ŕ1 K:9rPf)LN3$Ǟl)[0E^,Վienv'"쭭H!3vʝj ҵ%ju4BVôRzFu.eJ հ(lJ%}-QP4l1z6v+3Rr2) *H 7HI"I74q"'{hHsqܶ Z$u8;}7E5'f#ҢPuy1; ЃȝMpqhi*qT5Kri67[n&֙2/ *q]4M yZNGLwș|=jZpҊQ>:w6DqZIp4)\;fUHmmL\3M>[2%Y$R=g(U.! 1j"5+K5ɉu ٶxQvvTβN~ *u.. ݎ+nd-!1V-mƍ3>wٶh9rdS}.Ƃl*+#d+. i# i-)(ⶍ%xxİQRfΜaaX#*J0eM6Ȣ(KK.2=-ejŎ7#WM,Ф6FXKYsndBlCMuۥIgHN ̯G&H 4yoS/h-8MA(f&|.BN%m넮$RvdlSDpl!Ğ#< ل(1#l@+DojHc5NΐRL6Mv,P_tV(|-g<֪f?Ϸ-;J /(۰ж&Q>$ ki+2\Gv̵˟QSJ8-28-9ϓcS媁b/#mxE>7utnVj5H9jYքG`gg%C[Ir+g[{̐Ӱљ2/a˶HvVm7rd^qŜ*q%9 TCPrFƹi$>{/(1>Ylqh\%=-3-X%%_!C'ޔi/-PvwTT"47zŗH۩qrƱXHwj %;1ux:TivXo{v wܪVoV#C=eZGɮȚD[e dddXuUOfHqb[H Ibʍ܏Hr9^q:t%r̹pC4l ڎpIؓV%ّ0b6Dȉ:b6ՑdȁnCq$'n6#Ǒ$f{N)GmF܎VMhi* 4򍣦-ֲͤ6fclBqIJژ cq,Jq,+Vr,^jP`VBjTٶin,lTfߜ((V6Z~\/PKvjIhĮa윩NHS\\wfa$WR+hj[ vI _N6NlZdzj5N9=.92IR"&k&rAܭ0cq^3 } W)ߝBp$ilm L8 ɥlTo9UԊ&Q|CDo-jؖ[( Q %ʒv+ ⿩MMq)Zۚ9G1Q:Ȭ!V:ilY -TWQs_N惩>%9W܀oKv4 }3rv%`RTߤWHxoK*uh츖gρl)Ndl릭,)cȍFt+N^I*sQLAb$'miӏANI&%_ۄ΀sߎ =ۆ▢j:,z6VOuk+IӔ&Ch!$&2Qr:.OaŦajQj^.XL6ˍUd%'U#' Z-:w/+5e.y"yJ%&mXgObfŇ$FKclKnq1eJe@S:A ٙm8vwJ^BdO 7 K:-- ӧrXg 2o%:Zc;J;?ǃqPvyRZiݖru[l>d"47-NK .aU̕dq+?Zʈ:wAþf* }WcUJT0jUu\)N3giӕ֭T̅Un]ԏȭɛ^+h9a(O7mJgCewiڌ1veUnu#˶NJ1 MϾvD-nu#M*o#mzQ%R3kR)Kn۾YwYܰ}HqRcS,y{:[J0-5|KLɢv4H%nx@Nx)hDppQ+#Gy_#= l"5K [g+{y;:عPʍB0ܢlg%lGȎI$#~ 0ut |- n z,x t])6 `Ml#w@'@ˬ&l@U~ R5֮T824?M:eb$I$ S&Ssm|m2ZZ:N259K-- &U%/ֲf-)6άj&jͰ(ˢ^ꈕnYmЪײX\_qT` L[T]K8ltqt^mA2ର^M4%I`BXRFn۶֝b'HWV$XrV堵MJlݹt@۴)wXz`vZjT–RaA`Y P!} @bcgvT/ idɮ[D]7.$7 uhS1fa9@Dq#k`ٶ`"M$)R*. J l{-r*!ggS9s2b"$ǢX̎.S:l;9Y}39s'90--_ "׊rwKI#_.pfP%uLIF8ʁÄQHRld#A\5nւ 1>Ft8$_nWb4ԯN[z%PtOvIc8v-BЗz&MK^r!ǩvBIr#I b-l5hsc0!uI|BvJ~+1T%;eЪơGTEYP6OJE& ],rŐTMnZ&Rf~+D2碑BhENNgJ a#MIr`ȶ8'QԹ #jw2%z%Fgbf>ef*[w *k=_ M Ueqޗ ??Nن2; ^5u~bfgv6JɑM<+BŔ%; /mML+cLE94*Mv͊ 65q߼Jr m͌4R"`i"+N3|kIosKj%L+UH28KYj*-`6ʳTnR`\)aN"wRZ9kZ۸ !mMFak20YRf#|56kn TRܶT:6%$mv9m#a՞nd@옮%ّ8LNFNInlQXntDIYneLYr҄,C8&^dikpL$#nJ&UlJCxklvEג< QL%vB Ӭ?]$.Tr|GtO`'̭ ~f.m4mJ"yen̮t1_j1$(y%*ZOn3L̒g^֦JLYaBEؠRI#IlJzA/_(5̉$' &Zե{Lд7ȁƑ͚#׊:*ltz 牐z} 8kYH-GܤֽuH.k*cZ OԈTN*NnNZ` &ETJˣeʉja9krM2o$*HzyAHEl Vb!*Ģm1E-DT Ar+/--Kato ^i:ou$VډڮӔ mE`uH`[i#-`$ITr(XSSXFR䒔 j},`˨qȋ즩5kWT ܤ#ٍy JG7U[X/1$̖ڈL34;fVmKcmYB.jvĐp7Ir\@dQAٷMɶSMt,M KR,Ns&5$GSމGT.DD,i۸Q]4SYURj30.+,ױb*Y T*DffBf"D&iZga8'N&{."i4wqZ\t9#Ep> o>gZs%i+K*VUínunʪ `rEa)Ԕudn:Ԣ̻r+IDJԙﯽ9I$kH,*- F@%ĬcH*y`j:pdtGdTEVLJӕ[j+>#uތdғڐg.w(2 ;-In#Qg`iiܩr!|0)YYD]D^~)%3w$a}Ӏ3]2[B-8+5g ]xM18nx)58ݪP笋>ɼMڇh #|PNG ɳLP{ε ,b3Ng^\u<f2Ps OE{m[SG׵| ,!BWG<={}vu ,rJ%wJw ][2JI Bu lWmcJ`vZI@[eo,-a= Vݺ 67jG.( !^@몎t&yeM[$ɡjR7ٺUC_ w8l:90mML&<[gӳ@2"DFCx [j> kedx _(6DӮR&S\̦ͅ4ߌE4Ngά-Gquâ\T%Y(Am?tDLIB2DLq'1bKAM$`LG`lJڨ >Iz L6'6݋rܖB[NfM*ki@1b%5 )[J MFHN-H.Fvf [-?% 4 ʆ\FV%[[U`j(ɯI$)R4/iOrhNXp;E3'N rI͓fܾʞr<}Vɮ6sr*bN=Uh-r,w0$1nL//+nX=lP6Z݌\br8k\\`Dy\&ܗ,$ qٽP- O/Frn?_zfDfm찓vBy04/7P?M+ $h=mƎc9nl5U1o^ȵ) qDN҈FlǑc]BH+Cpi%L^t+V**0yGjOүF5ZL.h{1,h+mE f9Os,(vώeBbJFIrd!!:Ҙ ]%,`&r$Ilq^TLt# wR-[Gl˩);bv,edq\^drCal*VM‰'le''a<̄ z# !#y慰WHnHjII$J)G%z P8S:j;һΌHmWMASZ/MIMnɊvb 9甪RȦ'6fy%F $0R).wn$+檎(v03+e."jg &!$VIXӡ ]Q `T9yg``tS}HS*JTFzoj#7oF,7iMxEBC`L:`#ɒJm6D`7r#G1ljȍ$KnlFqLIrt8r9rcIfH`ܶC`9n$)'丱㺙mMеqG8r$9UHx6r$Ɋ'ZcNc7`qX/`8$ q#yaQumGn_ȍ6UF$ǑhH7ry `Inv$ɑF$`q$,`ƒUa1m6VnRqJF$GZMSԒ(Q,\YK`ʴj@m]A趥-l6jkٕ.J9&(NJ^c *:HFijxr,i k% ')Rm [J#\IۚJlȇNdr˩6܆#+0Yvqun $(K rkwoaCLFR^PF498IzjS{qdwUj6,GJR7Аv_; k 'J2e]_ֈh6>6I".ƔL" *caR8x}X-0d}zc6^ɩ-ǒnH`dZOw%E5Yl62gEZhƦTsȈ*ʧrMm)`CRΤ8-Mrv+L"2" No$҉A"8ֲ71-byT ^M0-Z[-nX +Ag|;Qѭw-dQ !Q40uo݉1t ^8[ʶ\Mk 6OJ PcWxiw[ub F6'ͨŒdKa 7әYoW{$%nkZjųh1y5}.\^ZFK]u22 N/u#͗uJ[$R " qi- l&'e0-XdLRc\ 颞ZnǒqjJזff+ v i\&J /f/fj]TQe~oHnfe#r`$2C2(R<$ RjejԫO ˍ32LWA\Ii 0ފVق|#'nߚhCb7e?׮Dȍ:+w@KQG()($K=PKȹ PtVQ̖6~d̄. ٮ,Ad9iӥ>aEGΒf-d(&u&~ʜQprEliMa`au֒dq丕'D.$MѸJ(Ә,n,W+ڍ שs$:JYMhr++Wz F]Gf[z'Ӏ +TSg[ےRa*-.nƒNʰ-Z к{8Dkޔ UYM+Kn8n!%])΀$ ˅t&M *Ij ڐdP 7*PԨ@Z,`Er`ٶ~f 6XԱHs 9r &Ҏ`ǑΠ$MT`;#(:2˶ʱNZdHI.FUJBQ#.`ʱ$7nhm`FrVhdVR$HmoȕlƒFn^G֦+ۑv ۸q#YE66r Ng8#IumG+m8rcH#q'U #ɍ E$m##7b$V4,ȍ$6HqHҨĉ=X `*vrM`R-WY-j`,{iQx6κ f$VԚS6ƕ9zm\8"KKOXNƇYf.'"'9&;i+<3GrrrGHZ2~Ɗpju; ~ 53,C=z tۅ̮^ҲiBb7TVu2ڋu6~IuT匱&$"Vb6 b̊+.򠠟M/b7lVnIkό%čUp@z*g"ߢ,,j^S?\*MffՈco\ 1]J[lqXJWF3B촪b6TFT]}(%5 Ts&7mvU/0iWK8>Q=q:Y̪MˇrNǸ#w#S x1$Ԍ8\lV}T}nJvtHRI&dఅu9iIuDIrIGK/Y. H΋)Lon=&#wV%gH%INl ]ױLA[hrgi]C-wD =etȖ`.s41ET7v8%uȵdCfм&d-g*Bp!+M MnTR#Ų1R}vح ;=>ksYNp9* .'ɸ" IδPg06> !x|.V͐ZƍwUI# Me/MA^/g*D PR*j%>lVAU#З2Up}"q5gRROvbrd62"}]"DZMZ="9lF)k#K㟂ji[KnCբ낢Udn- \[Vz@,}-YpǞ.aV.L mim]ʥqLzIU|r$qur߷aMӫ @8M[Uם u{y J^)[w 45I h~OVK䮧.V~B:\0u^Z& QLv44^MQ Uա Wǡ/EEFq,GᲲApP̺ђFT忆bV$ h{4Dmɍkl7"^V1M WPDxR eY8&yP6m"6PGj69k aB.@jln-" GkLBg9ĊUdʭ愦cL;d8qcH$n RU?ȝW+NSwnqIø*N ǨR15Œkv$I iTU!MqɰkiS6+7obʗ-'FF͐ \m9HgF{VLiSHBْـvRbQh97AJcSoP>0Ƒ$95 V$H-] FkEH$~#va_ C*rmD>_,۫\Kr@brMPȑ[݆c):͢瀖 'Q7 VFKM͠ZIR:Z<6bN؍ uNp!żZ# GwlrCj¢LSWֹYl6XZxta5kJR&i7n$PtNLccއ+نHyFQmklP6N2rl8֘v1\gK՚71z﫶u0TyHxɒd1Lxn[̂F9 EɣSc<696rZo, t?\:v/XTƢ:rZ9JUM?wG!Ga#cTpz5zP/}|Do|J" XqƔS12\#w04)S-Ynn%W+RJEϏ|zmYv/50VMs$EEnlJ &90&idK5-ٕ,r jh6eh嶱vA6|&Čcnќ:p0{ȴGU>DkTi<+bKL*BfY'-&9*8Q+B-WB7XXִLJ@zMb`)5l LλekZ}LHr,jbtsR ݓZmdk`tMnj  e.Iwɪ d*t(dGڊ{,vHB(_s<,9dT)W:4I㼞JTk}7l:sݕW|InXMU¬£FZ#w"鰝U p" Өmk#W ΊK:k\Si>v洈vdKtkȒ*NQJ5*lm&HeZ. ;iv%>vaH I 4n`ȍʂa?.  joEcǛ)kvk&ND8®L<؋VTv'?ef!5١܎MIލ6z6=h#H&[&>/L*gMXFiid:ZglR"YWm\6(tf(^Pӆ1ӣjױQ8`> uP@RfZkCXPKOF$y۪VŎ9Ad ~kUhW%qg*j9 ,\vб+nvk'''Ej6_eI RK+NZ͋`W$9IH/7rۈzV{Sn74xlmpLjr\V\d]tHYJ*, 7=3ZzK\v 7Ͱ2UI6,q =.}궬xҬB I_.uܲl˪6$Y]_ʰEZ+8C^NW k6tlݪ]W8qd^Mj<3eiiV,u mDVrUt#cm*3WR+0eN.,6 v=ax/a\GTh~i~ %\1+̇{KuʌD8IU.ƥ1lhcxJfNLf0 LtoaZgs `@j9wӄSnmմdEn-8 G0ڌ! 3V"&Q`zo߄"Hv/CvR"eA\:.bVɥzr8&ЙDk8?@#8l,zo6m+Qzev 7ρ{F`v+*IXJNFV:D't*s\DM_Bl `\N(nJ*v_ZFm^iX6IxX6nȍ\Jj#]P/lĸ*mi-*]وHʥ@v15HїԶ/4Y |*a7Ϋ/HM+^\mX}NͶzFJ$kʡخ-%־4' F{hmP}iVm.oi#j۠ L\FEu; Z44sFR-h7Ui-Hi*-:ZDVg47V,eC:ߒNn.l $6RMP n{mcߨ>/>~%03* J񼬂 3Jb[j#˓Dj3SQe.JD5M=`DM-*6YI\+ou]!hLtעINnG4|^Vc/-Γ҄k%\֫qISiw)VeHʲi`5p֝ Qx]F^HY^TB\+g]A CI#g~B$O2BfǶmdr*k֬H)M7$ay5"86ƭRݸrnHv>Kn#ˢ,ӷTtݘ'SmU4(LCofew"g-kedT |R-w "j6fb}`LsJov̀Z9ʚ`1uiqT ,ȵ Q8n$̑6roZ䲠bPsb$ٕjJaH~iUTb.mFH 6*mv -Ƒgs`2.6raHT("3pLnQr&RY'hYS NJƋMSrq܅O0+Sq \l xIfR/HnWSJh u/2 kV*FK@[vI"F*kM%I#qpD֜QQ#I0R)\&Pi-jE<6,|GjMKXΩ\Q*R8NѺMR7dQY؈B0^Q = Hv4L̫ ƖWEVwv,OJE1ΗLOFNagz(IQ&qojSę p&m IvY( -'M%7mZzUРlO8la"/N b$@ =+ &θ#c(]'WH8h3p]+íN)Pȗwu ?o4G%5lcbi<co#Ӳ: :GQbg20F.x:z`SjV1 A|FԂGv01YD]* {pIj]rRTK,&Qȶ$3ڣHQwlϭ(&F"35l6F3䶘(n4r߼Jn0rvR=35J6} H'^cJe$!KYf ;ΥN('Lַ%ia6`GB b),76Ne§3u("ĺDV+MŅ}7fv?q-XHD.mI)5=ᵝ +*/햧cs+.+!C#}1';2^.0Hm$Uutcˡφ-'d7-;l!OkvW;Lv%,):x2)9jV*.obb)O쫨%wǑVMhLtq XmHik=@_G ۺ(:(䞋9hUmzؔ+ڎ Ir]g F[ײPHl*Vmdz^G5Sm~ꚡV$܈-١u{pH.Q͍ۨBwSw.Dhh{=6+(M/G߾ٖLZ+O-?lN=mpk+"mZxrmFFCXRc88`tx8[$GQjjOR9ԙNiةQi$rh*êfc W۸e~#1L΄ePv%Ǎ$q4c>N\ TžRbs%P(ץL.vu)'%9 H!˛vp%ㄞz m,5_LS[kXEFrt)&[l'e/[[OQdT*QlG0mrn%ԍxLR`dKn5UF&ٶ+ 'bIol,ǥ٤I㤬}7owLJ Y qd{,j9 I04Q8ίֺ, OE1ph:ϫ+8&{ vqS{rE$Mn񴦭p8$։.7Gg'%.j8$햦BouiB4-ԑ+@xJn#^^^Y5#-mM#Knג'r5+R4DU܆٠|)i T_H&57n*,c F=3N$smjmвbYD9L/].e8$vo9f*j{ _VnDɴ]^$FQ Jij,Y ( {r6NxzFs/`Ɩ&ªi%D6\W-U,ܕ lةBLzYnݸnV0ɠrS8ى$T'L("Dݶ혪TvR F0fmhFEGRrZ͂rJri@MhUR}iI 욶I5%~֏X8EF("ljJTT[SXt4 h[̫rQ^M^X@+KC]&DiH‘eՒT!,MlNc%N;զʭ^hV鿌 (tjƀi@ؑH'&)" DlZ9TF3 ENZxw|4ȶI88dF<:F4~m@S0kmkJ}d[.nrz6o.+4?R Pq%=]T8}J"goa3ҩ&Ir˺\ߖ0*Kzld|c+'goi%*bqќ;wTBNzs94۸Qt#ΏqDĭq=-0zV$Ye)1nɍ#y Av5XM&h5'#G"Awo%"k5FRF+Mi[AoH)@ɣE[n@ͬ{uFH+PYԥ E%ƪkkXn+ %~MELpvZl0,u#OkJ*J*\7JwjJV Ur.ldt#1JQ')mI$/iԡ Xr¸iٶ'dEWJdD%GY Vn²qRZTmAsFu$E^"Uu#U &BmEAqw Lƍ|8%joF߿-mr.IFU)Ď۩Ķ8j} [OkWƯ1-W!֝(sȂJb^#hJ6fkb[9dB]K e$!6܊'ܸ怦vrx%-r*jiDʮFI*id'YdE@Wg;Ncw-$Cm `+i&ɱ, ([-DoJ: 4KN UWzp4adj[Bd5D5ܶ;nZDZ2JJ2l0FIXzIv-QI~I[Z3BZM<݂Cj0o"$KƊi..L!M$W7;8r6S&fUݴ*TjW{2BL9mBeJb+qFYi#ne-L-iL,U{f\8l`z[u Ԣ9#5E*Ho8עYΩz6fKɬDBZrPgW&K]R3ީi3 Dy~,f6B.΄hD̃d3:@A~T"ZY ҫLʨ~RXWF[4VS8wzm L` ϡY Ab2ؽNu FM*Ǝ,ArdgJ <(`+j$obU@3q V5rl-J[gzwHN#A1$=֝;yݴBfq#Ue]F-iuQ҆s-8416ڱ Lou۹_hdWAI(JAY+%b\$]#4oҭ+i#]59t(7 4֖Q$ZJXk'$ޘHn$Eѝd fɭOf(9nJTHq`3rg ˤ5j΍I# ٸy&I`\/0Fۍ#I5l[&!9d:vUd[N!Qk -{] V(jj⠺i&l50VlNV `/2wdC5L."ILδv+HU3kT\|-bX&aYk [Zo gW"^ym_TNa6c<%5Ρ|W)67-޹*5D<ݷUmWrdGNIMH)#OVN'%2++UOU$Gڵ;.)D+-iT-># _VW[ۥ0E+NjN˛ϬUHaūͽ 3Z}2nڼK*E` 竬Irm#I#ZRwm>"/[}"l,î;uzk< UK;Hgbz+蕚tppȍ-K¡6fu4]N`neg;'bK7WziZjOfVl8F.,}2BHs7TF_Tԕs62h7Ŷs2-8mj9pI4".0XڒvLPb{t =R9JC&n>\aʂ|=<Ѵy'd׏Lźs3L8)߅3n+kCIs\9D;6Ecg,d奡΀cG\Nk>6Aّ"vmc:#]kI$92AFrgbo˲܊f dW'̚Pߜˬ*}f15mly-3Jbq7G/q.x$9xZ#6b ٚL,D\o:֦$ R;=7j Sb8|sdْ50j`GlPMȾr 5:F$[I쎲 *kUi5fI#1aP(Nk.N8t-uFLM$9~hÈ`iȒۄI$&UTԓ 0iv+-sN.y׍0e6jZ@}.Q@تp<Ħ/PNSMqa[ɾDuʸYkWC35 +MF.fi_j Væӿ*T>V`p#9\3GmkO_#I7yWňrk P K(V KDBH,Gp@5^nS,*kf\ t.H/eI͉Q:. ѺTGP̵jYe%A$)l/ڐ@M5׀rR*1F3:c t ChZ@4%a_f@dlB(f C\}Jݡ`\|+Oڏjrwl'&ǫgrBaZr@sES L7=L |k((\*5OCxD_rSxd\@8:J:+N4&W޿^0bC꣪BvO0Bp5^E`eAfŏQv3٤+)5Q, FP֟]ըk|g/2[F F4/e"OA]NfOAk9}!I WEB)Tk"%X<;UFR9JTX0lΥ;*a/ nfHyBЭ>jӚaJ 0І!>*. ˶ѪR%b[ɋ3]D`[l4hf/ΙlR}!LH?EkE+&CV⯎@Z.`7JB ,}6%<)O ב.df "!0$Vrp33@Wo? B[<4:WR_ǾhIAH{|Ro|z@Wd($,$J(>0a;6b8`W8u J]IbK7:Wj_b OI>UыpNYi`XiQwi-`\”9(`[*7cBP]55?ׅT98` pƠ1)]7ؗ׀TQ[(R sGˡgXjji\)'{m\rY (K'~!+6Jb::0pF (uAԇEP xƚYGp>B-3 k/]G_D HkQԆy}x'ྞײ_Nl)ܽ1 dAUTxYM±w),8 DW[5_ ȂST8ꠓv Z*]B`'⬻As`jVsPoGŝ7Fow8 \{Ii"RNǎS1H9( AxN~cUEk)g2,`i9XAu| mI_2U\V/N@Фf!c{U7#EU6GP]x_l* R/%(,#s &nTm#ƭC(ƃCWځJv #Y@g<0rl Dx( Q(vrBȠPr b؞1?`G۸ 0X1 EYnUTm@@@#Hrph`Mx c ޺J\?" ȓ) TRׄ8/3W2by'| 1P *qŸ́xap@y< [:}U.ڿPc^ դ'7Gc]Ii͐hԘ?wwBѱ.G\xQ"W(~)׉.%gZt7wСǵ5 D+Ü!ðPGc1'Y3Sp^OnFlOvOS;P7c)&U".d#طc` FUhSXLJ7A*;N$ܩ>o>jϟM٬"nBU12a=B%eׅ-BnJP”yInެ1,PE|:NĪ`3 OB΄L+KP~C1B)'܌!,Q)H/(£BY "؃̮; ~R?*FWd3gh) uG2@ģW_S`Ϊ2JN8R}0 y8΃$VLxb=x :h+PCa.L yK*L2!lBHl02hbFhO^1%ҁ-ܐgXyH i5>| t D5@//zϲ^b) v(Ĩ3S4X(Z8T J{*Ry*#Y+SڠKVaΠ@bXM$34 "UKcZnX=9[eu!OE‘!s͏5K6Ms[ऩ Őr;/޻? j *!}\Qۢcm >'ܪ_*rlaRj~1b~ڵ,!$ejkudVag3yأ]gͤ)2ՍAdi% g$a':T/Mz{_^˝h;<9 WE/υq/ϗ{a1UAP T֫ -@?)b݆7]@Hi+vjw\]LH; ! ŲV)}J޴uT3L ̴F,8ˊrĥX:ׂJ98`zRƩ t :@i/9 t c6c`NM 9_^;COg5j1/ *K8v_`Ԕ`$ ԋܠg 2t"  ׂ_^W j< x ^\JٶW|U!}f=z4w^)Ӈ``e|d[vXհ m26[ Ol` z_]TNȇg5#]ńZ^F;F[ҁm"3DeLYUcXo)"^O 2rQ\HXb @ƢS ~!!P`j2R_22Ux [@?Xm9oqTt AڬM+U]c* ’r~/XYUF_`gu"&Ax{pU?;{`rpKQ/8p(y*&uh>Եtx_΀<'O GZe,AEӝ_N`Wd ,^E:r_rWffiO{WpEvx\΅)-"^0Kv hrVqr̜9̮I 'Q{XL_@ˆ3>hġA =~{117uA5 :LmQ,q4 Ɗ㒾3JlbP6`9h&‘ Pn@ReWׂ32!) t CaZ@57_[[ը)zNhv?u4uPgd>֥25| Y!\D.}$ t v@ CkG'Re܁֮aH TD.5cȷk"EHȘtCfSwX+7 `\4X-䂀^X%@8U&( R-z 28θ=b󓻣$!x!`87WwHN)(JCF2hovH7Stڮ|lf2$F8ٔf>z:2& uCC³5_*UteU`}MaRpWxy˱5OA5xRdNɁCUVx&94]#.DA s/Uh@%_WA3d~IZ`4x0‡!e`dNjMi .M_!Yf4N:gYŤ]LJ<줜^@%|ėZ~:͝AD̦2*% plKv}p7Xu ͮ8 IeO S8qH5sA _IWרlUh zk_7) t CJ@q߯``[I3P<ЇU}/~I -g.JO]Fi:XbtS_c@[r,QH>]3H6D֫na Wͦ) 滐qɖ`gCL"=t0F ,\g&(M9.wƓn6b`Wg h52 Ϥ:bðׂe " 9QOw',(b^ΘNH ĦUԥ2ܗ`[$h $ aM T-{e@`)e!6l4_c [, ]z% ׀7_^=L) #,`-B 0O H= b垪j%\X:a HhX!0&RZf=P"? HkPKP}xe*kW׃C_^ |tHkA;T^}tyL8_^,䦢,FQ2>[?)Z 6Z298htwͦw)Q)Pdc>ᆟBUܾ}`, <͠y6rO鐥!N^9W! fK8_Yodc%1=ey::UB!rBR)_^r C1*iI5zc4#Ӓ N!Sq%$_0X^q_$C"1JxR9i;+h&NmysXהmBD&U(1?1(ȍ< GDܜs `o5up20#=".gctĚq @؋iYwvsAf(|) $@][V`MRd?S6JaEi3,)XvK['Bc*Cf${2ی$1vCt~38eX'C]+]Nrqs8P׻ګG0)RL`td=H&e/#04н7 ܞhv ܫ 䔘7+m+:& LP}{"Q@YN ӟS^Mm8N 5(J[roŖ5*C]䮷H@ݦ 2}2aͤԵήP , ])0 "k"u$Tšt@˂tGt6P2\٥Pծ);B沾W d*InVͬ  uh=ҏA>@ $PC 8 dB&BZ CAnHط*VgzYnGW*QtY^Ҙ!G0b F^!4̀dĶ $#ɬw1Z NKvf fӘC C4rl!DŽb]Hwrɸ/d)J -CB4mג_] Oޅ3(sBw.wtuƐWM=_]W̐BU 0Fo% + YC2b"@AgF Ihrp>gR2:%H2xux`gT0&*!&yI$,wyWޕ2PEypijWך__@Ŝf|teӏ!0)  0 :eK+^!~,8_ڀ ܠcطz ixv|u~TX "Z  `_9H3X  118s DS]X,s\[@6֯cskY tk<:C spTܤ5ӳ* C_7XPդd-Q.B +y텕睌:3"pְ 6]ʔω8JEm BDME%% ߌrە¤w@2 s0i/? ^[ӡ"1"3>RXR\98[#ψpBej77sG)k[#(9g3G)B糓!s"/_\[΂pC"+87 rphu#/N$C Ş|),d8͚kGCPM:{ov9'`urW-Kglҙg^B(RG{.`5+U0;7|f"`=;0*qب/3"(5+$Vү]wΌ2_\| B r9F?BޣFD#B P@v$o+2hwBIN@1Υ/9JI%tF`la`5(C]ט5>2AkQsbP`딇. G XI`` H%…1?YF2c^T/WYgN )؉8NIsļ L^`*&l]%X)|bLbd)(Ck Zz6qqp+1>}IUļ^0,Ξl2ըh ܔx)j-T!.1ƥm-:ְtEgt"_c81LJUbsrxӍa05J7+1Oj`V=h좱9J<4*R T/<8R) 4C rH!_d9RPO,[:d+Ôvdi_Z8ȶ;*‡6?C1)WICؘJV{1 rI7 6%8WR;% uIʑ <ij4 _)WT5yA@Lf )21K047$#T__@;Ĥ՗UоڱlNBg[XU{Ь&:A3+vLX2W\*2!VT:<r]@A~gdT.|8ߨDQzl 6Iz3Y Hdv@l5U|(+wʼB3t%q{J[; ZRl9vXOa,i[ˤƝ!8 }sH7fcfit[Ɔy/ȳe5b UHt_^ C\cwtb@~p-E`@)l5${x~s U⤳j = ґ& |v*B`@h"J0&̫ql1>jA{W׀j}M\14T>`{/6LR'WE)\szA-Tj\_^S]i3 ƒWٚQ|E |cףgX#FˠY+f39m t KU|4uQi$@o2`,3_Q m)R9iJ~dW(& (#@qȩV>؇ L{HWר_]ǽ╏6D| ӓ&ɴ```DMNLQ{wXi䉚eU"w,`;Ӟ( sneI^;L#7sx^x.E/oI!PyÜ.XB yTc+cz{z|[̴CɗӤ+Tk9o糃 ꮯO)̇ {6䩢mfw*D_x(i At)YɏKZxs Z}Z82rI` XX5:hE}':ܞVA)\5ϴ` m&$M`)\H52m_Y`Xs- ٌkuy'@qAˇec[Xڨ%0A_ԏcNIִ/Pw(?ɻO|ʗPxbȴO#3QXߌ(I"SWNFZ@vY:忛rׂ!eg̥:EI(%0F%TOz_@P4O0 E\_^R?,uJ t CUJ@5sYں_d`A[)A%ǁ f\֎OHWׅd`ZWɼM Rt$k5ܼ+,Q`bֵ].&~2پbbr2W_:@njRu2,,T^R3DJ l=%৽Ơ#tL v(@0ϲ_7( Jgq`S:^pwWֺX ŤCK cW!`Š6$-^`!dh,dcIGKJ?̒?=%Bqඞ#ڐA?׀0T=W=R 5Ec.*CqBp\H@Ҥ5' X,wPL53]'9o XĤ@! |bXWP̞Iύ/lKebfIPUxu^:hc)U'="έז9e/"GQ]0,f0hHk6цԆ>,Bkc&)-gk!9?&*z֐ :cXJR3h1ɘ9~7ԤũCR|4/ھ .^sbǟ  R+U؆Mg7Za-].9 Dy_^c9W N9VrOGla ǯr2@yV8ŊEt@L4L%%^Z\}GXxVGQRTn\H3  (?)`bL2Ï~-5;CAVMo&Rx\TYJL7 \ [xAE[Jʠ'Ji5{ξwWׅW82)'%WPcڤm Q"qۿ\')T$]X \%՞ DE([R$W* ܲW !Y6eaC0 c@bx  \&*@| F@ zT ##삩G9 b71 ILe8Xh5( C]מ׸&3l[ޒp4\vH] AYCWfLc2Ÿ`$+Gx𔾸Wׂf b aGۓ"aZ!b{_ Өbx)$jCYbD gdPϧ ([Jdy׭P " K fh XFw@v;,[Z а]hiܻvW Ct w{tp,QRϰ`u "ȯ+D}7!`^Y7&7uXQhv0+C^bY('wH֢+ Oݠ7G JWy,cY1E,WO_Ws 軄(rs9,,]`(_D,U>kXVz7R=1JW`/D2 C]DXB9 t"J~x$Ǫٿ[}.qtQàX[C"˾![@ ;;2e[ԑA{|İ9ЯR[uʽ4n~E>;p! \L` @}[$JNϯxM NwX9Ŝ ɳPeID*B(_PC툔ry5uNZ*D=}كX  l" \rVMm^@t%/ۂXa[|- UQUؠSgEN|[/7@3JKݮN3iK:qO(f`΋H!a ,W2=NtTȳ[,%ýWׄHs95@6Y?e/,ȭRVw'_rS `/5jי@O*aL S9{ *!^ )tN^Q fb|{JnXX>~ɤ̈C樈1@*hc5W׀&);KCZ‰\PX'eo1>k׀#Yf@W׀!F5) sjC]JטRk\ <EaB}ָhJZ> ]3g1׀D@5!R(v${F),f|#1]<+8[ުO8ү8>NN- H:x5F8|!0FIjcF rpdžj|12/Ꚁ>Bj ?[>1駀%@*(q&4{"z`F!'c1>Q//8=Q=կ? HkPRL`Ub+Î1ٿ6_gHVǩ vAp]+) tr>ȀO/_B` @|kƟʩ/o(}*B'*@3Il̀;k#WkLOXIfFAཻ-#q|'C)mJ<>KY t C_"@5-ق_bzd֑&L.E*R%{*HWC|_W|!mL h:zSgZut8(dǡPʁ>n4 Ata& Ls!3"T侻T,}Ӏ[n[*\/&ٲRCKZGdb)'GD< +w"#4$G*/- 64LٿwX`.ՍgI{Zv ^,]x7ל,ыkMi$8hoW8 !FeI Ħܢ)Eq:y6獩9E±Ƅp۴; 5n L,I Ý,5S|``M98` lBEJP}HQ=wn*تC]JBHHX:?&9;[[峨B>~T#p(f6œa5@!H) u#X@a_} cVCHG / 2|ŀZt'_PpSKOgPٜ,ƢBJAk#r׀*l\]R",U'l2~]u*߲/Q UV40ʊ<ƾ{ 2+i"X86~.̩lܛ KܢIƖ8X:4<tWE8䘲v>%A&+0@C摄 nF|Q~@P94 7Jw`qxfgt ufL̤47 Qt G+lS'e澼A}= OԤ Gy&y3}XjcQL"98``@fo BY=]J^^gd[+z_<mii@\;,Rs`8UaŘXZ^ ABg¡!(ȳ8jiׁriB~(>R^RtA܊*ʤUL/ _n`z[^?YیS?w%)9h@p1Н{9av<:YI D) Kƭs"IuFCƨ8`s1 u)zBrtjVWw` [ң7));d90ѣp,׃zNG8ܜe(R"ܾ׼iWLr+P#cpb,5Ҿ3*띟`g( 3.dF& E]JQT@O1: |RBcH -HX;!u1@h „\O2Ra+(v"IRIRc_y ^Z^)9$ԙ{%_^ K8/1-7 j{9^,qRMEqYhCZ l| wfU0x:EF:x#2 zN $OT'lrpp8 h(*v 3Jh0 G9 O+9n׀(nt]01 *mrcůLf"^#\#lg  9>3F 6͠*\ W*AbzߴSove-i1ܠ(  ̶υjr׼( ׃L2S"\_JBYhr Bb_jcR`{TEWJtJƖSa#5 ҽBؕ  rO_[sM8aܞ!`効b>[>?_VdAu( ?XPhSb) ge$@bKO ;=֙b!h=sDF3`WRMd!Sקwh jc0]پK՜8"D3!a"tjPXKO0ɥl0&ل;UʫXyZ7!AGmRJax(_ܤ NCZ{C@0ܠy/]鉸Z $7O0HHB_` Ы#"v !M` 3:tQW}q ⨏;O|!j՘|LŭZޖ &/-J"pgH\Y,)!e<G d9>/ h'J-@ ;ҡīaZN:8$KJC]zD$1)eewڒM׀&8^ـ PiE്=Oe^M1F1-float64WE-AFsp.wav000066400000000000000000013365321445000440600332750ustar00rootroot00000000000000java-binary-block-parser-2.0.6/jbbp/src/test/resources/com/igormaznitsa/jbbp/itRIFFRWAVEfmt (@@@8qfact[dataP?? (? ??? ? ? ?(??(?$$?? ? ?????(?? ??$? ?$?(?? ?(?($? ?? ?, ? ?($??$0 ?0?8?2?? ? ? ??(??$? ? ??  ???????$??? ?$,$( ? ?$? ,? ? ????? ? ??$? ? ?( ?? ?? (????? ??$?(? ?????????6,? ? ??(? ?,?$(?? ?,?? ?? ,0??? ???$?? ???(??$?  $?$??,???$?(,?? ? ??? $2??0? ?( ?$? ??(?$? ??(??????0$(?0? 0?? ?????  ?($$????$????0??$ ??(? ??$?? $22?(?$? ?$$?? ? ???(??,??2??$? ?, ?$???,(?( ?((? ??$??,??0(?????$? ( ???? 0??0?(?2?????? ?$?(,,2???$ ??????$?$? (?? ??8??(?? ??? ? ?$2(?4?,,$? ? ????$??$??(???? ?? ( ????,?,? ?(???6???$? ? ?,8?$,?2??0$?? ? 020$ ? ??,? ? ,??? ?(?????? ?,???(?2 ?????,?$?2?$??$(??? ?(2?(?((?( ??$?$(? ??,,??? ?? ?,$??E??8?B$8?@0?,?4??6????8?2?>$?(?F??H?(?0?0?(??$???$? G,B,??P? C?? d?$? ?$ȑ?$?x?(?P?@?ʶ? v ? ??Ȟ?$4S$?s(?\??`?h ???$?0?$??|?0?$q60a???P?$(?L?(??? ??0`r$??4?(?䦿,䣿<P8w?І?P?r? ??2  2?ؔ(?@}?(???0? ??$?0? (?p?$??2?>?,??$?ſ2?ƿ ?¿ ?X??? ??D??x X h2h>? z??I8?p(?H?M$????(? ?t?>?U Ș6x2$???2?$t??,0?pE?4A?(?ԡ?T?<Ƞ?:(0?pC?>H \????0??_?>?},Lj(x?@??@t?fy?@u,(h8?@~x??$??L??(t?Du?W??洿>>?4?4ܨO?C?? k4P?I?K?<tp$H>@?B?(?6?u?ApH$u?0?80?2??$?ܥ?S?L??h>?$,?I@k?0?c:?@l?@8,S?hK?y?E`?$P?,??$??<8RB?>?c?TR?U?L?^?@h<N¿ Ax6?B@`?,??]@nBx?6?H?@? H?`8~K@{`NTAX??F??TЗ?^ v2hY?P?CT?̲?@`?Ȝ?A??W`?zZ?$ȸLP(?J?P?: ?@e@m?2?_?@v?VW?\@?@d?@>ԠRH?[??,ة`?HFH?W|?:??:?Ad?:V?ƿB?μaη?^_?>(??T?>? S?̰IVV??R?|?@`?_?L?Xb`~?g?_cD?2?M?ȫ?C??Vl?C H?A`pH?bW(O?ĥY?bīV?$?o? `R?6@?W?@`?8I4?F??R?X?0?_0?Gȭ?b??X`?w?SO?2?P?t\_?JL?@d?^??R??F??U?E?_?BC?¿b|¿CQ?\B?T?P?P? ?b??NȡQb?.iK?|?Q?8??pe@z?R??E?@eRC??X?h?W?d?@b?JP?؝g`u?d??Y؟M|>?8?P4@tKX?@c?ة?P?T?h?@i؞?E?v0?O?b? b\?Q???$?AL?"¿W?;ÿ,?UJV\? `?F0?e??T q?UZ?䥿0??R8?:?:?xR?@R?(?0?H?p(b?2??2`v?W??b?P??B? ?D@`?ĿcFÿZ?L$િ,?}FP?d?(?QX??H0?(?2?p\?B??2??xS0?\?0?$f?:?M??b?`??6?PH?BXBЂC?0?W@m?$pO?p^,R?0??U?P?W$?H??>??bac?([D?8?d??bs?V?,?P?@a?N?R?h?T?>?K??K??:??WH4?L,? :@P?`?Xs?\? ?ЦVO? ?` ?@?O? ?>??W? s?S8:??<?T?N?X?P??`z?D?̩[?ƿ0ÿPſ<긿RS?,?h?D@p?$??,?t?>?Fr@?>K??]?^UH?A?Z?p?8?Ч?Y?B?ֳ?:?М?RDO?a?00?qc?_?G?쩿>?2?@p?8H?4??A?E?ʿ?\?B@f `? 6?N?[?2<`Z?U?Ah?[?h0?_l?0?J?>^?]??gZ`Z? O?8?`?d@b촿`?4[?p?f?4?E`?a?(?C??b?4@l?@e?@t?Q ?@e?e??B??ex?S@?h?N?@aԣ?$?@a??@?@efļH?ſ4?譿BD_?ҰRൿXHd?@?CR?VĬYRm?Xm?(?Qf?V?V?{4?0??i.?V?u 0S?@?agR?졿ZH?$`??X0?e??P`?,?\P?R??D?V?elV?RB??^?`?^??A?V?]?Ee?U? q?8?H̤?V??<G?`LN?VC? ÿS?ſAP?`ea?ҸDD? ?t?>Pc?H@lL$p@i?X?j?H??I(?Jh?\?? r@?q?VЛH?xiUYty?蜿cY?(?H??V|?N?0?C?Ј?W?P?`?V?Є?a?m@l0P?<??Q`w?d?Oh?F?g?A?S?Ԡ?D?`?k>?e??$ܷ?d >?#Lſ>?4?^G?Y?S?ZPV?_?? ?dw0?@P?@i? kpQȒ_?`w4V~U? :qV?u?Pg?C?t@g?P?@j?U?Ёaz?,T? p?p?a?i?@g?U??m?|?r?4?,??T?@n?d?e?bw?4u?8?Nqo@m?@}?^?I?`?P^fI?2?Ћ4?@f?pRr?I?wS }:T?^?Z$qWd?U@q?\?@?(?@`@e?@?\?|4?ua ?bT@l?v?q {?l?4?Shf?q?]b?`q?@ph\?8?FTЁZ?v?C @f]r?T`S?e?cybw?o?`eW?J?tG@i?@?`u?a?@y?`?| qPV?p?@d8??b?va?0?YU?l`M?I@bC?Y?^?`? ?f?rP?@@fDZ?Pf@b?A^f?R?s?U?P^p?W?m N?@?@wg?^?fI@k?@|?P?Q?nsT0??U?u?Oqd?i?j@ze?@zYp8?]?phe?h?v?@`@c?`?`M?f?^?bY?V?`zM?@q??0c?A0?l?kfP[`f??a?SYqm?t?~c?$?0a??GRPIb\c?G?J? w?c@d?IVvc?`{Q?a@k?@ixP?@x?0?V s[b? t?@?L?@g??i?E?x[@b?M?|d@d?2?^Rqp?\?Pq?S??AP^@h?Ј?ggp?}?,]?0q@k u?Un|?Y?`(?fa@x?ZY?p?pyr?a?\ЀVt?$@dA q?@?sM??X?_p?0c@e ?r?6fG?Ї?aF@c?v?4? t?^mY?@a? ?4\>??Zp2@sc?@i?L?]?mg?EjW?@j @?0?v[`uw?u?j@t]|K?f`?0?8??jlV?u?M@qZ?x?(?qb`?@jY`u? z?a p?o?,X@m?U?`~bR?V@`?>`r?U?@a?L?QMYT@t?c?0LJ? UO?A0?`r?VZ? rW^?@d?l>@l? p?@mM_\@bq?Њ?:yaOP?u?2?gCl?U@de?|PYm~?d?D@a?@e\8Nt?b?k_?c?gg?@jpf?`zI?`?`r@s?0?e?@T?@fw{?T`u??lK f?r?Q?@od`uG?P?gQ?`?J?B?<O[@f?@u\_?T`?S@oe?p,6?Pq?@?f?g?n$wJqTO?f?c?zS?N?`s?f?e?$yAw?b?o?BT?^x?P?@m?E?uh^?r>?Q?F@m?O?,?MtS?@lM?a yf?@|?@l|i?q??iЄ? p?8?0?aF?O`p?Uat?Mq@e?:?Pnt?L?@o?[?_O?R? pX?\?G?,?hge?k?S?iZf? (8?@w?P?\y?Q?_k?R@k ?LWOsi@{l?[f s?p?v@d?@v?p`s?Yol@gu?}(?8?eYp?2?0?Tgdy?x?Q8?]l`?r?D@D?s?`q rU?@v?R?bXf?X?@mH?`q?p?fxQ?n?8?_t2[(`z?,A?(g?4@q`?@zF?h?,e[?@k?wQb?@~?b? s?K @h?@|N?f?`?,s?U]e?@~?@g}z?U?`P`?^?a u?U?@gda?R? y0?ia?@hD?`zp?o?VBGo?eeR?ie?]m@e?l?C?vR? ~?@rJ u?B?A?V?mtq?k?XX:?ad?i|@f?P?Qn?VfM?K?ko:~?[I6? sM?`|?X@f?[?H?E?gd4?`x?c?@u@a?pO {\?N? tAa?d?S??Xb?E xY?(?,?H?a?P }b? wB?k<^?@h?r?Ir?d@s`?@`KPgf?mZ`?@r2?klK?{?O?@dI?|?@f?a_i?J?@d\`u?k?6?$?|iO?@``{:@n?t?@?2czX??K?@p?@n?l?qt?f?|@c@e?W@~?@h?8ai?Z?@z?VU ?@b?f`?q ?Nk?@t?VjT?r?e~?@w?g}?C?QX?^lP?p?eJ?zhX?L?J??\@~e{@~?`?nj?@p?R?`}g?r?`q rP?p?J?,P?@z({?w`zJ?u?mD?eqq?P@j?T?G?@?fy`r?|?a?ykU?v??c_e??hA?Q?Z@?O?`X?@w?W??(t?@?Lo?T`yd@`4?X?iuK`~c??p?o??p?@b@c?@p?en`w?@m?ujt?`xwjf?l?S?uixY?k?@j@h?k?qc?U?`?U?P6?v?T?bK@?@o?>@`?np?rG?`q?w@kq?|?qF?k?wc ??_?jx?o?m?nu?d?I?L?G?6?G?8?@p?@a?a ?\?h?gb?0P?西]|TD[ҳ2(,(R (觿PdYfri?x ?v?n?Np?v??|????`??p?ָ???? ?(?Z?8????@??H?Hț?؟?ȭ?p?fȖ~`pȸܭ4ʳ@b??x?X?K(?0?8??^??L?(??,??Ƞ??X?Й?З????2`?`l?X?@?0?8?H?ȵx?ؽ??ڱ H8pؔS?Pp??H`??PШ?6?0ظ?~P?K?x??0??0??P?? ?4?>?곿?ܲΰ?N? x| ?޳ s??Rÿ ?P?H??̯ ?Dt???`?????η???\?,??p??@?Ȯ 겿̾िTRX?`??`???}d??ܫ??Ԥ???v?@P?\?0?H?ġ? ?$?آ ?(D?T?@pe?ܫ?蟿`?z?JvN`ܶ0Pķ0࠿줿g裿?P??(??Џ???@?8????x?ʱ??(??h?@n??@n|?n?y??? ?`?D??v??l?ޱ?|?ܩ?Ȥ?쳿rf؞( ޽P컿phX(????И?p?????V?b????h?|?x?Ȳ?H???P???xА?ԡt0`(r?԰?ưИ? ?2?:ܢ? ?П?h?`y?(P@`p |?`?x?X??蛿Ȱ?ؒ??(?葿??tp?>?p?`x|?`~?Z?N藿@f? s?TQ?fw?~Њ?P??l?Ʊ?p?HЏ?p?P?p?t?P?@?q?Q?ؔ?`?@j?Q?d?Sr?O??`?Ħ?w????p??(?,?@?P???v?`@q lXtTXЦ>PZ褿R@(@^H`?`}??~?h?@????Ģ?0? ???̢??p?T?p??(?:??ĭ?Ї??x??M ?`q?wp? }`蚿``pZ@}{}ħsY@Vș J[Ѕ?Q?PD?@eh?mH? r?u? q?n@?q(?s?s? t?m0?c@?\?V?g?h@u?y?|?P?nЀ? ?Ȱ?p~?Hy?X@v?@r?i?h@`?p?B?`?Ex?Wȡ?@dp?@i?m?p?`qT?@p8?l?f?0?ֲ?Є??Ѕ?j?p??P? ??\???|?p?`v?t? p?_? ?X@@hұpԶ`s`uv0 v@us8q@l0e?_?Sl?4?>?ԯ?S??_?d?d??h??i?\?@j? ?j?|?@e?T?_?Ġ?R?(?>?~?<ȑYbЩiTo8 r s u`v泿@vu`sL pmPe@?Y?D?H??`?b?j??q?t?v??z??`}???Z? ?8?0?\xj sv.zh ~ȚP`?@~?`{?x?sJ?m?d?S8?(??W?Գ?d??l?L? q?? s??t?? v? p`u?@t?r?|@n?h?c?^?RQ?0? 8$QV졿Z[`q^?_h?[?W?Sʱ?MȲ?8p? ?̲?C?³?T?H?[??`?.?a?Ȥ?@`??a??\?R?B?t6dP\\dk pR@r°@tu䦿u(`ut?@sP?q?m?gh?Y?@?C?ȴ?^??@h?*? q??`u?b?z??|?p?~?@??0??䤿@}?P{?w?ڷ s?m?Lc?P?F@R]0hdquZy?{8?|Ĭ?@}? }?z?x? u?pط?@ix?^?Hx?6?\?R??^?[?f?@j?<n?`p?T`p?ro?@n?l?ĸg?ba?j[?T?L?lL qVt@wPxX@xH?x0?`xԨ?wz?s?pd?hl?\?AZ?E??]??i??@q?`?`v??y?Є?`|??(P???D0?(?X}?Jy?4 u?@o?xa? I?șM@f?qБ?w ?~ҳ???0?@h??H?~x?`yP?@t?l?\42@Q?`d?@p?t?bw?P`y? {?滿{?浿y?Xw?@u?p?0j?0@b?H?U?H?A?t?@?Sj?^n?b4?d?f?e?dи?bܭ?^h?Yܢ?J(衿:?P?X?:@`?d?Jf?g?e?b?pY?$F?h6pV@`?@e`?l?r^? wİ?@y?@zһ?{h?z&?@yr?vX?sf?p?@iH?\t?ATI?a?k?s?@w?|?@?(?P?Ё??D??p?X?@}?Р?y?ڱ? s?̱?@i?(?X?z?4"?^ ?m? e??@r? ?x?? |?|?`??@???ұ?p??`?(??ֶ?@???8?{??@x?z`t?n?^?F~@`ov @|УPt`@??pR?z,?@r?d?0ָ?`??q??x?b?~?(?????P?d?< ??Ѐ?|?`v? o?a?H?HЛ@a g}nsrlrT?`r?r?p?l?h,?@ae?V ?B??U?X?>??A?\\?f?n?s ?tf? vX@w@vİuxsp౿k@dVI (?@[?Ph?wp??t??x??`{??}?>?@????0?t?@?*?`~??y?ֵ?u??q??@i?T]?衿4?<Sh@dp*`t`wv{ }~`~@}`{jx?u?`r?@lX?b?O? ?K??]?H?e?? p?N?p??m?@?m??`q?q?l?@m?bm?^h?a?f\?D]?T?B?X2?@m6Gx?S`?T,?S̥?T?U?V?QĠ?Kx?@@? $?2?L?4??? ?(?r?0??$$CT調@`x@f,iPkkxlHo @ljgwcD?\z?R?B?(??L?@?Z??b?X?h?Ц?n??r?ܦ?`s??u??@v?ؖ?v?0? w?`?`u?`s?Hp?@m?pf?_?LQ?|LT]ܣek@pX rpssitt?s ?@r?`p؜?j0?gȣ?@ad?U?B?0?,?H?$?V??^?p?b?`?e?z?@f?Vh? h?p@g?Pe?Hd? a?x_?([?pS?̤K?أ4?D?(0JRD?P?LX?8??J??$??$?,?M??S4?l?@o?o?l?@j?h?py?iN@j``P蓿I@c?J@c,?b?e?d?h@p?t?@{?u?|Y?Y?p?Ѓ?u? ?[??Q?`?X??F??P??U??I??V`?k?Z?WȔ?@hp?g ~?@b?a?@~`twy@tHD?RԤȣv4?QX_0p`{V?W?p?`?8??X?0??x??T?@????@???Pܠ? v?? y ?p ?H??0? P?B蒿葿XpDⰿtMdz?h {?H? ?@x?@??`????@?X?0?8???v??r??y???8?Ȝ?0?ܬ?@????(?x??D?? ??@p??xU?Xd4دZjدҶȥিh@?@??? ??Ț?????Ъ?`???0???H?\??4?P?؝?????? ?ܠ?u?x? v?H?T`u?Pd?PP蟿{S|SxR0ht蜿`?????H?@??i?G?? y????P?Ф? ???Ģ??x??????{??@e?@~?`j?؜`r?}?<? ?Z|??°??Ț?@??P ? ??<0?ܫX? ?h???H?>?T?@?8?~?,?@??x??Т??$?c?p?s??\?ܬp??Z??\?p0?X?蕿o?Y?E??0??` ?n?@u?s?yP?pX??4?l$?X؝?Șh?`??8?`?0`p?p@ r`g?z?L???`?੿P?آ?p ?u????Ё??z??Q??]?P???X??\??X??ڳ?(?\?h?P?P?Ȣ?`?X?l?Еؘ??8??.08:(0 ְ0H|40?P?PX?@}h?e?d?L??T???x??x?H??x??@?????0?Ѝ???ܠ?X?$?n0?p????v?}b? ~r|Xȣ|X4<Ĥp`@P@ms?@o?s?m??v?p?@?p?P?`~? ????`??0????@??`?X???~?T?i??Q?P?@r?Љ??y??@n?4?? ??Х?h?Dh<tpܮanp?`Ї?lؐ?p??D?`P?w0???(???Л???Ȩ?h???j?H???p?0??`?P?`s?Ȝ?0?8?? Ѝ?x?0@xdp䢿PЌ؟`h{8?0I?@@v? p?Л ?Ў? ???ȒЈ?0p??`?h?@}?tx?n??`?h??`????l?`?x?@Ќ?`Ѝ??0??䠿`}?TX?hZ?RXc?@??S?`?c?\?0?[L?s?4?|?r~?@e u r̡v@oZlyi\ prrA |^?]pP?y??O??[?N?`?]?H?@c?p?????i??~??,P?,(?H?h?{?fq?@i?o |?0@za?xMHЎ@d?Аs`[^Ȑm<2?@y蘿@?V?p tpP>|gx_?q_] @b?i? ?p?p?r?H?f?0?2?e?Ѐ??^??@b?P?l?@?i{?v?`y?zr?g?Q?D?@K?Ag?PkQ?e? r?I ?m?P?z?e??Mt?x?f?pr?}?@f?b<V?xgdb̪D?(`pW40?`h?KgJ`}P?]?i??c??t??,?(?E???Rh?s? ?_?t?hd?@p?8? s?`?X?p?a?r?]?X?stN?0IRQ?X?YjЎ@j?@uO?^?0y@g?p@0h?蘿M@@ot?0k`U?L?Y?gV??H? |?b?t??q?d ?@u??R?`??@??F}?F?|?ft?@e?k?en?o?W?:K?@hB?m?>?~g?@l?t?@i`q? ? }?k?Ђ?oP? t??@p?_?x?Q??J?Lj?(o@b?H2P6l`X,ԥi?\V`إD? {?pP?Xw0}?e?$?(?C?U?C?I8?Y?E?]$?c0?N?Z?V??8?}?,?a?T?Y lU?@mR?jc?P?`s?W?a??@i?[@g?`~O?C?@Q?C?(B?F@`H^x@g jB?g?vc@|cU?@mg?t?p0?tP?@?SЏ?jP???S`?]?l?h@k?@kT?h,j@?\f?` q?ct?]>?\ p4<pQ`2>hH?xd?@b?e? `p?`a?` 4?VX?HR(?w?N@?$???J??h?P?c? ?2?P?6??0?P?@{?Z?r?a?~?P?T?0|?cldu?Ru>r?tXa___i@l @gsW?kp`s usupzipbvD?s?h~?e`{?U? v?c?t?Y?q?\?t?Lu?Vv?@is?pg?rL?s?n<aQl@ae`qZw`yQ?zd?`{a? ~r?zv?to?r?o?Yg?Z?}?h?h?@u?k?~?@v?~?Z?z? z? y?k`{?`~? {?@d`v?q?m?a[?2?$_TaW|V@t?`@ob?wy`}Q @e` |z ?ogo?c:N@P??h?0q?g?w?N`}?i?`~?sz?@}?v?r? w?l?w\?tQ?0? xcЄ?o@et~?v0?y w?ydwR?`s@h`u|sK?e~8s?W? vh?v?j?`sp?t?`w?k?{?n?@?C?@?0?`z?p`q??h?rg?Z?T?y2gTtdr rk?wf {m{`s? {fz? ri{?ep@b pJk?X?|@e?g?@m?@dp?Qt?j?s?X@q?`|?`q?jp?`?`p?r?g?8R?Z(r?^jmn?q@nmlo?@imcn?ck@e`}?e }]?>?0_??j?Pp?@u?i?h@g?0?j?@m@h?h?f?a?g?@}c??^?pA?j?Z4?h t@r ?v |`t r?po@nOj@`?jzj|?@c@{(W?b?T?f?>?f?Z?l?fr?gt??p?@o@p?4s?i? r?@j??g?`?a?4?\?Z@jji?or@sf?@wS?xduu?`te@rp?mbhx?H>?q?T?@g?@b?kg?@?j?m? ?`q?(q??r?0r??n?{l?_g? t?]?J??@hZ?d8qЇ?x ty@d?@vk?qY?kf\?@a`T`q?4oZ?@h?@l?W?d?]?i?u?l? xo??t?0t?@m@n?j@f?Ra?s?W?NR?p?a?Kd?Uq?fl?@nd?r@wz?`xT`vse?j2?_`}P?6q>?c?\?@cg?@n??`t?xv?p?t?^?q?<n?@l?`~?i?i?Xf?y]???NU?g@}q@i? sv?@u@r{?qp@{?`r8?v(p|bB?$?X?c^?Ke?gj?_k?@o?l?Qp?y?r?S?r?f@r??o?|b?I?W?l?F?_(~6І?V8fy?nq r@cskr(?@pfl?d?b |WP?L@y(??Z?a?i?J?o?@ p?s?l?\@k?Wk?R@l?@gj?4?g?R@`? K?@w:d?Zwaz?@d }?iE@o6?o`s?@pl`r@m? rwhr?a`W`?Q?@aV?@c?`?mk?}?n?zl?R? r?h?t? v?`s?d?q?d?@n?k?i?tc?@e?W?C?,?R?Nt?_@@i@rj0?pv?vrug?t@ s_?ncgSf@t?[m?Od?4nV?<f?k?s?p?`~ q?@u? p?@p?u?n?q?n?@?o?`v?@j?u_?\?R? u |?OoT`u?@c`jq?@j0pd?`qc?olZ?co`?^yIP?pD?T`?_?i?0?m?p?p?}i?uf?g?q?c?Ma? z?_?qT?w,??R?\ ~?a$?@fX?i@id?ia?@mk?p@rmPhb?` y:}>?@k?K? w?X?o?d?o?@g?qi?|m?@n??l??l?[?@m?bf?@c? {@`?U?k?P??@?Cp^@k?iPkt?`p?p s q@?qqpmnd^d}Z^?P0,?yQ??@d? ?@g?P?@g?o?@k?nj?ul?Hn?`?n?N?@k?k?e?C?]?@eH?Y?8?J<_?NP?Ri?a`@acg p?@e@n?g@x?d\cpWq@U?MA?Q6R?D?tS?lX?cW?qc?p\?aX?6Q?_@?D?P?k?[?(?[q?Dd?``Y zQ\0Le?Js?MK?VmBs<$?(?k??z?R??P?n?Z?u?[?l?\?@d?@d?K?^?@s^?|`?n?]?@?[?s?T?@i?M?( ?a0b6jPxWJaY?cC?c>iUi@q@gP@djdCdH@`D?]X?Kc?a?H?u?T?rZ?E?]?q_?`t_?ae?h?g?bg?|?e? }?@c?C`?l_?Y\?@x?X??I???|?Cl?L@qXx_WY6?`u?_ q?]?_,Z |^@a\|YoSk?TWR |:@I@MA@m`|8?L?N?J?g?O?@b?H?d?L?eB?VQ??N??[??a??[?Z?Y?jY?C[?a?S?@r?[??Z??a?@u?W?k?8?@v? ?_?2|?Dp?8y?8j?S`tW^ abc@i?dp?hw?d`rdc?@bczcsaZkKЀBs(?E?@pL??R?H?\? q`?g?0@j?|l?C?n? r?o?@z?@k?@n?h?2g?Zc?g`?RV?aG?@o?t?G2Y@e?@`,bneW@eC?d?e ?f?gh?cv?@et`y\PWPP~?J???F?ZH?`}C?PN?0P? Q?U?T?H]?pO?ZE?^?K?4M?KP?rQ?|R? yE?q?V??P??V??W??V?v?U?e?T?^?Q?`|?C?`v?0?@y? ?$?b?6y?H q?P?Tg?\nbmhЄh@@go?kojV?m]@jf[]|X`H`|? P?@r[?a?S@i?n?l?r q?@`?`r?tq?}q?\`q?k@m?@y?@h?q?c?0?]?x?E?@c8@v?Z`|@`UeX?j?kr?gg`u?iq?fl?@f?`?Rp?>?E?X?q?R? vV?fY?U?_?sR?@{?Q?HF?oVB`\R`z?pP? ?Xp?Ђ2tPc?P??@s?`??`??X?0?? }?p?`w?8?,?4Ь?TȲ?W?b?r?@h?N?u?ī? u??@j?(?W?~N?Zd^Wne?ʱk?p?ԩU?cܣV?\?0m?{?0A?}i`` q?0?PX?ؙ`?Ѐ??d???U?X?t?`?0? ?Ё??Z??????0?p?И?P?x? ????? ???d?(??Ы???l??ț? wj?Tb?tpR|ܭp4Ю}?4H?ȟ? ?h?8??h?w{?T``?@?s?@h?`??`w?x???? ??8???@???@?x? ??P?"?ȫ??x???Z?ة????P?p?|Q?{h?4P@s(`|@`?|?L?Т|??ܠX?P?pv?o8v?@`p?|?ZЃ?`r?(???0??????`???X????ܣ?|???t?D??J?(?6????ܨ0?nÿſhĿ`pp``??@?P? P???P??H?S?`?N@c{?8? {?ܪh?䪿 ?Ф?(|???l?X????H???>??0???D?[ ?ؒ? ?0| ?@~?TĢ `? s?ȝ?8??}D`x 00P@k੿f?H???xؐ?sЍ?@@??`q?@n?d? }Г?z@?????t??H?X?\???x??@qY?@lȡ?sܧ? x`?|`?~?}?|l?{ȣ?`{?`zH?y?w@f@vr؛iءW`B?f?@u?0|?S?0???ؚ?P???$????? ?p?z?R?v?r? i?0_?I?@<`Z`g@prs0 t`t sfj p?Zp?2?W??l? ? u??v?`? t?|?o? z?@b?o?f?@cI?ns8?xI?zj?zs?z{? {?`{`? z|?xt?@vm?sK?@oRfmWv?@}_?}p?G?y?????? ? ???X<HSP\bhl2l?@kȔ?@jp?iț?f?`?X ?LA?:C?Q?U?`?a? a?a?{X?qI?lpRp@amegiU@kH?k^?kb?h\?e$h?d?@z?H? }?c??@e?}?Pv?f?p?S6?F_@?w@z? z_u{@t}Uh?|?h@?~? z?w??S??d??|??|?`t?e?@d?@n?(?`~?\H?@g@d?@m y?k:?fj?^r?UAAx?Hl?Uj?\`z?@km? r^?@eW?r? ~??e?H?@s??@??Lx?x??x?i?sJ?`{t(Pܯ䭿x8`p? ??w ?E?????Њ??X??8?h??Д???0?z?P?eCnh(X00ppd@d|?Y?0??Ȕ???p???`?Ѓ?|??$`z?y`v?Ђ??0H? 0?Ȓ?H?i} ?@zmjЋF``r?hp?(@??,Ђ?@?@O?0 s(q?@z?ؒ? Ж?Ȝ?v?B?q???0? ?? ??`?d??2?0?P??x?p?ЋQ?4`Pi@lb s@f@o s?p@|?l?aЌ?U?G?T?Z?@l`?@r?@kȟ?]?@?? ????h??U?8?Ј?(?u?pPX\Эp諿䧿8H xf?g??P?P????(???h`lHf0c`ȟf(hp@sr`Be ?6?(?`??Z??0|?h?y?d?>a?zv?$u?@l?Y?`r?ARp?@{??_h? t?X?l@?,???Е?bh? p??mH?B?(?@g?2? ?J w?@f@z?X? qPc?]XP?@c̠_?q`0@2qSq?a?v?\?E??U ?[??R?V?m? q(K? PY?hSЀG?jZO?R? z?Z@?W?h?L?E?Ж?8?0Ȗ?@??:???I?x?U?b??`S?l?a@j?ȗgț@l?mc?؛]Ġ<(T?Pdqa?@p?G{?G?H?Y??U`?U?Ћ?T~?P?U?6v0`_?@ah`?dt_?c?l???W??P?L?Е?P ??J?P??@~?O@b@d?}^ W?F?P~M?eZ@?T?y?S?L?@?>(?Y?Y?L?k?w??wh?c?`?g?] sE? gpq?vХ p?l@lpg?<RD?I@2?`|_?@n?mP?`{?ȑ?u?s??jp?6?T?E?Bib?}gU?zd?pfFo?`?`qP?q?Д?dX?Q?0?,?[l?Q?X?ch? p??jl?Ѓq_?(@?@H?Xa\p?wțz?@wp?<k`?a??DБ?Y?X?4?_?j?i?qRs?hH,?Su4^(?@g?U? ?e?L??8T?UT?E0?R??IX?6?? @q?X`z`?PN8?̤b?Ģc@a?ha4j?U 0PPI?\@?b??ax?g??f(?V??O?v?0r?g?keR?@kC[?WZ?c?x?DЋ?O?h?a`?8??`??X?P?0?>?`0?^?X {N?hL@@`HU??`L? U@S`K?0~c?A@f`z?D?R0?a?Ѝ?0 ?^??W0?I{?J?x? p?^?@{?A?}?\?$?@hx?@c?x?$?k??`?J??8?A?p??J?XUUA?R`?\pp?ȞY`K?2Xca?xQI?U?S?,?t?S?a?v?mЁ?f? ?n?g??I~?R?? t?W??R??$?_??t? |??@eЏ?]??O?St?Q?@q?(?I?a?XE?p@nQ?Y?x T?Hk C@g?~S zMdJ?I?cL?@n?@f?``u?[?x?V@v?[?@y?W?@g?? ?4?S??@k ? ?Q? ???H??>?K@|? {?eq?m?P?hYP?@l>`|H? W? P0E`8?0?4?`^`Z?`O?~^tY?@e]?@n?g?fv?\?P?? ?6@?P? h?B?_?8?Q?AP?(?Z??P?p?T?S`?M?y?T?e?0?E ?p\I$O ?R?@aДR?N?j?ؐ@b`t?e_?pnJ@@e?_?@w?Q?A?@mX?\?X?B?h?Z??K??B?:?p?SȐ?]??p?K?Ї?Dp?`|?R?l?fHv?pf@J?W@R?Y?VD?f(@b?ؑ]萿f?e $? `Sx@`?g@aB?e?G x?4@?^??2?0?k??^P?]??M??4?2??U?j?`?p0?@|? ?? q?@}??m?pw?k {`{q?`p$?p(?Б\?@hВ`?ؑ^F?p?op?P @e?|I`qU?]NF?A?m?>{?G?Є?f?c??8?@d@?d??L?`?p?a?`?k`?[P?e?p?0??e?Ѐ?@a u?2?Y?l[z?@t`uPN?Ј?a`p?8^ؒa?XmC?0N?2PF?P8?ЃQ~Zs`q?a2?hj?@z?g0?b?Љ?X??Up?Q0?_?X?g?k?P?^?0?D??K0?`?0?A?j?u?4Z?>ZO?sa@b?`UA@l葿e? P`m?\Ra?YG?}b?sU_4?L?on?R?`}?i?m??pp?@q??UP?< ?i?Ȑ?$?H?K?U?b??@s?x??@m|?c?@o?:?b?@d vxt?:? qP[?o@m?`F@d?o@i? f@Z?`P?ЂV z8kF?OY?a?bv?U??b???Q?Y?R?x?I??b? p??C?>?P?e?N?@?i??6?u?Y[?Z?TY@sKa? TBpCJ?Yt?h@sHa?p@d?ЇX?@}Q@sY`@h?D?`r?I?V?N?F??`? ?@bh?M?h?HH?b?a??c??@`?6?@?4??Du?K?e?j6em?\fu@{ s@nЈlqtupXfTPK W`AS?Z?xI?jI?Q?@m?S? }?2?`?@? ?F@?S?i?qؐ?@s?np?<?o?Ќ?x??|?p?p??? y?ȑ?o??M??d?`x?p@? `}?`aPPPpHpxX H Px@}i^??\?v?h?q? p?h?u??2^,O,@k?2@t?(@d?6I?2s??(`??(h?,P?hP,h??6?d0?b?(?l?0?`{?P?>?p? ?x???0??4??4?~?,w?M@`8?iJ?^?:i?$Y?m?2{?4@u?>~$w,0? ?@$?@?q,a?`w?`????@? ? ~?0?z?0?|?(?@j?T?D?2V??S?>?$p?$?y??0??? ? v? ??,?2$a0fM?j?j? q?$ {?? ?|?i?V?$?F? ? q2??HЍ?А p,@`?s?$???Ѓ? ~?@w? ?`u??x?b? n> r??^wh??~?(?b?2?`}??0? ?0?(? x??04 q` ,?n,? r?$?n?,?N,?g?$?$p$$(,?H0?? $?xk ?$ x?(??Џ?((?(0?? ??0?$?P??? ?@k?$?b?(?R??O?]?V 4$?g?v?$r??y? r?d?Ot??0@$(@ 0@$,sR ?S??@w? ?@?$??? ? ?? ?|?0?j? ?s??`x?$?f?$o @y ? ?P??$O?g???? ??ؐ?(????~?O??A@n(gV?a?@i 0??К$x? `?@?Z$?q?? ? ??P???4Г?2????`? ?P???(? |?,? t?$?t? ?@n?d?A? ?G?b??u?,s?2@g? @c?:D? e ? ??@?p?X???Љ?Ѐ?w?@o$?Li?`x?? ?`??? ???s??U S?,T? `t nV?{?t??,A??@z?? ?w?0?m?,?}?$??`|??@p?P BW?T?B???:?oe? X?y@o?0,Q?|@m0D?Z?PxpP8`}?vx@o?@u@?m?8`x`s?_?p~@m{?P?u@s?$Є?o??lh?`z? ?rЗ?4h? q??}? x@|?Pb|?H{8?Pؑ?>p8?xhl?o@km?W? ? ?0? t?8?w??@e?@w ?|?0?{؟?`??Z? vH?~??:?@?@|?`z?]?`?gj?zg@k`?0PW ?0`x?0Ј?z?i w`|[?@?x]?j?@0q? ~`|?@sW?@bx?c`r?v? ??Y??r????@??X?`?8??`sX?o?@? {{?q? q?h?X`uf? u?{}gv?@rؑs`?@c@a?}?:?@~?2?`q?`r?Y@m?ba?f? y?`rs?,Z?f@s?[f?2?`g?pw[ؓ`s?R8Z@o? ~?H@x蛿`? c?@pȐiЄR@M?k?`tP?d??a?`~?h?`?@??q?`??j8?r?8?U?H?dX?@j??@m?k?@u?qF@hf yЀ?h< x?d?x| ?HЈ?Љ |`w{ }?hp?a?P@|`s@?@ku0?c?Pu w?@p?zhP>lV?Kcg@gg?X?p?C?G??8? ?d?@d?`?R`?@e?8? |?g??P?? }`s?dm?@v?>?@p?p0tP`|d? L?}2 }P?Ycp?@cr?Y?k?p?i?^ v?B~?<?w??@k0?j?p?f?0?z@kЍb0^?8e?ksr?d?ȟx]?pq?2?|ji?@j?0? u??@|@? ?s??B?@~L?AP?`z?h?H?h?R??Ѐ??@l@m?@Ql?m?@} s{v?Pr?0`x`si?0l?yqq@ph? p?S?@t4?W?0?4p?V rq@j? }?i?l萿M?xf?0a |Ђeyb8su?@v@?`t`?^?jX?n?@rH?a?m?@~?}?`?6z?v_?[ r@w?`yj?x?z }?oЀ?j?r@?m?W0?\?`?u?`?@z??w??v?`?q?|?i?0l?h@u?@u?0x?`x?(?0|P`З@(ț {x@tT?З@e?n??@l@?@p?p????І?????0?@?(?Є?H?А????P????@??P?p?`? w??f??N`?@q^{PЁh`p$pH{rol<@ik zlJ?@mp?r??P?H???p?p?@y`?d~?U?@m?q?`p?x????p?p?@??P?p???Ј?@?p?H?P?0?@? r?}?{?Șt?W?ħQ_4@eЭePlF?DV?Q?@tY?h?@h?H?g??@o??x?x?{?@?}??@}???0? ?`v?@j?n?WB}?S蔿B???؛?d?T?(Ф?ؑ??`V?e q?p?$?<X?З?|??`?P?(?Е?J?????P???u??X??f?@vȐ?~z?`<huupuV`???`{?v\?s,? r?v0? yВ?`{`q?`|@@y@s h_ி쯿V?̯d?p?d t?,v?Py?Y?@}?Ѕ????0?Ё????0??Ѐ??~?@?x?Ѐ?@l?D?R?@v@?<}bvZk@@i?\P?W?Z??e?p?^??m??y?L?`x?`?~??P?`p?h`v?`X? Y 葿x}x??0?P(?0ȡ?`?X?k? ??P?0?@s?(?Q??zx?@`?`8?@yX?,ؖ?s?8?? ?p??????? x?H?Y?0?a?z`?R? 0p8P`<@XЁؖ{ uP?`s?s0?s؛?s`?v? q?d?V0?G?`r?f?U?o?c s?qu?_`y?S`{?2?~?q??Є???Є????@?X??0?`x??q??]??Ur?^P_l(lؙZ4[@bQ L?n?x??Ё?t???0??P?P?{??H? p?@p?ؕv?t?v?u?p`z?蘿`y? {?{?p {?Ѐ@y?j x?f?`t?@}?q??o?H?@i? ?o? ?@s?H?q??q??@l?h?_?@?V??,?`?[?r0? {?`e?F ppPP0 {tl@eX`UzMqNgCb:?jX?`th?{s?`{??z?@g?U?p?`p??y???Є??P??`?@? }??v??n? ?c?0?Q?0?0?0?0@{?Po?@dZ?sAu@fsn spunwo |@kpf dTP$?@P?@@g?@v?p0?}?`x?qp?b?P?^?z?j?@n r?@w?8}?0?X?`?(?,?@?Ж?x?0}?ry?c?r?@?h??R??:P?]?g?j?@ph?r ? u?@v?`uЈ?s }? qc?o]l tlp@k`kj@ik@hhЏ@k@hb@mXL?G@n?u?E?p?R?F?Y?@mb?f?k?0o?p?0 q?eq?_?q?w? q?~?p?0?o??l?@}?g?`|?c?}?`??V??M? ?@?`?0p?Pt?ZV?acav@bc@e`yfshdhfV?dk?cv?@a?^ ?Y`?S?L?FЙ?,З?(??8?@c?D?PM?HP?,Q?ܧU?dU?U?T?ĤS?S?Q?(R?@{M?T?J? ?I??B??>?,???B?? ?2????P?,?? ???ؘ?0?Б? ??j??s? ?@ X$ht??$?h??ؚ?0??(??,????? ?j??? L8?N?0?@t ? ?(???h?J??? ????0?c?x$?X$?h? ?????|V @~?(p?0??h??Ȟ????e?`($(? ??p0xl? ??x?h??x???Н?$??X?@??$p??g ?z?Ѓ$,p@}? q ?[?$?@~?8?@?,? ??????????О? u?0Е(৿?ֱt޶$$??(@?$P????̭?ı???J?$????2?8??d???螿 ,葿$? g$`x????$?,??2????X?(?0? ?O? @0@$8 М`X?}?(? ??8? p? ??0?8??Ў?$0?`t?$?F? ?@l`s,? w?`t ?@j2?T?S??q??}??@??`?,?$?X?, ?4???p??0?ij?0(?? ?`,?pp?x??P?, ? ,??? ?*???8?$s?hX???(?B(? ?$?????X??@?X??2p?@j?@~p ?؜ x hl??? ?q?@n?`????Ћ?0? ??P? ?@~?$?|?|??|?? y??r??k?e? ?g? ?f??h?p??k? u? v?x?|?? z??????,?2D?ܢ?@?t?2`8?@??V?$?8 s$?`??D?t?(h?ī?(???`????Dx$x蠿$П?薿P:?$?0???0??X?0??? ??2??$?q??P@ؒ l?$??@p,h?Ѐ??0???@|? u??s??w??}?$???4 ?2{?0u?4a? Y?$?@c? ?c? p??k?k??c?U?$?_?W?W?p?y??8?Ȓ?Ѝ?@d?? }?x?S?~D`uxlV?r?dr?*Qd調i??(?X?}`?q??f?(?R? ??@@??@~Ԧ?L?b? y@b?@@@B?x|?``s?wf? |?q?h?w?И?@n??@i?0?n??m?X?d?@qp?8 r?`f@q@rU@gșfЄ?Z?0@z8 }?v?xc\?`p?w?x?@? |@}?\`r?]?e?}8?L??`?x?p?~x?Y~?@m?Ѐ?a?Ѐ?S?S?~?o?~?@qx?fs?u?o?bX?j`?B^aaWI??S?`r?@?`(?n?D?s?o??H?@u@k?0`t?`~?}J r곿z? p?2j?m?Qd?hX?[?$?`r?ܥ?t?ܨ?[?@g ?g??Xȟ?P??b`F?PeN?|b ЋBY?,?p?@s??bh?0?x? t??m?@t?P?^?r?xh?k@bn0?@k @ul?؟d蜿 p?ؕRZb@v?`t?R0?OЄ?]?`??|?f`u?@p?? s?p@v?y?x? v?|?@h`z??@{?m{?s?w?\?s?`sn?@z?b?H?L?Q?>8n<l(@h?B?a?k? u?P?s?h??nȘ?,D?Z?|???j??4?@?I`rЙN?lT?FH?2h?dⰿ@advi?i?EkP?V???2?l?y??@e`U?g`f?@s?}?Z0?_p?8?0?@``?c?p?U??j??\?iІ?f??d$I?~@qk?șT?^8T?^?b?@qPeed?t?eP?6??(??@s??m??`p?Vh?,?H?@d?c?B x?`?Y?,?iG w@i?{PPXu:lBdY?JDX?S0Ii?L?D?k?` ?f?@`?@ad?P ?`h?i??u?44:?tj_?:?̦c?D hcW?wH?I?X?p?M?V0?[?D?b?V?l?^?\??DН?r??P[0\q8K?S0`?@?PSPT8`xP:4?@y?E?a???b?8?_???`?p?4?@mQ?`V?蒿>?R?H@cQPa@Q`hy^f?@c??@c??o??U?H?? 8?Z0?<0?Vp?@??<r?O?68?rH?zD?bV x @l?]I?Hj? N?RAF@i \?Uv?4?(?Ȗ?:Ȝ?:??:?P?h?Lm?Q?N?hL?ԣ^?E?D?,L?S?F?U e[` ?P?\?x?]??@b?̧?P?p?\?T?2??$??T?W?@ev?[S?]lQ@(?CR(2xK?PR`` y@0_?n?6?v?]?w?>?x?J?P??(?ؒ?M??c?0?@f?`l?@q?@v?lk?T@b?dU?ȘJ?p0|B^RV?@ki?ou?l?@hЏ?d@?bМ?h?@i(?iؕ?g?e`?Utb`s~tuvi`t]sdg s4~`?`r?|x?p?0?`??h?`?`?P?VP?p ?rP?@l`?A?@q???o??`t@~?r?Б?`r@@z}~p@{@|p`@~Ќv@lPYq8P?_v?l@?f?A??f?@?x??0? ?p?`???0?`??0???8?{?X?@b??F?H?K?I?8??@XYp,zp@$ w@l@\@fRr?i?v? ~ }?}k?@xbmnRv(?{4`\jl@oR?cy?0??l??z?P???Ѐ??|??u?ġ?q?d? p??s?İ?w?t?@v??q?c?D@Ыe p@m@dWTEQ?^?f(?j?gp?Zu?(?`S?@R?>?(A[@cgab?Q u??>??@??0 ?Q?V0?Pp?$`?R??@`?Ȯ?_??U??4??CNH,`0?A?0?CV8]?V?A??@?$??2T?M?Q/?Q?Lw?,ſ,8 G@uP?N?A???,??2?t?6?>h@4? ?:??B??A?? ?@?0($?`8??C?x?4?{? ?8$ ??,? ?0??:?$?4D2ƿ0¿,80?0??ܣ??? ?6Ȫ??$0$?j ? ?ġ$???X?(z?2j2H?,?(?`w??? ?, p0?ܠ(??R??$????г??4????湿$?/ȿ?Eÿ??(?̧??X? ? ???@?F(?(???(???P$?  p$((?? ?d?w??@?lhp?$?(?$??H??`?4?,V?4?ɿ$?¿0? ?`?|?T? 8? ?c?"$?t?$???0?Њ? ?<$H??p???x?? ?LHȜ?x?`??0????? ????(?(į Cƿ¿?Ԧ?w??? ?$?Ը?t(v?$ܯ??Ġ?`??x???,?z|? ?`(??$?̧? ???q??(?(???u??P?$?m?Wÿ2? ?|?(?p??m??и:??@f??X???Ʋ? ??Ƶ? ?p j? |?$?0d??l,赿$?ܤ(?p???`??`?(??,?? ?? ????P¿ ?f???`?(????? 0$$?$?$ȶ?$? h? ?V?L??8?(?ʲ?0? ?(? ?,?y???h? ???$ ?$???贿?ȿ$???dؚ? ??(?"??@??0,$,䣿??n???@fr?D,?m??T?$?L?$?p ?ਿ访0 ,?p? h?? ?@?? ????0??H? ҹ h??Z???T?Ȭ?X?0 ??$??2?x?$`$(4? ?p? _??@g?x??؟S? Е? ?p?$??J?(_?(T??ി,?$0 ??0??(?P?\??LP ??p???x衿$?H 8?ȡ?? ?w?h ?ؑ K?$?k??s??@?????(???h??@}$?8$?ಿ$??ॿU?(?? ?L? u? \ D?0????0X$$??P????x?? ?H??V??$(??? ?P?????`(?p? ?p??? ? ??2 xm?$?@}??? ?ؙ$?H(? p ?t?0?????h?T(y? 0? ?(?,?P?0?H?$?Է??d?  ? ??$?p?s?8?? ?0@??P~@c?0? $f8V?2? Ȣ?$???$?$? $?,?_??0?(?{?0?@??4`???]?X(??PP0?(0?u?@ ?p ?qW?,@(p$?@c$?`?$04 q4P?,?؜?$?X?,?r?(?@P2`tk?X??(?`?$??4??0???0$?Ȓ?2?0?p? ??`2 0p2,x,?V?4?@}$?Їs$v2($20l?h?A?JD?E?@G?6??P?,?4L?4C6ؐ؝$?Ș4?Ѐ$?e?ЉF?~:?Sq??Dq?}?0wt0@??0?vh?o?u?`(?(?e?}?ؐ?p?|?,@?葿d??L?x???(?N?`pxp0?a`??@ؓ??p??`P?0c?@m?k?`~Г@@ЋȜԠ u ?n@?j?H?H???{?X?u?yT?q?pu?(?a?b?ZP?`wh?|?z?@f@?i??@a??U?@hi@ytЛ@v@f? ?@x??w?vQ?@jz@pЈ$|u8?J?`xp? T?PM?@m||̡q@~?`pЅ?P???@|??t?wo xZ u?2`??x???u`?@y?]Ʊ??L??`zv?Љ0?Y?pXvm@{?Q?s?`??؜?@{k?Pku`w|@?@hs?xV?quPЀHv@|?k? q?p??Z??@|X?o N?8J?d?@e?f^??i? ??g?[??6?V?u??@o?pk?jQ?zKxI?P ?f?}? ?\?e?u`?P ?X ?E?P?]??h??@d?(p?BP?r??m??RP?@?`vH?r`tE?i?@x?0Z?nEJU?H ?X?@n?hpq @ЁHo藿i8?NL?H{? ?X??Z??l?0?g?`?@??`pQ¿`so¿`wxwNtnp@` {?A?C??R?(?_??k?B?s??@v?`?`w? t?@x?@x?u?@r?\@q?m?X@d?n?V??(??,?G̨?S?Pа?L?[?@e?j`p?@nhlhcRZS,O8V?<ޱRX`?d?k0?np?m?@mh?kt?iЯ?g?e`t?b0Z?0K?@K?<X?P?]?p?`??]?*?_??`?\?b?Џ?a? ?@`?`y?c?k?a?y_?0@`?]?<U?H??h?4Ȩ?4p??Cv?XZȧZY@YpTrLe?Dn?IU?P@mSgTQS?Pf?P@c?G$?P8?Qo?Iz?:???I?p?V?P?[??\??[??[?Љ?Y??Y??X?0?R??E???DD AĿ0?¿ ?Ҹ?ܤ<>:4?A?Kħ??0?6?A?LL?zR?θT?S?G???Z?B?P>?Y?^@?_sb@cb\Q@:l4?J?R??W??_?.?`??a??`?p?^?Ъ^?Y?Y?h]?Y?w?T?H?H?̠?4?N?Zkad@}@f wep@fte zaZ\n?R?H`?0?{?F?J0?Qt?Pp?It?E@?E?HВ?Bp?2@?8?,??B??A?t?D?,?K?`I?4F?8C?C?F?@??(?? ?H??4ؔ?:Ј?>?Bp?Aw?FzGL@020L,A,?n??$?6$ `?$?((?(?$t??@? ?4?`6?v8?Y$?g?6?r?(?g?z? ?~?`?~??{??g?o?$u?w?,P?>v?2q?0@l?p?(?(B???Z????`(?68??^$ț@???,?T?0???f??|??0?0?<,? \0lģ4x? 0?$? ???e$ܣ@?~?????0??u??@n?,??0?@???$?H?,?d??䡿?h$: @e???????c??xZ?$u?X?[@t?pJ??@z???y?0?{?(?`}??}?2?`t??l? P?@j F?@v? @?$p?0? ij?$P?(?$f? ?ܹ4?$?@(?@x(? ???d?0H?u?k?(?[@? l 8?P? ????? t?  ?ȣ ?` ?,??X? ?ؒ? t? ?:??\ ?@k?$?p??:??y?s(@$d ? y? ?? x$P?q?? }? Y?0V$ @u x?p$?4?$? ?g??@h??|??@z?$??(? ?,?@v? ?y?@?,?L?$Q?$s? q? ? p?? ?,?t?ȡ?Њ⺿?깿???І?$??$?4????t? ?u ?@b? ?>?(?`}ȑ?З ?yȚ?Ԧ? ???@ (0(А$p??@???@?,? z,?@?`?,?]?0?@h?(?_$?v? u@a?? w?, v?4]?$hP$@y? ? ?`z??`}?ؑ?J?x?? }??`r??R ?}?0?`u? s8?Ў0?x2?p>?r?4 ??0?L?0?E?p?j?[@?U vh?Ђ?d??p|?`?_?@?y}jPs?~?n?@kU?tj|?\?y?g?l? ?l?ؙ?`??P?x?p?В?_?Wf?НZ?dd?pbxu藿]@ak?q]?؞@d?ȝ q@k?v?4?`q??Mc? q@dHO?T?LS?p?,Ș?f?@c?`?m?Б0?Q?@`??U?ܧ?f?0??L?b?@a8D?"G?xE@@b?(?Z@}??P?? ?u????`?@?\??@{^?prd????h|?|P0@ȑ @?0?wx?` r?Sa? @l?Ѝ t?sZ? z?R`~?l?p?`?vQpJ?`?@``?|?x? ??q??w?@0?@`??i?@?????Ȗ????}??t|?r@?|X@r???Ș? W?p?0?k ?Y?f?u?șs?`s?yh?? r?d???E@??P?X@n?8v??0?~????Љ?u?Ѓ? ?dЈ??w? ~?z?@?|?hp?P{??l??q??@g(?00??@j?HX@p |ؗ ?{8?pЂ?PY?E?he?@{0F?@a?W ?2\?`pr`wg?@?@g?z??v??p? ?@}??`~?i ?`y??b?p?~???@y?@?p?u?`q?T?T?@}K`scpm?Ws?>?b?pZmrz`|bX?@q?@v?Ps x~Q pq?W@o?@lm?] p?YR?L ?(?W`?X?8? v?He?hr?@g|?B@?b?T?y?{?p???x?~?0??y? ?>t? x?v?蚿?f48?@b?,?c?̠?T?N?sPR?Pa? @q?`~Б?\X?G?p0`{@cO??u8?o?Ty?@iP@q8UHF?`?\??@d_@l?Єy?@c??Vy?a?r?Q?iz?pЇ?`@k?Z?,?@`??In? po`u?~m? s?p?P?4??j8?`p?М?T??vu?Wn?j?4v`vpG?`?@z??x?`u?t`xgz s@~@c?@d?p?RW q? {`?>?z?@y? ?X0?@g?jF?f?@`w?vf@b`p?i0?`p??@s?Є?X? |?u?@ed?wF?`}ey`?OK?bP?AT?iR?Q[?A?lf?u?`t?W?<Xj}_?@ ?0_`p@a@?a?a?$?i?R?zA?p?@@`?R?x^?hV?QNd ?jtj0z?`?_?m?`xk?v?X? ?<Z?`wR{PW?Z?V??J?|$B?E|?0?V?M?KY?@c?Q?o??y?Wu?]2?Ix?>?@q^? }^??T?f<@hYH?^t?T trT_Y?\?@q?E?@|?Qq?@`qP@t??6S?o?@@b? w?@p?c?? x`z?v?Љ^ȑsZ?@q?Jo?Ȑ?l??k?r?p qy?`r[qv`pЀ0?fx?w?N t@re?xr@c6`{?k?v?{?@o?@v@e?X?X_?lo?h@d@`? ?b?`q?$?`rS?h??U:?uc?_?s?f t?sZ?n? ~?ЈaYi`s?@s?`?@bp?^?W[\?~T?f?fP?@p??u@k@o@v`qf ?o?? ?y? ~?pv??npv?p?p`u?QT?m?`Z?P@ ~{c@g?@n?n?8E?@@y?r(c[?@`?s??Sp?`qQrE{f?W?Q?`r??a??I@g?t?t?0`? eP?@`?Wz?CHa{@cvQa q?@w?@o?K?d?T?`w>b|pXr?`r?q?@z?@a?O?Xj`v@m r_ qQ?x?j? {? s?q?k?t$A?hu?@k$Ff?k?>`w?as?zc?APc?h\l|eSAe[? t?[? ? `$?oMpRY p6BY?@e?g?o6?q?V_?e@a?[E?Dr>?`pf?@f?n??W?d?>M?]@nb@sC?Nh?@`?e?`q?:,V?dn?`z?M?O?yv`wQ?,n?m?`y r??p???_w` zI$??@g?Yl?P?o?{?2[ sb@cb?6?_?:WLTW?e@`?u?VV?G?H?@f?SyXis?:i?YNV?N@n?e?qt? R?iR0??@v? zp?kq?@e?sG?@`D@sv?t? q?b?e?QZ?d?WW?Sm?W?Q?E?zlw`ce?N?aQ?`r?Qg?p?@w ?Pt?\^]? vI?q`?_? p?hfk?v?n^g?Py?A:@w`z`u?v@bh]q?_|?h?s?Ѓ?d?vSjj_ln?j@z?`?f^?4a?x?s?PP v???b?a?Z? wd?Ђb?{^@g?qH?Sd?Sw?j@q?@d?k?j?`e@x?Uj?Z?4P?Mg2@na@ijY? ?@w?l@g?e?S?m6?`p?l`vJ?WE?\?K?R?b?m?Y?RO?Gj?_?{?[M@b?`?u?Te?pfD?hr?_?@f?ja:?@ko?@fB?at@c?Pf?d?@c?\?@c?`|?C@x(g?[J?@tZ?[q?@d?Pd?P? ]?@n`t?F ?h??k?S~P?u`D?o? ?\?`h?`tZ?P?s?h?@ rЉ?rt? y?@?M@?@@??xqt?@`c?Y?xE?p?PЀ6?2?8?s?e?@b?s}eQ]?(?a@f?i?@s?mO?i?a@n?^k\(M?R@e?6GOe\]?$N?2?RM?VGME?@d?_?m? ?EpX?@sE?LVm?B?`r?Pk?2Lc?oOT^VU?EF2\?Z?i?H@e?`?e?0? $?[D?G>T?hSVP?6?@m?Bv?2U?0b<n>?pG@m0`2D?<r?g?N:`4?`q8?0?JEQ?@j?V,W??g?4?V?8? 0?Y?\?]>?V? ?M? ?Q6?R?A$?V?,?Q?6?6?`?l?^??Z(`$@a0`62@a? ?(T$4g??Q?,?8$?V?\?R??Z?U$SK? @a,W(@l??C??F???V?\?B?2?]??L?V_??KF? d?(??2H2J??d4?,?Y? ?(?0?K2?S??N?c?QT$?,2?I$2?4Q?[?(P(EC?D(?c??a?@a?$?VAC?I?\J?(?P??0??0?(?,0?W$?K$???LR T?E?^6 P? J$S8>?6?M?T U? ?Y? ?a? ?T?$?8X?,?K ??8?$?L??TV$O?2?E d?I$?P?4 e?$M?$6c?S?ES?QH,?,?6?T ?:RJ?(E(P?,C?I??S?F???B?$?J?G ?Q??8??$?Z?M?2h:?U(Sh?@j?$L?c?`?D?[? ?JW?$PE?2?J$?R2?R??G??$U?PL?R??c(?W(?OB?_?Z?I:?d?C?0?0L?6X?f:?44? ?J2?4?8ABQ?`b?,?X?D?H0?R$\ ?Z?$?$?$>0T?]??@c 8?d?$?`s ?,(d?A??_?V?E?$SL`ZQ?,?U?W?g?O?[??< G(?P?,?A?(?@`??P?@P?@q?(U??{]$I?f?[?^Y?> 2?$P?@l??@r??N??^??d@??u ?h?H$???s?o?|?q?R??@v?(K?@gg? ?\??J(V??`?f(4,@i??K?P?,Y?`t?C?a? ?p?d$?^?2?B?(?I?$?b?$?U? p?g? q$@l@n@fL?>$@s?(6?$Y>??TS?(l?H??RdZ?`??K?j?kU?A??@a?u?c? Ic? ?@$?S(?4 ?_?a@d ?B?P?B?U?h ?X? D?Y?@j?_?c,N?J?@l??P ?$?4?@` ?Z?2K?$l d?f? ?S p?(f? < @g ? ?q?$c? 6??X Zu(?A$?`u?(?@k? ?:?(?q?Y??I@l2?Q0?F?0?n Q J?p?k?(C?,<[?O??jIn?$O?2 ?j?,?$?<$?P(_? ]O?,?a??Pr?(??@n$?]?n?$?v?O?r ?f0?b?eD r?(P?a(@?`?P,I?@eX,w?$U$?X?J?R? ?0VZ?`p ?@f??P? x??n??j?T(?j?@n??$?c??@e? ?[?H n,@??n F,?h,?d?T? a(f?O?`?<Rd?R?,???V??O??@`?Bl$?V?\0 |?$bZ@i?@l?Y?H?[??[??e?R?,?Y?$?K(?G?^p??W?haY? P_ c,@`?V?@dp???a??j?` ?k?T?(?4?Z@d?G?0p(a? A?U ?Z??@n?p?$sA?(@w?[?2?s?$?$?K??<(?@n??b$\?@k ?Y?(d? q?I? ?a?g?6? b AWO?g?(]?? ?o?g,?@`??H?$I@`? ?` ?\(?HJ$?k?`?iK?U,W0f? \?d?\?q2(?O4?W??L?0?_?z?@e?Wt?`<P]{_?gScYJZ?WaCR?R?b?@k?Fo?c@k?Q@k?Q?j?n?p?k?E?e?P?_ u[@hfcl?@gW?^@sJV?(@mR?C?P?X?Q?2?E?T? ?>?@z?D?r?O?@y?8?q?MARmQ`xZ?yY`{[?`~:?{i_?@`qe?m(?s6?p:`Ua>V@dTp\rX? t@o?f`L? ut?k?`u?Z?q?:?@x??y?@a?Є?W?h0?Y?x?Z?0?U?ip?@g??R?p?Q?[W4?0P?Lk?X`HfЄa?}QtSH?@`?0?J?a??f??kR?Sqg?`D?|U@wXuV?C>?m?Y|?@dp?hp?S??e?(? ?6?8?\??K?D_$Ta?XG T?~?`? ?,??\?g??>??6:?@g?cc|C?H?2?O?<|Kwfi]?x?^?V ?6???ؔ?i????Y(?a??p??w$?]І?\?8? $v?A`r?@no?@i?j(W?䢿\?4 qbț?D ?R?d?8?x?S??`?`|?( {?:$G?Hp_?]jl`t?QRI?Ȑ?e?0?Й?R?? ? ?E@?@f? ?c?(?W?aг?S??6??R@i?yO?`q@bla?X? YK?d6?$_?$@K[?k??b??q?4?( ?`?B|O0?PVw?v?`Wxj?J y?Q?8?80?a?Y?6H?c@?E? ?P?`?S?$?N??U?? ??@ra~t?ȑ?m? SpE?>Y^؜T?p@o0c?૿@f?rhL?HX?R?x?@`??B(?@d?$?6(?R?(Y?Z?0 @h :?p`r??4`?Q ~?f8?M?K?\?E??W@r?2?І?Pd?L8?@o?b?8?B?`@b???e(@e?F?DW \$X0?T?W??^?R?m?U?ȝ?G?8?@(?@a@o?]Ȑa?`?@n??xuȝSI@l?H@n?[?~T?a?P??k?T?e?FO?J0?Zh?@c?P?@?Ș?R^?V??r??4?`[Ȑ?@o?a(@i?>l?2?@@l$dn?8r@a ?Z?@|O@f?[D?f??[??M?a@c?W?DuGhP gz@a? w ?`sf@m?^?_?^?@@c\j?p??c?8q]d?X?S?`?UI?w?8?^?H?@i}T?H?@b??N?`??\ ?0@s yXR j?q?`x?\?ЙV?]?j?0??v?C8_?@l?[?[?@b?ra ?p$?U?~@?@e{?n??p???@P?@n8Pt?@y?S6?p?`?h?8@{?k?0?c?Y@z?`v?Ё?W? w?un?c?}?w??p?\U4p?p?L??4?N?ZH?d?A?ЄWBP`tc৿M?2?BS?<\?8Aac?@~?XHgE@s]? ?Wf@a?[`?e?@b?p?28?]?`t?X??`p??Q??`??(?`?(?@i?Ј?Q?p?B??]Us^?@k?B??Y@`?b̡?O?p?c??4?@a?Vy?<J?6?@?,? Qa(XEpdB(@?J?t$?@UX\],M?G? F0WK蓿Y?pi?`?XQ?p?<? ?ؕ?0?p??B?U?2?N??S?x?M?|? ?~?0l?2|F?S\?N?G? ?:?U0?b?I?(?`u? ?`s?4?n??蛿L:O?P?X ?8ZQ?ؓ?ș АQMP?Z?R?`6? <W?n?0?????:??2?`?< ?G@?H(?ؓ?O??8?z??\??@a?UF?8M?Ph?Kp?Mp?(???x?<?JIG?Z?C? 6?OؑCdB?,?(( h@?S?hQ??p2uA@pS?0?А?`??Z??? ?[p?Y?X?C??,З?U0?QЂ?(? u??n>W?@M>`w?@??2?@??$P?@?p?N?(?A?G2H`u$??PSHO?hX?S? (>?04? $?0p?: w??@f?$?U$?P0?@d?J?{?D???P??A??M??$?@r?,fKyF?p4K؞Q|HpȡP,?藿D2X8?>?4?`4?A?0??F???p? ?Д?>x?`?2??????>??R??P?0?J?u?F?c?E?I?C?S? s?0P? ???6?P?A?P z?SW?Ey,0B0EM$?PF?ܣH?>?@888E@藿?2?EB?AP?0p???p?h?<0??8??:?p?(?p?:x?>?$? ?D?y?B?@e?(E?2P?6?z?R??[??V?`?>?p???J?@n?R?aI?@4`NБ@$?260$d$?М(?$(@FMJvEl?Q?P0?M?N?D?20?:?8?,?(?4?Ȝ?G?x?Q?x?H??2?p?8?`s?(?Y?6?$:?I?,?{?6?:?2?0?J?p?I??0? r?X?@?@4?? ,C?tB?2LGELSR@ vk????? ??D??@?ؘ?@?ؚ?D?М?4??(? ?? ???,??0?r? ?X??6?a?,?p?A?h?C??K?p?A?Б?A?@?>?o?_CpQ0F4p0 P6ࣿ Ģ6?X4?x:<@8H( ??n? t???0?2?0?:?<0?<?4??6?P?E??D??,??r?,Y?0P?r? ?(ؒ?0ؔ?ؒ?,?@?$?0T?r?@(??ؖH0?У6??8p28$?H(H8 4Ѐ?<??(???ȑ?,??(?ؖ?0?ؘ?6?Ț? ???0???(?2?$p?}?0?j?,?K?,?W?4? z?0??0? ? ?Г?x?`? w?(6`x4(P4?,?̢4$?,?Ț(蕿 P,8 y,Z?$P?????????Л????$?2?$?Ї?@{?2@h?>Y?@k?(?`?8??>?0?0?X? ????l??]8?8? 2?8?p0?H$D(?$?( H,2p,mq?0?>`?2h??p???? ? h?2@?,8???$w?$c? ?@`?s????I?$?@r,? ?X$$( ԣ,pt?p ?x(? (?P?:0?? ??Ȗ??0?$????@???$???@? ?$`q?,d?(@i??w??6 ????0?q? D| ? ?0(p(?2?(?l ?|$?@0?p$?ؙsk? ??,???X??ȗ?0?@?$?؜?????P? `? Й?`????? w? @s?2r?z?? ??? ? p??t?8?$0? y,(h? ?ء$?袿 ?P?`? |(?Q?@?????,P?$??0?\??T?H??x? ?? ?? ?$x??p? ?o? ?@z?Ђ??@?$?|?(n?(T{?0?h 蘿0Ƞ?428,x蠿?蓿?P}  ?(?@?(?????h???2??$? ?,?ԡ?2??$?p?(?? ??4??2???u??W?T?@h?$w?$z? i??w ?(??H,蜿?8>x4 pq_??H??P???4??`?h??x?@??P?n?LK?@n? r?e?c@``P衿pД@w]i?p???X??L??H??????@?`{?E?lv@smhl |`ppЖh PЎ`| p]Y?u??0???ؖ?ؖ??ؕ?ȕ??????`t?L?@apu`wwvvzЄ`X`0pzq@a?Іf?Ȕ???u?e?`???`t0Pzdl`}R@e?_?Wc ?o?`|?{?u?t?u?x?x?p?R`w yzx~`~`qPR?S?X?@g?t? x?t?p?v??|?k?F`$T?8?@a`pqXS?_?Z?6?Yf`BZ?@b?B?jP0z`u r@f@`\?@w?p?p??`??|?n?P?@c@{ `}}vFm?y?t?l?@j? s?? ?? y?f?Pocn{zxs qf_?@h?m?h?d?i?p?@m?^?J@qtdPQhz`we\?r?t?p?@p?`y???r?d`p v v x`vuqi@j]S?r?v?s?i?g?h?i?u?t?@o?S?n@z`~{|z@xs@h`R?`v????~?{?`}?z?@r?`?f0`pzi,?`?b?g?e?p?y??? t?a?B?Y?@f?K?(dw{bZ?T?@`@q`p<r?`z?@v?]?@>?j?s?@i?RzP@~ pa`\^G$?M? u? ?0?0?{?t?Y?6?M?L?]z|vrvpQr????i?L?a?`q?~?~?@i?^@t@x qf`r{yIk?o?G?c`4n? |?@{? u?j?>?]e@fba^@l x~`@p>_?@r?q?p?f?r?|???|?b?bw@zzq`@dnsgagZW8?Z?`r?m?J??P?o?x? ?}?p?@fjntqhr r@sba?@t?m?Z`w|j@q???r?Xok v?@?v?6? qv^m?n?,?t|@d?Ё??j?s@@z0|??r?V`wxM?}?P?|?]?l|~ne?`{?v?`?q0b`?@f?_?\?Dek[i?w?@t?j?J?\VW(V?Z?k?@`?Nd ryrcV?n?@g?P?WaU? ?,?Z?`?@e?`?U?G??V?@a?VYYOS>?Z?,cgcaQ$Q?^?q?s?q?Z?[`qrV0?D?R?B?BQVMBA?`?c?W?R@i@h\4P?e?r?`p?i?I?ih ?D?2[@ooQb?p?@r?b?8@g r`uo6b?m?g?S?,?,??E?`?k?e?6?^o@pf]?\?T?D?$4CSV0O?Q?Pad]IM?h?s?u?s?a?[t zubW?q?v?`r?_?Xrw@sfF?l?n?a?B?0RT?H?C?V?@`?\?M?JZ[PB?K?S?:?XfdP@?L?C?0>2?a?o?j??fqp]Y?l? p?\?KYN0,ObgeP[?l?q?@i?Q?EM@?F??Q^S,0A8?P?U?Q?@?2?D?V?`?g?f?Z?6?AILKMMIQV`bd@fiifcYQ@BS`@jfWD?k?v? z?w?`u?q? p?q?u?y?~??}?y?t?m?W?Jlv ~P0 P xpb2J?T?[?@p?{? ? ?0?`????@????@z?p?`p? v??Ћ?P?Ђ?EЉ(@Г萿Ѝ `~X`y?Ќ??X?(?@????h???`?Ѐ?@w?m?r?`?h???{?ࡿhTXcG?Vt@P80@`s???,???????0????y? q?]?q?`?8?x?H?g?L0{4@b?J?``{T?x????x??(?Ў? ??{?q?W?:`? r?p????Ȓ?`w l @k`t??~?\?{`țЍX????D?Ԣ??@???@t?Q?XqtqR`u??ܡ?d???e?0(z8|ؑ2???0?`p`Ĥ0Ђ ?`??l?????@|?>?rx x@td:n????????ి( w? ??8??nPD?\??$??ܦ?h?H?u?u `zJ@l?{??x?̣?T?ة???੿`k?Б????@q?@(ȭ؜V@?Х??$??? ??lЋhwH? |?@??p?|?ت??`?^?@䱿Rp?????Oȧ\`?x?0?ܬ??(??ؙ?p?y(`Ќv^???0??\?,?Ȩ?d??`|BЁ?x????h?୿$?0?d??H?4???P0$ }??0?p???4??8?{?@{??(?@??p] ??8?p?d????a hx$?@x??? }?`v??????@u<,h0z??x??8?U?8譿ȪP@???Ȫ???X?0?j?@} `uE?`t??~?`u?Y?]??|?l???p |8@@?ؖ??8??}`ȥTb???,??h? ? ?(`}pp q0e?@p?`r?b?0?fpp?@?\?Ф???(쩿l`q?P?H?X?~?@~00ĨȥȜ~??,?\?Х?̡?@??@j?_@eUX?e?g?`??Vn@y0@a??????Ppp@`????:?H\8l??L??0??8?`?t?k?p?s?v?m?U?Tnv }@0sX? ?,?Ц??0($y??P?H?`? @آX{??ܢ??$?P?8?? ??Ё???u?N?g|PЄ`P ^??l?l?? ?h8`p? ?(? ?`|ȕ䠿죿e???|?T????????? ?@d?j~Є0`@`{n?h??t??@?hhȭh(??h?`?n?蝿褿\p@u??$? ???????p???x?@`@@ P``}@ya ?\?l?,?ؙ?w(8X??`?ؚ??Р䥿Dxq?Б?(?????? ?Й??ؘ??s?`q0P ||P@~d?h?P???@}\Г ??|??opԥؒ~?y?`???????0?????N@{ |pr@f?x? ?Ħ???nDЃ????ph̠`0e$\?k???????؝?H?@??`t?`pЃ0ЏzaH??ؖ????? d??0?Pġਿ 4u rp0~t?????ȕ?H??8?P??`v?@up qZMm???l??ء?FؠPȒ? ??X`Їr x??`?@??p????(??0?S00 0~ y q ?`?P?P?t?t??@e?? ?`q?p `00ȗxe0?P?С??? ??@?H?X?@?@g?wpPH|hZ? ~?p? ??`????8i`??8P8k???ȟ?P?ؑ??Ȣ??p?`v?Ѐ@`HИp``ra?`?@???ș??n?? ?i?,ԫ@k?0?$ॿp8`?T?|?ܡ?x?@??????0 p|00@Psm??????? ??а? ?? (ܯPW0X8Ё? ??@??ؒ?P???X??0?`yЅЕ萿`P蓿@`yp?? ?y?P??0???p??D? ?v?Ȥ$\\?kؕ wp( ???h???????@?ipGk0PkzL?m?l?@j? ~?`?H??`????$???ؐ< F s 0uiЈxx??Ț????p?\??@?`}?Q?X?l?S?xЂ@@zq@ppO@r?І????`? ???H???P?<d p 0xqP`蘿P?@??????ؘ??@? ~?? ?l? qP @pp0Ѐ~P0z@`h?|??P???????x?О?p??u 2?X?Ђ0К0{??`? }????8??@????E?z|Pp@ys@c2]? u?? ??p???0????@??b?بԠД~? |?(Sa?@c?H?}?x????}??X???c@jS@dЃXh`qsqfvxOx?`?}?|? ?0? ??P?????h?@w4Х@tȑt??IHH?C?P?l?z?~??@??`?`?y?P?8???p{xXyl8@`Y?`0@j??C`xp??H??0??x??`w?x??@r?@nP}Ђ{0@q>,YOc?@m?g?l?~?@?? ~?~?p?h?? ?\?G??u8`p}p]?>`u@hz??`?p??p??`????w?0?em|0`p w@s uiB@?4?Z?`w??Ё???p?8????`?|?P?}p蕿PȐhjq`sX s???????p?8?(??`? {?e?\v` @P{@sfX$b?`v??Ђ???? ?????? s?K z``Hh Ȑp yqV@a?@|?P?? ?@?В??x?Ȓ??P??{?S?@i{ @Џ0uh:b?s?`?0? ?P?@??@?h?@???0?n?X{8P yo,@o? ??X?? ????????s?6sЇPБ0`PzpRY?r?}??0?p????8????0?v?F,6LM@>D>6?B?$?,?G?Q?L?Q?Y?[?X?V?Y?^?V?P?T?W?S?K?H?H?6?(A@BPUSOVVOLVWRHGD(6<,,?6?,?4?8?6?2??0? ? $288?2? ??$?>?8?(?(?8?C?A?:?G?J?C?4?8?H?K?H?H?M?E?:?(?4?6??(2@MPRRT[WQPQD 604?B?B?C?K?L?0@JHDJF6($ (?,?2?6???0?:?2? 6@$,0: (08?F?8?2?B?G?:?A?D?6????:>AA6$( ? $$?>??4?@?2?$??,?8??BL8?$??2::B<,$?$??2?4?$?4?8?4?0?,?6?2?(?,?0?(?4??$60(8<>>:0?0?(??0?:?$? ?,?0?0??0?($?02$??? ?2<? ??$??$?,?$??? ?6??:6$?,???:? ?$??$(0(,(  ?6?0?(?0?$???? ?2?,?? $,:$ ?(?? (?(?2 ?(?0?(?? (?2??$?? ? ?,,(46,($2((?  ?zqI>Ñ><data gg''SSUTa`$$XWLK;:99  fepmJH[Y52*(vuxvIFif7559  ]np{  $0L9V5UP!m/S 5u#` Ga y6=&+2: T,x$U +^/7(%$<'?'=('B*f*C*h*G,:,L/4/M/]/ON0/U33Wu43X43]}65^65[44\4<4]43Zn21Tr.-Ox+*K(j(B#D#:]2D)9!A i tn"+45>[EsߴJ~ݰO@۫Si٦Y_םaұәea-ҖhiѓksБmΨόr͆x˂{ʀ}QN~ȁ~'(~uv~dc~}|xz|8>̃{~uόr!ӑm]ՕiP%יf׵؟`pۦYy,ިVގ߫SPDJXA900*%_"]q e~pn1pEs < $X  ,4g:]@<DGRKPQ=Ya0pk!)!u%%=$ ('{*)s-+n.s-g@0 /co110_v201]2l1_10a01/be0&/eb/'.i.,n&,*r*r)x('~&%|#"s != kdp^YiTPXKVFuA @(@g?'=Y:m:5< 3 9 3 & 1 2 3Y 2 1 > ) E !R!so p"<0@+NF\k)|k 0]N@՟@!қ4Q&Q07 f;qh8yBj, ̒%%,l4v=1԰DhO\عY/dyݫs{kxYwI6I$J$N3 f @  JSeZ _!gkp.RxuqhC Y.#!OK%#E-'%=(,'8)(5"*(2*)2*(4)M(9(2'?b'%C_&$I1%#N$"R#!W! Yw!" \ ^^ ^! ^ ^`v/bdNd@hojn)vx} ~*3tC_j<m\Ny @  1i#8x\M2:!0 p@0MhaF n|l|\KO@ߓ8a/l*K+ہ-R0܆2q3B9WA;H]OWR_fc@sh6]kl o]ynml=jfKw`sYNTMGD:EIDQ[4vg Kvqo`]"M~9F%\5Xvp0<G 7 ` zWit[BY+!!%;#(&,[,*C/<-W2/f41tv63|7474(88574}*7C4w*6O3q572i3%1a}2/W 1q.Lb/,?f-*0%+( (&& $M#e!'? <kRFlfT H q )i `:ZxbHS0T %<2ںٺl׼&-+ؾ0n׻2$՛׹4u׺3@ո׼1ؾ/a-])L&z!%{!M7Tz݅db|kwXlx!,D9gFUfwoK0\JL40B,b F`  w zlWgVH3;W1,;)y+6~.3.8a=DSHALGOEQRjT-U_U`UXR4OJE?1:Q5[/( _/  61G-B(cU  CX!1BSf=y| f X O4N6%PZjsV9'~8<1 >j&FީASݍY_tp \H5%Ωͽ6 F K V%_4xD9ҐW1elwmxم_ۡE ܯ޾+ w/07YZ{{[=U u>L1OWn ,  fYO;&'E %"^5[ F W!j[# y$!"&2#w'k$}(%p*&d=+'[,(R,w)K-*G.l*E+.*E.*I- *P,C)[+#(i*&x{(I%&#$!kW"PE6/k1iJ}e~ n; i S9J!   !C|+9CEOVn![_Pc2gkbBos{zq#g,RZFL.@%2&rq* P!9#JHH]w!|&*R.y2'4u5x3$1_-K(B!  N(5*FyV~gzzhCU Bn-0_<[} ne 3 NU0c$\.Cw`>{z!x#j dt%"Q%'#@($2)& %* '+'+', (+'n+v' $*&/)%>Q($N&4#_,%!rt#0 l!W7UeE1%a0`Lgj>  c&A7#}617-SxnhOj)GK+9 `nZ.fE[joejVF:&1)ܚ+,'y(+T2@N]{ml!![rH<3U'@.?qO^jv|q~hZ6^,VMAEQ<38*~": =j $:\"G4rG>YlH W| g]  R& ;L&!,oW*M6iB7JWREV[LaPgn!krgv|82]Gp]Wz@-$#|wFoc7UME@2M  ju'=" U@ p$  d!@f 50FWCp 0A^$Lf`r8`k&%F޵ Nأܞ Cۉ2ny@b)kM}_aUZ\qsTa*Qc Sb /X]҇]XhOӕvBԂى2բڞt۹HٕZmݠ%QT'%9`uZX(g ACso } F " !j$GHh]DeyJfU(DK 7>!r,!&p"~!""!"%e"p)" 0!9 B$ nKsVaTlw-Q$gU}xn Zes\KSJJG>35%O( _Sbt&` 6A I X _^  v `?A <MWp2!exK?2k[\&LC!d-1;@EIPxYD%q_hgVUck`m]pZsYsYtXtYsZrZrZsYsZsZrZr[r ^n5Efgma%xXMMCB7) C2*+| H8f lPS1*>!iY}V? - 8 Ps&Xa_<03 Xz"$ &!V(0#)t$o+%_,o&Q,9'H~-' F-'Ig-'P, ' [,o&m*i%s)$'w"e% l#= Z * 8e)tig*-, _[  0EY # Otz05EV'T-2pCfNsY j`edWVcg49ah '`i%bg>CcfUWdellfchbl_l^n\o[%qYC+qYM5qYK4qYW?qYW?rYgNtWguV}yR~N!GZ>"5_( } 4JJb}r\[!EM0xV%:N"b*tt)P89|m_ sT H0 > ) 4e -  $e  hg   *] 5 AdN\Xccoo ^yI&-vLyeqiNc9Z2VlTSRSV[Y a'kx<Kf+Eq n\ 2& g [ T.&WbKL+ie~)6%ZmY3H3 ao2CGSR^$q=ھ*c+ת׉נ؛w}/ۗ5bVFhY|&M=G]F(CVk#8B?SaehJoysjdM>*~EC:e(vu]H E  . ' % - *\5 D> ]DvE<=1LuGgY>LA T9"1 )+WY-'1$5!5!1%+*ML"18|ALUXfZt&1/>Ui[Qb 9o ! s "K f 9 U O Bhm ~' iQ/;T%y)"j2CCRR2cozqj'Z]J:T) [P;%{"I4WCGlUzVf@y*1x L Y/i-w!_p(ezm*O {{'NP$!HqNx;1L%:SK]&KYJ|C ` YG '[9&3N[W i=9M !f"@2T#?Ix$M=%U7`%BX4}%YY3%\T8=%!O<%ED$3T#j" |UYXd5<#@KqahzV S6 ( / VU}sJ-#?QgGf370 U3uIY_:?oM&9$$264chF <*1c:&E;O[Nj||mu][(PD*6L)Lv 6?.;iDRSl`?mw{ (2sdeR@ ,}  7 u R ] t#}  ~f-Li':Qd<)#Qg~zwbZuIc 0Nx40I]b*|mS fQ < _ ! HSp 6P)!K a~6d^{K^-%)BeQ3z aHq@U0#;E(2$WDx jdz+ O]57`!iBvrd21L#S0`=~ "V\|)|]UfU(_PU$_ u?@ j W _  nh\Mr&@!36;/?+A*?,;054.:5B&AFO/jY c<p,}i' % m  s_ `? O : > - P /   ' %4k @5JmRZ9bIiojwS |tPN{nJ[eI25'F9Np'8@[nvKQ(oX,Wu?xC]RHc3t[ fQLi1RR#jjQ%7o3sWL_=PgI<1Vx 62Y(gbJE ]J=2t*XwKz$ %WG a i, ak;6[Vo 2+vdBd_Lw9t7* !  1 ##U /|s=/XR<6:k RRl&_W!P0  g  )m UCLpIyAfBOo@:c[Dk4TRq_k r/wq?#`L}RWF`=gc7l2g3o J1q>2pK5m+c_t*@e:5\KJ9!a+33UPt A8/c}*~3|,X><||dJO50]J(>yNT'd1cju6@H4HM,zyGYT8)a$R HCF ] v K  M c  sH  K  }? xi t  q% mR mR nG jh mJ q tu yH }- k 5  d A   M Z    g s  h> [ gNn B 1 d# -u;JMVGf'uLS[}nk^CI@1+X~/&AjSs|fcy/EFK0 7tT&'Nk9jTK:_AtIgj-c~uihV=F:$jU8&]K="mFVNi}{$HS)a W'tcxH^H.Q$dPS.&Z`  Q  xE ,T V5 ,Umy`3 2 X1^=Hz:QtEiUpgVpgRtCeG|$9%X8C n#gDC*) e& r   W ""I dp<X&!'I5wiO *4aKu.r,b<TGtGQ",;Z1b~)hD#mc!n ZnU"md&j;~,ei3`;ZCS(LL]]VEb;m3Uv,N%6 R?/JdrH Pb!$*Z4);^CIP.Y6]axwjrujbV7XL*<M*m'[Dvk P;5m+J'' (Oy .~ 2/ ;  C r= 9Q, c1C R K$>C N! &%'% {< b(.Tz q(|4 LDtl %bZ /   l' 9;JdXA5b &g>Eom4YF RCj^$4tqR 30/}?&KwT$[c`A`B`I[nS=G 89f#';fWVx;.aH5ol MG !2bJhVbU,e0Qd  nKNy-Ae > \   K  @ { uL pu m l n qc w5 ~ e  / E  e @  m  1 {* m Qc X; Q J H G G I J L PT Y/ ^Q cu f g f af Z0 OAS- 1d~DZbtp4Fb ,mLInl$#BIu"z!fV<3Unb>IBW}%m9`lV&PKJLP X@cp[[CtT*[P?w"wD3(hW}Y42 $\_k3C9q  h $ u , KP" |+^    ZS4 }M_$Im{|tvuurwi~Zn\+J1X; Gm ; w0 KQ u  r # + 2 AR M.IXSbzh]xS# *Klqi_WJe%uV,Ex,Vwr#p%r#v }Bs/ TyL&($d+ b9{y@q#l&i)h)j(o$w< Q;=Zp:!0DPqhtlOT55U%KWpyU^Cg'*ADJm  t OE ~ v 2h PR m>5 * |  X   J? w   6 C D %   ?2   a e 1> \L 3i   ] 6 DfC,'B=S/Ir12 ;lkom+MQ.o!Y 6J qzAbxv_Q|.1I@+LhT"YYUOZE8l'^R>"a{-`PJ;r1eyQ#:Xy T70inAhuCWsTc/TF d;#00&/%<uWGPZ*c^ks@{c I }     63 JL [b kw y         } {g kM Y* ? +   l : ew3ib\L}; L1%0?GxRf{W]);\d $'>Y_sG[8gF}W->3Gf{`yX NJqZXkC1T#8#G*6uK/e:o9At SFR06 ]+&en|Dy:tpZ qsoil=?hP_H$ 'U &}k S]n x |@[ %7  E  8" y " ,J 2i 2h 0Y )/  b -  T r 1d sG  Q_I W .x|   9  K {|V`2) C7\3vf\ p9D`_2jHg}\apuF/^lvIMe@#le[RMIHHLQT\]^`aceeg s-F\l| 4V\We+_E,cmWG? %1 yN.~fYD@"r Q $ 0{ iZ^ B )- [  u  %Y 2; <_ Cv Dz Cq =Y 2/ #B  QL  s 0` yI f Og #  d }VS;8cZzkL0 1XI!H~N [U'-B&\HshRu0{kUb6]&[ \#`2cAl exMPF,r4^@J@4bG w2"SL|5fu`B&3 X>Mdomv6cLOg> 1/a_E!W+g "sO~v+@;6) w}lyZvQuJsFvMz[}fw)R3 Gu t   M g } & -} &` <   V [`yGW3/PL Ji|S'!w -NfXr*^i[>1N,kU!aSJg3c  ~}xz(9>LV)pMgvk^#CYxQ$E~0N :MldA b 6l.y3)u(S&y/4\S }f *" Nr n\| kI 8 $ * o [     # - )     O + ` :y  I r[{ eTo 6I    AxyM t%;TjpEvSdS?>`E->(Myv]B)ciUsH{\>)m2F+0(#" |i^S :^Z99yE wMhZ|M"{PA*A*8015+9):):,81580B)L3N!]\o 2 _,?>geJ-!|RHe{cW8nDL>|?,.n N q| G}G }md M e9@ ' ! :p    0 g6 #w@ &J $zB c2 D   T} ! 4 0t B WS (ck @ OY  YFs E)7JG}jVZ7zUDN =(9}?.jc#4o)%2m PB&gA[gpaYRrfZ\,Z!]2bLmz(uhr[_] K{*5'KnjwkU/?Z_j}V`B0jU8$r6HXYgFw<w N*O|zrjJaX" QHHBq'?47B.O%B\|=gat 1n.5\k) c  B f " '8 -R 0d 2i .X )?   a  V, 6z W!V 0: S.l4BX(z$UF[l0. m"`^ 4*>)Pdyvp&YqgQn< .r#Oq7b.H"Q*j8LaeZwpMTj:%EM}AKu|Ut2 $lb&v S!@2pL:N 10! P kq. 4{gz eX P IN Ct ? @ Dm K= V _ yif VR - s + _  #":rD] D78IG UhBsQ- x%`d%Ma@$=><<; <- ] 3!M8pd 2N=3_mfK'n~]W:2rfv_PBe2v0oqF ygT 2o|gQ$ts 2 Rsn G/R=>, 8`} On ] @U  9 $ ss l7 iI co sm rp  o`  Vz  9 ~l" , ) '  U(a`x`}-a;]N<|}Q etq&,HMTP+R!]mg+h _cN: >J$-F\-d}P##"KrpZpE<[2#.6# d[$V6'k]@k}@e=*E8O-pO > =LDrWS+*E4d>P2aNVtI[;bdbUT!0*Jkn$3!$R=yqL;.M` ".<y(<x\-:pF*yBpE \@xM!p[-"K5P0H (Zl ! 0 ;g+ - {o GUu }3^  f O     `  _ p< 8`%M % C F Zeg W7P Z` m}k {,Q e8OjO}0iK@ %aOGs,^.!:.:R^fd }v,JL|,M8#) a0[(;>SpEjLN w1Bobaed4!3o!6rV_KP8@%-D8VJ==882C Q ][kttMjPmw5XrC*mI- )UF%d`pmGq#+1mFLPt)4}e_xr16aQ}7:J q ~D ; S |*  ~ r RZ 8 ! 9 Jl <b 0 %a =x SZ a  9\ +; Vm 6( yXt=&eLn: bQ=|;[JOs*Z1RNoxNcDuSE#Yl& =Niz]I-kf hgndXlDK}:;$T\G;e0x%8ZQbI~KGenMuU?]R0GL C o3 E`# ^X wG lR,%q^.8`j*Cpksd .8Tp4jt3Md#d7 ' |J-  h VMe 2]Iv09D7h[\P6ZL1Lxwmhk>r%n6@W)/RP wysAc gB@~#Df}S&+5QSGKE)HKl,MJ*X pCC lNb}f i\Tx=BX2^7p#q-=G T g a YJ2s"sb-~IM9-4HUsbrp<VH]jAX4 'f@XnWMkY4E r) 6wG-OJl8Zk{|"TC{~*.?A:?uL$   5:tq,;?v#-W1+QwR{'>rDF6RO/P712uhJ`jZD%Sn*Q+P/M3K6I>EDAH?L=M<G@CBDADBAC>F3L%Tg`hl7|a=P(qbzS@F:*_n@ (?)d1C7/:#9&56-S!'tDBg%Axv!WM`2> LgSB Y I2 W h "> Vg>x y) >I 'F; u OB: O cQ/EA&EB:v17k_;V!-gck.FRLtTD64sG*bHj=uI(\x$<yq{gq8m]r}UcYR2O?>br 5 } XI)yk ?F  Mk  UO  q !w, 0nK 5lS 2nK 't2  ) w  n/ 1I +J U4 P7 yZ 5( da `.!}%,i/ .J+;$(/Um<!)4g_GXyh E(^PK  b{CY\D#C# %tu9$D[/pa[+c L *n2R`Z:{(lHl ! ' s[6w*[_8CH*W>kf1s-~j`7C5+/+<SSmzYp f/^N+V>i:NsGGT>a7m1-y*X %u +7AHKR[_cbYL9!'&lu-Bd6S@=Ly)X-]f)vmt%6z_VF.]X2 E+g93|J(X_/vXs8lX6b"A ^M`{6`I$BOdgGq#<w[]1PNXQqUAK@}t V $=) AE+ [ d e \ J) 07L SJ `H y hL! C_$s KGi#1ir9p (%~89{K)_ZEhn3r,l&zTB@ }I,v`8oAxKebX|J/7>$$~FGjs9<iyTs}mjge a,#_5)ZB0W&N3U3U8SB]<QQfCLjsMHRDZ@b;i8k7h8_>SD?OYi']9nlZ7 q?5H\Z: 8%Mz]upUp.C $1u 9Fc  0R? J!P*N&G9m)5$O -\Dq,_z=ZO@"o:@V\}a1ea$M8wPjcleMs 17 'tu(7~Ms4geecTA>-;`@5d&!dZ <{ k4m^KacT{GI<;0d".z %yABs`;Y pu3\ZHp^2y3|'O uK@ t m +i 6g =i 5n #ye  zNM+Gsp=~1^N9x?l x.kD`#^V@f&s;bD/y"mm%7O|m,pPEc`UD\"4W"1 f6>iWnctvdBQ:cb4 }!2.l8KBk+qK4cRVYJ`Cd=g;h:h>fCcK_TZ_UkN!zGP@w2:E4X0f,q*y*{*z+u-o/i1c2_5W6R7P6T4Z1b,r&H  !h7>h_0^}(eTN=75o%uB1o >M yS n g b $a 'c  i tq # GPr B*i  (%eFkL%h{jN/N#,>;XW(pUWNortWG@y"->   y||~toc_[^p -I L>r!|:d>d|Dd+!7M{e!kdHZ>'' fTEw\  o~ /] LNG If@ s{5 . * )& , 3 l> yRKV O1\  o| qU [S+t"%.nEoF?\phP9#kMV @e*C: ~!9,v;JqIGVW;dsJuHS hl#T`>=)Sm>#!' t*m*l&x 8q ;P=S=S<V=T=U<V<W<W:[:\8d6h4o1w.) $+Pr  E*j=&K,S1Z.U'Jb7)%'9[`O)1fW-R|@ 0ogL34g~u8b Fb,{;C6(r7?[^zO']y(3fVUyDj54d&h 6[.'v;+A/G0H0H3L3M5P8T=\DeMsXh6weM%De~sN1hA`QY_QiL6qHK#vFZ*vFZ*tGT'oJDeN'ZTH]5frT~ [c/ v^[ Ba*}>B!    ( `/8RU}tY*J~ .k|SXuHN!9L,r".e_#%1bBn/XV)mkh9Ln &o7^TUrW_yJ}J  I-6ry>]qBQnCxMoC{Oy>]7r- I e@;vcI9&c y.1eZQ>J+nK%A]f)s3i#rPK_rK77J#"?0hkuJ 7]-s: @jq Qb [] dY gX fX a[ W` Hg 4qG }a3-tu>}(%y{HI1ls@B\.{WRuKRW6&i51 X#=&0$< Qx94^:\H~nFvz+$jA\]O,xB{D7a.z(!=QejeV D$.'+.{2q3m5i4l2q.}) !9q N&;Q^ wJK|x5lpU\uM%@t#4G)f:l    !   |[<#p)`0M6=<+AhGH xL(iS]YO`?h/oXy}_>Ll2F^N%I6f[O/"#i\D}G&m-*g<IJ]^.W;`" gi jf_%0V@MHmhq7#I l[< {zTx=LV3s(n4r?M:knxN3=K=B_"|H'aQuhqd~Lr'a~Hi,Nm 3!n$e48DU lTLd+sA `:N{Wj&XsI_;M.<!\+E!-   $9P&$c/-z97GEYQi^zj&{T 1Wy(4;=9/f4yLUm+u7}-lEkC:\tVLFx MV$w>R3   )#GumDCrOJhaIP7) :Jh6~c_]=x!_RQ'>Z9QYT L2vr5T7uX\: Ps*D<N[`3s@ Nhp1Ob+u6  i)7mhFfDIT*cqc }`V1nRx:9&#85Qzp";hs].`XM<19f'@6BSgl#Z]u8avRdDR3?)m2P%A>D!W(,v6=KRcm"p%'UDm^l[I36%Fcr*Igc33COOQPTQ=G8 dWA#J9RHldB.YkI! &t?YCU j>Np#O3>^w>h#\PLHHHMPY(`Clf|.8Jl#=P~iJR"):h!=-v5j}e*{ ]i_CS(6wQ 'wV2\>{HHB5`"0 d99kQ79T5QKm^ 8uTbF !B3zhDtPGR*,^ gn}rfsarh n}h&a:XQM:6iCxR7r+ F  !W'9AO[_mo|B\kx|pcJu/g uUaCM/x5FW#2).25w9mx<eq?^mAYgCvRcEkN^G_IYJSCWJM@SLCFrAmn`N;|*"DN4 vWzR 1$G.P2L/8't@N$s`&+;RYio=Cnz49a2^FfN1Zl{4gp:\vNrIoGrIy!R>^lsyAnc4YLN8:aEmQu<h4|-("5PZiv|yyy} &I+k.9=GMT[bks"}De.Gf}%.4$:*@0~F7{ N<xSBv&ZIs7aOpGhYl^qchw|mcx^YTN I+BP>k850-+*+- 17=qF;Rz_VnTl.Ary;>s2OKIEd}(kT9 "Tt{XfBW"g2N%S)J'H%M&P)V!g3gE|[wT.]Hp}ox0X&fA_)9;l p^*Fv#Tig{yYK AZ5+  # !841dG=SG ^NeQgSiQgMcG \@ T9vM2eF'I://# * 3;yEAPZpe{TqA_?zI%.Ikv!yVX2z3# F,3k>hYFhUGM   %+* * !B]Du\,X*FM=EXFhV5)Z'mt]l.X,b e RH[QacAb$0~6}\gg_}/}EM=EKJLL2V2>.b JLIK^rAty?t D &Z'1B25Z>a7 rj?t~ n_!ql1b.aUc`V[<OR4ظ$׫M:mY3ܭ0:R_pG^g9q| LRz<_5YS3 gf)(v P~Q k 4 t   s[ uT  W" fi$#u%&wgZ&Od&>k`&Ein&6oH&*t)&t%H`J$" a D  i I  Q m   u\P <! h*tR85xAm #U v FX}I*MP{kq2hyNjx):(.keICOp 'Qx/Klx;Qvf/ )(YPb aK2$p3( aK!k$ V]C  ) r E p u2 O q w p 5 V   (CN a  P E  +[c%9Af | xV9|z5|yAT>'nZ}'_pFg!Kw 9Tb)~qg&>{IE[Hmd v0P;<%N#!X3;6H~-NV=}9S $ 4TxO@V  7B a  O U i a j / 4  7 P 26y!?o"j ! Sp 6eo 6ci)u/so` &$Te,|*S=>{}P@x*ݠݗSW!r3! =nTg"59kE?GS z44w.^rfG E 210qo{b 5(   Mt #  }2 &/"\ #XM%sH&Q '?3I;'Q+L('I X&-~&@ %s# 2!: Z O ~WD < N &s JoF >* @'Um,fD /ce/>3]yN_~ߊ c@FRgy=A,3E}JZښ.`ݕ>O9v+b`T9V[diV10 Zcl oa<|~ 2 Tk. u * uC 0D u$ E! Y# a$?rY&>'>h((((8'F pd&U$(h"Z \yS p $ ` nI |  ,i'h t y2(eWD(>-5=-MY@Q?M;-@&r_8n]ڔ`{ PP( AվEsն)6$׎^\B܈Mmn0e (PpG|+iw%3.' !se}[ o  u#  [ OS! "#` n$ %`Wg&R'G\'M?'dd&%TQP$ "- g u   U  `>S\z=  xgx|^=xgBLe=oC)~B)Thzr1~]mhx2Ri'>7Hu3}Iw8!3*UT2)KtV+ `4F/,P,P8 KDNP`U":e h UcT?&# [ }u so vI q u)H78uDGo / 1Bh[ k ]gUFKA,m=h{6af 9 Po`eaUM>\f`:$E8A|Qj82m) EQ'o6o2 l' ~ o 8]}"BG" dr  f( W L H R h T P:3o?-6C~ l\cz &N]U?ww'=1bArI~vkk `,W)6'$* 5<-]p-+oN3-PK x/t4;}3 C 7d T   E4 [ 7;  F "v /q w 5  WL  T ~ b1 }Q  aQ9]X - 13GCsoSKkNO2f#H|Q*^76Z.@Gj-i B-d;s}>c%^$7mbfN#sz1S#MBzwh {Cw XDc=1NS Y16:O 0p 9Ez {e n / <(  c g ( A~~ I{ 0W  9 % h `N ; ( - U! h $@ O\nC UY^WoUT L8HjJcCF7MX`zkqz\SyaAgM_"IHi:J_HoM peh7[A=B) k/ qu>xYzgs _ / K~cQ  hA -E v '  s 7V s z |m f I   ? i 5t q}@ rmKP  ,a i\m#ZK8IAxPu]y x@`#Y?dzE@&"vq<8Pc\s`X3I 1SVi fs}3h~}A eg ArCCvu% v/1; Zu;d [M  : <) `3 yf     |j W! 5 P rq? >S~B1+e)`M*lbW;mzFkmq%y+6}q>lhAfn?jz;t4)u NjM }\A- ^zEW\:*FX}O U&   XQ {a   z j: E | [ 0 \%  B_ _ [S!74%v&\Q1syV\c4))DJSYe. u`#n}#SoL\:m}:)d ?WB$m/d +b,Lc|+rPld.]\X G1 0 VxFA& H z1 DBl P 7J7 u i   k R$ u 2O #  Vx @ ng"zs92N y"# r| j_J8@q`w?Y?y-3-+,S!HAD~ j7S$@}(^m#g`m$gc"y;t]mpY>!Fp0;YRT[IUY Y W  u8#M3 *! c! p v,$ _R Rk Nt Rk aM t>'?  p fc.f<BR G#& g 7 )FYvG^@m1~OmP!\`mu*HK8Eerg-n Nk0a@f_lx%-HUoI/\x2Ecvw8q0M3 ?YNy|R*Rg 1W9  1\ } Nj v      c r UW 1 p e6 V O} --a!) VQtdB W8b=] d*^$ $ D<PebBnd)x(~| {w,pX&g9[P M)l9>v*{G:Fr^d;H=E-w{4`v>VP_9U;bk6K/Z5\.Z O{?`-@i |(T=STl{#v%BU5g" $+C>XRrT!Y<x?t)m1pT{&^jmr,Qbh1+T ]^onP'Yy3*38p'!p|, l a ,Z 9Y <\ 5d &rg H]}@"$2*M9\y.Th1ZPoO~6@ 2,;VfpM4?9x7c \66 2G 3 z f _pA8LG[vLj L1n>n nPxf730(P@sIZdqI>G:Y^%1pcwk?P`GSRu"Suv5!~Dw{HKw[7VF)#2 ;5h {H~ K+- & kH   1 "H )B '"   ^)  . z~H| G%d  _X}Wvn>sjQtt1]EJ9ru3!8PwG Y# ;xO]gxiaRap5DXr%@Xs%@en?H{giU^gQDA"4\! +0XHAjt6IBv$  Lc*s>ok>%LAd? {Z<jZm`^BG~"hvL +u-s5BNDPg Gh)[H*`f/$D`V_e@u1$W 6kJ99F\ z9T tyoY0cFX[N#n$CcD:e/%]9G(wE7_z1<Zz8m a),W=CP=JWJbUaGtZeF{\aGsZXJcUGODL/V>_-jvSuIgwq$Xp?@x$+ Dw*R5/?zI@yQ iW\]T`McHcGaL]SW^Nn C_J7y)i%Ee8Ic6@v}.j+Y6dHqU9s+'q #4 <9 / !"\/ ?eHQ9ChPnI1\;;\Vb|ITa"@&} `MO-E@G W=i` ) ;WRlS{{fy `OFMq* :j'W nu`;W79nfA ;k g. >t wc Tj H (? 8 8 E5) J5* J 8 E? 8I &V` qf 1y Vt-@ T*5YPLCv[L_ch=p!r.>eH(.}=C*8epVr\`/n,Zu\g.WK+q`~P,% o=Vct) /X:<|7DX35q&l .5uZ{3 5~u Mv ^r jn ho ]s Jx 0k - >bTEf#hA%_;e]RG<ia8ZQ*G?8Rmk`?=KmY7vfd5VNN P=Yng x2_CY2lt(0cNRkAhW0A & W,Qw*v nD[*}JyF;b0"w&_ "$$o,;}7iDQ]T=5fxYYj}lIN' Wm*A6B_K#yTiZ^`TdMdLdLbQ\\TjK({ ?oL3|%l  Cw"r9OWaq~#1 =5GGQYZidygnt|zmZgFC,yui<XHu:U*7a6  hM 4{%p(a,Q0F3;70:)<%=|%=}*<78E4Y.q( 4s +LZ,DJ[sJFvy\qj2eE_M]T\"P\ I_8c"i q{PMKgc?5^LI2G;[`pnA?1!s"qcibhitx:<|'ulLC[`I-=5"( , b/0SxD7/nV]L;=V-Hp4!` %d   l (Y#'x|7^J>[] r_d{)PL#;18Pt{iZI$!6;/Jv\NE! G0 #(*{*|(#$6mPB!W c ,I+bS3 toc {w zu qe Y< v3dNF4D[MX33bJ:Xa ynTJv7Sk1!: KZ,<}I#WjczUYp)@-{,T|fWoN`KZPcZuj1u&NrlCIH)JKJF3AT;|>3`):'B^)De\ u:jg#G|f yt zt{lR=rrXGA*vjp_:QC|6Y'8_&  qaN]fMk0Ny9#7nO9h'K9..9*P;mRh X,%u?]gMVXnH 7j% Z%&H k^BbbkVvD6M2pT qM <d9kdZ(3't)a*;2CP!{f}xX4`8(Bn#7JYmTgCV$sC~2|&T6%&3Gd!(1 y;)rFEk,Rfca_ZmQ}H=`,3Y(69]g)::KrYg%sFY2mS{j}sxcrZfE[5N>{0v!pic}ZZR9JA9}0`%Kz/M*     #8 Cld&5#EhUd0tV)}h#& iF=xfcS@y.Ai{;^&8/6=]B7GKNOQ~Q~OMKHC1=Z79/X'0t )CU 0@VO^!kHzhAm'2<?:8-{]g>R|8s j`xVLLA7x,T6U#g9f# F+,17<^@?D$GHIH EB5<_5:-_%M0Ll'>qT%j\'}~'(lNzsrl g1bO``_f``bUf6kssP{&fuRycTM6[!- wq @+6A8JS}l[qMaRf6g.^#kWmToQ pO pOoRlU*iZ@d>bZ^hlyWvPG >O3H)y+s  3Pb"3.CvSb@pg5~m?/oN}lwqmh4dP`m^}\\\\^zageKj*plxK#X{\!x c`M6\  GH)7{DP_\uq.f)a mUtKyD{@|?{AxFtKoT(g_L_YlqWxNE;\ 1K(}5v $7 Fmm%/;5CbMU\$b4h@lKoQ rWsYtXtVsR qLoHnAl:i4g*d#a_]ZXVUS~RzQqOsPqOoOmOlNnOlNmNsPtPoOkNiNaLZJRIJG?D5B%?;61.~*v'h"`YTNxNxKtOzUW_g!k#u&~)+---,})s&c R?a&:  vJr&Z#F'4+'.01211"/.,@)U$lJy "Adg!/!=YH U.aSmw=xp+3 ?;9. cr4uUl0` TeJ#<2w&W8U* I1kVW}z["|x2 x1C 3MTbn"4QElJP0^7`=b>bj$p|?uWzOxo+qGdbm>a'ZOkJ/=.t$Ow5O Q?w1G%402324z 18`6m%,?'l!>TXw  9UT~9UZ4C@M@`Nd_im#ms-oKu`yy~zXx x~byayGtt,ny4p|8q|8q~;q{6qLcPdA_2= ig  T"/)W3|H(*/&+@Xd|-o(<;>,2(w4|;D%vQuRxMr3B}L6j|~BEjc]R 0S{|$,J`k%F$=,w"B m_3{,+:nY| ixWxH}N3~CE J 0P    %4X ty B} 8y Bj&9%S{TaMNlaGaG|gf2W93;T gu:1tI"KJ,JdghJm5i-(%3\:y<YGLD ` dM *z o e" r P 2a KrEy2w<*r8mH8 K > ; 2pd,o7,I[ri %yoNcmb *mW?, z_G@@y{(NM\c5kD7:>gi J&= f V P h?m!{/5tPE T C f ,X = Z D  Q7 s{ ! ~+6?J%OQP";1-|&Nf$Ya8d;I#`'ZU TR C+.J6c4v"q ^MhWj ~rkn8erp#[@ {H#4Cq|\Z2}zG3ku LvC]5N&5^/] n{ D G ~s I z D  /t 0 $ -   #X ! nM $ . >vJ 8,U :MJ,6> 2B%[D/5ff!R76G;# N@z&H)+l ;*T^p2#!J~xIybb9G?V(\ yS 0 @" ^ FO 0 f\ * F xi GJ s  p ` [$:<A TtNk/Ri }C0((lSv@"@W;gnrs\]#fV*WG.6]a hnWoPVrk66F-zxAmTMX&Q)ZYzBOx.W85"0_i?p0(et  i  n : N h '1  K ? HR WTy H o,    z^  $MZ 9 N fg cK G9   kO^9t:N~h7yN P.>VFx4]]y395tXr]WeWs2gVD|8inUk,_{* -+^*xrzj@H#.6Tg   y~ J<   X , A l  tt  4    sj.&Yy*K6Tc u> -"(b1i8LgqFI ~MuKr4Q\DT; Q)T&EG576O)!sl] &6=w kYZf VFufu<pS{rII$-`[!  6:   p ~  zo  s - m '] f)1Y f#/7 l ( t  :  m BF h `X"5gOTH".*Sx\-S/P$ V|%ri)kd*k%qy7 v[S|2~UhezwL\/[Z"h#G2y9IDRdfA~^~fevT Ah/T1BzG \ pv  ha   N Z   | G   ]N  " \ f7fAm1TJl(a{{u^)28\zTG&4?=c0(yq2d9Z?RgEkJNIIEaFdI?T3 bM#xPZj|+ 7teQ,r)bYu~1cJu4S -M% g yl u Z 7 } x_ q2 hR [ J .^81 RElURsNr6R\2gU*{l:I^Th`k!quc}uXi3O5E@?/Eo6*oT+`:>P1bNpm}zb_\7ZK!W; T*Q#PO)Q9 T!P LG}Bc>B Mh|J]mr[[K)4>4.@+Z'#*[k;*j?_Q8cXrht|Yl_,SYJnGD5?h;764 2  3 7?t^JVewLv~Ac!HW-v!m5Mg{4Z'<?,j[[% #;3`d+h]#p;VnqUY@|'| 0HwfbN:)t(,L 3X : A E G I L L K I BHm 6$L1& I6d%~#RK|r&[8IY9`-!Ka  pO!,AP7BbJ~Q]UDXy/[^(\V^Eciyorgt^j:z$d]VPwNyNR \urqndVVF5T6$i>f"$[7lM|1fTvcVQAp3C)" !+2 $*0 #  '0i 2<IjNYiIy}H4nU^70,5=7@`I rP5Xn ]8`bdf~eb_ZMRSMD109,- S$EX E2$BN5W[]{)biiqw&{A%P8ZD\GYCQ89~ys{k-aGYRjJ'B;U4f+!Qg #, jA*!)Hs/F4&8: ; ; ;<}<y>e@KA?B;@T<x9<5^1-)>%l #`6 |)3t+z,;~Jn[l\>~$]wqmAjkiuiqjjmHqv{6NiK~kQV/@c)uF%p84EWDtg~umq^A RLIGFI)Pg:X~bvkElQx_4Q~A+S1 uRu (s*~6cE\Uzdod?z&0=8G+7 s~oJzLungY_tV7NGE@ :{4<+# CS  n*!n, 8C AMV)a,k<{pttpuotprrpum#y?hUcd_X(OwD 6(6 *4Q-N@bQc[*s/1w5dlc^R\p [}[x_Sb%iMp w=~u~d2rd,V0I8<h/3!z[4 O-9^uD(M}S-X\y_gaWb|Idk?e_:eXGdhSbwk`\WPMPG=)3'.I x%HX l^$e-6^>"EOK}P@V_Xz[]\~[rZnZlZy[\^`b a`_]tZOVO=GT<0%f$s ( nE |j)2:;jB*INNP2O8MOLf%IDFrA;,3L+#XjI1< E h,+7AlK>RuW[ ]_^\XMS LFEw>+6x--n$Rd  sU$%,?,/110(.H+j'$> g~,FhH |  %,)2-6-7-7+43>L\ n 0o#{+2`9">ABTDYE]EbEtG IJ#K*L3L8M(LHYD!=E6.M%4Te  ?jn%.+06E: N?DHSJ:J5IFG\Ex#C5BG@i<93[-X' WH#v I$ /:uDVMT8YZ]}-`Jc[d\dZdEb~.`f]KZ.W SrO2IdA r8.M#"P_  jDu` O!?s#&c$`%`%c$j#x"-!;Z{&2 =BK(N,M*S2g J p2<{ IW#).Q3'8[=CTIOMU4\uafj;mPoQo;miem`%Z>S~KC:9-@{ !'&~T$+2@r7E:= ?@s@k@q ?&=T940W+=%@2 v@L9qq$(,7.O0 f2z4(5)5,5+556D7T9c:n;v<v<u<p;i:e:]9U9J7?605y3^0>.#,*)(&$[ .c!~ KX%+u ]R4/  =^ C vKX $1d^$ (<, z0S5~9 <"=0>3>,>=:o7J4%1N-($\>n *1s \D&!sRCDDCBHk- t ]p .^"+)+/n5:J>g@sAj@W>8<:7q4P280s-D*&#!mR(V* { n l} y  l}HS&+ nJ .Oa"8 #%'''% #>u:W > s Jz!O)V/5Y;1@kE+IZLNPPP|N^L>I FSB=971#q*"T ISg Q7 }k[WZgq|) J 1w d$)ISesw  0'O>j\y!%#F%['h'(s(0|(:)C*G*L*Y+f,o,y-w-n,e,Z+B)+v'N%"{Cn - cp06n8 o:<!"#r$o#y" )VYD +U` :sa$_)-!0O2 p5(6;7@7=7/6x5S2!/,c)$h$,!p.T jw17~}n[ G8   0 \ I{@Hep 1;_bV}#D }?F}V C& hVwKsGwKWk$ P =qLU 1Ra#,/&nSw3S+ jv49~vg_NK 8; &0 . , * * * * / 7 "E 2[Jpa}(,38>D:@9?8=49166<ELXar~ .LTv~"I7nW| "$.& X'*y)D*S+W+O*9)c'-%"i*_@^ 7=P@ yjDE$ %=]7d 9 'xmw  Ig6&W>rOUUL?q,]I 7!}_}C_(B (    gpDI$& maSD6%   =-ocPW 9R j>7d^lIv!KNh " >C\P* t jik j p z  +XL}CHlsszbiJO), |g]LA?34'7*B6WLriLP!2 Rg  4;]UyiprkVy:\< g| ); ek!ja>3 ,!E:_Vz !HLpv #3@Q Qc ]o h{ l l j} l r y }                      } m ev M]6EX\13unMD* !E?fg),ILilhidglklo;@?D__'(+,}~AApr=?ikCEijooffFE&&xx&&VW<<rrvvfejjeezzvv00NM^[<8@=WS+2eq 3>cn &[djs&Yb+9BF;8}lase    Hwc-~D Sv%-d(!+ .G./ 3 U"5!!5J! 4E 2P0.p-q *&[n!+M  @   >6&F " s)T/w3F7{:<6<(?9߽B+޺DݹFܵIlܵJ۱Mٮ{PٮQ۱yM_޶I߹VFPDCA`=I6.'!t *BATHbR lR:   "K 'b- 3N@;AG5(N Tf Y!\#!f"^!"`!O"_! \: VEPIAL<=6l~3/) $ $ r 4 )B4>H#(G/6P:@RFYM?RtWO[+]y;` a]dld^aH\[TnN,Ff="31& ' Mq "- + z5l=+CJ.MN<4P2TZ)`k6dohFf~d`Z>RCK`Co}:  0 '_ /y e75 :a5m()k 'r$m)./[2 x6k;W>?N>i<q81j+$8:4 ,+d"&*m .E / 0 0 0 0  1  1  1 1 1 0G : . --+B ('f $b!"p:n Ujzp!T.K8sBDKReXG_flL1ptB4vyhd{^~[|{F;xV@sjaVOI+=.x o>-] A Ugy}odb\XTRN| I!Ed!C! AX!B 'E\JHPEW`jvADsb& PP " ? $,.1#& "D0B$S`-dtxlKbZ[ Sq)L{H E!D^EGXK"PzDV_h&}q{gYumc/cu[UQ-E69*P6@eF&I<5~ O aU mr|YEz nncZ"R.K1D=B71O- V' "!C)!X !+&.7:%Is\^r| ad N Ee(T Ap-CIcs|s|"cR,M8 F'C dUWXMa@V[*R 24IFM]iuujR;K#{  &n|< Qt r d v =uMlYc/[!VERO NOWP.QdR4SyVjY}^cjsv~~ & oO S ` QB<3L%D /~ ak$H)U.#3$8r>QDJ4PGX8a1 j=)roiyy~qu~7sGr2AsRztx}zeSsii]QO?. \G(7qER/ ^d G i| F ri  z$   EB MI /. M ~Z z v e l  _L Q@G/5sY6}"1?#L-Yc(kpr_ruarp4ld]rT{K~A75(!X E@$Q64C/$R a n + |> c ] tZ xY,p is0ekbmbg#dj r | 7 tW f ^WGT96&Q0 |!G+S7BBWK5SYP_d@joO?sw|y{zz{z{{z~x@5uqsqmvSjgdb,a^1YdTQN F\?75J)N 6iOpc i qZ1D/z/ *  #h-(z:!PDKm1O;PMwtH?<p1( !   0!;uX v s N)3WP_iAdnK*X , b%a7 EPTWM"Ze9YU};N&DR6[%'e):W6#lPA`xmuX<_ L5tPj U 2 v cRFB\5"[(? E1xxK4& )h|)42r;AE Mh U# \_  e a n y X xsf)VR;'$ '{A5\}w`D("bAa5C&(#.t1/{+# p`(y@"[w|a[E'8L .8[6 }I s r x VL='   3#l pnI*X: J0 \ mT D {QfR<m> *~NF +n:SHGU])aj0pt~trjRmf]BR}G:m.I# BBV *%-(_p ]HJR- xz;!)m"/v48P:z<=> =;:f66G2*}8"zP !/y=uL=Jv _} l l g ] R N - A 4p )7    -?w *+ 9 V KK a sy|\<PovZ%@CM_tvwy[BC- R1a]"& #(nk^SBN0vCX(n:kVAQ,Bx  1?XLAXb!YjrSzl('+ y qF  i _y  V M ) C` :C 0 '3 ~       x  !/h ,T 8O G* Y k~+{DaEO( }?0Mjd& Ao 5e?-A|R n_xd|h.^;(gIQF ]2XoLq(%&kOvx V Y  ;u !!<MS\e7ry%{?z$tjU*[+I 5 Zs  ' 5_ L d]y$0kU{@_+S%\P|B _*C2i9@AF^JO}Tz9Y%^eM6lztIpV}f3[fQGv= 45/m,K,C-S0{D5=>GS&br{w^kHZ?GJ4W _v D+H@LeS*w f_ HN 5`  #HH ! }+R 0 1 ,T # UQ    /[ C Wmg[u%Y)>"{UI"'^=+Rdwu3fPwXL3E[A]AEMAX2eQtwiYfHM6E"%DTGyB48J"\tn+|E)iTzcP>o~ue>S},@K/v? Un RD$ (+a.R/K/F/F0P/a.{,*.%!h!q@ H=  |E:1PI)p!,s!Q'x  L+;_L `]*n`~dB{oZc? Y >P$ It C ?  =  = @H E GN( ZhgZw~|Vs Wh94 i )IV9ha>|9< ,#y0#%:#-'I3%#SbvuRWw8ZL -Q7tbi\ Q{ < V Xj yJ {  " C q ]8 DW q (S ) & Dc7(uj?1?i3Rhz]o*\VCxH/=. 11'3@nM!YRfq"v}lcVYPtHe?:82=+&x%l $JXu z  YI!(R"5C6MRcFtWe\{qaiJct_]\w^LbVira>}~4jt\T{> '@s |4%Ha[znf~7ToE*91R-3-30P7BPXbwpj[NE9.0/'c$"A^yRw/iM^VQQbPCRX&an, ~QVbr^WS<I! =2<`B%Q9vKw\|k_mjxaZV}UZaXleyxne@cUWFy6;&*k W+T 8DMa?V^beg#f eb{]GW QwJ>}B@<60u+Or',[$P#V$j&%*D/k69<D LFSXZ`dgi#i$gd{_?XO[D  8{(2*"g /2qFZvo ]3(EzT.6pJwRT2Nlx]@}"Y%NDCvnb^E * ( BF O W h` T t { { c v lm k \ G] . $<V`p,&p1PD0\].C>WK0V`i~Shuonuhyd|vc~qb~d}g{jxotuoPz@kjfb^YQTz0QINWM_KbK]LWLKN;P{1Qz0Qz1Q@OcKFL>24H(" _ L5#<V]rzPeQ]> ;- Q -4 ; E[ L y P v P X M  F :1 + "5,KScg~F~eVQ{,,u$G{pi$SZ-\  -5s RVJJP)OFd{bJP2~o+"*AU=pgw"k}Q2xQ~Y}wP~\;&,~JrYfYPM5C:3T.4q,#r,#v-'1B5b=5CzLT+\\;cpimppo{kMe^iV2M2D9tw-&!~)!3_6+287DvOTYbyjjqipGmLs9kBtFmLsprmo{h$^fQMB x0w{.)WI.d||S*}oYd9\aWpUgVDZra kMy8KwnS7^1 z0)FW&[tm_}fN@P8449&CdQc,zxjLW`AB+0_"?Y(q1BK|pqiBcc`k__`Ac i|os:w %6vddQ>}X, 8,Eo%"r'V*M+[)m($ 10_i    W}F f)2=:\CLkVJ#_{gqnwjtLd`x4aN{1`L{Gc]y}jtuk?`Rs A5.I!Cg]%0/{4`)H +1) @R  S a iB mU jF c% V C`-&@-?D;_"{ ^!67d M9h1K"} eA_]|uD-;5 p`*&JR`0XxCXBonwM]$KS9y(TD 46)65@mI=MXQmT|V|VyVuUkTeS_RZQZR\RhTvUY\`e8l]Os{ F@~j~ z!x0v)wy}XLpyd)-Lw4,z6N(ygBYhb:&C$m-RD*P`y;,v;e\|`BbbGo*w`6%rWTw&"}~&l]rRIEC[@]@LB"H~P[;hxgQnNXT;;#M :%[%61l9@?'C%C2BP=q8'2n+ #D[ E 3j#"):-M/P0N0;-)#sV( 9k,'D0|8K?'DfH[IfH!EF@y8J0$(_ 9*f4 I{^pBu$_C\a[Y>!ro:x\GjH0zLz&8fIX^xKehq[zcSDNzHO}aR[{hqdxReV1DB1Dod4-Ro-L1xVjD^~VCPbLjKcL@P zVC^ehtwOP~tfLyN2s;n(9sHV}yaLrCijnf resgq$k&nPrGi{sbZ*Q(Gs<F1&G\ ? /{[} $' ((&%"wzZV ?5' (YAk"=0A9%TiAdf;rjdHX0NmFA@AsF+N8Z:i_{{IN[t9j #}<TMmKkP5BEoTPpI5IPZnVj*S:1 y~<I\y3@su#js`[6W=V6WZ}_@fn_xH~Eo^`RyD9]7,"wrSR ;< ,1$1$: *N 8dHa| & +-0>3H4C3/1 +"l< +vL* 8PE@R~^woqi%b6sWzOLLNU|_,uklik}_L Qt@-+fq`9Wu)isDbUfI^A<9:=QCSLW1d]t~w -[?#*< `1{@r*MXz`rfm~i~m{johse{`Y_O^E 9+ P}Z(X9G7VtNalsy }|ztmYcYMM|?#1"nNm&d8!&*,,*&B 1l+a'?5D PV%\_fmrutqkRdZ|NIy@ /H 3uT(Z3<uCWHEKEKQIlD>Q4(%e ) L9QPljsrE|Brrj d*`-`!aedk1t%:Hv\{}A%,0$18LK}\fm R5{Ad53,'&&*P1;IE[^tpf6WjFN56#ra/DWbZix7C__vwkgDL, vgk!_SLFQ;0'|k[Z]j ~' /C9D M3W\_|Kgjm~qqxp]k0cX]K(J:(tQQ/>RL#~[khWZqtL9~? 6r1Y,Y,n06? L?~i\rofMV{El2Z nM"4TGs*4OwQh.[PIDCC GNPVy`7l0z8<|%c}J1``*N7AjIEPZ*UM!WM!W[+UEP`KD#<q2$(cpS9c")-.-+%vwRF 1 ]!fE"-'*,,+$(:$dY Q8" 5H]^gq23r|<vVsYrNt2w}~g$(BkU9G="|' $i+;:GLQ=|Zrakffidkgilev_Q)X`NBX6*5u -q%7LabhtH>eRiy`/-~x  svXq]R_W2:%R8m0rd()6;>C ABGF>V70F#ZdD(6E>NT-]K`Wgn"hs9mehgn dc`UW7NbD8<&hM5d+)8;}JqMFb6T!]~7XDHy}oM9w]bPJh2H?T&]ENK@6yI]DhkC4S?  8,yWn henkv0&kygbL 4hC -- . 4 #~UrMcB(fDYEbkUsgB\sdO8#cmfhO<#Fve=F( v`hI<NSj% KJ^#+x]  Kw  2 - ]V c uI[& A0( Q- _[ / ]   /  % .Cs Pf hR i_M:5>xI0 ]XLZ<dVVqqPB?<-Q6NU-MdLeNrK+ g,|M iZ]3oJa@(N&;\-Cu6?vN 8|SmRzO'lbdgRt]leSkSvPawD[he`C}!6qYA~XoSr e fAp/_^vB[^vB[[vA[Zu@[Vt>\[vA\~O`84]9JuQG$@l7.(@~]VmSAF&XrLtrCZR  vZ #q ( G?V Xtv gk gk1 Q[z B* "r `ZzX@46$Yj5|T:a h6x`,3`{:k*KZb#n)LV#<{)n"ZBE"o @7uR<)4.umf >R;3 E,%K(OS#CQ$/M 'J)7O& F,D-=1*>4dI KO0S 3S ;Q&V,TX/TLrE85 G+<2;3-@ V70,C g/ /,A{OlC`CQ% $kd,t  TB0h6V-;O_e8G\  s.>.kj:/.y%0 51)/4,0-23q8#0^##rFp I/Z's%mF--   @)Y3GHW sQ^r#x^k8]UiOJI?782%lrI  #a>%j.6 ;"@1C/C=9/ ]^<+ l!$1#Cro`\<} >ir9%,8S ?:~rlZ4 5({_\"y]f5m<_m M { / A *  dA;"mB ]#^5e<[ssCnoSxLa4BM qC\p"s7!!fDk^o}ybl}pb`7OV =(*kH8dxA~)Z,=`Ce H?,|) *t-D_yObc!nrn^!J81HABv6M/L/F4G3H2G3M/^%Lq1 * V:WggoH/ ]%j02@A4eu>:n"S>ow[Dv@dw~yCM 88C'EF IN{RqY{hr^Z`]cH[Re?YMg5WGh/UCj-UBj*T@jO4m M.oJ(pG rCt=x;y9{5}9{FsAYOgpY-E31M'dVQv2A[qf [2DVr-ux #CD15|\~*7AH_ev_(gBD-o ;3DI JI"F2DP@{941S*5 &b@Mn9\  QjFq_*H0xh?0@.R?}Oj_mb@ uy% uMG!8Rj A%tf^!G;0O3 $X7E8MFP=N3K<MGPPSWTMR9M(ID A>CDPat6V5u: _1HeUB2o"6|   #{ %jr&/H : SRmcqd3{ V8vSE;q@K%zf D ') I-9AFnHyGB <03BY))Gp>xE)?0txQB/[6GttyEorUE=kb'<?_jL]0P }gT*5O75G^Wde9ql |Sc%G 93+ufGS9^8 v#g >nWEHoXUzoiI]+rUaQNLLK\OsU\-hHwmA Y-}z[b7El&-iecj2\e:FjE*B ld' ZY sT%P9*PXfpzK[s7P-g3 t2m&W,K.B164/6(8;w<|;":,7?2R-b)i'g(^*T-P.H0;32506-745:3G0[+ q%6[v :"P/l@'X8}!Ltau,HUy=kzussuy6>O|o`C)\6As%`*Y,^+n&%!]& h=0jCUZr/\_G{w#o4k5j(n u7ccf_=rBAc*56c@3G|MsRkVfYa\]_xSd]Lyhy(=[pkOffY8GL0_-bQZtjjy+k S`7"nYQ)}U[+ST*~TB%tW)e[ S` =f$l tdy?#  ,:R|iy|vt s}uey>{?JsdF%OiR/&JsOe!({io}cPsCW6-:r>dA\BS CGE4HxMoReXX_zMeK@xm1Yu#<}! `@+$,;Rz $@|18jq~MefWAFG3< O-={`b \UQ{ i#(UYBJX> h52t/K{+Q}*P|*Ax-1t/k4_;ODp>M&)UY#gnvu(6|R_@&K* Ak*F4~(=M E({JtOqQnRpQtN({KN E}(=;7T1f+$Hw 7G'M+P-V0M+@$1    ! 9 M+a6v"A)Q2a<uI2Wefqz zo]d'UE6h*Qg99  1Y1%F2a>wIRJ_rku +,3".   }oj!S;qk;'#O3WDjRS_e?j"+luM~ 2} /H(gwU:mMc`XtM^C=9b.##a^44c Meh$-e<{ od,4\4:NTPC]OVD`NOC\O8<OS0:Y$"`jcuiUG) ~pMbTJ>s5a,Ry$Bb5D%)   \+"l,M5u)?8zIgTR_n>j,*vtQ-h 0'vF?/qR^4xJR4Rq"=Lu \Wvi-%]2?FSaM_LVoG]{C`Bb@bA`A\yDWoFnReIVKXM8BHR63X)`h{ pByw^GuT3\:fy(L6f#B*sLcUT]HcAgrb~)A4g %l0Q9s5BDqJd PYVQYL\J]L\QYZUd PJr Ix8AP9k0(:!k $ ? Z.h"5v&<y'>|(?w'<o$9l#7a2_0_0_0Y-b 2k#7r%:+B5R?cOz"_Ro!N+yAR[_\UEa41[r]Gn3O_0#  oL+$'*}+x-y,z,{,}+ )&*$9VHlV.eUrz~dw;i_U~Im=[5O-Bj$5P(8)   % <V+b!1|*=-B/F/E|*=h#3M&-  gC $%*w.o1k2j3p1v.+-#X B!-BCbX<kt~-L"f/;GNUUSPJ>k1K!  Vt_Jk5L^ .  aD"% )}-t0n2i4d5d5b6g4k3q1x.+'*$Hd|   -Q'y*;<UPr"d^yF{5J_n y~ |} y~seS<X#Vv_He4IZ,  `6"',u0l4b7\9S<L?zFA|yDA|yDA|yDA{G@M>V;`8l4z/*,#T~ H"},==UKgXzb/j?oLtPuPuDr=o.j#f_Y{VvOmKgGaD]B[BZD]F_JeOmWw`+iFs`|xa|2l\}Id8Kc#0( U)%*|/p2m4j5k4o4v1,'7"\  9\!,},<8KD[KeUq\{`deedb^}YxSoLeD[<P4Fy+:_"-K#4  1W)|,;=QKcYvf;q]}ukQy,l `~QjDY5Fk&3?     0?L$[!+e%/f%0e%/a#-\!+P&E 4!     <d$/5DDXSjd6q] &(" ~W},n\uI]7F_#,( e?!',{1s4k6h7e8h7k7q4z1. )*%Ik @]"+/<<KBSK^NbTiUkWnVlTiQfL`H[DV?O=K9H8F;J<K@PHZObWld})nIzp ^*oYnCSv,6,k2#,t4b;sRAjiFE]d@GR`;IUa=I`eBGpkIDtT@a;t4/'F q  Cf&.6BBQQc^rj:vSo}xcM|0rh~\pQcJYAO;G6B0:|/8t,5w-6z.81;5?9E@MFUK[SdZl_r eziklki~ dx_qUgJX@M3<d&-=  fN>!1#,$'%.$9"Nh/X!(1;CON\Yjcul*q6v>xD{Bz=x2t%ol fy_qXhP^IVAM;E3<v-5a%,U &D5&  .H k)05>CMP]\jj{*rG}^mv{wmbQ@{!ocsTbFQ7@e'-6  ~gQC 9"5#3#9"B!Pbx " Ce'-3;>GGRNYS_T`UbUaP\JUCL:C~18a&+J!.  =]$)4:BKQ\`mo}?|ZulN*tboQZ=Ef(-- d9")/}3s7}m9wg<vf<vf<{k:r7|3/(7"c  1Y#'{06?FLUXb`kjvp|3y;{JHPLG<|7z$so{do]gS\MTBI>E5:5;x/438{057=7<AHAHMUNU]f\d jtgq$s~mx(umw$s~clhrRZU]9?<AGL! Sf6#W9zOvl`REHQBKUk;"LSDJES+\939_fs=M>MF!uj<(%?0Sg),slcP^@[Er]6a Gt 1 BEK 0 g)Q*6,saAx;~PU]%( G uo`oUoU3z`U9Qmf$^e;.T']DMBL1%Xd\t6{n{Ch[Bk2'Xxp:')4sk<KPFKfra9/U>4Sz<LzAE@\ Xv %")#JL*HWuf`pkR.1367G}jzgRYgn *)A9Q 4,V>xozu9  P!"Qvya[C.pk=+=dgxapgriDz238F@Nj2o /qhri8%x{:~35^ail +Dbwn b! oGdwCcu/ZmOaMhyF *:p_p|=B|M BIFM'#[xv9:zGxGAul60> nKC!f_|u wKefNE.*+EBlm--U<KN q LR fn}> >>>3  '   t  v VT5  S [\Y]Y!`\0,&[A#NLvbb--mq_by~ G&/?K 9 {+?T~ ;[(4MJd(c}xKF_ZX,i(>_lM3A&?H/${xvogNB`M [q `G6' X    C#p c y x  T09 g_ vG ( s ; O   [lo'qJ8P) <gl?Dm{G{u>V<|iv$Cfh&)`\{`kMd(YdN> .1d~~%8 Sdcs08T/LIxhdM>yPf?_G,rT~J|<riJpP}\oOtTcC ;VBUR@xjWUPow:dpUzxQyT'R%s4_ 714E*c-X"h/Z!Pz{66UowoeyYcCT LGk!2sad?XJ#fxU%w4;lCj> bP&`O%0!:OXG/~`oE@.$8;JN iFwAxFxl j4{)}}n a!3XKCSKan?+H k K A;F@IBLE@:E?RK]U tin:u S4zgzXkcfZAF-}c*f]3BML, 9sYv:va&2D #)OC)J{^gI)RbXJpQcD $`>y4WM a-x= Z(;6#AbN{J B0;E^'Gb l6Kz Jw,]I@UK[m@1G P#?8aWJ~7=mCr]{Omn_^fgbr]^{OlOuIo`|Pl&c9vPvIo:lAsH!B1 *"#-\ krW9r whw4.M-_t>/2LP.]eii]s/{.{ mjggIZcesyuo +_:6QMw4>!N#LB wfL)Z &8 BO!c1x#f4w.k8uJxCpJxCpcLloPjhNkaKlP{DoJxBp(i5vW%}F3|[NyhVIC9>5F;>5MBaR}j_vq[#ol\SGA7e-&/ wZ&%) */{3~1u5|2m7k9az<aEcGE9XL31QO")KR0/POH:YKU@_IXs?]w>x4-"&9"c  t4,cS'ok4Xx17<IB HPRf)yh*yi+x7v6tC{:rI}<qZBnS@oG};qC{:rd'zLFC ?C 9.gO C*WhlYN@m1(-q]TT\I=!2#2#=!D Tk   O$l1(D7YHhTv`j'mQ}gz}tw )Pi*3710.17;501.z%^1p5roYN>U(h&w2Y{=P8aIMS5^to|dYO)9 +)84=@BPJgUbs.z[ s!h9]LTO7bJOuAd9,D ~ CC4eO"jh (3ISGKg0<HPSQLD;/z(_E: g)koVR@q5) 0#p4b>jFIU&fLukdN+4 ||  !)+36cMd{Jv#hCYb=kFs3G $ D4pU:q{5 Mc!.8AFH>n&<l?rdy[fMQ=B2f1%1  tI.#'( ).r3e7W<KxA^9kG1$[NLU=]/ckWs-z|-zCv`q#j5a JWH.dJxEuB^:{/%%i $ W)B1aH}\Dr{, ;GD6tWx>p#f`zYyYzY ]aad(h&ga}[{YvVoQsS{Y_cc!e(g&gda{YrRbGP:g2$'c&u0X;G{@h;rDV2lHW2lHO/iIM-hIZ4mG_7oFe9qEf:qE_6oFa7pFg:rE{DyBK~?U;`8q2+7"u G"w:)N7^CmNuT \+hNtc{~j}XwSuZw^yj}|j|\xKr<l$d \yUpOnMnMjK`CM6r8'E" 9!x.V:`4qF2bMSULYD\?_@^F[QV1bNh7uDP<o1'R   Z,Q8vR;k r4[ }Dt{/k@bN[TYTXQZPZN[G_=d2ic&o<u |tg\QCk27 r}TsCm<k.f a]zSnK[>O6?+d2";  }O1""%()},u/j2[7N<A@u;{Be4vEU,qGc2uE|?~AL<^6v.$%b+q8&]>VHn =Z,o7BN[gqx{ywpgZHf36Tq\lHN4e2"A!  & ;G$I%O(L&@ 3&       :Y-x=(N2^=oHS&`Mmnx +563+' qyUp9f$_TpH\<I/g5"<   1D#I%N(D#0    sR6!%)s-b3O9>?[)xE9mL dP]SXVYV ^S'gNDqJb,zE>?N:e2}*3"b  > `2D+Y8pFS'_RmpwovUm=f,` \UzLrGf?]:R3J.z?'g5!H%/  tgQ9!%{*o.`3U6K9B<>><>>>@=E<L9S7[4g0r-})"%L N(H,g?N[Egpt   {fq@eWyIX5m9"+ rA'q-c1V5N8D;?<9>x3@g*C\%|EOxGGvHBtI<rJ?sJCuI@tI@tICuIMxGb'Ds0A==I9R6\3e0m.p-p-n.k/g0f0e0e0h/m.s,}))#Jz < e5 K-]7nA|IPW,];b@cHfJgKfPhMgDd9a%[ S{Hi=V2~C'O)!  ~]8!%{)n-f0b1]2]2\3\3a1b0f/n-t+ ~("$4"Lf   & 2>!O*Y/]2d5l:!k9!o;"j8!i8 d5k9 q<"D'L+X2i<wDMV6^HdclnotqxruqyrqpjnbkYiXhVhYhZiUgPfMeHc7^$XPItAd9U0E'j9 _3R,O*W/`3i8 q="}C&G(L+P-R.Q-R.Q.S.O,M+K*M+Q-Q-P-P-O,L*K*K*}C&t?#i9 Z1Q,I'>!3/*    " * 3B#P+`4xA$Q-^4n=~EN!V:]Qdjl{qx~}x{pfjIb-Z PFn<`4P,|D%c6@#"  ~vfXKC<==BHTao 3K)`5H'_3p<DM(WG`kjt}  ~xqljRc9[RJDr<c4R+wB#R-4  tjg]]ddqsz~ % @#`5G%U-^1m9v>~BEJO'U:ZL__eli|mptxzz{|}~~|uogfO`3XPJBs;c3S+~F$a6K)+   # ="V0s@!N'[.e3n8u<|?}?AAA@~@{>z>z={>z=y=w<v;u;w<s:o8h4a1\.Y,S*Q(M&M&M'L&L&N'S*X,\.\.[-W+X,W+Z-Z-[-Y,V+S)R)P(L&}G#vC!n>a7X2J*5&         2J*X2f:q@H#R'X*a/f1m5x:?CEH J JMLLJGDA~<v8l3`.U(J#uC d9U1>#   l]MD=6 4 <DO`m   % 3E([5k>zF!O%U(Z*c/k2p5w8<>ADF HIJJKJ HD?|:p4c.W(K"b9L,0   ~jYH=4*!'"+!,!/ :EQdv 4M-j>L#W(b-j1p3s4q4q3o2i0g/b-\*X(V'R%Q%T&T&U&V'V'[)]*_+]*_+^*]*V'S%M#J!wFtDoAl?`8\6U2P/E)4&   {{yy}}  / :"F)T1b:k?qBxGxGxGrCnAg=\6W3F)=$5. & %     # ( / =$M.Y4h=rC}J M"P"P"N"P"R$Q#P"O"L |J wFsDnAd<U3E)6 &    $ 2 @&N.X4d<k@pCvFwGsErDl@`9V3G*8"0 ' "", / 9"F*Q0`:qDL U$a)l-y359::9;;;;<=<:7|4r/g+Z&N!j@P06  }xrjcefmsz  + ;$N/W5e=qDxHN O!R!U#V#X$U#V#U#S"R"Q!M N ~L~L{KMQ!T"X$[%]&_'a'c(e)d)a'^&]%Y$Y$U"NtFb<P1@&1     ( 4 G+T4h?O\$i*u.27:;==>=<852u.n+g)]%U!{KsFkBi@b<^9V5S3K.D*F+H,O1S3^:lBrF|LNT!V!X"Z#]$\$X"X"R S ~NtGg?\9Q2B)6! - "   !( 3 :$C*M0R3_;lCoEyJQV!]#e'j)s,y.~0121111x-r+l)f']#W!OxJoEe>]9R3N0E+>&;$5 1 5! 3 7" ;%D*N0Y7`;g@mDuIxK}N{LzL|MzLQO~N~NO~O|MuIqFc=Y7I.8# (    & - 4 ?'G,Q3]:gArG{MSW W W Z![!\"[![!X VS~OxLvJsHpFoFsHvJ{MRTTRTW Z!Z![!\!^"\!^"Z!Z Z!Y SPwKoFf@\:X7M0C*8$ 1 "  ( ;& T5d?wKWa"o(v*.0368999977420~,u)o'l&d$^!YT{N}OxLuJxLxLyMvKvK{N{N}PRRS{NyMtJlE`=Y8O2H.@)4! %    "* 4! >(I/S5\;c?gBoGvL|ORUY\\b!b!`!^ ZV}PsJd@Z:I/@)1 $   %, 4! 5" :& A+A*L2P4W9W9[;c@`?a?`?`?^=_>]=]=T7N3H.:& 3" )     $, 3" 9% >( @* @* I0L2P4L2P4P4P5P5P5I/D,@* 3" -    "*6$ ;' M3X:dBlGsKvNzPzP}R}RzPwNsLiEhDfCmGjFb@`?U8F.C,6$ 8% :& 0 / 1 / 3" / 3" 5# 0 1 4" 5# 8% 0 2! , %% !   2x,+f gP5  3" V9lGW:[<]>oJW:?* e+4&;>@\FKC/=90/*l g_UYW:?* 5# ! pDEeRAKJ*+;{@PE}hfStzPVci`H{A{4>&C%3)S9WTR9 R7t"!9@q\>]13_s@ d =I Aia P_ =b < Ky cr[ OsM 7 )  SI0hmSA(DnkXdYA%P0zZeDvzAXncmR%!:Nn