pax_global_header00006660000000000000000000000064137743431260014524gustar00rootroot0000000000000052 comment=5f1f12133993403cd443ab3f012a085f9471141d java-binary-block-parser-2.0.3/000077500000000000000000000000001377434312600163135ustar00rootroot00000000000000java-binary-block-parser-2.0.3/.gitignore000066400000000000000000000062621377434312600203110ustar00rootroot00000000000000/**/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/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/common/converters/Java16Converter.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/common/converters/JBBPScriptTranslator$Parameters.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/common/converters/JBBPScriptTranslator.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/common/converters/ParserFlags.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/common/converters/Target.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/common/utils/CommonUtils.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/mvn/AbstractJBBPMojo.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/mvn/HelpMojo.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/mvn/JBBPCleanMojo.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/mvn/JBBPGenerateMojo$1.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/com/igormaznitsa/jbbp/plugin/mvn/JBBPGenerateMojo.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/META-INF/maven/com.igormaznitsa/jbbp-maven-plugin/plugin-help.xml /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/classes/META-INF/maven/plugin.xml /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/generated-sources/plugin/com/igormaznitsa/jbbp/plugin/mvn/HelpMojo.java /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/maven-archiver/pom.properties /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/surefire-reports/TEST-com.igormaznitsa.jbbp.plugin.mvn.JBBPGenerateMojoTest.xml /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/test-classes/com/igormaznitsa/jbbp/plugin/mvn/JBBPGenerateMojoTest.class /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/test-classes/com/igormaznitsa/jbbp/plugin/mvn/mojoConfig.xml /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/jbbp-maven-plugin-2.0.0-SNAPSHOT.jar /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/jbbp-maven-plugin-2.0.0-SNAPSHOT-bundle.jar /jbbp-plugins/jbbp-maven/jbbp-maven-plugin/target/maven-plugin-help.properties /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/ java-binary-block-parser-2.0.3/.projectKnowledge/000077500000000000000000000000001377434312600216775ustar00rootroot00000000000000java-binary-block-parser-2.0.3/.projectKnowledge/JBBP.mmd000066400000000000000000000062421377434312600231170ustar00rootroot00000000000000Mind Map generated by NB MindMap plugin > __version__=`1.1`,showJumps=`true` --- # Java Binary
Block Parser
v 2\.0\.1 ## 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.3/.projectKnowledge/JBBP_Types.mmd000066400000000000000000000123071377434312600243020ustar00rootroot00000000000000Mind 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` #### 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.3/LICENSE000066400000000000000000000261351377434312600173270ustar00rootroot00000000000000 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.3/README.md000066400000000000000000000440221377434312600175740ustar00rootroot00000000000000![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.3|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.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. [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.3 ``` 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.3/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.3 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.3/changelog.txt000066400000000000000000000116151377434312600210070ustar00rootroot000000000000002.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 compatibiity 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.3/docs/000077500000000000000000000000001377434312600172435ustar00rootroot00000000000000java-binary-block-parser-2.0.3/docs/jbbp_byteorder.png000066400000000000000000000447371377434312600227640ustar00rootroot00000000000000PNG  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.3/docs/jbbp_complex_types.png000066400000000000000000002073031377434312600236460ustar00rootroot00000000000000PNG  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.3/docs/jbbp_fields.png000066400000000000000000010510141377434312600222160ustar00rootroot00000000000000PNG  IHDR ~i:0VIDATxyU?-(γCv9/22:0bbj9+ 9@?q̬hX9pKEײ.7JS(W ׏}}pY{k}du搪n1S,|A݈k?´l~GsH8@h%!UqO~<T!W}Y~o%3fX=nܸI1c|4y7N:{ϝ56b9[t|!Yگ{t\JUҵV*יa.;-Zgr] ~ ;0bĬl5z-[mvƒ# ]n1ひ8zWc@jWNuׁ{iݺ]ك!V .رcٳҥKÊ+B?k䨣Z=t1~>c`&fWRJUu>7G)MX4jCS.L/Z9rGqAZ1T;XG7߼|a.ر ͊~\|ae1XqAܹoiK_.8ѢEjTthM7 ;o~)W^yرc飆?1xIt*vGe ~803(wBxk[ۦؗ:3:#=}ɻ'> ٨e>W{5~DԺSҦK XsʚCo?tn9i&7;?x?=cke}C׶]C{9/4{6E?z#iӡb~Lu=`'̙1sW$Y=#thU}>'e_,Q8bP}v[>Qzgy&AoOm6lQsթWNkؿo< osu>(I1C0u+*{N;mQ7Ug_ 'x{޺u/= Vo[o]z Տc9s{K.-*O$cΡ Α, rmvlj:+^ X?۷j>鴮;T;޴{1m{zF]n-{1?NĴ=w;Luj>pہcsoGܟw%×dK|nƼ}~qӤ?ew=9|s?ϤRߛ!l& E?:w*tӚo̘i3HMWH=LlidC?b:vY;1f̘OV\YTw`+?9q5Ibhgub}a=]އoil[Zfu}S?[Nsw}oN|3jѪZ#!ڴhb/s#1:-gcȐ!? tL4)$IR\V  ̟L3~ȼgɟO}I#+by#7snҷZ~^;|?.?z5d!5?\S%sYFCz?I?uvkmǨQ>.vǏ_[#+__q}KwMؼWocIv7dw}tM>3s:+o]v6iI STsM7Iƌ~1=k<'7q%/$8S|+[KI7Vֽ>*P3-gOҥK ~,Zhq$96xc[5I~>гKc78b]qжelw}:?>~S?[Nw}oN|3/Q6-ڤj+ؗ kO]at(mglھtwi&lIpi¸qfwl?3 ~\uy?pyO /\֠G-c.弾3|$C-$mݑ+^Sꫬf2lf6k& ~L69s|RDcѣJdfAHfmֿK$mw \:װhȢp!fwl?s3 1!>S<Z(㨬vwn3Y[>wK:0X'uGANx-,>bq_ ~u_|vA߅y e61ǒ  e ~? !4QcVV[ޭ[0zk;vV[|3|lCp؂i9It1b˗/oPcٲe֝x~A?ӱ7(pѷKtڻSĕbu[n2ܨ ~ˏ_[2HGG |x ~:q@j ~?j!̿vDL7`CH2M! .x6Kڴڷ̝fT!cm;mx-[N/?<1bq֬ZNJ+MW¼xv!6 Zu<H;YuNn/eSNE? Gn׽_z]np(W*Qqzd#m ~ĺ5C 84;-Zm:l=} ?9'?VO]ЫSlennk ~Ҙ€B! '1/Qh&lIM]szvj>Wr?!C\6rȿ-_|m˖-/>***.*qޕub3i~5#Iلw}hժm)?vy@ز?c38|7WXq᣷~?=zI)Wy'Եٝ3^=հda'6aIf ~?!I:3***X3bp@ _˥\~$#! ~جEؾ8K2MO8@(@hz@GP? d??p*P$#!T$' ~ ~~4=UIFC H2MO8@(@hz@GP? d~Dݐ<w I@h@d?$#,%f)~4KIFYJ2Rh@d?$#,%f)~4KIFYJ2@PiUU h/(e,f X% %Ȋ(L$ %Ȋ(L$ %Ȋ(L$ %Ȋ(L$ %Ȋ(L$ %Ȋ(L$ %Ȋ(L$ %Ȋ(L$ %Ȋ(L$ %Ȋ(L$ %Ȋ(L$ %%_?n:/QüU.k?̘1cq&Iƌɓߘ:uu?wژsNOnszSMl##J2@^ݫǵսޫsoj{ g<[~? S/7.ڣke]'UecgiӮMXΒfz!.]6mۄv.L؊Ǫ'/1{>|l C.\cdžٳgK+V(^QGzС'4.Y}p6kZNrHFJ!T>]I#OYM.9ӿ4 S9ᓇ7Eէ^~j֣W9c.,!曗?>,_<%;_TsgUS[¤s'e뜅s6Ew^Nlwl/QR3ݔ/~~Ɣ nyϼLإcEݶ%?f^=3=CḙDž;;Ȱ B׭(>j*ǟq|z-h om6lpE'w=m?`[вUl(˂XjU(Dd:nx>gиAq?ZkQ`IFms>'/{ZsQIzo#HÃEiߝ/Y~/tt+Ǝ[N5HScRUr?69p]v@czg^gwMxぴ}?mٶxvwZes_;:geD:w۴ ϙ S̸jF6]:={ GttWo|}uٲK6[Wgwݕ{Хkl*:vsl쉕O+6J}Fe;SV[=wۏ ~@% _?|Pz7xK1Y3CE14Q@I&'R?.Աj/_;m)mYyc~Ӿ3Ym_KZн`'̙1sW$٘sgUL{yܹ[},?ӿ4>;ҳwlyq!}[rɿ> v\^=a7.bx{+&wpzN <'s:B]1ϫ|w?JyFkq($#+:QS=]7rwN>.T;Idr&3nyjc\0%)}Tᓇ7c+k}14Su1SC ,9s{K.-*O$c9K}V?brܐ_Ngw-ȶ>ut7s*ۮZdzۆ o0̻o^8jp헥m۷ ?}{ZN<*=&ev8f1i{e{=b[neKCN.Au҅'eGw,#l65놎gm|NwO 41_KϴU3o.ưQ︋H y]S91p{qg($#-nHm+};si>'m KّK PSh\ I,[s9: JZ=f̘OV\YTw`+RU18?w>i_ qLN!޵w9L϶]/*]ΒӺMwߨ^W;/535b *b|꽧vӸWuޢsw7.6'"29;];B],1DtIG=3~iE->GϨCm]{t~ƢW>|UUOwADŽ/NΝW+w,}&Rم*̽wnvLzƿ_}T.E5?jqk\d]UwXb1gL^҂!C?8/1iҤ$IsZ* 0 33EgUL#ؑ{Nܙ[/3{ؾaɻK/=Az܎{X87^ )|-]g]&r%ҾL6G;>{gNzCE?J}F1T-vro ~@y$4|42XzG %?`~<׏%I2uۏ6(@H;1 ;^}{}qvOTY,^QF}\{ǏRU1lb}$Ⱦ˖]'*:~gߡs.:ZBIy1VsX~2om? k}.Zjs ew}g\sF~Р6xйw?b."u3?̒kGM):)E}N;ƾÆ^cƖ=(n,THGLlҾ{D1;}\?%->tҢ-Z$cE~ k)jC?bIF%9Ki_ٻgG1ڂY1 _Zm߶y1),zuQ׏a:wyЪu:sMgT_bKK.pG9ӿ2=޳{cyb1p=>gT\;}{Y|})L>orv1qn=viVn5~N<7Crԋ3\r?%qwc_9≠jFނXg}kVQ+[ou.4ߐ'oM-ĝ!b8}W㹹ix^{-P+]/x˾wyMޔKvx~{+HӵGp헅>zġi{.+6X|P6wkL 4t='dZ1r<>gT\1VwM%0 {C,~xF ~\yy j~ jjkj\ ~}%D*ϙw߼fe%Sk?uvr/Wgs2It1b˗/oPcٲe֝x~߃FV2q/c-`i/7ܣqzL]{W{Nw}vS5ڴm?1OuhWV k!.7[sDBim[3*t.q| P$#%MVq =B3$|_.i–[m}#6A{fR}՝8X l\ ;o9n:|fτgKYRqWWF1qܸqkVZUPcŊ&I+{jC?b;Tϡ澼y|'='q:RΎ-~c86vϙnmӫC;^g #Ȏ1wϋc#jŦ!c-1O8{BѫG6x˾ds9?{u}n)ϨxW ;sL_ݸE-P@$#UXac,#O]'Fymw6{q>_Ctȑ[|:2k-[v_ }TTT\TBBJ)U| X#ٿwae(^|u?#8bA~3bޛħ0Q*;P7$_UViȑs|x|mcL'zӏ>⊵K. +VQg_ nEEűexo$0rNγ;F;zU3 w1l:{-[3N~s|[e ~0L$3f 'v.o~G[Wa̙av m:t$:vv80Lp'4yc[ofe+lp8"^+S/oί$I.,;@3d?J ~\tEp Z*bq7dȐ7x\ 5CԽ{DnMfh ُ?{nN]0mkرA[׮ ۵K?? 7au~(*;@3d?~\x[5vbW$;3t*";jTo=ٰGe{VDR׾n^pe{4(*;@3d0q/vڶ:t2tЄ0w[[`u5лwuv Z ݺI_UIǗ3qW,3gN(',IEC:+߃'MJۯׯZ /{e\>'{oUoxbsK_~1kv.6nZ&Wv;Vmڄ 9'\?W>#F:v]GWZYe_|h&L?z#iӡbsu^w.oժm>/gNҥKZC$cECg<?&+_=~iC4x{{jx}ֽϿY?_C-{ WFc׮Ώ;kd(*;@3d({N;mQ7ӿs¤I3wk% 4r?F+W0ŊoWTTU{kP BnݪZod{58}G{ֽku׬+&M IT=ׇ~QEEG xA/w_n}jbC _պfM7fnٳ/HCowU+5}f j&m_GIe_esJ37CV(e~.s㿮7¯;O |b{ud^?wo|?\v}+]vIҾiiǏ_C ׬Iy={ˆ^J[oWVmڤq瓚}Gy> l~Νr]s|3hժl2ͯ_K4Y#=N;mQ7жmǴL|ޥKp≷3Xwi{v]+:ѣ|-NS>GV;~.0/Ⱦaukv3 ~\yἧ .[(X81mn}Y> k\ka!>=ozȑi{Ä[n ~捫GWh5YcV-[i(#k]zܽq5^c>.NdժU-_w܇C +"CΝk}>8غٶ{\D=vݵukGW |,ΜC-vn-v)SL+~l,C\le$i!oyvs߬vނèQW? 3H˖C׮ۇCfeIǗ;}wp kۙ7Gy;$;ZT~z6hѺ}ТeN ygOA ]>uú9~|vBB/:5I#֛֬ ^w]Ccnٺu6w~a9s򓂮_*}QٱǺבGfu2e ~3$0$#jǦZ3UG3f8裏^3{tҰbŊbﺟ?9s8xǖ(P7Zd?6Ze ~DIt:.7n/:꨿9 6/C yzg27OYd?6Ze ~4!OaHI5 ;OUU'JCC6^T~y |Z ~~%̊(<~~%̊(<~~%̊(<~~%̊(<~~%̊(<~~%̊(<~~%̊(<~~%̊(<~~%̊(<~~%̊(<~~%̊(<P7"h*P(kC6/l9y|h^,~ 7ܰdƌǍ6I0f̘&OԩSgS~mgxg8ݔeS(~@)&li0c+v vcXq}\tZ-\cǎ gK. +VQ7:jСCO(ش%F ~L0)i=[t"/GH9ן>?*h"m8z`?&7.3OM'lf9;oT;oɻKBV-{ߣ, ccժU#G+a7h13[/]RtcS(O~§9x^.R{ؾS{sW:tݙW^a9~AaW~;tuz>pϯȍ1azg4d v\^=a7.bx{6ݲT='`˓9 k!.SsURſz?j\Nw=פٶѧλ7F=~Nwlö[. λZ{|/\vei{mOߞָDPVE˾pcD}z#jpm燃]**U O~XzGfmj k!.14wЈ?!>VϨFᐸH y]S91p4ٝe?yM[Y!ɂU\-\- +6kZaܲwz듷.?ru>a{̘1\;V6`<'"8+bXψdr CwWg.ygIvߨ^W;/*CY@Re *|꽧vӸWuޢsw7.6ȈT.k!.:Þ}?]آyBimgT\|~]vٶ݈E+wUFlڒLv*w+%.N=nKKyaϜ ٱ*}ǟq|Ŀ_y̬o닋c\vX1g;L^҂!C?8/1iҤ$IsZ* 0 338w=g]|~F9}7,ywIqqw\G~?XcZ?!R_H?n郞[:q~Io9y;y^)j<#Q}׋R>BicEa~gCp{?M&]GOpc/@n"y-xhAy??Ӿ+BCv )̻o^;kT8bm G~AAc`ȽnҾ{D1b*{ԨQ믿MqǏlb}'Ⱦ˖]'*::wra<8k;XCwm?G!cV Ui{θ挴AmGs1~/]D*̫g ~@3#Tդ<7o*Wָ>w|K`3(nV1O'V>QG,#+SFf}qcwKZ}'aҥE?-Z8IǚrNn XQIz.wڗezQy̵?qr—ֺ[Ne)R*YuS=Zn^{KvI1w%wެoͪ3'79L`J^zԂq^#1bʭOޚ^7=x1RپcztO69s|RDcѣJdfS15wGLMۗ?rwe_{kپᓆq?{wUy!$$"Ȏ`$He A 4J [oUnUomjr^RiH]*V 8gd3!!g}9'GBGp+/\sǨl/>UKzL>U_GV}+^a\GrܵF\zť$#4eK .mT±WcϹA=s2ۘ1.qTUSԸټc/v:'WϾ겪Q]6U2 &uOO~ϨB"s6j*Yd+4<Ǽ"ې}4͖\TTt>߿~Hj<=4C>7l>}ZYcr{庍OZF_ TxI}ND>]^#ul}l>x5h_9a.sJC}3FVE_c%@9 ~;P7de=B~<~q}u套G4ږ_m'U,bbn%SqtZџnltC)**>iҤ'N8v6p~~H-ZqZx2{!~Z"19cHA5zӖ%%ᔌgHtY/|QWCgX]}Rt]_}{_V VNyM$;'[^S^Ym1uƌwqK䲫.0O} Z?4Ԋ/'8GhoG:vUex!=f̘[h_{;|'Gyrĉlx'i?~Si&tld{-{׿K_j?$8?4fZSS =@E@CLah?h"~h)44M 1FF !0~h44?4fF&L#A~DSi?h4b 3F#@A@CLah?>Ey-~h)44 N-~h)44 ҇)4%?4$x')jf)45?@1şF#8h? OXCDSi4k~c?F` qL~!")45?@1şF#M/(vLW)g_R9nP< ~#DUUI"&TNX#:6ZZ'ͥ2t%dy{uޒ5S$6>V{e%OnwVɨyΒ%6!Vuo'#f^kk?ED\7=s.+k?o寖hgWx<&uUA?tRSP9xsTd_SeR ksyZW~/K\:X6DE;Xpr=U(iHGU \l}dw+_X{VntES%&.Fzdٖ?M֡ ~;~m'ǹ{Կ!)p#K`q'Ͼwp l+%93*t7!MZEr;g^ϐ?:\A4OfL#:c{JNZ_ZV6m_lA9^SȆsl lƹ5'kdIj{T_ ~ ,O_*yL}.P+i+_\\aʶ)A? 2'e$Rpj~)C~θsj ysMȂ^WɤI҅<^RUo[ulMWp DFaVPTGՊ'M%\dꎩ+\VbQ+[5Nv}&![K?Ef'X~HKHvli׽**xLZ6Eo:Ib?k}v4}hYuT9VhxLx &޶s[} 'c{*Li5㘵5ETXıOrj\+\|^Cނcn#;p##a u罠w)5բK#4;'Z7)Tc݁u.F{g0+TqYcZ=F4}]e򗖻SwU!=']*`Q+tz5oz38X!Y: ec;2S$!-8w#~iV$_'p#A~8KෝۯWW\A?y}{33+&TN0_7^<Po5osXe68ŗKݙ{ꎩjO+D(aPK4R AQ?DIXmLr?hF?jOox1NBhsA=̠jk ~du+r_:dӺsetɐ̮V_Uubb61R+dxov< ~m'*/PK]B.=GG FZ ?(\&_Ssl}~VPmp`g]L.s#9s\ %8PK U}=e2z:fZݢg w9WU|6r MͲrJ給;թ'YJ*UʂmMP?vP&8~ܪqO.L8ѥ&or.t>}̜܇ꫢ82:>_~("DaSTu9$Õv}IG:`mP#GK0QVsW='W۱jLUs0#jZ6ǫCr}/xK@`/E?~Pgy"|)@sCo)ۦ$ˀɭ%>%^׷8G јZ±}P K}VBzzJ\b$% .qk]>!ZZ}nv-S{,-e/k6YWzQW=oP͕+ʦo?y? AQ?D\j m8e5oi=J7ׇ3|.spD68溳û~ AQ?D\eXp |,zr9G><8'!3uRe;N{f-m9ViQ4pԅn2yHȟF" Ԥ~ +}m"Znaqͦ-i?~("IM/\V(Cs%)#Ib$>5^rJ"z|k 8ZIme]D*~/?(c?F` E@1şF#& ǿCnQ7|s۟^`I&lRRR͵^{dgxN&WWsZC庻tζ'ŋ%[7ύs`> c?F`M~TU}$EEk0?D$f]ZZ*ׯ}ɱcDQUo~:9f̘kB  $Bi߻%&22W~\_Hٳ~T}dk5G3;q Si4k}U['رԩS_ZZUAAF>C˓7=*əne[ loz)\Lٻw_5=ä%ݿ%?Μx}T_RsdHdžOXC#*q BW\\S -OW+j8*5;[c{TL ~OhXO]>5_~dVU!LL~h5{HLLK.&UU/2/(XrҦ+K#}^zveǧHLLdeeg~CJKKa l ,1f ˸m;q" ٟzp#1lٟG t~q\ftO%:6Vt>\nQnK sP\,%>5UN PBk-{y镟A?.lc֞a0p=38 ~ 8Q* (,m|{@:,?Si4k""KtxUn\?~ݺvs7Ƶ/t>6m۾A^KOϑ 4*QSSlEEc…l6 ~kaÇŋ} ^١Egl?<uZM_(AArf۹*@7֡pԶ/ ^1Uڶ _~go g!?OX}=2iRcKsԊ eeGJvo̙wcc[^4~܈3cԊjC2rlc{^ ~,\}xgl  MM$edMMHWˀl؟mul m3.~F՜<;\d!Ȫ_Y#C&O70خfz]=J~)SΰZ2yM3M0{٦0?8^:خ)v9< Cz헧Z?s9O?_ch]'Tp`?F`O'\{a+TCQ[{ا}ii7)(2޶mg}[TTlUx&i3YcZQDE*+())9{?5j`{SWovr۟p#ٞ>m+rY _(@6gdԝ9c4V~W_Itl}o:zTbb?~}>MXbL;XTxvg_^ng0il1ng!OXhJ}˗r;8oߵ;IOѷj%6Wp^gu?֬yZߺ:f̘!6ͭv'NȨQ$g{qN \s-a+]-=u*,U6wxDEGuLٶM 710\գG߱c]qe>zڶ'W_ ~go g!OX~joA__}%ѱwqLՇzW($ ~wI9/|۹ÇpG ݎ۰1So~AVotcNeɒC<$ <.6|بܹs7VTT "qfĉOlA>C˓oCgWcr}着k{϶Z_x Kl8?p~SP %6T:$_o\KrKK!vdz]y8Х_ ~>O=TVVhLf%:x`@ﱟ:?ghyr7ސ0ҵz؟mk6lfd:1/ݿaQ!J( ~P }Wߥ6m܎ILOWR"ޮBВD,1tղbҳM%!!M !\p֮}W)֭=?T֞ɓHn= ʲe7*M4i'NX };vev8??J#a@J y7ި-b$*:Z_2{ـjfgfS+CX Z ~~\|Uz@kà&S)=Hx׼cDK#halh)\|)xٲfP&|hѢ#<ĉc>VDž2?e0} PEO>) 0B92N?h2lh)\Ucƍ[$CނfKׂgD_2?e^٣ao/D&æ?Zm;d׮]#*\Lr T2D֮Ǐ3F?hRlh)\𣹕F~a6?~PFZ ?(͌M#-EfƦ"@3c~@KAEi? A~4R AQ?hflh)~(&Ei ~0EQE] AQ?Mh!kZ~kEQEQiL4м AQ?4((^A?&#OQa>F((W0Si4EQEQ? ~1UF#AQEQE‹Si4EQEQ? ~1UF#AQEQE‹Si4EQEQ? ~1UF#AQEQE‹Si4EQEQ? ~1UF#AQEQE‹Si4EQEQ? ~m|qEc2 z-51(跹C߻l7},XprҤIgl6|s={z'{ʊcw#U/<$s=d5t@ؘ4+. Ci[-38R~6[ǹuf]G@@E.:lUUI"&TNy#:6ZZ'ͥ2te<5o{6n[2fJ\bJvld*5otYRR&JdW+Wum ?D$f]ZZ*ׯ}ɱcDQUo~:9f̘kN<̈́|T$Z}U4CVA- ?nz&)\Vݳlc>?_-70+xLL/x ~JNC.->QAe/x?D$vǎN*_ү V7gvjlF}819י?N~Zz*dlO;~8&쇲was_s.+ --xSyqcwF@B-Ԫ"~~tUF^?RMDKɭe2YRp >%1|pֿL2Y<8G'cn#Q1Q9J5}k8qX+..)8  $h}k4Ǣ}y,]E <&| wRЧ$M'٫@ CIj +_d|OJ_*$wHquw_Zj^$.1N?_76DE;X/gd~/QZ[Od%RQq||.ת;['m.hc_t>֞]uxY}|%:4?zϷmW8}!)p#o=JVfKvL[,O>U4Մվ4ZM?kmҪɬ̙Ij&lQMqsZzg$15?Ǽ__YS\u{bsڹy?4];j޷UՏWMf|?d%.߸JMM_?.V9,gT?.z>zÂg}s;h8uoYy~sBZg_,WȘzI57hү{8hV4q&:J蘫crZBMV+!wL._69_;@"ʘP?lV~}Фs{ʻx. P?u?ϻ1.f<~'4Z9?n7s8Gv?/ ~h ~:U@vG?.t^Ү{;U:a@_Jtl]8~M7V¨=]kS! Ǿu~9K*Ǫ` 8[ ~xG~fZS`ؾThAq_-w6WɈ1YZcZQDEj, ~3XrU{ qx ~ezG@BcWĉ?~jBZű:k}Z658.ֹǫɼjcZ=Թn|xp>5}?_]]M 7:1 X19])tv5q 4Cc~ΕIsWcV"WcI'j[٨ =wSaO߻αx. P?u?ϻ1.v<}'4:FاV>ixpۑRuu k@8 ~yP( ?ݠ(*y[>} ~;:?rY~0j=.aS|UVIL}%O-1[ ~we?}_Z2wC߮9VQ@Zø8 ~ykߎm;QqB.t>ʎǪwg++dOHBZqGEӂ Dߺ:f̘!6ͭv3y_ڷ?ϴɒαЯ?ԄPu>o{0OWu@>zH +X6#Z!]fw8ֹO6xvb>_٠/T>!ɸ ~;ym{&+㣶GMr뷩jI;7:&y3sKZ!Gp&^[ ~x]?q^{ՍﳣT{vz6G3{=ց0`B>#4V29|#jwl?Efi2\Vݸz +T;?ot|zt?v~~cIcjH?TP99rxs.kTC&ռXe68ŗKݙuꎩj5O+D(a\c„ g]O?}??NuՍ7po"ϓS ~yo{ܧ&:a~J6|[d7?\GZڻ߮?TUwn_Wz`V\[;*Ɵ P2\}vŮ& ٘P3Ob:cGgy7ϥPG+ (Ux Y`ŏ%Efi292!A~Ԟ';p *b"1뗳F?\F9Zڀ)Y)w+ri:pV%Q2dHfImd׮+cz}q؄X>yWc̔hRA$OK7<P##aM3M@5k}سg6wi+%/mM8&qopB ɽ~r]}T}\Y;Uj[?֎}U 2뷩gVc4FjRqZ*CTM! O84iWKGMv*+Ǿkއ`Ƽaly[~x{+Mx9lԸ7>.Fb멯+;.5f<}'spۑqs!A(@Ь6ɸ\#AhߪUF?T\:h"˜H{"}*UL6ŭM-SmzV+e{dR$}uʹEsD_v܆#\Qa_YV_t:$KZ"UVIJYͷHco Umǭgl8~i.Tč]j.rISGyp}h#C+s ecܹ+**83q'm6Bϴ_<sV~RvԕkMtw$j51SLWͮ춑~Ml֤iޱ}5s/ZM>|RCiVMO. ~z:j|ZS?.Ђǟ O8j8)y+4yǼU J꘣d<5]d=}W)0O?Wϭaο_#F}fn5Yӳ.s9[_98Vw(\jxXN|Jյ ~EgyB9a΃;$QyR2ܮB vm:m?zd![ou;w++܎-}{eر_]_>4~Pfh~VLY6ǫ0Cr}/xK@`c(6-J}߿܏`&?>PԘ#un;J}8;! -EfyB{)@sBoWBPmeH/uogVwhJucAƲaWBzzJ\b$%! .qk]SPtFKW6Q(XZ ˞_ V+cWMuL?y ~(EEE'MtĉBǎ{fϟb:Y䋚4MzjrM- MChQ$'jbf^%M+ݚ[zj?:7uQ?VMNK87~Y-7iribc4iɐ>,? }UίVP^4y!cCd|q=~z5s_uYDkuMΪ |p ~@Vժ<'y*uk?4S gnC$g&K98Bln6?BFN@,,,\S\\σ8*?Ӎ:#ONLм((*jBtSܘs1 QgݲѤKgV9GM@ AEQ?5]uWPX"}eIa9s\OaosP̙:FX'={ʖ϶D쳪]j/ 9?.u[L稅`b^^䂂-Z{cǎ}+"|'}G8qol68O?Mi簋4{m}CM07;~l=oWa1ZjRZ]Mzv7j.*ηǿ_m"&kn @ЖZC}NjŬfO. E A(@К| +}m"dVjۘ[=ŎGSmZ& ld{-{׿K_j4ӼGj{1A9=_\}}~ ~1yS(~AkR J\IH(O!RHڢGVҶs[nnQ4ј4!s M^IF&1њ&i*s\IDAT2&kjrY&6>9_ c]&i?Y?Ԥ{'M^9~#h~q}H…ҾwoKL''edH|g# ׮kBe%fPq~Si4EQEQ? ZdU['رԩS_ZZUAAw8k7=*əne[ loz)\Lٻw_5=äf9[B7ιGe%5'Otl~T}EQEQ/8q}Z>N8!V㊋/??J;IjE Xfg׿pl1&GjU-q>UaO?u |eYUSi4EQEQ? 5̘4e_vv/c߂Ox ~&{r%Ӥ#EK]QWwVڴؿr~}+V~8.] }l~{Hiiߕ><]AA1͖6i}% 1ؾosm'N<5=Zn$۟4# xg?F˅ ]e2O<˞^zK\bħ`s.׺)jد~\tl:|x'B c@qNN5p_髪v>8/}vQѯA'JC%T=Ǟyy~&>N(> TLէ~PEQE`cQ.D ᙙ]e׭;`lOIi';w~c\kLoض ٰH555VTTH0.\xf- Y׿vѾ"<|X._ާ0:\tDp{]#X(GZ**kPQ*`᫟j5ənIzNվn_%mۀG}vP}hO(("K~ )+#&U~xZıac?6Tnm9.c{llrˋFԜԏ1bqZCRYyHFml+Q ~o߾>;HRF؄}E/ 8?+vVW~~M_o} ˥Q5'OZ~e5eY2@[jel_;\d!:g=Ȑɓe % *)1g0N~e% GJ_Uiz߇/F6jvLsy:kcErĜuȎ;6YWkl}+}UA8;*5~[>?/ou'S D&>[~hO(("a2 &QX,G~W|K.S]}[VQiQ}uƹ\_c͚,? ou t̘1Cl6[f'N( i?fmi=&_s-ax+]-=u*"e~;]fS+r8ܹn1e6=+ܠ|>poV~VwU:W(uBY~%b>F((#?G^/)mȂ&L4NƌHHHZ :1a„o]Ċ] 3a//[7c_?%K\%m7ayy0Ů].+O\SSOl-0qF6h_(ҼZ)?>BQT EQEQ@8{'W<u s#%%KjkOJJ}ii\U]}LbbZ22HffWɎ_jubc]Ss~8ux|;w~p5k}سg6w;{3rsq?wٗީPGJٞ>m+2=Vnz MĿ'>ٷJ?Vc2yn#F8{xǫBTLLH5'OG4*Ȣ뫯$:683C e#>{HiL@a ~\wƤ騨h7nnlKU}=2iR:c/v3 ݎ۰1So~AVotcNeɒC4+>~4&aْN:*b^q5*<}?w6Mcq"ޮB)6 s"UV.^8Jbc%11]_IcӦ/?ZVxYzI\\$$ɰa?I&4CHHN($fI@ABS6l{ܿʣ"꥞c]X<bP]J(|2wfLL2}}/2{s 7/11E߾m۞+.>(ݺeHxxm*eұ !!J;FFbYdOJVVqU;wΥӧ3LdžZPVu5BwkAFQ2T@aRRqd{|o \AmTh0?CeD_om$u ~o(=njM(y =v镕S#XkWڪj[yl~ GHyoe~SbgOKUQyj}C=?msmֹ|py:8BFC+yȑ+d>۷yHOOݣTÔ`ͺǵ Y_'\+?- t@{u"eҲegad3PgddY`owOED.:u^xL&I;Ҵ&TC>[}$u" t@{uXl RGQI :LhsKOO絿S˽t@e=GmJV:u" t@{u M~Z{׀ ~#:Pe.Y"/FDJbJd˖gF?7 |p.VE:0.7 ?("0&FAEQ?d"߻(~( t@{E|p A(c2]n?~PE`L:  ~@DwQ AQ4(S0P AQ4d o AQ4311o|< ~P6Jp)͝{Ixܙ~7w?h tsgbb"͝{@B7w&&&]ܙ~7w?h tsgbb"͝{@B7w&&&]ܙ~7w?h ><6ҁ\R':OS16o $8$ح}{?Nw=~gƟĔDӞF];S}XzjӽMu6hŻmZ!on-kީly_?JOJVqV9%9~U~\{޿+K2*zB{w |$ i׻GkEIm}WX<#.˖[$UOj*VX>7 u,m G. ރ> ]0To-&HkWk@buޤFP3sOb#)-IrɭQkh 6}$A:. +>\8Zwk-QMú  _:\]•Gm.[#"6B_uپY"]vFP۫ÆjKpH݊V,i7em2^N17k*LXgS뼍7}~ƍwrcn:o[rNXipZ6e+,+%GK>|>\m9O9. EOYEr ϭ?Th Ytƶ8ƭKPpP;wvs^W [4f4m_]7Ok㑯 j}u53&"r[dk\:+{-hγ#p{@rgp5:BB:me,w;Py}QQ2byn Z RvN xKgxנTi$MB^v'%QqB::Cs'a({n+S!_?\Z6 ޹O 嵶C4cV&$%Ew>߻OB] OQ  __(y$O]ZJNmF ~lMa2_t`Y7%4w5%arPL|< ~BX?~AQe zg9 tQc6yGl"GmI5Y_Fq'?6Yf股vQ#/:VmT{נszgZQm< ~`1ܨE?MjD |Mu*җ2;ό:GOEw?}zk2#W S&}mo;;~(KΕٌt0a)P-[:GO;;:T²L:\9ϻ*0 jn$(8H/u޷l:mSܺF<0B[XVqȊV瘞NG@ҿ/>~z3Y<٧&7 W S&tk>?("RGp@ApH4PSܾu~l&0>3U{~L~Tmq>_^ߦ}5c>=?@E@q3ux;%QrDj_,a C=?F<0B_LLn6?MYզ{UOVn7BgSgeW+lC5]Z89k$Idm#"qvEw?}zr ~E54<?("rg qo%ĉ1(zOF0x!5_c{KxtDDH^ڼ杚5ۧ@ -̠F!^VY I꟤aُ1̨u|tQB<٦k\+9%9rm77,2LhMvFWaL]W˲y}]j[F7y2P M@VUQ=y)v~>\i&khx?~PE ᾘԈ;&ao oO0:૶oO_x:N*T BG;yic2GEQ?@zRZ6 włWh>$K;6 M7ؖ2fv_[>=5wfw$&M޺qߘ ?(: AQ4n=Gm+n:IV~01d@E@K2%15Q$,(YY31 ?(: AQ4311o&khx?~PE nLLL?@?ZsJj{w֭7oޅq]1Lw;8^TThN k |MC_+1%E?iO<6e J%$4T68$īƓxr.?D ?(hx?Ui **'ߣESOG~~YF+OEnu̘1>+잿a3iP5Wjn[eIA$I1K\vr2G },.*ꜭ-tsgbb"LZux;A9u"p#Q-ۯ#<<ʫߣE$tG ȑ#RΝ;'PeggߡCٽÀ2x \Q!qqNWJ:?r_*nz=S6P~ߌBnLLL?P-7zi5Ç/u3ֽM23hUTT@O>zQƌY)mH ؖҡIO[6n< 9>QnNYPg,#7tֶvzKNZjŊ<駟{v~jw/;Ƕl41u?^aZpCPO$'N8 XQǏ>^4?+msi~nCCkB1wS\ %5m~ccONKZgt:TkھQToJ߸Q$"&Faa"9Y2/gX++KoW\)9zԭj9=q͵B nLLL?P-7 IuR :jƶ ~ 4Z:~ki;%f6߼np;%h"Y X޶mO?\vxbM&|OMQ ?ys mH|?O(曵C{ЬC> ~ENC %ǎItf5ki\BG֭%(8aRe?$ }:l:ֺWմ[v6;ڏ΅ܙ~7*7 Iq-ұ@iժ GmkHz1R{#eiӞ-] [ۛ~aW;x9{C۶UJ~FIJJ:eim\Cm_ZTTS 6jMeYmp f:Ȅ [֬BCrdd0`D|رq]B'd2}%%e6-C&N,A_c}B}~LLs22oҭ[>_زGLjOIv]w=}~Oe塡˓{ݻףǛoyc?(R5l"!svq6i~۟PßV>^.8lÎ˗E|bP,?`~nl~nǛۃerEy9( ?hǝb~>o3pT}=}/ը!ը#*2i3]jܗ^枿Evmmx\~!͝3^|۝u;Cn:b5QRrL4%8 ~4nZ,fMu}ð'{oVpEBB-o/5uǭ7Ozlu[i7"*Q}#B$AVw>q\XQqE H[Ϛ^}XoߵU:0Ζeg6yjUϖewo5˽5yyyWϞ=Ґ!CκqlII69/]*|AT~VCR_WZj3P l1*srEiDgasNl9K$$,zI}|#FGoӽ{g`.m ş}f`Q25IvعS_QmzQ=b>W_tX3QM=KSG|8\zK>o!tc^ ~XǮGNN/;vط:G#Bf[F#?`j䉉66>HOC Ejx1!F;/L;(S۬ͯΛ;Z쬷a/,n5bE*da}<;ZHv}6ꭼjo ~{Cpgռy\ r}7{FztO*aYYǎGc[-d[e,T_ָukacF_UNV_VBO? FW.G4(Ȣ% عS_˛w{\~{2ցmsmɹ_/<܁ժ(fu~[ov7a Ճ5vXwd„Z'5] ~X_5e=Rʜ9zďE§n2K]JJj#*hVii|5ԩkݫFCѮ?Ԩ~߫U0~ҷ)1{G֮]{Ճ}Wrss_5L=nuv_r/je=~KDL^κuh 6"#eɓn?Tu9R-6mF̿}Bauǫ>jܮznҜ_~~Uys\Q!yFiW]Lo׫,z -=4wvL59}VSS˛Oh?<{ u Pzͯ.w>71۶UhәOuWw0ʛ}uS sV8 ?Wf$H+L§ZkXw~ߺu77eܗ\>_5:bRRmYYY9o}6o{Rm=v{mF\k6ۨp "EO>ұ;HNcxx'&ʖg] Kb>;mZ*x%ǎݏ ĵk*vt6C5n,i T<V#3F mUxE(u0mD W/ر>Ԩ(y;uҷkWUV͛Au V$1%E2/%{ج!TT5J7C6׶o `MËo~u̙[+۷\csZ)z QݷH\\;?yð+ouؖ-gGmU,맥]Gu\3o4A2;;Wj =B{靪{U\Zc޺ǵ O>Zi~pó ~(^zKww2nyxUk|!{ޖ#zei7z]> ~aWը*oiMll+ILLŲd7gd+qq b4Kvȩ|G=?g,X[o/yO: /jnnk*=URZ6ڄ wǂW_v{:~FC›_SE֭;0 Naa@ @ߣȍg]o%kg5c]щzd۪:tD1o~ˎNIIi2mZC>,xU`yiB:@ ?ߣ}N TKHbjDK("YŲY?:/;:YQbc[ɪUF ~;j ~ԁ_vt\"/F5=Ee˖t#CEQ?7T"EEQ??(?~PEA"@EQ??(?~PEA"@EQ??(?~PEA"@EQ?k\A"@EQ?kDf Eu~PtXSL~,~9cEn(AddH{ EQD"焀En$E"焢 S@ !X@@ P,~d (~kP?`2?5 (@`0~  0 ?x L`E &@A^C"@ iNkSZ:˄N&l,Eo'괯sw=J؎3$(䆿? ?x z~R81kUVBo&@A^C+HDH(/Pt(c ?x ?~>O?7טPL} ӂqq2)yrB_A{-WmT[}}Ndϒh ܤ\9:hqFIk&11&ɱZ{L=`:}׭I7iTFܻu٩gm=}ϷY֥I}+#_51`mSQG?b2?5~9f4F!t ?NQjX7h.g^we*H_MJ [T?^F-%!:ni?If}&v'w9'k `@? A3@ Fݲi&m E=ۨA,xNO{J;')G_YD3qyf3<զMՈjT5BHQ"}6Cu?/\^;2/wqj; y\CFNN9zr{m ~X_w긖vYB1~ OBK5PG'*FQ/S ˲֑/Q?2a>}t{m^HP.Ihpԗ}7;1k?T",8Lٸm#*_l%Ad (< ~B ~07(?-~̯F,{w6EUyAͤo7,n?qhz6˓ck?>o/?}e˴Xk [?p=3~ ?c:S@@?TW.z ~4k찞|iܝlFzVmTl}amu]E=ٴixS?p2~ ?c:S_?Z4j!3*eJe#[lwzi  ז%$IVᇟg.Ihpԗ}7;@%Baa-QG2늾,7)n>;f;Hmy~d (~\OQ?~P52a-nF_cam4f[UwIv"Ɏ[wȳJjT}Z&'OjK9:1~ ?c:S_?cQ<+1:QN=[csޯy|#H&50bPPTo+UɅvѯY?Ek< FDQU]tx1~ ?c:S_?T($HiX F5m,E߾gӞnC2jT(m~*gT恛ePAZCCUd+mŽ˞q9q^INB/{ÞAev!HDHRQ|;[JF ?f2?~ؿt(_ cP?LE<A+h#g.8,wì3%~d (~\OQ?؟_R+ $Ag&@A{Lz"GpPn/SW˯~%$Ag&@A{Lz" X@JB=j @ P aӹ~Pp~zd2^JMp?>[n˼y.7dwq񢢢5G{`|Zg`mu=gbJ~.Ӟx¥mΝ.f;JHho#xxO΅@BFںY|q2~ ^UUNN׃_Ƨz?e͚5w^9}(Owcƌ0|7w:o mkھJm28hD6i"pkNn>\(=aZ唔:gCpYgUvB NS޽a&q?P˖fۏʑ#G6jy~~u7w:(?6<)zv8Bf1c|޿M2,Ѫ詧[Ǩ~8:!Qq劄FD⋲_ zm~@M?Չ)'8XoÀ !_G۾󕑯]𣾯  C_ycΝBΝWСC o0tB0` 1C&WTHd\Gիi`}׿ʄ[%GނS6U~ߌBރ7YkVO?VuD2Fx!6?oǐ!shCηYֲeW>zQƌY)mH ؖҡIO[6n<0Q\|Pz#+ˆ '6rrIbb&r-wʪUj ,YGn6mN;BՊyOSag_322NLhO ӎ-[: ~{e}>}d:/:lP!&IN;[QǏ>CX=,k٥kRK.CJXdDĚ_W#XыJGmP횫MǩS.֚ONKOڪj[eo(Iii#ư0i,3gja镕on=VXB}윞ڸs! {_ Z ]gHmG p*a]^(MSwtn|FZpFc̝chQ,o۶Oo]V<1&iǠiuT0Cui\B5:Oӟ< ~f-P4Ac?,p;Ѹuk vT Xؿi(1n֬ƶ%$8 ~ֲ~}Uk+jԭvy;n[B@ ryjnMIb 6j!qeirvY L8"I-a#_NRRU w{dhiԶ/'tzn[u/]uimR Drh! -ZーdXaڽQIɓĔ z,_k;ξ*msTΨ7JZ4 ѮMrlv3S p[cذEN;v\:8Tȣ,_~@OF0h2qb 4fʕۤcb˔)4e-SF ٲGLjOIv]w=/&lTN}\_H"?ϟ~޽?|&鯞/j~kˢEn?Tu1B-6mF?{|w*pGRP ) ((cm/끋D-F3QL18xi(o^^K3erEy9( ?\iSۧx1HF FWI;vȬgTWk>\6/}Tkn[B@ rC;qX1iQ͈*4:|3bzt?TxA_./x7ݬ^CLqڏ G43/zw2u\$}9~{''x pۧ׾R?22; ~TT\~+ƶYի?ݿu(#<γ5>??XsۥKZ(zI}|#F+mUAcX>UEk,$5cN}s.G)gy6zz.?;; y\CuW#i%nosVyPfxBxfC-U v?H͈\}:S,726wƾCW\h?#^vUo0U#XBP5ٝ[6 ܤa"}5"eƖG_:ԭk3sՈjJQ7X~`< ~df.qPumk/88DSL ~t:f:U` mʕW&bMΦAc$'j<}̔_~&1e1L5Jͷv9"_viھT\/k/))q+c(e9=zb?2,q;}Hmo6#$DL nPAmkZ_GWںO\~5Qtx.o?mx\~4nu}rg;ҥIf$q蒄WO.bYI߈143*eތ+cs۟[A&/??MxfY7ېkGkQ,_>dH}~ڼ-QᏰષ7~63}QDE,.?|K'#~ܥ6lͲv;v\˥st8,Gjj!tc^ ~k#''OG8vط:G#un6e22^qY_fYTӦNP9}1'&k#|ھ=*Q6m:-oqRٳw뗗_8}ٔ)tz K-_~@_'4ׂӧOf޽?vd 'Xiֱ@&V#E VY/+zD֭uޣz'{)Gtl8c z!̧W`+ lF~du钄V}6gܩSwmvx|.?:ޟͨ٪򠖭￾my:?*u ~X?TTGղReygxOӦ= ѣK~m0ѭ[字K.%%Geod5IIi?TU;N}\;~UPDDʖ-g]~[w| <:?fϞڵkzd }/(/oᇪN/11z9i$oؠ 'OP}H{۴I2 vov: 5"JzQc#-U02?ys\Q!yFik]Lo׫,z -=4wvL5ӂ?}VSS˛Oh?<K?H!A!re}YnRn3*%,O{Z__@C' ~ȫze#cO?/UoܕK_B0qUo0?6HEڵ,+P֑m;=ց!&I:tw̟ݺ6^[/ϼv |xы5B7n-?k:IC Һu77eܗ\:ul٥ƾԹ%a2.9rĭǾ}v=`ޤm[j.|5mT8zR't[$'1<eٳ.Zvs㝶ыk< FQ ^ ~ZKkkuUx{cY9Y*2.Nz~fGh\xӴl-Ȇ$TUoFl\*ng=#}mߌIo~vy{۲/gNarLUX!kA`+guzG./Q&aUzy #ewn4p6:e kl;)5c=6ثi/ycģes{I7@iC䦛HhhDFɀeomҳOhmu̚;= ~*/ 99%Ҿ}?1$$$T6m/M.úvjfJVVqU;wΥӧ3LdžZPuCղ>~ILZDFxcWx{IgI1QƒVXJUjjGGPYb~#"j~k&k#1f>PmT!,:l6/ر>+y;uҷkWڪj[e߼Y:ݏPmJSR$cbYgOKUQyj}C=?msmֹh\|oz~y%qG|3 j.MjRznMT҈\:V(8^}JNFy?~GF_>j;xσT݂Jff9rJ-+{^>x zƼuǏkA˿?r EޞJBt.[ *JCc%:4ZbBc$}6Sl՛weY6C;NrJJQ#48TGiiۻz=}P$HiX 쨑7mҬꍈ=ӵQ)nA-iVצUd+{-=C7@ %==}|FFƙ |[o}w_̿D/:u^xL&I7 w%iMp |,xUi׻( }p~G<﫺:jy'7#~2jsupD67@`! 틶myo\yj wYQb[Uq~GZ4yPWG,b!ᅺ2늜~^΍;k6"$BL=C_<G?`\}W%*Ql4JDl$HVql9{kDlxoM>>9Ͻ  (o`|??L(70|c&Sp1)`E oAE@"7 Asz"n X@|w a9fr=E7,~ ;~3~P??~\OQ?  (o`|5"(~F;P??M~?Ex/7 vh&&ƃ GLLEء7C311o=`fbb<(z?x0P~`|;4@#vh&&ƃ GLLEء7C311o=`fbb<(z?x0P~`|?$Ek\'4@s+se*s3(rL?5(pfbb<w(ߘOQ?O'EQe"0| "IE)~b|E) &pSE?7&SL(" ;~oL('MQEpw(ߘOQ?O0(.7 Q߿1~`7EQ]oAc?E<n(G~$~"x EQ?w1|"IE)~b|E) &pSE59I6'}Yŋ3bĈs={ig|e}d{ 휢nsq&M2:gQv5zTYSs7\ǝBp EQ?w1|?RSOJ-󇺠+W'))IfΜ))SݼrNεk>o|D'KƍtJDRbZrۀ2O3A2cئT %%n]i3f;}H'izuKI!QHa -X ubtr#?{o;_UCUmg<;vm;^WzԘ4i9544ܫ%Kӧ9rD'%%]HLLoNtlǎ~6[IɒҸq̝{8V~4e>oTJvV+111SӴwt}^>g?۴1Ω|nM'uTp#\9i޷v5]_ŕjP`߸B(ӓgic]DD[,ҷoTV?|+Q\ȃeHy'I&#G |i\nᆪF]q\=RJ%c/?bq~NG2oi&)]:LBBvj}ΜJΝ!f&M ծy.rZ֧^|H 8>LpdeԸ/pҵkpMzCo_Z۶-֭OJKNzl_߁kS;:+9X`|"~LWNNȲ}zj =8<ι9rٌ qMu7W'?>gݓ'˳U)kg۷aIU?,یo(T)}eK@6,#p<*#A+V諟L;pPB=(l {5(L?߷sg6`]1qn}ŋժ!ժ#*wRz5m]j2|s}U7uX~szaW4!>Y_Mvu%?\O(Wloo{Q6jn~z8GM73\@ʄniUСu6s翌}*J(ߗF:U{slG}W2j-iim[ϕqoPl0|QS>|GG||sj׾ׯ߲B~mVO@GEӴ<[ΖVZ;}hr#1?3S;JqZa;}_ 7wh;m3JC՝CÀ@}ދ ϝ6ׯTvlΩ}x]L6ϝ#6mWp] ~fGc!ϥj"%aԪ &[bVشiS+^FJ#*l`ZAmtcc U .^㵾 6|dSsWo7wpPQ`e fXWsAM bO<$z>{A:{vޱcQʖ-m{)"͛KݺqRVUU:TDž 6TX7%((G^e˾kxܗ硫QX6rqoPl0|%qTD+~XoWUNUB~tOwW8vrŏgOj V^q]G$|)V/-M$$HXټv=;pGEj1^}nBtGAm&.OvoW_Mҷ.a:\IR4ф.]ZٜV;Cs_5]VP W;noٽ_},ժE}m.>U %LjԪ"5oܸ~멧*"?T:~{׮]uAZƲOZMz_˖ ϝqoP,0|%qԕ"1y^c{pp)? ]FF[um4]wsvlۓεll׿$08XCEܙho=tJc둚j+S`Xb^T)/j^ ׫ym_[d,zUxP J#9FYv]`Uqq1y?ڵ?\nwbZ%@Pf K͞=PRR_d„Ќ'˔pXЍ7 ~dg`Ծ9sF C8|x{RjOz=6 ~wLc֬6Yb̹]&NQcС/Խ{͚t7g'h\%&!!kеk݄ zu%̘zltMئ"uЄU:Ȱu&D K:y\b~ݻ lsȄ?Gew=3g^]Y8)jbEl YZ5lh.On^~}e|PqhÇߠkW O=qM:l7uX~sz+St~8򤤻lSaӧ(_>fO_|Sa33ߕ v-.?*U*/99RSG;Wh߿9FXX/9vޕuJ  ڪ||7];ǦE/>b^{?sf:8^@C'(/ݹ7ƅs{ٷ??8-fNȮ>&xwLCUtt<"<<ʣi;w>Rw^g>:ߜפI`v9G13UhGڵm'YYN%y l:&cptc^G@U ȯ,'_Xٲy +WN_It uX~sza=lS+$ |g!]&^~/\Ȑ-\5̙.MW\̝;ZDnڈ:FV9zt̙6kvKUup[7WT_}-տysy;~|1KycnY 0|O?M; %JhhׂJΝ3';;۩GffGkݺuoOsv+**֪%%K@}bZAĉ2SX5>:&!aaRLi֧23UWp)4oҩ>P~P+JLԊ' G;TZQxPRE %re=ȓ8n߶OK;۬?p][? X{ٳ?'u[7jTWn)aa%;..&`U&߶:3a*bY Zj׮'/۽Oݺqy}fT;؝iZ^O*0׆ uĺ7+c6ZR?Kwq^d,,K\IoeʜIӲ{,,˶˗<(Ε[۶mv#G\'qiU#!!IOsg;y-|9϶YǏAw S?*e"K/M'|s-}|PP)!͛+L?rOo5n9{vrP6=o}xDDH e֬c}ߏ>hZ=xդI=[ntvLW,X0VZ;88H*W1n\?ٶmW:wn iCz2p`g|g1vl_V-Z4hPG^}u,Z4+Ur=Rtt|V9k)GA}\6s/wȧ?ȜmH%Ƞ Ko7J=2haGYg/[v?$$$JLL<3jԨ?q_ݻwKӴS8o|$͚Mp |ڼY6q 'Op_oR[Mfvc[Mz(}yWzDV:zA^> 2qII~|'/Ye]sGYa,6}oA#jZF$$$g{sGIES+{ʕe'r]zɡCNzK 3g(j&yizC>6oNW O>>ΗoyWFFJ|Xm=*̖2eʖ%yr,,y7 Q߿1h״GEI@P&Mi0+>" ^={V S&,P5~|i&*#AA.MԓiӆJV{eoo[d¦S2aIᄌ˘7ga;%-WY{~`|?v|L?~P& ~PE9YyrsL<Nˋ{e_䙏T7Yn>eI3ICsƘ1~YEC-&SL(/*ͷ%d_wd˔-?MWYq@"VGgԓ+?1~YEC-&SL(/jߗdƺݒHedge{ \14Md֦i0Yq}maai&L"~xg ) &S]5iRX=\G{o;|^c9{{]_7qQ0GOyhDy&Kdg,(!oi,ni&L"~xg ) ^'ߒiӆꕒ2 QD]rw%}ݫ2fc?KƬ"۲+_zI$S7/'G3geB@;?SOQ?ORٳ˜X^ Qu9wnzL_Nږ#3^N''K' OmdkFG?h&L"~xg ) >8wn?d|[ȹGLYr.mbh&L"~xg )b3A?vKzr睍\H  ұ}yBf'GDIHHt;lXcȑmխg۴iI۶-֭JXXI ԩ[l`o{86'#7o4kv.wk׮&3g;Y_CjGu+q/ _;:N:wS\ݻG7ʸ]=68vyתUؿeKOW}] R|2aSw^Sqsa\p&&f"ʄ(kw~0~(e4o00{HX5\MvwtlhhlذQu9'y?/ux\jr&PU[nqվt&LOD>.:~N(Z^Xu=۵Ec \gpj?k|(_?+:~$~"xXL4cҸ 7_|sY3[7ӧqlŊdɲqBiVcZy᧟3}̘>.?TV])=7IJ(al쳿^jl/U*Tvx(:nN1jݻ_W}]z*dVT䓗宻/Z4ΥW4~V&0IDATt@0>XE3(|FO>~?-X0gnYBC-[SLl7_طvc=+]-BBcbO}ǎmu+(wҙ3j_z 6mժO,AAyBտMpFm믏R6ʷ朷~FxZ˵jU(lÙׇ;4~V&t@DOVS~78,{[65IIJ"?:t;hh[lAmϯ,mn+j{Kg}ٖ;0Qvw5_wߥ#GH~>[Oi6tb1AԤ 7kʈeѢa>}FG^OJo߾=22\ӏWMV'O,s5y1q&-Yl>rL~ClICGnὤa:^ ~8W[wvM0P,]:Q_5iӛl:q-QP״9Wգ9߷ٟR%Jp)~"5D௟ ~IcϤIWW %a't}]gm۶'Zn%~࣊Izy&[jƱJz78)_Xm~{'&60A U99ɂcERL8Ueܸ~mr?i}}No׷Yr2=3gXm6V&k'Y z8&uCkI\Ts_jj-*ewOho=ZWX3PXm\Ni+}pgdjNryW  H䑄9=oG3fLNNw 4hi#M?Y~?9(Ph4~Vvrb˃VPA_/v;*TAAap?.Ճj{@}W dsKۗcleK?/ZGݳZrzrO?^rX*`b{ҥl/^V6or_[_~ Iv'aÆp+y=k"b7EQ]_?+;91€*pj\d$Izv;ѧY3eҶU BxcQmC&}}{o},^y!nخVP+g;lilo]q| ""di߾zijڇɉr|`cFOqmaÌm؞ڣݾU5C}ǘwT$JDĢBj56TW&*?wWVV[ 'M?YL("K3eWR.M{hyۥyR7:ZjU O wg$80ؾfxlX׷MJ 8mMV||/]~a9ym*\dl**aw,%E.۴qՃǟHMThr|xhcxtl8T{}[#*J./[fSA˾2eK녅ILr*sG۶m? t7]Ӵ<[;wjJ]ecn(4_TNsVp'ԩ6QHX[S7.1QUpeZ`¿}|oL㸛bbkg~i9luUΞmڕ*y=׳JrcJ͙a9o9޺oԯoPkUӕJ8}ŗKVakǗ_~)-Zn۟X0(.D;WcL^^#{"-{Z7Gc{of0(we%jP]dIYڷq֭?ʔ*?p?Ttf7KЮ׆ shwM6>0GA}`Y $$Xbݻ{xᇳ222 ~_nРZ?QL("K3SMb58q6-Mk5WrV5lhloX.]}qQQ*cxeߡ3>vtP^HOi49jq&wdR˜?_B1P%j wX=m/LQe\㶚5= ~$''b.i#͗9zzGoa7EQ]?߯E6:5K&%I%<~Ӭ=:2R^0@6!%KJ…9\+N7vl0S\bYSjU2f8"izr| +DD諉2D˙qsYO>a81ϱV[U髂IK,Gu}VyQ+c3Gfsqf5jx5kVT:rK/d֭Oka"b7EQ]?߯o̰ի{%Vg:GR^l>ܦMM9FȡVP T8#cR!vptOUjG#R+U4L.?TՍs||ǎ4m?QL("K3k[֡p&K#FҥK̙3%##C233Ǐ>1113!!WS ~e*.5L("K3b\-iirh y`9`uEɞ={ԩӯK~j۶PV!'"anEQ.0(.DWt/ƵgҤ<}w;*?DE|)~L?~Lw~(!˗]_>K`?~PG1(.DWt~\y-4(@c7EQ]?߯NZi AQ?'EQ\4_1ӝ"q@~pU&11X m=.L?~LwǕO"@ZUkV!NbՔ2h&|b;EkEؐ rs'?߯NZi AQ?(*WTm2'&F;h&|b;EkEDG1r)&FUວ~+EQTRE,"cbe_tTwf"+fS?x~P|Tl+[EWK$\74_1ӝ"qlsvMW6~|g/^Έ#iңG?i{;h5j{oSm.פXS,ΖF 5kJ`p~n@`WƝX~P*UjL\epL?~E"q@#q Zrd̙!?/w\vshlBʖP)WԮ Z"sGml}ȑ#?))Bbb;&=uJ6hpLҩsfeIC?n*mǏkʕI{ /_e.Ipɒ9,7I;wΫ}Cb,&Vf/UJ)˶J$f" ?Xj՛*-Pu֭[sz˗V˖sQys}vɣ+.^,=RS8)I?: PGuT%M'b %5jHɘX56V3_LL~" ~nfI\\ o@O?uƏ&u붖0)Y2R7NsOriР~}u\Æ۾i/zؿW_E9=}ƍְ,wWL2cW>DnlFn+'ԓ' XWՔ}m3KS%%$,L%2:ZnVIxwSdsݠy\EUy\u3rJ}uk |Q+nozN$͛'55KKPHT][Ǎg9~ơCRsg]wG] K9: +m:x,?5U@lREV"DGK8=DZW(ǂg$& AA2|)S&FJ;)NͽSRIDDwNOtW| 3ιmcZ=c-6UC,ǫUG,U0ҥ2djkaÇe 6<;zϯo\mxծ-11z[l2oDŞfb"-+WX)iig%00>dx/,sIO1Ή7 Vߖ~l?gwG=.gee.j*˅}Ǘv&vImWS㜤yme-+qx#VI1HMY P lXЈmPAJ#j!kȌCdŋr ql5^~8~wqT`#Whߵkm*bl6xS+n׃Vǧ8]j6"6;Ǟ>\CիK LlJmLL~ Kå$1u6JM=isN:/1q\w9v;}jUmƌC6=[s*UG۶m? t_4MSjliժ\~TF._6U}/BC(lb;{|NPuСy0 0P_Ez8$p ،ou_: ~w`sN;eiJ;f6; XL<~47bE31+^y-5]?< ~Xsdz3_Gntwŏcǎ}-VoxN}}\6e>'ҩ?I +[6c>{vwC *c>E2eGAm&C(*M31+_y-w;Oiig%00>tZsV׿r9blWU$ kA}Vcݺu5M{j [fM]Ѓ+Uc_Tc_5*K̔^ ׫ym_;(j\q (y%v}J#,z.\૯W2Q8j7a{pfAPuR&& 4i_?T5k- #Fl+S,) fPոqհA}dȐRZCI?:;99Kݻw߬iHwqv~K۬Y IsBdSF EyYre} \ ~A['=WlkV%Kd2qۡV71gLCI9zT|3sq͚xIV㪢y\-3q0׸ ~Uח1[76|~O v5PA YF6u.o?ixpar?EH E??Sbb9\E"((TPu ~HhH2eD΄ TM;p@_#4"¥GҼyrKNz@/jE۴Wp3gWܣ_CRsg}[ZVjZsrׂRE V4qdm6O?xP|]nQߦ7]m:z,?\oOQ?gi&&x|E?-g۬Y% И<>u?mNڵGOݻ׫GBB“~WΤwZvrm׃$ƸsgL(4i_¯5j4_*1jfZ19Zn_^gF"33O_뛻wiqIES55WP<X 'P$OQ?gi&&x|_?W]e^ ~?\4-\#3Dy#~o}W$)=qu D),DZWG۶%.GI@@,i{y,\ѵ@&QoWM*<*<4ʢ~@ar?EH ~(2(P0~LL~ ?( ftPYޯ_AE?* Q\U6g?2pLL~ ?( ftPTX7&V$&F#9DZW A~p{&Ģ޽%0 ϱTr׮{6e&J1,|LQ> p rL3S%1fb"-?{Eq|+H@B9銚E:R&JAEiGoh*x<+rʝ" d7lK|ޯyfٝ\ʂ"AE'"[,I%Ajb -PY AQ? Hx5UdzNNO(b;uWf 6efn^H X UK.9g6p^VĬ&^E#~XF՜^ކN_q(=Y2-y05Uٖi?_SE(/5Vi\ʂ"Ak6yy*?]+Ι#F{Wu]v.?(S~PU)Pj<bIIHpGp֡|jP7,cݮqzlnPcr8P:0rl?;|y6c##Uw+,X lT޴ug?KڇZfE}zLUÛ'9M-򃩭Dx>5Mrd51 ,| ~j t啒4̑ݤCm| ~rFu[w͝>UPS8o"?ۅoV,8aCm DU(躰Pi!k m޴uퟚþV񷭛cM?}~xSVo)ir,cm?׫'ѩ?5UmkefSɒʁ@Z+ $| АխEEߏ,Zm ~/Wm*ezL;aö+MvY_5oYJ ~[~ϸa8i~xzRc) fJ_SdmYz\ZZ?.qYM p* AlqY͢fTPWM6ͯm+qzz 7`v*``[.aa6}m߱~b߾5pۭ3Wul*Y=xN Y_OiUƍs{]Hcۈ{LO?uסS侑#)_ݳfIQ爯7fbȳECv)3R;z !zl6Ppߌ$a:TƥY,BCBӝ,No @xIqqn+oں꫚}#S.&N1-d,Y%y+ ~ }<{wP&$3w#åiJKl6PuvFY=x\ޠT0m32L^9SoQ}'&ɵk}?lիի;͚I\T~[jf 5S'~ZnT$^mԊsXz]_O_/6uWn͚rˇy~\=?,5[T [.-yiC))W@>_/m< qPL a60C_jT+W@?~P<.R4&3fYhȃZ=ekZ}tFs^G H< تUܹ ?ʨ< ^ 'AUUf ^ z(9@0z\D H0虢~P?p g"AqWE?)yNL^ z(9@0z\D H0虢~P?p }EQ~T z(9@0z\D#(2P0虢~P?p })AE<'&\~sOQ?T z(9@0z\D#}"`3E8 ~ +"ASUPUbyjr3j_{3>իIg޽]@w%@+\~sOQ?w& ǎ;o߾?/XCرc_sӦMk׮gggv_  b~7S% rȑ#Χ~Sݿξ~=M79$:"B2dP6~]\ N2Te@D#1x~/YME~w 2ɓ'Z~ӥK|ylXBoq(ApL?A ~7S%nٳ>裏iZ*~}~/(LGԄcyz~q?/z(?)d7p-ZH|1zVuҥ8qnN^5哻t1q߅[gϖ ޝw۫_MEGKdx4JNo߯_7oCUKblDzu٢:kVÒmFGE鷙$B>?ߡ6=I;뚦*(0/X~]v̙ҥiS>i#߭Xa/~NO'}+@)!<("*n:tȧǁ޶Z/ѕ+ Z0dcyjmj4 2v\Vvu%KJOnP,0hF˾?&PАbݤo}?T8Dal?LK#}+@!A(d7p8q§DZcǎfgg{i>? 4Q3m׵Y3cӺv5/8P_]w}#Gc""[n1J͸ڍhfP|xXf(> 7͕?O,;ǎՃwmob6b/(Lc5k?jv9sBB ϝ[ǫh?%%ad ~x_ P~0OjNRZ۔w+jemkܗ|Уm֞<)L"406T\c~|/?MA+Νst1BVkRiO[K hMݺw8l7{de֬/ V0m㢢C"Í67Xf4`D?g6lg鰵]=x˶۴BCՈ5{ł3rr~pmVUYn׸qzdVjڥ}x*0 ao'LmHd} DLnP:)wŶGwiֵԪ__"UђҴt:UV=Z.~+}Mk~NXB碿S *;ۀx/~} fx|=7:Q3.Nsvm"u/ -gբdܹ%PLNBEK͖aK7ᆢM y?L"?C2lXuR=^PO.MM)&BQ*2ɝ96 ş^j?UczX~o>ߖVXWmxB?}:dž(k3~tM:S駟~j_Q3CCuhm]ܿnA߾2[7NPn v]:WS ڵs&6x#H?JyƏ &j"(?~8%Z#W;wʄ{۬Yj7e ~DK1cd2~n}ֱqĈ2=Tކ%V}ԩ 3 nEʧǘ1cbZ']o?6 U$ҀzIIRV-cv_6mrh9~b""i7x ͛(y0kO#UCФ4*`n{5HI_|!,T$Qf*!Ql/f0 Uj?J.sWu[KhCj_~xgW }! 0;w]C pIBZ_u릂v9zde3eכV;wkgW `p׏ɵkmF},0a֒Le[5{Ⱦf)Pe^]j,QNj޽/(?U̙>k7z͙#[~bl;t&w{ 5ȶԩnCw}>cyDL>3TU6H>gedλO}a4iӆk0MC_~~#c!!!oliHc=cnj1YClլ#*2lfc}ft/gOe}L{9xgW }! 0>}!C/iO?LϞ=}{ XQ#Ywu˦M.ah݇INfGE9 2:q^?}P q-Ȧ_~q~ƍ{{):ZڲYcf()*4}z1ӈ9bΝO\og%Lh[>fEnqv3GFxS3׍?l_7.V=p?ucm==baAA}]p:tH;=ԿfΜcكAޔ}@+PAS b~(Vϡ*pۛoJwKLpotݢ#G$qcaoѳú[_z̙R ~jܸ_֑vqnP~ۥ5*1գ6XfQo_? zJ×~2m]'g5`hZo4hЇ{״.[o*֯O,&fP3`_P~dKPL?@G5t&c 3_zIj4]3eC@2?CϿM%FbK 7Jpu_T ߲iBt\Մ{+d>YuY(j^#`X߾b^Dp 2z_T6?!B^{̼zuRw~k׮ 'O:]bgu?~KXD13 2~dxnƺfsa9k,+f@z:}o-G*ӧnG4*Ȣ矍IՍ?lY7.}vy|_~ݚ$eJ*y#4$D֬) ߶l!A*o?}Pٔz䏛dʔ\iРDD尰P}ݢ]סB67g[cǎطoߟ,XpСCr1QԿiӦ}ٵkﳳ z:HfNs۵K͚%QQƱ0gf:l3lfͺvgİ/6*Gg ZJd7HIa%_}/F2ed+?e_-.\h^%~K2|VcRws?U7j%S}V>~/։۴oݯ>V-SuFv./}v@$ %BQE#0 p{$8AޗM?ThTd~|Z7^Wf rȑ#Χ~Sݿξ73ڶu;etBkX J!S6 ~$7j8URF9q£DJӦŶKJ*cpu~T$1==`u,Tx]1_{5jkϤb*}v@$ %BQE#0 p{$8AޗM?DGGx|-rr ~ּ @\6EfC;2dȅ'O'vK.\>zv} JtjW3(=[_Ŷ)oHU"cc%zui?t>3'U>P#*>ޫGʕҲwo=|UA(f3Q3Jxz ?LZ_{u<[/S׶Uşg곪4r8EDHBjə1CfC}?/U-zԗJ3m'P( e ~pPE5`E^+ N(e@eSa`|V ~l[gN߿f(꣏>jNx_x.[䫯 q*p~K,K(G`(ZIpG/*R ~ MK ~YA,$m֓( $5Jm|}LԬ'z#++Mf!'o0vuAB)׭ mQd6[c:So[փ^_~e?GJHM}q"* |("kV QFˀʦԂ~Tg?,y;䃿-r:vjQT*8#?بr߽?r|Zyyr5Fkmm11zlf tc8q§DZcǎfggW3gJFv$ mJydv>p~ʃ?8_(~A0"n'~2?>|<>nǩ:wPm"" gJF{FXcݽFyzut@6M Vxm~~ax⢜=\9wCcĈbZZn_~5;;W/NJE?8_(~A0"n'~2? ~|Zmfƺ3_}VWß?>cŗD~`,(FL~k% e l޼\TAf6qMj,[w09},[:X޵kr ~L0ġC| ~ٳHΝ11E?8_(~A0"nZ>fc]U(*.=^G)?T0c*Бgv(y Bo h4;11Xh#Z3f_V$_t_-@'O5SH $,"Baa~cd{]w]PܧX5{`Y/EQ?s @JO.ql2Ib$i2ņJxp2qzi"}_T6.jЎiҽ{ cyBB8A~am_7\bX/!!!N_}aMW_]9TK,I0`oG*ۥKV5럷8KJDt7=1xl @Gk|y?7O>[yr^ PaC/?,kWkgX$,4Է)ٺu+((8=hРVU_՘1cCn=&=sP7ů1c:6AQeT B~7QF8ѫ՛fYT*Wzv1?@T!Ǿ_/QQ)N"զMMSOJ/2wܻ rɓ>vtM?y3}; @>d ާ`+OЍx[@s76E?[z?… СCr1_B޽OwmHU8__)3*jIT3=< _6[Kci2a(E?T߾?n,!*(I I;4T}tk$QD( 2yzx#..J""¤n$_ݤNz(u:#d횡.j99%>>ǐ!~(ӧO1dȐ%駟ٳ翳o .'yAXÜ:尾2?JO&X׾=෠D`bB],>n#zPi0Z(lkm;!"6m:2tP)j}^^999w€?8΁77#jТ?*f|&Q̲;5Uj  g0֭uSnK_>|Ʋ+two.h߰6}aefY$22RR ɳv[v<.:])1_M/:mdhv撻}4u3رJh,WlԄҸYs1Ց~ˎ''T,u뭽 _$L۷Y_ sa̙9Wܫ%'}MqοUJ33%fM}oʒnfɚ'nUC S}*ka*hV%\N'edF>rhMv:}{u{c0嗥UWIv=OoZog<64r}Zu~[5X5X/~dW[VA?Bғ#fΘ~*qIjׯ_t%? }N<<`, = }OM|zP!?"ǟsXf)PK0*A#GϹyCoj Ŗ_q5=2u㏍].1fךFd]'YS6|p z/`UPf}>~xwcQ-?ԾU~v]&/>:un~y4.gP3_ڝ=JVNHR-6T"#BQ(1?373eDHJp"Vn$Gql;sn[3b,76EVL1KXhB ywG>w\_Q ~|mJoCWV4D?2GKM]xX,j-FԖgx.viC qawMu>lo"]뷯xp9-/ sSS>ԡzݴ5[ItdԷDʺiz6W&Q-LFL_gKk_e:f窋}QXOfVG*1&{vɼB"B~7k\,4*9"͖ W3vxPf Q۪Y?WKϢѫ`:6d-sQQt}F|5YXg``ӡ˻tm,W3~YRydXv?G?:k6א3 h,pkq;]W9+#OaW_Ɋモ;yG]s>Z37؇Ll,;wV ?T-0^>mСڵeĶmRdˣd?x_u۪myDL'[:OJWOm,5f~ۨY?%%Iʕb8a˧c5tF )4A[6ۨ5q98NJ|?اkr3\0`k;=>uH=8My?֯_¢E&MjN|K-CV`ihca]:r Z|6^_ ݗ@ qo qVmk'!۾~:5H1gjR~>xp9IÛ>`z\ }; kTH6ݐ/.W*ס} E.HBJfϏ70l#emwE=[GN4֫oWį|̵B7۾M6PV}3|I_͚}A]SC>YMGnؖDk62J};jwo[5W V}z~sC镖N/۔wcҤI:S^j|ݷqQ#j˟/;fIqUz7[Ռ״76|\Qt?dzjEr\jϩ@veӆ6/l.q oI~rFR752 7 W 3?5B:5%c5ckR3^aA͝?WYjؒ i,LI>))R߶bIf|$xrV  ;u?yihyy~ږ}#оum( saX}}[sℱҢ|=>?1=d8b!f4Pr[?a(n[žL9Ʒo;WշFbr5۶L ,wvN<߶s (?68>Wl> z[p Ǐ);;[oΗ3Կv\VO3Ȩ>5YfDIH}`nPsQ 4?{jP8../*0}bUzN8zbe=cy˫m Rݮ7Ǥh?U[T熻ϙ+-fiSjT+9u5zu{js8ڞ1}qKCUگC?x^Th́'ׅ&H =aE-ijAy(oP~} ~0Un,!ܪ'݇&.͊K~-þ[=mjCU> سKnoe#6.e^P1 ׄ{ qcɓ^u`Z7p2cy]wر?A:tf __^_[QfQ~؎yoc* ~cͱ~}GʧW.=o|jM}}qSf߬moO7#ew6(OA͕s9:F!VXNif/@uSJc5} @:7mޞy }~_#>A徜U9㬖VR'%B_fO4lzuN==&E*߶烷 W 3%?cfS_om[8[ƌᵋ5u*죚~_*dg[עaaB,Ju=ϧk:Q ~xS֒$d"O/XdHz,"iDڒ+T?T߾?v?F۸ MTǚmxf>Ml3TK.{0;Hxxv8:A(5?~xۦ;{X&[ϟ7lه;,a$.)I6*ڄE^0sIlbl#ms}pט?cPZ>V7cn빫\o_?+MJ3}޷UPcT}mlyY @m7_g?vo|7ݯϢ-T8D bWmݖ٥\)O,<O՟gw/m{zL>Hm~9SRe4@,^0Faii>a6jEfeIdIo(RSoPP!?Wǽ=尯n۴յkӳp&eʍr#kWRQ?y睖k׮=p77//7  rȑlf5?JL8㪚aÙ3, KRV 046?U5' gJH5'N%_}Md֫7}/Gi+`[7?_|l$QǐOv7s9m{.KVO2}cR?wر/LjmR3\w6gPFC| ~ٳgj=GE>^k[fԿ=aXi#Y{l*||O.mm2Qj_&VO^(UhyvC}@Of5eZ&1FoSWߤ|p|oǤMɫ~F5FNȉj/pݟmT6ujqY7nE]!q~V$oϗQ}jW ud~@jffP3 bS?TY^OVM82eH-Ū qՈ:)7Ǥh?նU | sƟ篪kmU;S=ƶ qaeE ~PItٹ4Ce:~Y?fYj լ4_A ?6?v-9ӧKb:z uk=(v`%<2p*aCŋ@BT\EDHͺuny~w>2sr$*>>o^:Vjۺ;w~ hC UMnU3iOYoiFT66mfwS}v|GvߧO3% D){mS{{|͌qy6b4ѨNLmкbUyN8S;=OysLټ~F| s篚A%}<}БǛIjanC͌f1rli?[q/Ab{;vW'Oz[ׯton"(?~8To%_}e,oէO*ԯ@z*r$իZ ~4VzE.o'$"}4謧R;jn@W狪_h%fIq`gf{P/ڝ~LV[D[7Gg 06^15\XUS>ζ!ly1)O0QԬ$<<}n:Gkf=$j[75RnSS?gVGY:չexi?,l1Kzo~ NO߿,//O>\NNαK_`5?jwA$H;SJNgi1Ua'_Uk_&CXjmVL1_&YPrssG9fÇU9_PU,nӀEU*FH"❒"ɩi2vmI*oXn-_L43$3'̜i̔?He".al?r;V@lƅ ~(كsrrO<^|o;vND~=z? xj~qPYZNR>Lԍ9~0Ep0[]m0*o4iҩCx>w4l~(1/u.LΟ/~@ET9 ce4)m3Xx _VsZBwqotƔ2YUU7 ~ 8‰'| ~|wꪫfå*~0X|J#4$uS#eT/~PTŽF 7 ~ʹs#FK_Pj'OW_mrzΧUE |(CV VkV/i&A߿9_gώ1*OQ?,ˠ^(@!DiuV#Z^SoWZ7ک=&A=zׇ)w?Yփng婅*GE |(!RvZM!>7SaVojM[LgRz>OuH=Oː5⦉7?~E]!q~O[I& 3)eBQ56?"\KkjV*ŗSeRXu1Itt ?Vk|>}9rī_'mSۛ~6? ާ( eP/ EQ jZ]#ZkzVyZu& ~OCӧA=yGǏeZҥK]XM?GE |(܉J B^/jX3IBBB;lhlߠqS}">qw5IE ~(كsrrO:^zcѣGOEc,z9_(FE;^z=R[]:BDG8msUF60B=8 ~$Z6̜S8HR-} G?hB*b[ޒh5yKI|?|d5AgS?2(kl~U[V/[ ZW1 oIigm׶cgW\~;Uy]2wu璵~Xl 4l)7!?B#LQ@~rPEqjR;:)k_>W7M4 =^\wm ~\֮ú%붕wY0f;iѺW+6qI?BJe^/EQ\#`CzzsZVeRX!}BdTv۟8(zT-%ރo ~*aHn, $&җ|5 0o}Paw3q[ 1e"ۃT*?&jV*p?d4kX(> 7kN.?,+K"ir "l$)5]zu!R!T QZ=xVu*x%V'XZBbVlzX5p@mb`l&73mlUܵy?B@qjI5Y/XEVptJjjV絚Uh%s6\߹1z@*@25˯&BK{Z1$V3Z]n+WN3c6;BS/_SE\!.}SٖtW2Ȟ~1(  wP*@W 1~ x!))~rZ]j]|l"(?dOQ?|MQEp{L2 ~ȇ&UCnB`~PEN0(  VSf@!AëdOQ?|MQEp;TOWȧ&BWՁO4+˶sתаq-[,pA5J*W`}ۀ@7Z~W|MQ夾:_g'?';LJȧ&BWf%))sBܼ-*AR\9٦M?#sNreo::8vYL?i=v^WOg+P,/GׯKpXÛ6ɢeqrOr);eDS'V$$$XJ..U;ڵ3 ~ ᇥÔnP??N|WL׌^+}JEiMO{Vf}zQm?%;!~<;@~@Zj,]|l"(?~xaV \_~Tib&'>LٱCz-Z$fv}};0@V?Uv?RywW/]YU~ '=@>+:|6|LƎ s ჺ~s 1iӦyr⧒+a߾׌c*T0CdWdJ%dkco*3"OlY=*ݖiG6e_tC9)u?@}Z@>f6?0߲mm׿ )"NIz˖zp#X1iҷ>ye(:b&}xe0y][B%08X"KJMJӧ %L?rDu,)u"a(!)sP۫`%.IIsbVHXi5f?w.S;UkWqGa u~j\˾䜝Bga`+""\F)?ҷo;)Ux]2<3g޷AKM?[Ƞ +˻Rd! oi;% #!ye'e;@^Gn@>g6?07~*d.YR x%=/{]U@{S&5kSPATJ?4aQD\f;&QQUb11.E@@Pԯ\ձf?nJfIDATO?I=:/_?<=gGյ=@{,cwV[?^!az{dɒǝ-(V,R<ͱ/_-'?(kW ҥoS~>;wF"# IBrT)){dҸqM)\8\BB%66Fƌ'} ;ީpyt/GnvȎcflF˪U`,ۺu>e4o^WRh8}~xS:tCE Kȟ~_%-[6у o~zԫWխp̙C]:V 2p`'9t c=ox5s#Gp^]n'RXw=lr2/Uf_} [ \uXI|wAf|V&<3E>>~Y>-]}|N~vQ@Go:rDP~;¬fpU-GfRl9(gOUڶ6HyY?읃PS|#^|hWv2jqrsXv(QPL~05*遏y^z8:5+!%g7O* שOUA]c |pTX^d1vm/ı~}TO63˸'e dКeڶdwdge?v)+]%KyKG{&BWf+͸qgImg cse3q"fYb,6{lB*a|R砂,F׭ҫW]K\`]͂bY֯J@PGWhY676` I^mʷXTŲL@Yc? >mX|˾^ ?`Y7qmzqصE}vw5}ݺgݚ!ĺ]ڟ/_h=pn Y@o53צI7< -ٳ+ |Ut0jf5SFbFt1QQEeٲvLiԨ͹Z2C5$J͞[a^D}azش6ܶmsٰa̛ZJnqcTXV?ߍ8>},V([,֭j6zX￿TfG;f_?h䑾F٣6u_l,Ԭ&mfm˸dܖ2f o2z/˳v_Iɒ._^&< J&BWf2ի~/^,qXV*6Xe c}Phۍu}ݾͲw˗$!C2|sB=,ܠ">/6KGZl{25{j.gh[Y:9/_?<=gG/y4HynjR U1H`=hߓ o=ӈnQB]1;BLlW~ZW OIc,ScY65P=$$}:c}5 ] l,;vl_d33Ě58M321cy(Z\[oW!8=>Z9B}j[+եKڼ>)L]e /LUHʺz}U63۵h,?Ng`e}&Imf}xKd¶2~yO+.ȼO+K(/ޖك*C~-  ^}GUkMyͲZ‰&}ۃtkjދKո8 /Z4õt}YoC E:˲~?o7DQHƍ~pu?yK_O򾋲?7_ߊvPͣ"***ݟ'e[!?vFt@HE#?fsĽ;6!!{wi۶-[~pWfuh6re],˃K%))ƲngˊDG KL|K{~q￿ضmSqqq2aV^WA + 0PWr5޲MZ>RJT Οw+,QZ *QyuLNf/^8:cG@+ pv,OާEfX'X1}&S6?<=gG/=@yЛ?V3>$&ޫV3Kq6?ܮB-ZK {*˹sxPժU}9&;ufrf}i2mܸfko׭{l߾DLsزe Cn[wTTQYl[4jTef81QK.U.6̑yg~󴏽 ~ӧ_E۶~ed!9C?KO3zǏoYg'O_{_o}q gvȲdDV~Z?Q>:[Uh땃&yLz'@|2zN?*@W<3~dsرZjH&Lxw7.\ _֭OwY=ART)=T3XL5={٬2RH}&53;USg(0wAmUE(QeK}F w?޽P3Qo+Y͐7TCՒ1Tn\`} J1cdΝ6?}~}Tn^oS뛲!9;ۏ|R.|޶ׯ_Mos7Qfe p J(\ -s>{yS-a5Ӄyd'e]҇LC`QF%Qyc}_?K}+ݿTV!o`_o^$?"S_ >JimeV_ OkȇIK2fBy %inxf/ҥ߮fHСCSZExav b2_o6q=Hby?Ok*R=@̙Wg],YLHiҤՀ}w+b,k֬>ݝjW[l:I`}sGKw#"#Th@mft໻U)){dGyI2%a2fL?ٹse?t ujvMorȎcX-w@B߶ل#gb67Goqfw4hP]Աʗ/#Q|f]_?<KYWԉu:_5Kz?<[_}"Mߝrn^Lڬ?~M?{SVZͦ++Y8LرoP,(]6?qH\f讯 VBCaСf#[,VL[qؿoڿCXBҷo39yb++e^Ҹq%)\8L?RڿAʹ<@bԩSWO>]5#A*6n6*1r6)W/mn~ jrvWfS m^9{=^D?L7{L, ~l)^PKv*(QFY'3WF ~G͚e%<<㬕*?/q%R~竮$1tsIII^?m۶l6{;sIY쑾"˔~ "AQE UY5k˹s??S{V .]feKF* @~h/Y\1Pl-[JYeX?FnqCU۶e2on/}{s0Ѯfd}F;Yڵ?ϟ*qqqq'AZ+5B%JH@PEFJ )@{G?7(gok~I~iV ++ש;V86Ce'~[ AA..fڇmԲEÍe/| Cc̘?T$JcY=eE6/- o1ֿvE)X0m&cG<ѺukmٜTW^n`=E"AQσR|:u ~?Et3%QN6r|R_5f'lc6W~GumٽIsa`#:+eƏN7o.޼&E~EQ?W~V L@:o L>#=of ekHk>~|QT\Yi,=,:m, uN!!AFfkW_toӧOV^?c6Gxg=*A!!y|3BÆ}+d;k ˱exZ /\Gʕ%08l6ƍFݰ|EQ?W~~}ّxfɲΝ"dnҨQEP%OF{ґ 衍mk푑ն8̙SFCUvud2on/)T(h5F~͍ucd{c`џȖͣeذgIc̙%vϑ#G< }|w'Oo#t@z75aa2ߍ_XB#?~_sS~>yR:NW3쮳 YSUҫW ~ |MQEpzޗU g& , ~a-aCͦa8{fԨQ66 o2ֿ"CÔ:[ \?bcKL} %pZG>e_YP&OT޽o\pЇ6mڨG|G`θfgG ~,~]XMEY'OۗU,Y>ye4_SE\!Z]*xfCgH$""Tm VP|ix&A4(˗o!}&1Z5 o.jʸ ~Şb6W)R>눚)WVyyX}{ueѯq̘vӻwL?G}tU޽￿ܾ}&d;BOǏi\m~Ǽ3gl^ŗ.eCꗀ@1( ؑʶc Q}P>SN)ӦM$9{Pj1vZju6../#d0zRs Wz F )T>0^ /[7N?onj}9dHs(Y|UM=|Xv()vG: ~`B3uvA!!RB}@y!u:O6JWf,uǓK-k /VL+Ot;^s`BXVcs\ڟ8U;vN;%$<\իqu߽[*7o<UZH_o?Hc-*Mis ~dwD57>/ϲBsN9kw\ wzinX?"*J.[&׮ \uq>F{%ʕ2|ѺѮf2* iCUme }<} {sh6`Ѯfw>IDZ|~b}N|YoWsQ!%վ }K>=}F 8{I߾?) ("B/Rɮt3`\X\`v5'O=e^ p@r.l̮E}eݚm<ܿY,O,)!1m}6U !Gc=~nfw9K^#:+{_7;:aW6[plF%'իuOk(+?1OW<3v wDhƴXxl-҃lf[ߝ40 ?ǂ=pBŋgk`"K9p4;urM%dzަlZi~xxY]~SBQEhlb#r K'L?~xџ'ߴ4p;1/@Y~M]3k3eB%JҫWZrZX6 % y YK‹3_Roo?a !!-p PDRlfS6{Htm,! uNA!!FÇŲk\?{Uo30,4&ᇍuW"@g619_*nxf A F/VAA.gOv5̟9s@Nփ-GlWΝ#e"59%q>F{d2FmHYpd:gD\ն&7jk'C7lOX5iC^n]}lq ߲EÆIme*MdgÛmtS=6@4W__hÇO޽u,ݺuv}OLL=c~ [K|#MD#ڣ;t3C?\.˾,OʔSxgv;9ho'$$ȴi$))IΞ=+S]kѡC6mIXwJRrIPP-V\*U*-uǞIG^Vkg&~8냯KX`Mm6.Jrbr?ug *DY.^t>}ȑ#G3Scru^5xhq үVZ289?ͱjcD6˪f,nG2-,e—??曆 .g3?Z:k6#,nkiɰ>wxzsRv= ‘z` f{dC5`xK-1ҹG?Ys^:qY )V"J6]]•kPVPW,]{'7HWI7<3. *͸jk%h3c+^B (,򑱍ʷVsxG*-/j;f*qxiN?oTw>!nh5xf\ xgUm˵ 7ȼf/,k6Q3X׷Zo?R}v.ݥbaQeZiT9Y?T`?Ԭ"jjF{|x}{~/n/%tF_@]/3ȩYO.{ ?::`PA,LMcĈ ~|f磻AieD8jֹMܯA?T%(n.,%K1;%1ohW3~]fi^NF-L]zf-Ǧ"C<3Nx*[*G((1? )Re*aYmojhoh/Qzq$^`c?k^c,;e~0DH@mom#*zV<1 +*h(QLYB6H(mݻ$((soܱWBBCe+Exo}%{H6lI=䖘 Rl9,R4m}sk( + ~ȍW,,?x8EQEQTNIG6v߻fz?-@W} zql/6c#c3?ۿpxS.պ\ku|c=׺6lPݚ%GO kTxz۽(}Yis$~~L۩Ǣ^w3l̅6m/ūG]FM"Vkz%h5EZݯUϝ۵h<@Ω+7~ylJQEQDz=jFFFSe\q׷Z-"!E]?. `3Ȣ鳊e2[桚k9 ~ޱs7̙3?x;ㇽzi;R\y^ح݀DFlhGx&~{h9NyD-0d^A ~:ϩu:rBoRUZ=U7j-%jW p1M)("ς A)ƲMgˢãm;;଄*( W[>xq$^`ck^c,;M Ppv}k; ~|X Pn,ZDŽ& Ivgq8^0 g՟xפ$6lb6wX> *Y>=e 0v& HDe 3+EPPm{7wT N4lLuho- @[scfwguÙ|K`9 ͙ATC͔1rIZmjV=Um; p1M)("ςŴ 7ȼfY(,jʰm[gM#jp؝+v6 ew,uIli ~h[lg9liE&ͭǟil>?˜s:UXdb 8qqmޱT>Se/76!C<;}^>wula|t7жS![bU{Kq6' Hװ 8tڝ%$U{b[Kdſ7ˤ =RlUc:HYӥ >i`c͖lqC9(P ' ԪR <իZ}Ekn9s ЪJ8 p1M)("ς,U!p>öu,úwGz!E3Xh1}:0e6NCU>v BQTxACU2DX olf9cǎ9Qc޽mO-?խk HT,a)U:ZVoF |8;Ʋ~,Up#\6jUnӪ}{hVǭVoj5HnI&t3v_cREQE1?T(aO=b.kp)RDF5m0}u<>CWz~Rfy9K26cꎑvHNLG<*11z^zzeavt; wE%aazHEB[lB?8(ni_/ұc~ݻwOp[g1{Z>Txm2zzHB*Rh1 BrkZ2`Hy6ۤH,XJPԷSRy?{$qأRV]} )Vܣ6뫰FzR(~&E+]6ֽKWJT:qٿwӧOonڵfZ#j\Ծ(G3v_cREQEllI & xaCZ P7DjG?PW״[KZ**އnQ|$<3. a5f)EQEQ?˱G@)Q^6* [N|$AZ50ݜ#?އHxf\ Ak`S(~8Fȁr0QCres[8>2 O^sICf@!Ak`S(~8FȁrvB!A%nqI>u>&ӧu3]ԫW\y|G__}UE?|ݻ_7ҭ[kwi#|`SӧFzqmY/y^ w%Q+K`pىlS\gz W~<?3@"A_EkGy>>&7'ʔ)LիW ӦM$9{(OwF۴i|m~ 彟{XjG./7e*5m?/?b^]BAJjr/7|>vɓq̘A(kss#G9;?_tt{;z.! ?^7JQ*|~NWn;L?ܑUS aP^NkGZ]pl"(~u zv<&cǶk!񭟿/]H>}ȑ#ZpUV2׃~U"<|>oBϣ| ǎ+q<ptm]~~dw=u  3Mdŋ899ǂ۵Goǜs$R(o˭]d2f4A ! xujʛ3,w>&gɗg{#k͚5Dž jΝ;7>>gf:gOUIc=Ȱ^jΩSfܸ YԭV"XB(j`~ mddAIHh$N.p G~%;חP}.]ȏGfKÆ Loir;3ӪUf9g~/-땭U+==O?ls.s/Kɓl.zD>2i}=5bV{)yry4xXm[->^6,2Rh'I ӵXΝ%4"B_.]d?z4hݻucOǍvu)Q*9mߩc.GvK;W*6n,a KPHVcs'+^OW輫ل̜_:_%*Tʒa7G3sȐ KVb,}6/(F )TS"9w Οw}qСF{6۔VX6|V5O߫~v;+_w ߧ>,u;v4>Kw*3%<~dW5-TU$\.B dfcV?q8e6, ~0`d5ˇRz:uʹ.*2'.;:G"2+11ųr]y4 (WyU(`ٳE":m/o\yw}9O <'--?Ə{mV_UOv~^?==GF-gJMdrIIIL%*a&(0NwǪ Z*yoQ:T`C7?`v-LJꝍtY׹Qz-}넔7KmtJiIML98 \~7uQ>=/x95ǩڵ3˶)99mdgt}h ~7͛7x뭷F!._ny\)22#D cD^:TG[wvYZu^ܳmٶUNqWu;vy;,uW֩#? r݊;4e۷ifaZ>5vtn.?~cU#GgʦMmsOgvةN<_>ltYСנ6mdږ-Ɲ;aNW#\D񱻿snl/Ly+V!<"c_ygQsXja"˨0{z3sږBuWLߺU.jVJfyV:>mx {hD*ޯF>n{o,+"X5-)!I/0(=J*FX$-]W~y ~4. ڮ]=wX唹swϲiV EZT|Gi5e{ ~#A#ޤk1qT"O# (]#(&`,Ξ,jd]^Cܿ;*U2ޡd͒ ~TXZNĜפ'ܺ1ƴQ'Fv$''F<ѿs )?ر!{c70ק55z_.}EZyww_}Ubs+UNɩ~ho$}>qusƴUǏ(^^}$19VEOө}1]^v?ovZF-=BݺngqP=}gT=Os 'U;txk{^ɲey7>h؂kΜ._1JBQ~xZTұ+U2GX>q\qMRm[zV{Dz?G(ךj;X} 5qzuO/{!?@PAg'ib[mD'$^0KZsw5RȺuAG7]մӌE6,joozy ~V^_b[n=/hN_n1mǀXGϝK$08j,7hj5ZȞA{dDC:UDE񨵣pԣEd^qGA@yI+#`5JؤDcČPW^yq ~t~cԺԨj xo{}C&WxK.ri@ǐ!CD|{;|tQB98 4( ~t8xwd:Dۯ^fG<9e~UGZڪSQ?5*$&%UEu0s~+ ӽcԺjoR:;ݻW# ,'gPdIyndW_˩Vs#ng~jrw`9{ը l ~icwsZβeyvCu/^-+1 u6g^[`[WO ~ꂂ\kv۪@cJ/kF {o,ΎAp?uz.(QMs3穎utձAfo;{)n{F'aiy:Ӯ(]\9sw撫6֭_u:<7?>"+_G2eQ&$O#s45C na^0#G/CUb>#6\bQ?QǭzB{=ʗ/0aZVvpp? l:AQߕLZUh%/_nC.5EAA@޵{]a޽m/di {lP#P($O$RRy{g w'wUCy$;_eϨB@Jed'f|H#`Vᚻ66Oui~FZVvp7!¼=jtme~}H!?@<PHbdV͔JqF!G]}^*j^y?d-SM1| ~I-^eAij!; c^tA@h#+< G{w"R3M{Y;U7ʦ-U{z况keϚ5v~G5魷y 񙞐h5ʾ؅+55_{W^}E wCU=#fT,C{4͜^<=]:d,?5sWaE^Ayuv]>u鶗-l}ϠA PzuÀN>|R#77ߺݩSnn?hm^u-V$&'yu"ßx"h|?{HmSJ0:LmVtG~Zy7P^c1/_\>l> P~C҃%9s:m]NI\͚FȠe1pr[Ul&w()ŋ5}X~ؽUݑ<>PԱTQUk15(dk\4qF8=E4KvZm94}^z|Wm?7G]0߲[o< s:j_ "6xbvGTA@h(Թ7m7KO1Mas„.~(]t٧Ov}6H-[^P̻ DaT5:BO?XqNT}zwDŽ4 Y,nXzi R$)!I* wuY#zy+߇n)z>5F22`c ڮt#beZU}\ngNcu5?U'n>!K/+_nV-+ǎ>¥i9J] :^X=DJREs$nFJQ')%Efn1(C#JuvYct]wI2 }*T\=H( 7%NkG=)5B𣀚rrGRŤ[C~NwrV]&Oڭ[K ILNRU+5k,;tc9("(G>w-gj??h(*F9ꜣsԣB (]#(?b4EQEQ?@L#GbBJ%[ϖ3# 6N;jJnZ)5BsLgS(~a @D6v \ti?~8t6((@`(wA%:ꨣRlvԟ8}@PFP~{lJQEQ9P?h(Jrqݿ9j J~ ~1M)("!mEC1;ζ[mBN?c:REQE 0B; Zmǩ5BsLgS(~Fȁ"xGDQ3QlO;jJ~ ~1M)("!mEMM׊m>Y: (]#(?~=t6((@`(wA5]molok{gpꀠt ٔ((r~ Q$87G͉Mq]wsꀠt ٔ((r~ Qq\]6 (]#(!?yn((Dr~ Q>m-pڀt.h*t6h:*!i5BN 7ծumNetmVZt/q6 (]#(? t ~e\zDlk׶A@!QF{Q3`;+[9e@PFP~@A 2p8*kp J~ Hu:m8r+q\-2 (]#(? "xa.~O:j[lg 58Sk?Du Sk?DuQ1u\PNe?(@dj1\9Sk?D=]mSk?Dv΋]9Sk?DvΆ)5BEJ}co;r J~ 2 2RuQb|;NPR|(#A"稣f66q]8]@PFPB ~Y(ǖ(pA"~P?Q66u]9]@PFP~((@c@"DGmlqt AQEQ#>Avu=kGnݺܹ{MG v81. (]#(?~PEQE OG9wgϞ}v͒+|pzĉt#*gn}]ؗ6( EBocs5؇k?(~D رcoРAgw-|GuCff>{ OJHJjjIINNRer7ȑp…WYF;p1-\൜. (]#(?~PEQE O| :wa±]tSN}>{[ XTrWu+TbҤ7KFT?O|bJbKsԏXu tA@!A((G||t6mZ}.h>ou]Qigo~xҤSqmtJ~D~xQ6r]8]@PFP~((@ _>gϞܹs%Çߢ8ŀ@˖0CNβ|׮=+HjjIczBB *yOQ.% >}\rIg#o]:7ʺ#>eMbldT3>1j ^,u광KKrrTTxK<׃=gJjMNJJF&36Y t;Fw' J~ EQE8?˧ѣn޼9dž >u ŀ@ǎB2cFSg,Դ$6"ӫa-OBl϶U66(5{.sz塇N:u52Y ~,_Ԭ"rՔzoW2BGml{pt AQEQ#>A|:̡CB ~o~4|a5JEWJnwAw+f[X1r:K#xOWս4cS7K;n+In2zz0`Hɻ.5"{ϛ;whN1{wСSRJ+˵o?\FF 7ot0œްa'cY6IHH4ed6BjT{&Ç?!ZȬY; ~6q]pt AQEQ#>Av}U]tӧO: "+5ݛq8a~4hWjT@!j՚AdX!$:># 9LjUNTxߺn%Hws;tF.Ys fWa۠ɩ2f}fS!qϛ7k5G&IOlfH$W )'[5(:ƶk; J~ EQE8?q˨}J!F)?ܵbc 5E:m͑(u/]]jDB͚y){>xrik|MuYsFʕiLS|i>)(1s3yQIڭdF8;EEGml5?tܾ}{+V>vأ 8?u71bǿt>P=,ڸ խ'ZJ $%9{ju?G(icBom_kx6G & _׹>gU7|͛7xWٻ#FDJ ~C*v6Jq>2彈?T;Ϝ޻luo)LD2Q#W4Ȕ% d #~Lt-ۋE=:u vtA@!3ע䧞z2gQ[溃ƿh׮]|qvh3g9kμv):o=<t~G4^~Dv]/@k^ΝB ~1?3?{[ pw?ԈϮ;7dѲer*,xR2ߜWL{]KWUN_p]KJrr1WpTPܞիO,{=ȷ +Wu+\f]~U=ڞ:k_61wwR o#+:s:~%JYzݻgggʚQCO]+U*~//uh&;Ov#NTנhv f? /^zw^_G׼:1Ο??_~g ꨔ_|SNt]O3?{[ t8ب/:0Fw\vY;x ǪU墋:ӪUk"KPըQD^d+4?X ~jҤc]e%ծݐ|w^i\eʔM2mu`ίQ~kp2o;1Rfssy?`pQ#\{su!=M]ؕ~XPt!v|B;ZO>:~XA#O{%~EBy"A b"H1IerF<5"p>􏦛^T1Gtb9s潃 :gɮ]av#X˗65k0Y ~TXRSK{ڲl![bqae]waeOy*WnoR2j_p/lnXC8WNmt?pޯ "|s-ϯwg^sN;;_HEKNvvboD;vPo+ڕWV{@B̹cǎVb7@XLVVVW.{{h+V9\9RRDZU׈&r#~G3w ~y힙5iDjNΑ:V;o*՝תjgY}ntjccV=ˆr>Ci3c{W潮DV3-1 }r\}n uJ k 4@5?'MAq/.a}[*Z ?Tj+~υaN@uU@+&ڋ/~ǩGym坮=,i"k{ ~|?ۯ:u;aXߨS|upΏ?~̬rP%N_>agLaҾW;mkGa^ gw\{vnvی/yw`$%9Áke}suJ k 0xCf!vP#mܳpiJf0HqwJ4YjF^>1S|-Po1o`kc<<'2sLc{w+C%DZyt%اkHBMQu3>A0 qZ1W3 `f@jH)ti(?q;wN:v2l9t"O t 2DTp>o>,;vc~c=^PC-ohݲsաj`ۧQwx?:p3ISt5@$y%Xg{/7uQ?B9V:ww޻>(1={SwV^';_(nڳwf!5=!kzX{sG2~oYy+8u"+nڳ툅9<-S]vel0-?rQW2OZQUm| 4Rh/ni[cM7yL+=_ݰphv kp"DX}7m$~1u-L󻁭wIWww!ѓ>nwaӯwBv>|͛7X~+]Su8.V̳_9ug~ٞm~G-ZY]{ϼlLW#}ꔜYo SgޚGb;753];[o}O{oJH++U ϼ =V YynZ ̾?s[y{l\`מenGۧ6?c]huzν vnBsvεj*﹭N=6v?nڳg ͸!?ƌZiҾW;m'U(m.\-?"%42^z~-[JMW,>vTR>>{敩Zg}eF9+{cNO)9ncg'?hZ?WnH #:uWwU`E˨?t]OիױݻwJ}lٲe?LJ{m=,ZVGо]`Ԫ]z-Zٲ΍8V:_Tw=fVQcJ,N=6v?nڳg ͸/eJ;(ei;~ypؽBiszm!CI#m39ך_iS'``bUrQi՚T ^R0ђ(U_=jmed'3w6^=Xl"ӶL+6H}v"@q1?ޯ;۴ᣢ>I5B]&SsA<W^7 0Ç->rssם:uX߻lmѓ-[!eSRDRy7}uw{eM:VK7,GOgٴ4H!*${mپyw|ЁΙ YK9l,?ZDIDATT9!ڢ\2e<^uxhokm=~<긩~swjp+lh:*qvV&վznvξ۹^vAgm]R{vQ(+j;a݆uJ k 2(Q*>/jkiLڸL3d١ec֞[P?:~FQN~дޮ,k;qޯ K.3ݻ|ݲe *ywa*ǖQ{QwUC`AlFТXZ1#V!M.|\7RbRgzWZ^L:xbI2%6>ϭFp?E-Y;g ~?&R?;i\y o#+t:8~Sߋ/_~Z.wѩO9ꏳΑ,>%ŮiR/@\ 9t`E*%1_FcN~q-lJNKJ=rUbq-?_>3E 9u]Os̷QX5?\wѩO9Ï2}"NJ\QFyG!pmF1Hg'?^ -1I*IbņT$UkA*QI/-!vL@E#:sҡOy?EYK{gv @+r G@$D|~G\J*[&Hp1J7MK]c (~((@ |v"@K %}VG!*d^ǡ\Kk"(~D g'?r6bCbã#- |f$?(("q'~ : IU(L9@-L@DEQEQ?" A w7 _[PT AQEQ#>A|vm W@@?~PEQE O@~w@pEQE8?g' ~DEQEQ?" A w7~_ AQEQ#>A|vm W@@?~PEQE O@~w@pEQE8?g'D* qעm~a>fY?ޯP(u^?PEQg A wV 6M ?#~%?[ȅqG]V A w ~t8Z}¨C G?x>;6&ǒ5qw/{p  9;ur蓝rr)7dOJ妝,I~76GoGKޖ/0QeGdʱ'c!c5}N7"~x# ,t? A 4Q~Th)|KٷG׶Gipyoj/M 57x 1kq'9]qyQ: \_}ۜ~2v||]u>~FΞ=kLFȣfҺ`6acr#B $~x# ,t? A 4Q ~׿BN:eNoұǪFk/N6;\VnrUҫe}KelA F@Xh@hP 2bt<k\|y/3vy/d/ \16_*[77mi]f-z\Ovx ?*5эG0{\ҡHoO?ks<W7BC<@B*)-~*]N>-~:$>B=tʾz.ճ_onߏ3շ>0 ~LX^KgΜ5F y ޯ"5g'Єv~+@@Xh@hnx# ,t? A 47~_ ?ޯB ~e((*>y ?ޯB ~LzM (/)J;jZd((N@s߃ ~DEQEQQ0?JQEQ@ "!A ((>Cu-Љ((@s߃ ~DEQEQQD!DJQEQ hAnx"A((h("p] t"((vA 7~_ AQEQg4b~~:REQE; bL$UmNx08~yޯ"((>Cu-Љ((@[R&VlHx8~GG[x$?((@"r׵@'R(~aGDLI(Q8 Q %3:ZZx$?((@"r׵@'R(~aGDLI*[&-1I+\L#>M~w:&~I?~PEQE EkNEQEŽ6^ HH$JR:㤎xqҊPd AQEQg4b~~:REQE; bRRZ7&(zF`r\K.O>P];^E}@+Sܺ(("p] t"((vAоFqb\30i>G7yo.#Jn~;^lD:n߾Ŋ+^;vu]o;bĈ9X;Xv!y E Rj=ILN1)!1)z*_|Lɏy 1k 5^_CvP[.Ԋs!"p] t"((vAоFT%zWsMGdk?ȻߝtNraT("O=?eΜ9yfEW1\Ϟ=vuP8[w3_9yY|ǹ0_eT]Z~ A#^ ~ @D'>s̷uؗ/?1ywIH̹yr]/߮cV.=/k\ΡtP >ȳ߮i>?PAfg ~\8! UHnzKdކo宗9?L|'YȗGeT(")W=x`ٽ{gggʚQؿYӞ1_rjq}%$1=7Ud)[?2חRTtR->Lx8__;%#2a΢1"Q D#v_ ~\8/״F.J[;JtҒ$)%RE2c>57JY;o ~.iE:J8YjҤq=^TC^^4pAfg ~\J޼W^xN*T7^R ~:jJM9{r}iF㜨vo߬r\cv vmJڈھJIכd裟S ~.1֏ >4Lru#nSJbr--]w=I!C0%ȅ~D0O~<>g6FE}Ә@m=HuFUbCs7qyIQ|;GH)ԓ&{SPQD@+1kb5M,1Ecvglp;a>[:_}q'k~)YuMYpۿ^^\~㑣dO|*=L= %\"rW|>xrZ qFtow(?yٕBrUkҲK|V9rNqs䝑Q)(4XD:nܠty?{ݜӡǫ3puּfVߌ:fmeoL?a]_WF'rڽ2yvO>ֹcM{rm'Pl^5h:W_RlԬ4he*Up !4"K2*U.]N gnksLn昛ƨ OtV [**ix;{Zjftze,_v}+acc͐fDҺk֒3{ˎvkʁ_…1Ǵ #ugMN|QTSoSzsHMFkj?ЏAv l0-ڗ1:L5EZj!5֐*ժH6r|'kM#9vIC6-4skCj0jC2ҧ!˦C]آP:x^eHEhXϐ y+p{p!{W!SZ2N x_7/.ɟ-\(?iԩeHcȧFw,~C.rDFjv\?>kHvg~V4cMoo,|&T7ջ6\v:R՞ɸ׹{>TyN/ҐF:?;2d{$m{8}==Z;^s7E 0#2GݩϾBw3؄7ꤨ+:j߹ |?tkU6>tL=9w_#k~G>6W>y\LM}cRnEj2[ȐUW߲+C7گ+yo*xctIkFHq{mu ™*s^~gVώuUxuP;OUc]oS';_gP8y9ʟҥE(nsu`:"5Ti8N[*3{۱'YT紿o8z{쏩n# YThS9S}R[3#! c P~WṈs݈u79i]A3* oO1=(F5@t$ =[ǖG ~QGrʹ@n|~rSg>EPpkތxU~ ϳwR*+<Թ '.ŇaWRW?}e}>C{>b'q?S~@)_E2N(nl<47ʼ369')z!:+ˢ;Ev.]mԆQfds+]3ԕaaB֒15_=CWUςZid'YrxL=/ Vb%kL1Lɜ|TYTBW 3Zȶ7I nWfI\%͑:[b:{j2mXJ摜hsre6-Za 4 ijË z{xᇽNjZ"%/ӂy rUpx!6u3ֶN"71d@vޢ޽3ĉ,n!5dxJ=5:_}4"~׎@ڙҴ.o k$ emoфw^8c۱QOFs{{;Tgxzd ?RUruih 3W S7>i>9c99.8ΩW>"=~@1o|(?5pԹNլ_Y|ů?7TJ}g]l]!ڭ~/O}'L.e˅}*Dnxs9/9IRp; 9>\gϞ->iǏˠA$1\ ?Uo5u iO] ;[{:Gѩ?ntmgR~qoǎզΣGyC m\9ykdooe_!t?gස#^)÷߃mv<[nY<|Rs~I=n1mxomc{q A^E{ *K7Hn܀HQ/|-{Yn"c{Ҷ/DV}~msɳG#s*g*Hw9@Q/)Ј=SĠˆukoE[4C?}lAwu6l+~ܴ(ZHS}z>u'8{{FHɢt6U^!guޱxCr,Qfkߛ/CԾx֢謧 1m~Vq:)r/?喵{y8r~`fڣX6`eI~$- ?ܵ{r㩿[G>. UlS?.7cȼgժ6B\SO17yS!Hƽ.X?*,' LqHiĉXHǟtFr|-6+=}\Q 5B_ߟoǻ;LJFFyh?݉fΣ;;s^F'iĊrۗK1%MW 'Id3\,U 0rPVcW'H[+<~P1Rܖx‰ÉӲ_m)}./X7۽C'X=ا+["\Gɟo8l/C!Grux~_d ?_8s3O 1<6w3ߢ ?9%2-ǾmsC>X-κw3$KEc^<+τͿJC5do>cʄqt;GgΣ2h:F=~LV[;n=R?ֽm'`Z ɪ ,|/SlX}P/jY46 ?Ѣ3g^gۯ5}YwMe:Ұk$cNc}>c;n Ԇ@/:kćKRAbd.VĠyMqB<#:~n3|U) _б3]]J\m/o/#֏^rג*dK^ pcĉ?ĻJG}O?cH~-lexgWu9lH ?P3tJ ~d?}v$OQNd{g2+匿Cm@%?#|#pn[4ᇞ:fe֫$9k=%Kޓ,Gs+|Hr}Qd TxíyS9K>QJ!e >GZ`|SG3@:Nv:UIk,Ӡ´n}u|\rG2ec7eDѶrۿ n|ѐn+ɜ۩pŗ]r%?U&Mzw uVT]ͱsdPewC x5jƴ=nշUXC)f'5]r-ݨvK{oM̨j{?Ow|_ksJЗ+W~~e ^f=6ٕnr}wLxD5nXlo ʻ5/+FE$JZ?3l?>&q.Վސ=gB.] )`}>yC[dQ/9{ҝ?njȍGs>^ZGn1!Uf :UWHpdvqj*ڨZ%p;/:5dۘᇓN|570}:8ڄcՋ!6w3ߢ ?^DZɩןoiOgCSw:chxuV](p/,G48#Y#59o*vI͛eSo8@;:0Wn.vC_s?;ڭYIqZL\2um22Rm9ﷆ\P%)#9|:ƍ^s=3垔[~@yDZc~adHfFp*ZDrX+{>Ju(*P႕>dڣxJoѻEu,X$\lFȰ$(WPzr$DiT'YrxL=ՌZu3O/F\,H'_/8׬rNΑ50|Ž+ķ'&t~$q4ǎzqOQ䎋ڵWή_1W[?29'iTJ_=ݝDqUas\8~J^6TC4dڢUõF~O>ɐEz^CO3䔶~iq}T.h89#~8mf)ʂ޺/8 y'<|&Pii}:'p Ͻ7̍N氓L^XOGᇓ-ȈO{\p29o*vCj*0~@'M+i7\;-cstEYzͷ/䳤n\i2+I~u,Yʪ2m Nw!$Qq͚:u7Ǐw%ѣG|o< ߁w';_bJz^]Ij:E>/]Ij5Lbu]T_vectON "72ht'k7EE{L9[KҲXj-z6Njv'x>]FQHCM^eWSӁj#<ާ94nqv> Z[ꨮcK#bBGH8mK̶tZf<\wzqɺ'^+*O>S$PFt&-~G:Mr"i&yT}L"]甛r3VWn3IzZ͚Kg{@ ׎ìkfErh Xr8?<=RVb6k,{u$Djԫ!=}cvyaos7-Cu;q|樢4uoPJtskN[*3{]G߆N^!E<*??߁ҹ0 0,pDenU߱@yıc]S.:Mg:MHD v!ǮvIV_w$t3hyff FZHy7ϓSKMFfZ jI^<a\"C˴w9CΓ%ڵdwɎoͨ'ڇFn9 LVZeWZٌle ;{ú5%I9vTAENuj'Tؠw\X[eR9Y_9mWᏴC坸Kb$)۔SNs#?]mvF% %ğjGp`~DTgZhRِ+sKBW,OFt.\F+Tt*hԅ"??)4O|^aV@aVlU t4?\}#[:FBIvCfĔl̶fUkT=)wj$(P\%-휈ux]5>pŜ:,ktWҶyh.V(xh ٴ!Fc5_]|F }.OҞ+T˾S!aU(GFF%۬U{H~g/(jɘ ǔk1 0wƿJNX¦}S\|e >%`3J<+ G)(GD}`|<R=pDaa;@I| ?2*eHdrThG"s$'ܨVʴ*EغĐ\`CZO̹9y`|;+ͅlil *@aa;@|| G\0 0I'Uog yɖ|c *@aa;@| G\0 0I'Uog y\h\z[ixJQ 0 0J $l?Na!H:z(K; +BJΖ/+W^AaaH~H1 0 CtRP7Wi56C[gWqaah%?6Es'R 0 ~$T@%7WY^v\[ei;6:NWqaah%?6Es'R 0 ~$T@Q#g#]<^,\qaah%?6Es'R 0 ~$T@G<yƞ) 8"0 0  $Grh.Daaw4~(4˕\-4W-CR?0 0 xG(/!( 8ba ,o< MJlx2=sAaaH~H1 0 CtRPwy~ x^A_㕗^JIyPv7Wxd]Q8*~0}2s> <b"UWn/sr&s>I1y%+=^ VZ\iݴd1 ;@@YY2ͥ+ʖH ~|A *8mHugW ~|p~$W>ΖVZft.ܘ>A1@ TxdrfR'[z;s >?x^AөT | W.# ~|A O,x峬,iOHFs 6>x(}~4@#{x<2VZ<^UNa.g#?]x(}~r+[CZ-ZP |0 0̚J S j!A 8#+[ʒNV+?̔fs C)aa?/Lecc+ p"G}.,K)4N]r?=On&UU1qΈ Hʕ\;c-{^R*}Y[^<ϼLP^ҙaW+?)33Qap&Vbڭ7 }bܵkWMirQ_b|b2SG,~Kp"wogN x_H~,j]&8SvM)oK[d~h{iҲTUͬofLfxxH<=9n*=X;!63mⶉeN!ߡUl+3¿eeI/ZB0L8ؖcGoG+4NDKT#%9J͜^8tyk֬"9ks{g\ 7md3xPGj^m|ze/7%+gi*;nd󨠡R[~:,j}>\Gi3~Hž=M^~@YűU_]%c75Xh4U+Q'Zi)eb ?=ѬϜ$|yO'퇶fݦusis瘢k>+l_ʟgۛOh?vۙ}S^ >|?[;WN61ZFĮ'Bnݨ)QAWF!{u]?7猷~Txt~/uD*.^V#=3s \#1R"P'hWyTWQH|06\tr7]a1OmNs~:'fm&KMEq*>j0c-٬M3+]9~Pn y&a,iʋ#̅ ~$FJ Z1_WWgV]!KÞ[Ns:}Uĵ>]tu5l;i>'e9~1ڟgtˡXWWGpmNmSZöZvjhx6Nm{3\ᇶW/1U4co;owLG;1է~X ul@h8-X=FY$qá!A%5YP)CꜨcKʽkTR(m[SF2>C{^lND7Ain7fd^6?E>qj,zN'4`J{g_玁ߙ5rB8:K1zDp_=􇓲cIPGuC|^8??\GY^](֦ub mOx滓{1_^0p +:{y5=whϟ~bA#L$CQZL9InNs/q2F~@YőIZTrۃ1N^Qh*իȊV%.hܢfEoML= %k^_VVEz-?ڝ1sCM5Eu,qL6G2\5a.B>-I$"۴y->skmt YĩLuxwTb~rSE.Snj G `mܜ36"95cƔ'+iVZV ɑ ICW#߿{neK;ڶ-}^[֯|׭"iCShP^v]ᇽuyW_҅kVt :QaKb,%C?d,cP[4?g 93ۅ]?XmYn结{1s۩A֫[-ld'u5b2t.[2L; H-Wh t $EQZ,GN&*4F~@YűgJ#3#8$R44RBₓǟt̆g6y *^58l0`ݸvkh6H1&VK^D>~qF?}fKho:tLmfW/O. ߬MqD\9W˰@;12\G_ &2enjs9СCX ݟy2G|sY޾3?RO(N뤢UwpJW i:W/ZlE^]i٢hq;5|{|i6vK7yjZBn03lԙ7~( {ӧLlO?2R1BYӚ'qiGc܋g~F>Е EV4ꄽn9[ mOx{8w i_k^g\ݻ⩃[ Oub҄VI~<21+J)o<}ϯ2}tSPAb.Vy/K\qDǠcqkһX]B[KF ~)U!F2Q߮['%CV ل۴r=]7#>۔c}A@Nt~s+q9g>ր@g eUS'=^- ) ~,Zh޺uGG>߭]C(??Z]ʄ皫*ox3Mz4}9ASܗ/n^3 N٬\`S40rX !ᇛ:Ξޮ+vڦjf*ЕAJ_d1NFxrнLGwm{+wSNnW?C ss{׫}gD~X QÛFNv28Vϲ+:/6?Irfݛ.pۖ̾w;#պU 7!9uw}r[T?>}oaP=5F9a$]Qltv{VwyW̺XEP"|7y^#(툾n'zUVaEݧtcaq5rŮv.8AwXBMf#?ĨmZNj Cbݺ6 DoPӥa8yVf-?EPFPDWXӧkI4^}G~~~ λ ?4:@v}-kwRby+똈T{M~lj|=YM;od>ew׿7Z1C}{FGI%{i]?hUd'ne 4ke>Bϡ0 mO{=n`fA[3MT)Cy4WsmR!ح)M.(yv;Z]ܯDnFPqxaX3F63rDŽ'CԢWuoݿ?+{>J\`~Y^X%w-);- (#o0?|*BsʪVc{ei}/H̠(96A׮bb7ބ;VE-q*p6~lk{5ee3b֣i]iRlEw,yAG(l{FA*:v222Š4b/s?* >/I1HT_VtR<ʑfҚn >zk _~|"i߶cAuVrԩ*w>:bNLF?i)ְ>o;|i"X;q]O#<~80 >$r3OcSeUg*9u*Xs[#`I~<9B\?}/DƋg+.\˅QgcuܵVtWpk9kEu >sW{WG<'1N9/?~בqÆ 'N]HK|Z8]ΘXlc3n;^ 91ϧb ThQNuә]#/F^p".iҪ_d .x<<Ԭ_thKmq⇀Sͦ0ER/&6K8ф 22ND9?ڕEk֬b G xEW7j}{g/7XiyGph> Dѩ#֭[oK$͛[á4A#Li{"z +ㄌ1*|ǘg(^[4qA"[sVRI=b>uW!ssLkF(APXQ vvL}ljs,HK|H_ʃ𣏁|>zHUGFxk%~<#2(CsØ 0hcɂN} /+8MlGp/Z/cG-׼)Pg/8Ԯ]5QKdG~li_#H:2.Y#G%x|sPD{i48!c831RҨy#YOn6eZf^߯-a#nKVe@Z3~@R.x+RtY>](G2Z1кuʀS=rMA"b T|cLXF:>mTеOKDܞa#_q||;}оfBcپ֏N'Mc~=zw8\Js {B*1ҙ C )p>[6uSʏacCP!? }A*ix<'+ӲLiF@)̅5'vbB?\se~ʅjw:uFh4xt`|7dXXᇦuhios1o\! 0 0P@ƆJҗQ'w2\GeyզTzeBFRJ+| Q20\Y*#~qȠ> Wfr׭dʮһg2w>:?ztWRGq/9愳Cs7Es>$v1Y/?x^CEѣt4W~ɖ[hfHS;S9'vn;UZUtjlUVs:tPG% :mZ7ٶlWN2qnЌaR:z4M)x3eLC.=.8Gѩ#O4^{͕_~o|:C8+c'^,ɘ? ~t  >/O>e?Jyɖ<^GҬPAp4[70;a?مuVPԌp6mraL#p|{|iBbW^)'imyȈ##U~tȸiӦO> };b~: '^,ɘO7{ڭ7 Gt3~G\~@3~@Rc.?23'[.VZYezr宁fu4_]fOo'0btKkwR }Ǻnc,svoNGkCN)ފ^]ܔ{i2曓=6{R{$0?:RƆJyɖ}%-h2?\)((8ԩ/;2jԨOD+t:̡[~:,c:~ᢰ*"T)1Ae٦nh>unͩR:q:3Ftn[qYK22K 1k}ҩG9RWlFRq"~q_:/G{ 7y^vs"2UZFk 7n" ?V-;ّi$SQZQ V ~l:KR1#۲u/Mt9KnIiD磛{$P?:RƆJҗ1'w2\Ǟ'8p#ge˙V+ ӦHM ,-e=@z:j=Y Csv تV0yk9CMאȗ_4BzvotXtV57]=\u%ʙ8gf;Lϟ+qW۰bGHܺ5+FmXGHޗBK=(|Npuw}t[laS" %AVe_YD]mTGgԑuqWdtPQxJuҝTC9~k{߹z/7S췶We~6f'LX÷vm617,\RlOݗM6ncg;Yxv߬9+eAsVd2e\u-يO?c}z}g,ɿaە>]9TV]6h盹?fq߁뽟/ݓ۲? ~ d^>V#^:[=}i(t#cVcF 3>6^Yz_?1Q ~оϵNH 6>^͝鉩N4Dx@ ~k|0Pe| X{=~U;;%[bo}+T=a z&e*adp\}o_ܿ/xCygt}]]ߴMf69o?^g|mh([9:`:Ñ ~UO_]u>yM7:f 51sT{KfCS}D1'A?l6}>O v:՗e+}8C%Ѣ'}I/O} O 4AROt~u'Μ1az}=9|h_"Qs~[=_>RܰO_ϱÝd=y7QGU jQ{ ~%;TܝfI 9#X~qm\Ma4#ڮl}mG=}i(~cx(cX?{}/y ج~蠔jK1'AB]''iz|rH?&tUUo6A٤d^s}I~J]~}vsw$}rǜ8qw>u=~zFq˾{m9s6UKvn?vVغW97shUl sL|F_Cy/@jw$Kz/ oe1I?~*bDZ}{&nn}m|,I+IбvkߍW0 JE_CKML~x:m7=/::r##n7wR񯖕?~S w-㷮ni񉏾<>%>g4YtrH ;٤_Sݓ:T?%ACZX>cxg?m۳ׯpxGr9;]7=g?;g~ZptlϱYYLFU{wr?9/fJ]Y d4^ۃFk(#^:[}ssC~ٹб絑c}V>X7[  y7{T̺fސPKF=yi}KFCG#4{09ii_GZ'qEJXP(5_BnMBx@ ~kΎ 9N+Obyyw\^"7BCŴiuj ?˿%=?/;n7yn>fΘwFgJK|G7<9+_8|N_lrP^V7]wysvY.m{O4~>Iyxba畯e6a;*Y!{ȱ>f ~dՇƤI]IJ?i&?=/,@Si8d1xmv\#HVO_j|srΆ3f{`cOh*L}U7qw ~֢# yiMBP^CjaG/)R̙HKjJv⑤ŋcVe[I`̘ LdR&G+QK%s&Ҿ^׿wk6ĖBl]>McexMd8e"א29ZJ_R>/-3%h޼t޳vk3^A1N5LVҗK@ +L}k..I$OlgͶJ pDF!RJbDZW%kqM*McB!z+'I2IG|,6^AK ڠ5dBRJ)?T̙HKj1.ͤ7I!nn= PRJ)`Ls&ҾV];?hi(j%(+6s&*RJSŜ/D,\+'Iܑj~L(UJ)09ii_GZ'qŦS4")9mK ȄRRJ ~c3U ~m1'(˧qMn=''4^FPRJ)`Ls&ҾɵNE.#7;*'x M㋥aPRJ)`Ls&x?IIDATҾZ!CN/iIam$ J)ejؐbDZW%Sxޙ$+*[.}`d\JRJ)%@3.b|s&Ҿ;6q?=ػvKRK[lBRJ)?h@{*L}(:\.6&)UIm1xʪJ)j2A =s&Ҿ*?y1=IMi+J6)ą?X О9ii_/εNxkg4j̧Ҷ{@=)?T̙HK?6qk .-)ě4^0fWiLqfWS1g"-\? ~lv,48-Mc|!^6o^LwWC!H!bDZ}{&nc%qվ. |THIH qc)@{*L}U5qk*}%x(IץKUSHS1g"-넜XƶI!5ێ,KDgx0Z)?T̙HK:omܺ'\7&9 :I48;+MIBhOŜV~<''Ғ4]n7/;3+MMBhOŜU}{&nc%qW#_VS$N)]gx0&̰U x~S̙HK?n6.k).W鉩I*ߧiHc;;+fVC:zY^_U1A>ChuŜr*'ќvI)Ӹ+ 89+f6MLOX3ur_~N8[iKcBe[+KuchmX|vMWw{υ-GW/@+L}Bc˾6rmZǬ}ƙIO1^;^z`5,VKV~rcv=1qΔN(1v ?::V>rzU}fNݮ?g=Fϱ"bDZ)}{IQc ѝ񿥺m@+hp./^P7m8;ׄ1}Ru@5cɼ8w ;qO3&ewXwc)1aUn֫{}4?M3U ~lmܢ\'Mcev}V8;+vQwؠg-9}nC׮Ջc_^\'>z}"=9k専Y$).)o] ~ TيG|Vh9 ~P1g"-\??F\-cf$4{H0b3)Pw`w TTBg厘<Zy[GgGys9{-̏[͋Iw? ~,U9БOTݾ^X>~J:njtǎתXwMW]zK#qCeb5KWN1g"-봾eqq_W\#7vJKW͛ӗ. Ύ q6 ~-iOO,0k8-r};~ko*ئ@u/"~ ~ V~m{/m}#UkYQWNX+? kh8-kkڑqwO!vvWh" 9kj [-cɩ{V?U;ɛs\guw]d|X%uF]6<~(q:9h`sU96vz_ޢ(Ѡ "]8&UOxWģKDgxlrM tA5гߖ@&Ν}PFGWg̝}ޱDzWW'nVva׭}/w'>Ǭf:ۯNŜ 49v/n6.k.%Il/l9S81^cB4:4^bsoo{|uuۑh8+*EnZ^munc'秗ș̽cRSbcW]Wo7wFP`E>ߋ k}IӸy1=c@i8t;W8[ZFmݭwDU*H,, Mqi]xw{.~NJ9vvf_^m$([,J3 @i8tpo^xiO;\,.]Ŝ8WȍaqeqjMwH+uFc4ЇsY^mkYJ8#I#We[wwk Wt?Ȑ#40Y+? }m<{3'$8e*)]ٟ.+hvC cVa6&ӆu?96og&ncsbi $aM1^@Kh"-X~RJQA ߅&ncƗȧqM3ER]<&"H!1~oR-_9?৽hﴉۘkϙ3i\/mi+m{mwwuKWV? ǐ J)hz9Kwmk[D׮Q$)[tAW? ǐ J)hz9w321Iݒ%1-Mc 6 u(!0 )?oLWJ ~@ ^kGw__ ';IViỦ+I_G\{DV޴R ǐ J)hz9}X ~״ jpP$gi\{ar ?.:=g +qŗH&N(GBcHƄq4͠mqI!JA|>Z>h ǐ J)hz9+wwqnI==ej_RN8ƣa=cmb” 1iƤGoվ[Uz걝]nڕ ǐ J)hz9y}{^qN__9P 'I#4u0^udfttvzւuR1a\)%Mo>@38m6ki|#IS5;U?tW@F4QwqW|rtt \_kԷZ>~/=O#l)?oLWJ ~@ ^kǬ6^8;$i*4.K qn lZlF41{z{ҥiuqAswV;Sh{C7&+?h9Mƙ}mh?w.^*ߥiI H?m췤Nj81_9/A !ƕRFs4 &n㌾6^qfsjͫw\z ~@S~6ri_mWPI!ݖ$qqy1^M0ގYo_]L6qGGe:Їͤe]6{ʕJwbh*Z>qދfVo!=ߢ/lIO}O,ΝC:}j&hڡm!6=8%WX$&.]*;4ސts]NW+hvȁ:c3m޴׷ ~򊹚]c(V^bkBfUg5c[W⮤k,-sft'OK?GWgw7vWyg?3[{=:[-u-O>3&cI脙?n}L+O[{aR[ |?~g4)靱iˏl}4ymv^2vWիg[N(O6CL]׿4q'$ݓ4'_+;-7渔`&o6݊sM9~xHzbS}[ׄoAux6>6`ŷf~u@M=w߻]D=ږSN9tZyb{ل⮓~l[O 揋<6hPٗ![ նgk}]ѱcd(;IV;n5aHBNYe(v=P@V>Gnx?I Z9zmUo=Wt^Y`J?ۅn;8c'C[Ɨ'y{? ~Uw5U.Vcۉ?AS"&_:޽v}V+k'[šgNz+ }Ԍ NYp/ CjV6YjWUJYLZ-kn)g_cdQ*D*Je[/^~ȟi-bBuo`ͮ/4_"?/+j:xF_Ckyjyg<'*cd/a6{=~YZH1g"-ʾ=a/I(y@e[OOl9c* WЂl"j&g\2ǯG6{5ݿJ.jfVC=M ~d+?[]9&V߸wmߖwۏݑ'~j2I6لhwJYw_L,>*ȱ]uۗRkW脙ϡ{?OլB$QO ٱ['P\6q!CkC}jկsMU9*vrm䁛zh H -~K$qRVU_ Ҥ{cuƫ].0^@ [%/yS?ǾZ,?w M>ӫuUwzWoxc\slk_6} ~vj#zMϰ_ù#+s&$z-m욾=n'~Oi߬]}mx+H1Wo}R3bmhbuJJ?wfW폼ߊ+Ads ?;yzc m>| Y{* `zyl2z&ƯX-tm?nj'?w;gFg5ܑk^;y?Uw8faQqK'؁ښ]5a!{XgI✕3b^VwT,@1s{kW7y5~4׆PpGO?@w惤iVmIەO}mx+H1WoyG >UXw O ^ "o?l?F6;`v}6v_qՠEmxbGŝύݷدYx5+sd+5TdBy_Wpx13bLO- 㫷yu׮`q)A{N.Ok'֟ hVVɎه /Y8{n7qHWm_ϜxvVIyoՇ?wa I6wJ޾skCc#q=?@]׿;G. BTg4]vz WЂl":?лꫯ9}' }{΂oÓuu_[G"gU=xsOC%[fɸG<&*(?n^lR{Cz/S&u xDF=wf T4r`mV nAcȭ!"*l[{\\jf|ކEv*⮓G$6k$^oi{I;b\I0x+hV6bn]x>paײe._~\ńkC {m?qq3cq;/~~UǏXXUd-N/)OEqgI1sZg].:af|޴|ܻꮶ/[O,GGe_RoY+(I&D=FڝΝ?oJyɝs=ofW-c7֬otΜrH!{Y~Oo3X("[% 6!-m0'i%2IʶB4<.#gJu+hV6bn{y{,}_:e˖xՉ؛lY$&i;wZI mBA ~(4Jt\e[KۮUߵ]_[oڄPPJ))-g_k>s4"C[gmۅoڇPPJ))-Mc޼4*z{cNCח-ԟ̭ +0 %R0R[Lv:߶4vmR=" ^oڇPPJ))-3]Ӹ5I#/FXߴIy<&x &RJ ~Ho$X4έl+}'vŇT&W`hM,*acwmU펗2_m?pFRJQ)&Q[|4J $S6a3\jܚ `-@h/ޥ|Ib:Θ}|[͋O.S[XOi?I͋cؠ8q*[0^}|^VݒSv/_Qi>[8QlWO4lKخ7qV_)| m۟vv8[%M?kX5چ ~"]u&Ξ[ڭz@mUu]w{y[s|WLIfDa?'дҮ.ƿꗥMwg-pVx[6Qפcwy58(Aeߑ_oڢq3w/{IՌ%2DZ߽= OF0RYcܔ =-Oܴmc -ѽA!v8vqN+s:Zy?Eљ/iU4I mzw~P-Y&Pqb?8n3rGL?#5nsω眾Wyȕ[͋Iw? ~,U<'vŌE L4҆UhW I?o[XVpn?6z;C~WL E跴?5:4~$mS@;[RWjmWK}?`{iOO,G~q_[UR5l48QXM]j7zlVh #=l~8 ]ި_|kW.9y >c~uRcF0,E跴4I*9f~ D&O}hg`oE)?A44NVŒS~Nwʓ7G꾽>)~#[+kW8i~҆3|mz̝#cvb5 `X9oiV$CWeSCJOڮMߔ-(uR0^@  /]P eusoKutu H,:zdž_:_WVo3w~J]]jȶ=~5!x6ފR~h80{iߊY-`YcNMg+Zd+Suۑh87*@G۳=jWJF*UNn?*M\nV*Ou9}bWf!>?D1gB"-c'L҈4۝~Tlvf].0^@  h̽?`䚖i?)}Ni̝#`ɤyScկohŜ li+k%IxqrvhV>r Zp>-{ǮW2>gۜaz٨we+~Tga/տ@;UI+hVBPhNŜ 4`Μ/ĩǤʶ$&x3">Y/@;UIx+J! BA ~4b΄D[/iIq1*>]ivf|x[wuqMXjO{i5(T̙~㻒$!)_U,^JێϨR}ڙJ WЂ?nqĵGkM+.1Z"Ԡ?S1gB"-} $qR$l2TϷ@;UI>r Zp;pp/(9cX+|E51qĦ ~V?5(T̙~K|$iDƮƘ\>V%G WЂ현??5(T̙~Y9iɧqIe[Ģ|o`Ҙ έYe|x[w!8~A_ًc S&ĤbcwzVnU=~ꑫ6ږw}tN1qczqo4q׮*-.sFr#]w>~gk>+_] =O[B J9s&$n#$qtG$GJ.wvåL 5x6ގhcf23::; Kll ~?_SZ{=3b?x|Kݿ5rК@s*LHD,$iTFSb>R}x;+ze擣cm_]>o^ǥ|iy cTo;m޴XUqߜXԯ=cft+\+b]5ԾFSZPhNŜ m7$i|>Mㄚm; t9CM_KhK/.`7^7S#==ӽ}Ҵ]~ŗn8ubmFm|}9`#:N^,kql[u#Jk>.+;SPhNŜ -/Mca!{zbjƏJrvZWJP +cjVF2Хnd%}'wҰ7|~m姷?G\sFv+k}H}6%М9o[^'KDnl@;?^sxcx}nVݞ}ml7M'w+~~O焸7DZ46־gD*43!%i{o6[Z-].0^kGճMz+`O;u7ƔYSֻSbf2sG{X_\'CP̙~4$IzƂm=\}R}w>gkǵ߾6;h8mbC+ Mxfa;-}р{'𠘽`v8.zw%q] Xl]uH0!vL}Ʈ$⇥m̛([J4sQ ~ױ[mL7-n0DŜ cbɒI!rIRR}` ~4v++6Yúb΄DM"M$ʶ|!^T(..)5~Rnv~TrW6i%_)=xd~l_96sŜHص7TO8%I{?s ـ]-?\.*oVrM(Y9_АbNv[I3I\4藥z{ #>^ e~(Gc??sÖq^>ζեA*[[.Տ].J(CC)?#!m@mXtwG}R+4.XZaKS F|PJ)~~@+?oǞI$qڭ0B([(/\.J(CC)?#!m@ݠ44޳ζXR0 ,[o+գ.x%!RW ~ֈ|>uwǴʖ$KcS&R|s~rWBJ)%h{ŜmU1IQ[)gzTw@;X].J(CC)?#!m`34^q{ߧ$IrޢHfȟJu{ R2?RJ196~ X^Gi|)_nmv]߹\0PRJ ~4FC̄RmV(?h=CJI_[8&LnnvU\.J(CC)?#!lf"Bmf1~xqLʧR]RV(DoRҤ:-KG FsPRJ ~4FC (h2?{wUu@N&DeDɰE@Nٔ}_UdQiEn>*`iB}Պ Z*HQ3ܹ$L2{^x{ϽfB|9դBFҥ7"g1M>J VreJۖO&99N&{YDpky~pf[2'Qp#0@J:urZ2-۪~n+Tg=_.W=Đ}VHA[皠[ZW%{{jE߾~L<7'H~9Cݿ̜_?i"H9q/+Y2Ψ~xf7 :6r(g01(@ժ%}32\wլ%}+vmJ;R<}]f5a" mr M(?l$g6o. 5%))/>sR3yMɦ9J|f{N.I?d4%^*yXz~J{E9+G> ~@!0@V-QK- aTE)VU9g"I gZԺm2e6jd lWZխ+II/uVQ_/}oŚx>gˍ$'&5_gI޶}y #UMyDze=笔,ïNNmh;{yͽJJyMݿ1ӭܰo ^] 7t'/_.=z>5%?#0y%OޣJ6=>eϽc \B% k+5Lɷ?qN%oT>FZ~||;\R2u￴_e5 | g+iJPr >}q8%(b%߿G,>?CJ:6Z#^'}WR%͸_:t %s~coonRJ Iɩ?G9/Q6VG> ~@!0@QR+!UܢK7 gE,PE^%& ~4QCʖ՗]+9;{wO#g{v˖kx|̘|'>|iS?Ə޽{ÚtK/w\ 0'_WW뛌'yZ)k'hk+9bc|W1(^Y ~kYUI2'^1{{+d|YJ^`gQ?byjƵuz|Yϝa+9Ӂ\#_;Yq?"Lb PLjՒt!YeaFׯauEi0I\ gZ8l'&6c`b>Cڴ&W  ./L,YMʕ̕5U*[]&V~+}USRdmvLrl_Z廆 #<+]RZF]@:_yozפOH\&?wɓaM:>qי3ĉP2sJ NwʿGv)y~}k[ޮ?~D/]0WЫ,j+$4qjVIyJ~RI e5ync.m}]mP}נk(:5C¿OҫCl[`l{4iw~Oc<S;YA}XQ5+py^yYC=˗W#B? ~D뉢©5^Æ=}kE (tT׮}/bǎcm*%'=_ ~${98{}v:3y/m&ii?Ϛe;+Ӧɨ믗ItiPTKM&cnwy?_~M&>|\\?̟C_X9A?o+c=]OWO='o=;^'wXt»_/}*Iڟ:α:qc{SV}JFQҶu1!  TA=F>/sMd,\ 8? O?]رEa7z@l 0@WB*J5@=پ}e *Dm;zF|fu+qCÆvU8Is]j?׾8!JNRko>k''%*Y3>Q=:kxqc+Fa ~\>zr+%xwSEmӥ's+=_P!9$p.׾i 9cU.W^Y &ܻwoXwIUE $ua=Ay׮5%7k$kϮ^OC?O9뗿*y~%cnVR9Iɷ>{>oe=0h;F61VVMڻ ?Y<`xh=g(gӪU? Q˗MT(jo#?(|G8S̾3l@&@C:lY߶gƍmɾm +1-򵕍۶עEǁ }"X|S k1cr=!N֓XUlu+UP27 ;q_OhOL{^% VX{>>ԉ[>)NɘܮWKIZƯ<_ޮmH2+TdL%[&A`EA?ҡſY*GP^ŨC.f 2Dp%KZ\\E()0uZ I"5M߶4>b8ytoמVܰ؃6ny`lJY-eʔ 8-CsȾy Xre~]OBpԩ.+5.Ŀ'o١}yՊJ>{pu e'JĤJmTqqo|u*i}_N?[*ya%ͽ200mkoߧS6&Y%NQ{ ~f̺øOA Uh=g?C:SG> ~@)!OI!A#"AJg{:k $ ~XNddxy~$Lu ~jk)) ~hwu_:}tP֭t_cEﭜfœqxaP\.5{}^1aߏ8X~B ~=ux ~\o~|T?"Lb ?"D"Zپ}k} $ ~:n[ѷ'L$ eJ*Uddrh۾}ٵԮ\Bo}ڤEE0͜9_,_?rٞ={;33s~מ؃ Nu%g*i\IT% JjV5V5Lɛw=1 JRyD8Crc|uu:'7_O׫,|_(%o<"7 ~NnϸCNWFJ^d㬢~D9+W#LbLГ,"QҾrԯd&cCI^JRSTdp%Nُ5~u)S7~RҬd% Jӕ\w|_x]%]ʕ ;wNڵJڴQRD% *5Kɷ߆:yM%:כ$;[W_{ƾM(REI|צM̙ɢpa%{ԯO?%As D[k뀾4ڐa"3I!msnٲew^9qg/͞=ˮ]~*'χ(Py (*غYg?fVo^\𣐟? Ƅ!P9b|\#|] ~L\CCЫ|D:qUF"1t@@Dyvm%GPX2ezޮWCzEJI78z}Zժnm?G>@`>=!f_2l@&!G5s\Tkcn nU'?y~*f @)VR%%gfg1/]2VrρfxZyCs%+9>p_u0{ %<^]ɹsֶի+r6$IL?_VtĻ_ Mz%F)iָ   8z?$!j{Ikcǔ~Dlg9;6g؀LB(PNSSRF"a|?ȫԿ +ѭծWP~ N5kfU3?f.8kܸܓʖUҪD:ѳ}۞=/_OAþm۶Y|ެȏc}䈽װ!G[ ںv' Ie(" ~1&ǠAV^AKxg'LڧOJA J23*TpGŊy׮]F26m2VVڻt8z?(}z6 Ie(" ~1&*l}W8qB־Y˗?QsmW[226HL|\\ ?~I~EIIBOZ;fD;rRotM/ݻw?٥KܿD"l_6ڎa"3 EQ?BA D%'+…J֬QwkWcRJBɲe־mX=j?Ζ-J~[ɾ}/tdN%xۧM3l޼W_5 /d$%^ǩSQ≯em#kjՔlݪd%[رc'秥K^ܻw8qB4={2cƌϺtMff@_OE~r}j9ȑApaҮҢ4q'x[&lDI8IU+{~*mמf_;0l@&!A(G(~"Ę?ΞP*:?Vͽyj8>/6T8u*9yz>?H?t |-[}#%*ާrecU'O/~'#Gн{233z f}ڨ<}M\A„>u\ _V8*J}2ڑa"3 EQ?BA D%%;*)WΘWK{`?v^ AjRP^5"g2V 0:yG˥$9YIŊFBj})Y^I~ JjTҪYf?ΜQ2sڵ5(yi%7}##ch(?ϟ`N>-p߬c:uD=r2k۟BT2xӞui=S=>R`}}K(njW.z=7/yN&B@0|;;vf؀LB(EQPE1aMX* ܷͫo_ Z#>sR_OL9IJ dܞk۞ZxB؁o@©;  "Uk, $2~PE^?C`ҥKX|c\.Si/0py\۫mf 9}S.kv'%K+ZM;9kփ45vSl}|yc߶[k=9i"*T`VΑϞ ;`g}x:dqZIoFWRVCi=K&ۍ̾`؀LB(EQPE1~?۽{x饗\?y?h^+PQ[BF>qHlOWejPw>u&?}?&:%5׶3z-9ߟ\9s E(1}moDgB?(~/@!0@|_~N<Vĉ_gff~=2|Vj*zyVx&n_o@fnmO*/Ք ~7=?x+:H1dJk;Ϝ1Ǩ~vm}g}M类#|zŏ!yVh}5 WW湯7-m"mlu@_ۘ}a"3 EQ?BA J[D-5}]eee/b t >\\.WޯP&밇RJOی/HJ _(h[[C'\J>?t%+^Fy*zJ @PbuO }1G^aa[O*p پA\%cփ2qee+}iء_Ԃ:|R6>>rǞǟ=~_{yO1[襐 5Ff_G0lLɚ1O/ؿeH 9p;]EEAf9qE@ P1Xre~]OB }|;uJ~=: g%19-ą5e"stE[lG_! HrN;M,==L?t]yZ嫞ƘG?[ $\[q/SM:O*Fm"muP} QTy o޻ X?@+vMnsI\|bi.BE`v]w-uW/լ$&'zV'IKO՗̉뵶sz~E4s|?9rgϞ̜)5I`C$[EWI(0kؖr%>'ܠW䗶ЈcgeRzm)$կFndN'98nd4i/I)%.>JZIY2`AgI2S+nvG7r~KmVIKO;KkA P?"D"lwH_+_w2l"kT?lfv;mǼ} ~Zwb'81/se %nwc1ЫzmUWn <^(z"@BsH_S̯ ߟkTQR9_C 7>1^@?oKF\)9W߽~PT)`~%vw!}M2f3l"RW(fNB r}V1b$XkO(9Gn|d6|׾ƀgɶyЦ6ʹjɀ|ﻢ.$iiZrh @#AJGu!}-k~.cE.UL]M{4;˭nnmwA]3l""#AJOwa@^?\?(*bه{];6DKEPUYw=ϰ \/RI^bE.E@)GJczAİ \/R߸awdE.E@)GJ2@^?BQ (9κW QR?x@EQTJ']kߗ(r)~ NQ=eu]+!C(r0Ťu~@I4?8;u@T(4&SEbOy4\@Q@p?~ĭq y QR?D\{Mqf2|" 6QM(REI|hTɜ9JN,|%+W*iJIjD%u*5JGYcE QCguf͔$'+IHPL':X T~mU=wNڵJڴQRqΆ ̚oûH<;{{W_39~>e=[^|1K(Ͽb%.E@Dm޿>('u$%?oFxBY}1Ѿe~m9& F$\[~JO2B1y[D#g#P>lCzk\g?1ujpP/@L0?k;CV)@R?DA&8dvc=Ν72j^]C)y%YYV{Z3j%mX6ڞz Ј2:4qq:@2`RH~{=vJvJεP;bul"ž}J>Tɘ1V{N]G`4A}wI?B?<>0|-(qЃBQ!PT'QmkLo@Ij_tE8T`aUdm{a}m<$͍s^mU#tzՉC':=vJJ\[ :hn׿H>yo{ Ԩa&f\8A}s[QRa?+"|Dm gzpo?^.Wzeo^)o_p7@н磌7.5-U+c(l#k˫osW0Ca>yTT`:F{n݊~̞]p#KPHKD>DKEQIO+&odLVڻt)9Tlxuƪ:DuީSի3lެ$3Ӿd?osVwUG8F~5vGU2bDR"LN`OnO*??t.:]!>DKEQIW_ _?~QIB3U ]=dmjZwml[Zûs'N(Yڿ]́zý`*0ἃ$^G$j>=7`R晜?jζ zG86'=;(q)~ 2I)xg)YFI2~2jOOW}1Ѿ{w=-Mɓ{t?TU/x6|=.X%v5Vx-%~/,[fMѣl٢Ca-Paì͕cnjIL(gj4ΘQt#C$ c?¹?]v> ĥ~"(Dql^IЕ}:tȾ^Y㧟mz m[&D^ݛƍsW0B@B~UQBH=Ն EL=;+#CIj_BrX[C>DKEQg(9SIFPk<#c c~JSGȑF#P'oo9b睹߻v^ jz;7oz5} G`"k+lϝS~q_*V4YV̚7CH<շ*IL۶m>PұrTl0#KPO3=d((q)~ JDrS82@^?LZ(Ğ?\2@^?LZ(Ğ7=d:(q)~ NQ={\r'(q)~ NQT >EJw\@^?LZ(*RPD!wa}3,g%.U[փZ{Byt'{n QL5w__c@ŁIE3}{ Qv`vQR ~>Zz/+Fh5 dF-ȑ&[Zf*uq/NK$ >T-I˗[Y7hP?oaU+ߏwUҥqMqkSuu钒.Wn㫏[#Gm Zl^/W^1$99zϛoϗ~5ׄ7^g*.%͚בL7 RYx3WQ|Qܩq~leg++#Rwd%.UL]M7;˭n8ma?X,#'O;vg6X8_{_ׅzΡWXytj:F+}q`ԃCEr ~8=?QҶm{}z<=s?tp0Pl)%-Z_J退W*c֯zu#|=֞=6)ܡ^56p-kUdjTRLז=z,a]W_^up"@Z7gTOIIJ>qtp*..{23 ~@f~t`w׋ !e^z`*Gs_߷bF|`ωR<ߍ*C4~Rm=Io.TjVZY|<,g-pID?}o߶Cw ~ R=zozJG%;vkln'*;[ظ?1}z}zݻҫ2F+H0^c]C߾V{jJnU+i?tudN%~ozEoZ3fXW|P[(ҭm"%е͛gw:u_[ԫg]lzu0+n^eD?G=d%o~!pC)y%YYV^xy|A{X'1Y<}^c%.Bx[?lfv;mǼ} ~0vʽZ^w&Q~yb H R/EjRٳ K ~+(5XCOo0(MnY z])rM?}>ڦ:,)'*O>i옒:$pM7J瞳{ڽa wPۻ6V6Q9wܸM8tpĻM!]{tW/Z~`^5 e߼?6*w 4mjm{#z!@TR-G?&`&Oo8Ϭ6 g6bVŸ˕l>>gG_k۶"yo߮c5[jR62A~C=C RG/(kvG>~ ~i߶g\:p/\PRѦW c ~7TyCn}{FF &kǾͿnLu(%6iXF>e@hCrA?ȣKm~@ ,]e a?*Ԩ ߷j+PQ1b?Xh\>!shHOmn{ulYtpo[\8[HE^t1D(̉}<>H@*]V/]K-!xi6+(mVY]D61amU?*V̻v*]+VXK*3g =Quw{쫂nz-^ df%7uUCIDATݷ(9Wn{ڛ}>C +i2~xuݭU;흧u.0J۱l"sޞ#ͳS7@ʗW6q-6h;^Ucg+%{M)SH^&J?N&_ozx]}luAA"!Iz|R^6%W1__S־V޽y,|DkÇg+[$$X?ϱc`J5;gm[ږa߉ԫ~}:Hs| Y]_6/=Bw{~uv7P_|<+[z gqWYw!Ѱ'C֭*Nn(0_s?jJЁsM5͐6WZD2WP;ib/ը`ۥmxGw@={(/;TK'+ٸ)_ jz2e~rN]: LO-Ng%od>kaì͛+yU#J^xAɤIJ[USu;nm?W]=z(ٹSɺu %irw:eJMJ4SJ:w޼kWC8G8+UF>_|d2mڄo^}2jƳݽ}' ~re~wp`w !$q}g'(+-rZ_cɡ%ҤkIJM +_+LhҶmufW\'q5O.7,1wcl }Ea>~ AO>}ZIVyڽ_-[z!e*G8ոqV :@ɺu?DIJѫ9kRa?6noHnM:hhS$'|m?BTo^}ի4i1?|pLbέgq aw}AW:&'+tLZ7\]VdLRʺo@irJ6mRҡ1?!AI͚ z~g(9SIkQ *G(u+g$t/`_סCJΝS~Th]̚7^chÄ;J\.#$.z5=u?u/7?c?_o_+um?Bka=Vz8}+7}~NW0IzuQ2r<{sFō=CqtcWkJ*&~O|nϺfiFIVV!p5ɛ DI[~!hK'd~_uy{)֩uTrFz%J?(Xsrq #H/(4&Sk~+PEJj$?~,Nr`}OaDx@1i7N%K*pEJš}m~0!4"Bc:EQ? F8^?PhLZ(ĖGC>>ҀaCiEƤu~@l:C#MF8^?PhLZ(ĖC>ҊaCiEƤu~@lоd~0!4"Bc:EQ? ];#]F8^?PhLZ(ĖC>r3  IE򙻞rhߛG1p" ~ИNQ-ߺAWQ #H/(4&SEb9wwh/3LbBQ!)"#^W1? 5_$t)M;rfb(s)M ZPR? >2Е&R<=<_Oi4EQO&E*XO9DEDK;{^?ٙd7ْd7~/elx?m8e%}FrޯÍ ϣO~@NunaQy@IymXog]#9p8nCQsy@I]_Kq3j>]#9QiF6L7j>]#9iQ' G DQ{ oG DQ 0k +IZm`TQx@tI .{I1']Fr:>6t^R Dۨy:'M( Bk? '0jTx7)((7xP4y;^r'&qSEDtkKqގ:v@ n(~ ~6qH ob7EQHDz?ێqx71($ޯe-J@ n(fThH ob7EQHD: mnD)M ("hQ;-k&qSED4è ҖF@A5jX3(<?(A%7>ă!A2ۨr\šTIS֬x3g45dnMS%JxzMuꤩ:^4hhQM*5ujZS{G̛ڵ=I[5Ԟ=Y?+)O- tVTҚ*X3Z5Mu颩CB ~̝iϗOSGϡNFk5{H&\#{]8KH߆p~ ~ٿo_/_ڿKG='qoi .XbEQ?Ƚ? uoykYK20] 2@HMM@`udpo|e2\f4ȼbEMϟqZ2p@@v?&N|?~X~d]⶟C 4T ? ?|L$mm'5!g=_w g=듓owI#Z9Kz> y۞Y=h?FrB}Z Ҟ fMQEP9_ownWp#q؏uN2Y =q[Q3ہC rm|w@fM 6sdVC 4.?[LJz{=gƌۮ~f#<?|Un [~2As=ux1Ào(`2iM*D=iۮ]A辁'N812H7h\ꭷTnW_lБ÷ϴi%}z|3 ~rޯ՗$H{jxӔG 3 ("@f5.[/O5}xkw45c4/YRSy USfyoެ/ /x_g]B-b36g5㏚??cqnMnʗ/p .j4uJAJǧzUַoEygN?ͪZAl{I=3D+3{NHN$P{ӒG 3 (EQ1,`:yFM*E2s8.3pTTEhjtڙ!Lf_LGs T*'c{Uz8B[s(١h{նh>pLc8߬jjdV 6ǾM&KfḍQ[=弿ȣ|SEx, f5zM/óFOԀŊy_s~3p<5ΜС4ue+T\S'M͛wm<Ȁw ?e 2ձg;j=ᘛoɚ>]8b^Mmwީ'sŋ=%Y)a!EygfU2Xo]G='q;$P{xã|SE`! z(7JB0jFQCy1ÀQ˔EiSG<sTRn}dpͻ5~?d7EQH ~/5ը<^ {tT_}e` 뇼{8@ e Z]kBueoOǗ}+鄵MG#ŋj֎A#+UGK?_ז?Xa? AP ^$\0mF+$˂K>0wGGڷȬ2sX]^9kHB}U$`>|WBNWZs?npA&)~@i:]#Qx1zptNJ ¶=$40r)1C?8hd̔ e9ޜ?xYdcQy׵8$Opf|e""AI?oˌD%>#ҽ%^. /=m={ $^$!_:"{B/!m߷T>]l* W y7j{'7;f ܡ1b#ٞVg(%3uKow~7v=LYxI8nK7[I8]?XX~H`2($_Iv 1 fB-@b'Ouv.yd~61Jm4X$d6 3oKPvim}d߶We`%!! S|i#"(w*P߶jsK"$0 ~!-榍|?؞a m֛ Fy痾oK9d؄aݏw=k`a!  n(@^5*ͨ ֮b"A2H>H]Af5~nG`$"3 /u[=~8ҚD2;Y`@%bq̾CNOFt/<~:qٻטotv.}+$eC[S?nߙ[dƏXSC#1 PXTf?wsyޜWf M[1O¾z8׈ec7 >֭[k֬E]U&MSO1~]'ԩh4D#iwjاY4v*HڕyuMkT+J BR*Sw>u깴gX9ObEr3ެ*}MiG`͌6WՃ}T7ֹQ*\*tI!UJyUC5d,׺ڕQ9J*f~4 (" "QgmϿx1ՁVp"5CTv;Ͱ $AIo6geue85Q'HnjΘu‹KYr}8QD9¯g37L]\U1q#5CVㅶUgCf|&3L mXĜ9Bs Xsl jA~!Mxf`nL2Ji{̀_wx藭WD] ~H7\עE qF޴iSuX?:~Q\Ϭg>|ZcQ\C,/RY:4[ή 5O"^'XCڛ,_R < = ;4LPU1&AEQ? A~m+m[G3@L$e/3Ec l:q/$qQ ٯQF׮]y$_cDꑡg<ߥ_0u[uw\]qO )Qxu3wZVRw;ܯ?5Z}J=u:4{eo;OY ]ZȜ!W5Ps }WWc/UP,e"cqO<}WY89yKwC ھ%F1dQb7EQ-ѴiӴCطo{P_ͯ?J+FmmkvY2 ~cQkF]- }&_%ܻ})C0w5G8DŽȪ ~?:֨u=CzG{GX 3ű]|ԛQ ~dq w]_n.{׿+n:ƷPJҮ$UHA/_>aVk}f!p 5E#@?V%p>2j  KAc7۶mۙ3~GEr  0Fv?:.fo{ոq빬gA-$c~,6nߛF1dI<;u0;Q˔EM^;g{pZxqٶEK\?P ~lrGJJJXS~0(qE 5hk[Ӥֶ*^{%X ]Z(5[~ҜBjaGq n<<>2ǂO*P_PJ}s[J곺>I<)QG )]#Acq%GǮ;m/L~jjP}6mtmVC}TZx؜CjUm;%.?vQ/qM Lo_֭jNzT.U"+VRB ~*ZocyѾܨ lG>bƁ7T3m-nQBIs _PEFm$ )]#AGVJ-8jc6/6R3=ow5W٥3y8z ڸqǂ Vp,|UR5а,R> )PFLg%꥟^ZCfвUz=%,սL'veH `qiW ~sѐB\ڵk7kxX qt'Uhaue3M$J>v)^u儖z\rLjVR|sP" ~H?ְB?_ ,Z&ߢڽx1{uc>^Euz>C[[IUɟS33۶5g*읪} Yˌ~sMHᐱ֚%ך{hUusOν82c~[|t*{mzj5f.Dtlz:5avաy>Q ~ jܸfXpUV=?Q{^.C[f=Qlq7~Zh%x2k?=+Ԣ?fYm_ov3I{sIx 1Fh^k"~8Ru~.5$ !߯ݕyCBr_%Gs4?יݣylF7!jժHݺuvaܹs8p {ݻwiӾm֬,]~Q͇7WP!C[ka6W#&0gB zVoTQ?g4&Z/3\xRuqVMy^f: v Cwأj-q?_n2ÆSd دw1{ rӮrC&痚DND=:zO%!;7CśUWOٌAªōjSV9w_Q,Y4qFJ6<)  SF#ۃRh&6{k۠G?ftH9Ls 9jOVX_z<{N1̲[oH>{_C~Yɵ쳃$uıM6lAs}.} gyv)٥mDz -]k3 i|!( A4jIh)2k?=!3gط}im?}kבD#PDfxϫ69+|PH=ۯ!jfa>cÆ ~|~ܖ:R֪77!|6 ~ wZg,w;Ǥ\e~m+V_N/ ZͲ_-JDɬ"#'7>o^>?XX~ 3QC@[5G)]#koZACrպ?(s F2ykkT8:8ksY;F-铡4!iqfjBLsoGT!'Tρ'h>k7'^nԸJ r;4m}ר?`ܟ#6$B\ey/zZ("B\呶>yb (]#AQE<~#myb^;@PF( r]F}GzxA(,,?fTh+ X~`~8{?>YO.:@F '`!B"(?v&:dk?J oԿoгh 4̺{e}\wuF7GtJ5*Yz ~db巎96mڜ|Su]WM6=s35n~5q%IT> j* ).rV-'t[vrn-?"PJl֬0HIIQPB+6֧='ׯXS:> 2KTJÜ9sr,uw OAN]}h6 ]#We~:żDtzX d.e2djg櫂w7ZG$JB6nܨ2#ۛ5kvnݺ=ӟ&|ɓ';={ԩY'Nsj?n~]7Q ]#WBt?f*iWRuH"g2ghi+uw8?yqiT*[ۯZ3n/GG>&5%I&}%GP~5vퟗpӧOW_zwT v[_ΝU׮]5zl~$ri?@t }+!wgԍި~i5ȐmԿ5Ȼ5ɭ,$=4bՊXb*fjW]a_7- 9E;r͚5r3\[]׋>/+N:em8_~ֶ͛;_^=CxdɒLQ6l+D+_TѢE{iڴڵkW֬Yx7n6m?/^ܷjժtҪ`XbZjfQi#˼m!T=X]A~ĕݏ:1JUkPyGټ~s~LD*^fJta?S?-z:Uvlf(DfHwZ^3]|Iu՗ ~<3L믰Y:^9J=Y[|7U9gn~mDJJJX9slu}n#PURŜ#G&MeʔQcǎU|c -G6lXzGԢEԸqեKZȌ$ZINNV'OV5k zϡ?Bl͙?lbֱpBUvefT7TFNW8($!|L]*SvrY2Fmmڕֶ>yZcQk}bE\ݯǰ`Mjű}\[]nvS|s=# ~>=Z(TߓmmKڕ䘉7Ե_W)o> husNYZ~Ҽ/)jڴiڡC ~/ ~T^]-X ɓ'UB2om۶ fQ;wNu5C}?ʕ+!Zjֶ)S?ǎ9G(̙3O?nv3hSre).nR\z=teQ] #s(ťZD2sKJ\b G9)a69wv?o쯮 km#lr*Xڧ5ոqֶ+x@㈂;g#}cZG _^UWZ05z .8O>u=Cz;KS/ @{wtiuȑLk֬qk֭k]BZ{x:tP:urԼyW ׿m2[v{5zQn݈FM\d|z ]aΨt(" =셱ìgRE//j V.Xɭj֟<*VURܷZL7Ƕu׵A? ёQ'Gۚjj΍!o8rCw[7?G8q1ŤI2 >p_ Ȑ26f̘?BÇ} *`@~"El2uβ,t6<Qc}I{ҿmIa _粞:i="-@H# 眲C)U|IIõ-[HII +g}l%QFe>c .ls@\Eɓ'UB2Ӊo;vh?,Yb+P9oƍd#V?hY>DW8t7*ͨtE~PEWBt?psоf,~_4v h?^$./Gp^둡8o%1_ollZV (!_7rNyVUYq֌3lc&a-@H# 眲HHķM#x?-Gj&Munc믿VWcw}eiSl*ʕScǎU'OVr\?:wvݻGx衇eUrr5kh?d5F6oެ)@~q?]_n+,F(!{@wz- ~RJyǠnbeEΛ+Vr̸a/ <<36cUT (zyQuY2?dm idտn9.m#W5z ڸq?3#>/?U ŋ oL2UW]q#PL"֯_J*἗_~XbLR=XvU^HD:1Ez7{z(Jȃ<8:h?֭/+XCU*qE 3"ެ꾠{qEB7=pv/J5* ~:9JXW]~`ꪛR|x9e[V#hذ?G8p/o]vs%Gw߭FΜ98.uԃ>/J(xs]ʕcLkRVRyRwF1~?~\ꫯVEQ7|aÆhQ? 5*ͨtE~PEW=a쾏.=~e7#Q^^5:qL2ϟ*ZjuOy g>3lb4|Ѳer) G\f^+ik?(~+y*!3R +{np5^^UNy6Djznݺ۵ku J{?I&3u]#dpn3gǐp>+s o[n75~w"9FJ7XtEQq%O?X{Pu]/nTZj1(%ly}fUBj*?@4ʨF+kak?(~D(yq<N:5k2eʨ %K5j޽{ MF͠ ]`5EQ?M,,,,yk<%oYX~P?4 47zwhEWG+LF(G1oYX~P?bTQWAU~4]`5 YcG-.Sm=E ) XFE2ZJ7dQgJ+LF鴇~DXXXX~E ˍ`T?"K+A7tGBVa{4kȻiEwQ#xJ@B?Qqy!@n+4٨-tIrAC jUnv0xv VUgTǮf`~\dQ;GWdQF+r.BT5jڪMjQXzڱO6f`u3Ԃ KԜw.X2Zxs痪zns,_m^Z=f=)6#~[[RN-S/ؒ!>oW36zs[_Vw1# uj¼Ca^;mmRZ&lUɲ_`^_ŗ$!/J[۠jbDKE7pgvw{(܃'+7o-݋ͻYيf0>/( (i!i&]o15׭hߠg ذ1Zڱ'Ñsw !2ܷMk˵zƘw2ۀoP~6˹r}z~3 s ^?X>]t2> Tns ;#3dKC/ 3lg  %0jQ芐0Q ?v_m9>=}ɌAYdo .K|e$2ݷM^%@]}5[j͌յ3b7t?L r m&8{uTٻX? FB>_nZU m9?{C}sï=gV~XFK WJ0K ,n-`%P/3}d&9|H!-o[ ȼ9ecм} NO2gla 癶zc6#wB˾2AAnn$XzpY3+Y ͦgVl`aaaa<%XFc?|缿V+\yfIt-=e_ӗEO_O?˂GJ~\/ AR}rp=|}KBfY|˘:Bl?魞OSFS~o }7վӅn$0J(4ao e?O(7 >["B.EQqV|^DA{H/| dTQ Z j.U yv4I)[͙$`_B>tׯ~R-}e]\n @OWd>t?7нGA8>}ă N;츚vYכoŐߡ>s{p/Zh[|EwQT]B!7BPD Jt"]@:bQAEE"H!h@H=lM=;w{gvg|s_ _P+=t B3IcCAE@R@S,9@|r9 bڶQ)c?E3 pW>v>f}>:"3w086p=g59C˻Ok2hm7wsR&.3v Z8;~9r/.G^9/k'-29{Kwp::n&pG2&)㇞H8|u,YGz`0PJJʍ`h6qMvbإ.LW̕Y*6h3su=0~`<-?Q ͔1CAaR?K7s0l{K >daAo|MOL^߿7[)H[mpvYYeзʙ W^RgGlu]^y&Ϻ\q-cHZ:~4~h[sw-wDnݺ{yw֬YIYYY̙3tv\ 8v>uER=yAM_ÝG@P bYq}'>I]gvSth  CƴS-4\NcƏ0~hW $OQob(tsFz  8!JME 8w t-}Pz>g yi1.Aҟ]<,e0 L>;=ξ*e1oc9ԶY: Ԗk/ZԫuVo=GzL_/0 N>d6sQ;#iCyun9-<?|}'O3x{Ϟ=kn/~4'IJ5ݭ)@hPdpu<1sβ"q(14zh|^ v)wG>99a Ѯ ?og2B7:a@ GNKm9A8|Q7ocǥKH \{'$$q%VhRA2yƪHe뗕!!* ^79ofah60~P hW 0%ꖨP  0Cέ|k8tPT>de0"tm^?T-Shd(5LmH ~_ ܪӾۼ ACUJRD)z\۴d̐9HQ2~Ȱm ӹ| GmW=n3ޮNM5gF%y uq'흤ZyQ/'J% SŽq2}-kM^cƏgR.uv7Hi eqd45bPB/۸Zq~B{Z|ak:s0~?hW O,s8-zfc rDx!  (o60~ 8pn[ҥK? =9`{O?!??êUnXrq mPaQehΩ9gSDtMȘH~tFWmq[3V2.X'bl|AaXus\{7U#VSV6~ދzKSkӓGt9Ɔy-+{ԏOIF cKtæjK-u@A`i^uQT! [T  =+ޏ@S#:AAAON [Jȑ#eؽ{)Kwo4~H4Ҁg9@~R'?A( 2]h>8p *4m7/ z6*^Of@Pu[jQڲ4j@ ʶk`JE6^O0ni}G{響Z_+O? \H*WB׌ ,lp|i_S}W3ʳ9ߪ~4TǛLJt)Hkښg%}4:a vu/0~JQ7E-Px^0P`9  CdhoRjj 1~ t?kmڴ“ L8#iCO?E^φ-A܁Gf9K~q&=^qm9[?n*e_$o Ɛ 2eP;v}fja/VZbI8WkIGA+lNh~s\ݼmu4|RfΨӹmHT AƏ$u떅cd0l\tڴiCz'?^"o2~Lnxwv<>ӢWSJIs 6er ,Xb{˥U6JZ"ظ2z:Y㪿z^+O? \Q?k *yc+bubDWRkZ{@b`8QEPxn*@sAA\@|gѣ-?N:u&dPj|Us 3~ s)sf^OFWcN?'ƏC5Xt#FҫW;={޼Op<#ꦨ  9- 0~@AAfǣZ>k#ڳZt {ٸ'U8uLg|5F=z^}>9>(cx Əeزe{ar1Ty}PX s{Jw9[C^"ue2_M_-]"c"%~+\'ӶEbXtb{ )b6a UV4~Թf@  +Ocŵ1XnxP6vfڷu.AJ& WcN== 復ҬYʒxoqΝ;_i߾}?_kPx(QwEMPP 9  ? c7wJZ\ r1lذywu>s`\0~95`> 4i$hֿL24f)|OiĶdn˺ Ɖ4R(Uo0?q cPmF%eEYm6ž 6WkIGA+~Y )sS,{QopSC7%XO[&e-I]J~~~vMROz9?{+\d߾}ɓNkDU CqNڊa@sAAyB3C/~xF˚:ޯ&i0o~#ΪK4bm1O `ҥUW|[}u ^b //,Txn{ϳ6ZˁQe髞"ɣOjUJY<Ljz߉_M);q_mY # Nǃ O?2NfPxpã¥l3ƦI&vTa9& WcN== cÆ ҥKrݻw;!!EJCM Ro=j-j1+"w 3`   ?`p*W)?c[>ÂdP3&yIӥKz6 Vqڿ{%njgP/+TjQŠQ@PESDx=^zRC R.uu%*yhC-mA^É_N,s G8/]rj^[-m5/lӹOV펵u94~,&RT(ɰT^곤C㇫ZsAa8|pT$,œ8T2-<'Sm^K/, * C,RC@ ?   Ə|fjXz e^gMxzqgZzN2\pP׷'ӆ_hrv[)6~uD>.:m_]G??ƏGFܡ wˢwh{gi٣R>H_\|}+p-㇖{-.Gmc-~h?JχNWeÊA[>9::)!t\{|ތ{K󹩦i>u&.}'o6?=zU}#>>~߽aѴS95"{ zϧsJ&rP?\əb6cዋ9^cٲe322H#GsYYYR ?   ƏkI+8`ӿ&>:p6d>~Qf={7,ekE'>;e~v 3s AF>hw.lOII{]ƏVac2owuZbE+>lP0mEcę1x8_6\>qaYcHzfiÕ/=3e,L2 $>:bEirun-sV7[?)7x^yW9MK$*߸<*^)427*O]ft~~T\1:+3s AP3~hDnݲ0t 0a!^D_q5 I7Wݔ-,Ȍ#?Ȁ.nzC#>wa((X;~rdnƧԩ%MNNЮ]PǦ wgҮ3rߝ}kC/]׿no?C]}Iڰ?~(OUoCM[q6CM4Xܯ[5:7Քs\ޭmn^yF{0~ ,XxfAAP|͛ ]5O)۴{nfEGD?TQ{kҒKne;&@>iQwEuPC@>HǾ 1{=T?FBZZڝ'O:=q?ɿOv; @]yƏ;ĹQNh1tL̆BaF*j!?lptLhlzmuu OCgm0I{5~d^9$;}w9H|msg/ZlS%?o7g"0ev=2GFXw< f7=_2Ɖp[|rt\CO[xǘyboXS2$8#7z^79%H2ZdQfyfs\y`''8<ap,X`!ƏzhnFF];))) HҐqqE\W̥NeKGhXqu @>HUm۷ ɓݽt钪YIII%$$q;2o@W0nh@j?3[d?&>~?`ǫ9`<>_&aㇽk5yK7oR0WnC 7yiM,w4{ߴ ׿QcoYQ /}tZuC=g6dIBv:fQ^s[w9J,EOOj+J;OqΈeNAk1g/zڢl̯?m  ,0~~C׍!K.W]=ɚ{n7E6|v1bza:?/rc^3~9I]g0~h ,XxfuӥKzMYYY_ ڐquU=v7Ys+?6zdWTu GP3s  FWJSRGzߦ|ƍ?{éLzL-6*T)$Ւ,^|A>fB(qY.-ZZ>i^ҹtq(22*VlF…2~̜9Ռ *2XjAuW):(08vG6q W|Cׁ׿46)PpRVq:r@bJIw?Zs/ַs>^w Tyޗc )Pa-tn~6'?77cW@"eTP@nӵ8/z憻w%oBccIm+[]y [gj!{m~@[a-q=楖׀ 1A:B:żmjy>~5ϋ&?]*ꎨ\崨M AZ 5j$Z}0m4j۩d*wuTIO_4[Tv…KЪU7<yHL6LuZ)2~ 6|ff`۷mwOg}\!A?C#}$6x1zu`o'cC&KD tsy4mN{}(U\ ??ۺ9g\@dS\'Im-۩VJvLh?~׆X1m{V*h9Ýs)68Ύ_~&PtQϕWe^j} )mlcq)fjjvs וM-z:牣:Bzg{s;擖kyV\_74~pa>9kk~,6v7$i>Lu cuMa { ,0~޸+Ab`{'&&5joYYYs~|뭷\5 /15m""y^_0;M~~فhy=g9`+00,*^뷊~x wReN=H>/e3-X;MrWo+קի]G={Ηֱ1ԯK&J& 2i+e^?q2fq2hxo|l0?1(/Izw4quZSg_GϽ! ,0~ܹBsy0~؎ɁF—|r*ʅyW ܖ e&O`(9 ßCLLUy!C^ZCSG*֫L.OKIrիoSTT'UKJ&Mk~|N{C69|FR&6VƏcǎQ-t:)!-miwûKGe}Uzq{@ecLs O6שrr?w'gN~jwp9epg=1Xp6{c΁wz\sCsI\uLk)fnse 79|kdMkdZYDpV6ΜdZy)w>Oxj{3㇝+`|U/~xv:FWI&+gi[#Hc_mӰ,XA0~AP4~%jPܒ)?^㙪Ş|]S2##ㇽ2K\"@s@}Kiٲ+Ҷ=Wo0CI׮3} ÆiQvժV6}JI+m'[ju@rNko~B;whbOiHcmYwfhߜx,ֺ6~'kZ+J&PTepoJe2~o>.t)m(+uWFq O?K&74o[=ٽLCM[<1띗`7kvz36ʌ&i7=|k曞v+cBp`Uf1+>OÝگJV&MNռ]}A ,0~0~xU\ߑEG43ܪҠA4vmIA'l1om9 x٦Ev,1I]Qq |^A^F GJNq miӾ¡̾ѠACOgQ``x ]|\\yiy޽uW-GFFktm0F&6m2i~2i 8hwPWc`+k@ z('ox\Z_5\.c#'P8E*MڬdݫSjPS\5~o߾f9@1N][-Z cOK΂c>\M 35@}w渫@}RyofmZjv[Ecu4렼ogve9yu>d]7=#>O8åK S̨7ggm]S WAP (3z];7>(o;;`hH&MLvN^0~3o`C5;@ ?`|#GlS{Lysa >{U+SXXOW[`n,GWcɒepf9s~jlw! #mը(e03='74u6KާB&n?̙S<%%ɓ'5;vlBB9^Oбx@jXa"=$nZ_S٬;'-P@BzJ\5IsTӲ]Z޵D>N/ۖGƑ}2oZ?:M8]D.g&,#\{:WQ[&kt]b~ ,)v|\knO?r94)㇇>k뺟φA0~@nS jsg_W۶ݜvwǝC)4 @kkt%==M?`}̓N0!`< 2/g .'D}asʼ0dk}`hhlK\ s3~4k]5tj8+}L9`SPNwӟHKK{%Uhb۷`>yFdcsCgc_>u='2>z_~{UZM9,Wf{˖]ıUCLz4dԧVg-*]2 NTxy :t.dzvƎ>--펫8qjǎb/Z4Vu*!PP@0>@UW vn-PDQ[)T&SN|ڵ+%I瓳gNA0~@0~!^9Q[/E|HP3Wmh%)%{/c{o1b]^5keffRVV?~Ĭޞ׃rJPKR P!usOh^w]˧z7sbkG~7SڶUO?p?,Wsxh:6n~u?j ? 7ȳJwV/g9(_p`0PEFDЩ~\iiRlp:;AZ>!89AvjLxn"oGsjjq, jjNb3'a ? ?HQE݃.O6a(+[ǚ5whٲiSUz ECz >}Lի;w,M:t+)) #x oNoj9%m~B9?Z/IMc6wc2Ms~ՂOm^W{'ZkNjf| xgm]S r{۴ׇݺɺ ug`Ϟ.!zW$epVv) #ZVXHaD!eDHVM^ϙ\i ѡukzCbc+D>NrE]v$LJ8q6 6pۊuYҢ]7qhZf '5k{jV:^s0I?` -ℨۢ -OLUQ1zcʔ63iZ#/|C8hڕֵC9=mH=U@G7tNdHgm]S rG`7,6f te/4ٙ6&&JYn(2;v)e(oXm^dž`ylɰn}:l5 E=ڶֿ?*܀ۿ۴NhPժԴdIɼP92R {Ƈqu6~;o^imqޔ cЯVY4SCM=5_C 6s[Ϝ``Ȧc|sb @ϽB7~Sbkיzm? eSi $\)f>oC'75~@k T@EQ f<$Ѕݾ &P2}w擯Ӹ&5 PpɓC{QtՀ!6~+]aV5+W/U_ Vkq6eYkBH {=vexaCu%)c0>l($g G"LWYO??S>t([֢v涞9 xG)|~@ϽB5~ 0~@AAA!,kْcchpMܦM=nP6m[ݺ@"biKRWjqi ,KfCo'`p͚1k[XHH?XCWYUobڬ/샣 B։Ub[b0y+AAACxqgOy}85o.m^˺մKkp<+c^68k5 E=ڶֿE)^ϱ?MMȦ̢R]3+3ww3oX{{  CCb QSœ}p4I_4~Q=&![nSSSw7%''_h׮gGv@`p:aƊ!꼨;U Ub4<'3`   ?tOAA>!wxq_?m+[ϻu?K2|ֵ+FcRmR˺մKkp<*ŊNxnd^&/^e+CCix TX웲 {vf8/}҂fO^i&  1aajEE?ZE=tf=|dm=snj1:t]^9sL"ݳgϭG}vcS-Yev,cu6]ȦzKԧi%R 3^męQ>{Rj`(w0>*W!'%luS1GM={ڇaw}sAA~GjErWpW;v 0p}#"\}Q`}BB(68z$911b iiiwN{QQ2&ZƷqۄC G?ꊚ#E)׾߻Wp}0~@AA= 3~|߻#"(#Ə͛Sr夀x+p`=sv.=,"-bbHp0SpjM֥OvUվb>VDzeuAZq%=֩Ceq zŋKBi&`ٓڈc*gCg8ӿCC1onZrZZ>8z$9͘LOWh mhUU? Fҫ}9(ݓ;O]5~('~f{9nY'G?g:_Qj/*o"Z|sAA~m ?r14iRT=nz¿5G`0.*B~aǟ֣ҙuT?>-]~^7׌;<5'zf/ѨǟbTY w|'jOZ/ Bax .(겨 q 3`   ?{AŘ̜9Ռ  @?i@H[?#k2~YWZ/P&'*{ld;oXةÙ qnR ?R6ճi͵˽E("ܟ" PJBQںE8k5h}%忹]oDK38}>X@:;l Z׌5~Ky[]%]zg>zKeMzjԮt>k|A&aDJYG!2BÌvҊ7Yܖ i3kfJ+W˭|e35iqT_xx!j~o=;NZE号]y7~sAA~!7aÆ}a>%} i{N%ci_74*L|XW3i6O}h*tsoC?ԶƏק[6&Nk8۵XdMbARYK6.h1{^&oQ1nK"??ۺ+ƅ_='fu][vRZu0@)Eo+oy[uj}L\tzw] |lGً.ؔh>W"yvQ8ҦQŤv vZ:8␨|^AA0gAAA0~(;  ?|1III}]YYY6~|#s˺챲Rf{92~taw=Z3%4.,1LuG KЗ/Vm[j;+[ejt;s Ζ;һE= E-`դrƜJԸYB IWh k״9~%yG37]nY4jT٤2oIykO^?tz[k|xr[~-{ ty}pH],wp 5;vieڴK0?^L7s6fX.X%KÏNIQ2[EJ{bUVb_CkԴAΞ#-1{AAߣ`traP1hK,hRRݺu"{d0l땈'O~OKYE 2xqY+Ryceu˄ЙuT?i3q v-6T3UXS.ϙ*؀at-ox\u-^h1~![(ߥCHyGPn+N5MbtI.? Z'N֬orM[K,Uǟ^7 i?Fe/nqF6}mlEf̵V(lr9a`U2-?)yۋon㇞jÄ:/- yCP AA7APN?Ȁ˴̣ʱѣ?Ci /_Ci\?i'߽^Sy*R_߳41T)U} lLpv d8#iN8m,)c]@xxjCk{~ \Ō&eZ"ǐ!Cdff ޱcAK~Ps6߱m[\n|#Ə[_7+n`aHI(J?Ni(P[Tukrh1oίp,7j.S2~;`hH&M 0~AÜ9s>y`cǎMHH8g0"?ǕF5-ŋ _Qeq})da4wdi:~UpefO?XeĴ=aR 6qNm)%~p}iiiw/]*[,w}} rf#k2~ Kфck55[Cm=mdp*,?Vm)#u)sV>Ԧ^6X6QR(pgÓGjش%~|ɦg=Vm,"og#$dL>q&2+:LXbPDa8HTh"D] + w!jFp?'@0:>+?`(`;?>;v}ZZWO?qՎ;?} JyE3= htg5~8k_6EcPHDQARqcӵU-T:.B:,"Sf+c~SM)+)81~ǪS%n>9]T(_gbR6;ėkL 5ԧ&Ж]_9rfN*-ikԔB?Xo҇Rx 룊ѽwZUڸ 0d8U:I(Y&wڡ,pMԸykFZJW?TuSR y^G\-6~p֏`l2VjE*EQ!!OԱlY[77={JER1\X hBݺta@fOv8  Ԋt?miWt|ns*U謝? -lޜ(A(XlsH'U{Jz:iT27DdS>"UJG{(;Q#ǰEL P…ii˖Rwu.WN8.>KK+u<TUe>}KvuLP~JKob0bĈ]v>k,̤,)ț;~3Y0~v#;jVM{?F.MlPcy뀻-R䐓dž`cDP;e&㇨[uFp?; ]߃QӒ%6m*]Lfb}L l$'>6VfTdM)lh0#XlDfG;v>Xd^f4~h;&,L*+w0N~|0 }ܫW;w|Y:tWRRGv*ӗ+K!~6Ə6 #l;wY58 gZ.TzBG`%ㇻo xm9|e3ŕ)'988ֿL0~߄ lX + 牍 gx.jpjr@*g_.ߡmLLa5j3)X{۴2up捶q%-Z5-T$b!E 5Vr29)n\aZ?vmyU9=>sW[\Dh(`[r2%1g&ӉL*Z~(LkִXƏýzIi}/uIԔ3F=*TG~Ѯ5U??Y"ꖨ: tK(Δ)CaA/=7H=jzbQ.S'T%ZѢT92R2 s=&?VTmSb&u]5,/?ҫ\!´}%CFE[y[`ysm?xlŇ)?ę}Wg0Ky=_UctM, mߟ`G~|3eB =sSi5~ TT0G5??*Q<% ^:)GoX?8D ~ fAAI~ܸA*BPzaCÞiCٲu.UJ޶{5?:Zյ[76;9ӻ:828. cƏSYԵRϻu=zh*u+L>ѡJs%JX ?|C0>@xFmQ0{$62?8! H 7HFKQh}6mjTC9aiĤg3qu,E:Nr\>'jvT<4TScthpM|V?`xA0~=KaQ:.*@WNb?~QOP!`|`tI7 -[RR *.ekWܕt[ol8e= ʋ,gQ@%KAAEq **ࣂld R@!}JN6isN4I>ڳϝCۓޕⴰCҥFQA,! ~}hD6sV5RENw$nUn+_^[ym*[ˬԵWO F~pcժ-[z:nbc}?Yhmq+~}~xOQ?>PF0KDSQLA?e*l6I(NQ)4 $M5<)~E>~|`)⨭: 4 H㥐&h y `)~E>~|`?Gt2G9~VAR&XmI(NQ)v况4 Hl6iJ;ZxF'i(@@3:EQ?rt((~F#;KjESEQaQ9 E3 9괣9|V9/rv3>f-:{ Kx!nf?[׺xxE+᨟uQigr3U٤-dzQ;Os<+S?֙{67 ~{aFY)D)yȇ/S|WyvV?vLx:mfD\ DxGȁ޴6Q_"gޑ{jΊ+["-s]#mad]_~~:"5e"/=+eZ]뾝"s&4*h3Ү7-k,/rsݽFGE.Ok]?=AҮugwW2ྎ':s"_;F^Hun k޾!}@3:EQ? յ?~u4<Bx?T̨glZy:Ƒ".eFIrm:|rH͵u ~ڒsT* >8{X~յLV ^;0z|_iR+P#O:pqڹ!}@3:EQ? 48ꈣpT`?RT 2V p2Z†)g)} , "#:WR#0ݧحFhW[dHni~H_`CykL5ݗ\|k_"/vo\H*Xa-?eW"DM%5]ըݒ6Â7\,u3Ү7ib|OIk3DzkIxQOqehp۱ir׽qZ6k?>q?v8݀G\y^Gy}j'@L WIk~] DHtF(G~9jN:`>.X",?ȕl6nʲe%`y(UJ\k ~RT0NT8GP#<*m[Dn/6_uv\˴u󤝇S׆ޏ:juTywy%um2u>N#4F͟ PVеljk_w:N]k=f]oԱH"ejz+["?_ Ns;c9^CiT+ߎ*NMV{OBѠ_Q`}/0dNYݏ6\~mxJ(G~8G]pT`=(~y("]CvU"\ um_Wƒk|5KQގ<¿;0i}<5z6k͙d-Oc׾_kfI ,a]oVsH:(Gw{Y"y_Sdݷ58M{>8s׮]~ :0n']gý]TvuT<{ *&<PR"Uײ|>%l`?9~0~/0;aG!@ EQT  tsiGq́<(~HPT ,y#8EQ?B}7Fڮj`?WBY}\n1u!TZv@<Ձ9Zz_e/v-2[ןC"t-?5ڗsg]~cVeVX0lM#Q@ca@>"rk~V; ~9/ jZ G ̼>YSVcڌM0Hqv8ꜣ;*>P?@ث`V\*_^Y]}uT̨}seum`?҇Nm[ycRۨ0zjGf׈?q[5"A[# >Y?cڌM0H6EGMsTI?#P@d)"A ~ONgE`gQ:Ze<o`|/0~ V  :j8ꨣuT,!P@d)"ﻁv0EQ( |urԻ=|>%P@!>^R9)"t&AQ͎ZvݎaIVDeyE]:I(DG-{Q^p2RJh qfNQߥ4~@T'wuQB"d)"t("@+>Bã=ƹ$B7n5uoOhn͛С'xb8 h^nd&i 1f,=r?ZgwաhG@~^WOr(~@|x' _e?PfO~}!"3fѦMٱcdF-OJJ7!!a0 D6U[mf&3S?(4~.?Y}G("a@nw^!U#quFѩ{iҲT*! ~̝; 8vCפI6l؊g)QX=/  (DߝGmǧ7| }/L*ժKB%&&VʕJc["/N{Cqϟ_[mҹz3Xz"qELCOה J\brW~O. ]b%-Χk䋉@ye-?_]]峽չZ2Uޕ_pӥktr /t~U|q!!!!ny + \?d b6Yun,;YR?oixNS何32[E1OYgxz:N,o-/O9c.>,?Q\\[䍯K3  x\>e<^;ևv3ylӽEy?}V_X'|{os w[BrpZ>)5ЮwwC:z;>_]ssWAպ$s熞;a}Ir3(ߝj>F+_3_5S$yL (_}8J*-yɰ F|?:W_s(SB!vdj-x7o^m o61c|%JNjW۫Ќ{[.F_ HIIY2rH1Gv{l+JiU3S~wVUn3ֽ\:WkW:0k0p?፣vWᓟ5?ɚKY}9sj$#uN* ߷ڟtk j?vIU<Ƅ7PAn/i󧾛ϟ^m7`PufuQ3e0־{L#`{&AQE 7oV5Gѽ侃drS[ CUwc-y1-V\}&-=^>41nR\}CIm ~4AЋ qIa} zQV:IJ6.˶Ky}o=dLzTz5kuhWuWm;5 }LyGdsUBws6*ڴ*?Rm4f1}~pbvq1ڇ?UC_dڼw뱟NhU;{6QᏩwW Epջ]meVs _m+J[//?$\`{&AQE 7쏿 t= m}䋉ѷ7t25sYgg~|6?t(Q>ɳUi:sYe2 ~ELL>?y*eGMlwA&,X:FNש}s}}3ġFLtj)P~I>ZG/9rTgοvy| ~g$M#( ~,?AH>|?Ǿ6kWFp66_u~W#68*T'{TGw粑Sh,MݬuwXݒ/T۹<^C%pZr?Idt=b +PCuwߵyf59mFQy8-;9kk~s.am*l~x3({OQ=vv kďo籍}7ї5i.CC\לKebxz8}΀GȬqR<⯐Z'KVKruHorFpKe*RZuG hܸ?#Ѿ}{Jww1뮻 !vK?>ݲCFjp(!}b̬٤Fy6:Ic:>lʜT}~,Ԥj 5Rn$~yv_{!=y3PfC"_!u??l52E6Rmnw`>fW7?r> gEQ̓Wx"Ev zvʫ\p/TQ&뼞6k=DjWM? YϏqǻ>m"#B9]#v1sj$=5k{G;(ѴifGؾ}!F-Qp9@FQ}+<&=1RE)>:5T--8P#)L}}T  ?7 48B@1፣Ծp T}MȪ3k#GN'L("y~P?~tSc؄ڨN:V_gޢ%h1}YrAPr;D'WPd2^q_kLL>^[ܿ8m˧o_W_o@bͯWM[muG?'qPcǎ/_n*1vblRf⅟zf::s~Nluyc>\kЂKn1?]#^p#[EY}W p?U7_쀶lّM(uF|,j#k_a0F*{clչay}( g`"A A _*\sjեUNl+w2mݫ[d)o =Ejջc9 dkCU:7kչs~b֙?T5H_h7?R^yw 5YhRZM_/ x}Gɍ_'}ܯ6ddɰ>xY iׯh"j7},F,ѥK1#Gd"qYf_<+٪VVv\ZBTT?Tr+Lx-w5o7-dU_wY1OYsimЂ FG:v;+hOP\G]8Qcܫɢ[=oC3'p-ZqN ~ڵI&>'?pqz}>ciȽ^TmcN})k0eʔ/uORR.͚5;ۡC=>PqA !GEl]H?FNqP#=WzjA&ϮGWK3\y:,Q̮Y[PFiTH\Qm^OLL }L6m[6m$?d7oTF}B8N^8r5FKƍiҤ?;v츘W\ }4h` 03Sa(ʮrwL>&j&M0i30~ tjw2c|_]h"P4h2!!pϞ=w.]󎧆s?Yfv DݯyVf):S+7nH^Fjx @ D"A@6G#kҬtV]|=ܠA%w^:<+l6Yj,ZBT@ٕeќp ~L:#G3zn},8~ɓWʖ m;vOUxV=Oԁz) G=N,_Tcɒ%{vb)30~ Ek p|u):S+͛7tS;vf%#@8#D"A@6G""YjʴyPhܸ?#Ѿ}{Jww1뮻ܿ>Ï 8dVszZB>EGpreiӦ͎}TFIBh2Hݨ ETSYzG&6nXkԩ$''lѢE7o~C{x|ݯĈkתU8T:uk|7#& ( lZQ% #xOӼ5Lώs(_hV#Qر˗ ~|Gż b"^qġZ549s|wjj@v̿{Z{ۅ}]O:e8~yzn:?^ou-ȦG0mM"nz?',C̑G Ivk׮cGyDb b"^k)P@?ͱ3fѦMٱcGOjyRRҿ &;/>}h;WZo_HNn~jh ~& (~kШSzϬok5፣A~sZ(~SY}VުU>ql{@mX" `&(L}DKnܹ_ǎ![פI6l~͎G߾}5}?"vpC2~9M""] ҠM?=A~$}IpCILLlעE~fv}g 2 F `کiin'%oL^k~?RNHMD?FJW.-/ö t䐾k^+ 痂 J2 7rH٤MMo;G k 6\nLQWfdԮQ>5x`\խRT}TV&<{L>2Y_U2a˛a? ~lڴNRR /$$$^ oo=bݺu~UC^zRhQɟ?TZUz-ʶKsZt8Y .,Ŋ͛a-[ȃ>(qqqzM6;w ~kj_W^]J*%111ZpFZh%555(`{vJv+_Ѯk?r@ED:Uf[mr6>^ ?DSwi}}˖l}#N<.]V]/? %;ů{: {OqkqX鷩֠y=JeXXbRRIC}Q Z^ A o~b'q4c$?}kCET#6o|"I?_?k0zzv稂9j-4hРeBB^zZlA''|n?}, RB1W#ORNi-NȰG6Eϒz|5䣓3GWvC6яQc}Eݬvî#tX ~B0IۗJ;K庾Fw6"庻suJ[M/h:~Kbtѣlj˗dɒ5T7k/T~YC|5NJ+3:vWZ^o5yd׬e˖f5kǿ4iWL1c{Rn]}PRmFP5ZիaÆA;P?yo fNz' *x|/_N4z@О P?C?3t1S2W[ʤه Ob?̜31C=צ]ԈǜF^}Q"{%٤k_} ,qws{dΜ9퓘CU-{LnfkJ*Z0{&9zogr]4APKßve3@ (~!j~||tK/SA 粱WϬz.0sjח-;9õŪ4 g} Fun]<^EB pZukQ~>r?2w-d0=#^>=U;w6xk0z 笹.?B>~7LLLL:Y~o[qI !? ݲ{>5|1f_k0zrc?} ,9GE7p&&& o{کiRu=_5JAvHg2쌐?oZv5rlH L}DRNh˚mϿu~?3ȿٍVӦM/СCۣqďŋ~dvYho}/ѣG=Fy饗PԲ1cn?yo85픴Z wT`QiwK;,39;lGpV ~pc lj R#~wDz'7fHX}{?nYcfS;G>v-fν3oSm5,}Zjy i Z7H[(6y٧JT,!jCC \ǎ-_Tf;P?֬Y/TmYU!~Wj˖-^=sLЯLɓϟ3g}<Y?֭[˗/6bsMd#Gm)'dvҸzcɗDxeӋ?,JTu~Nsu2*? *:3@׵ê0DN u}mz=/>SZ̜][Ybu|m۶oQ+-DsNY`tMn馠_ ?/S̘1C}[DZ ~2ן:uڵKƍqGB#v spP&,u*hEJ˞{~ v Э:w?d?~ jn pB9G ٵr>[dKhh6{oY߂;39m{?wVf-pϢ=ۂv-f]qi5HLy? pb47Uƽ,ڻ:~dXtgbb"BP~6\􀔹~Y?&NkuGGe)#tr??FQ[@JƗ4c6I}I[{ݗ{Ž}ϐFkgX_]}κ4#1l˰gOرP+Wwl{@m_?m"P^aҥKZSSSGfXr_ /"%JȰߒ%Kj;u^ϱvHý &$WJC7Dr|ZJTub'{s,?1d@!k8XɴȦ,MGfV{szumk?^׸3Ԇ혧D A(Y6RNHQMB"$_l>)uE)dC=5Pӈ#5@\)Xܘx6l6]>׵ ޠK_s=/wTR ׌'{RZMi3zBum/T#|8j^+G~KLLlעEǎc|jj ݾaÆP?6m$58lN'm&ŋXPԩSGz-K. ͛7<]Ob qqygRJR@Yv 'OG0`??ȑG~ v *Xm"=Sp/z#AA6,FCuWZxf}?̞CV-LOb6YY}aQ#Bx xl6+h +Qٱ(Dw6 wG/{jd\D*T)zuv9kРA˄={ܹtSSSϋȹ٬YEvZJH G0mBBEXtgbb"BPn}dUB1e8 ,qwDj@mBBEX"bbb @}K底H"7&,VP*ש,Cej9R:ȽM@[~*VxUQ5 l(}^DLLL_K~P?~dv &6 1Z)"EDA7EAf *lRj:h t(GhMM+u}":*שco=F{f4!I2n*5 YGkB(}_K~P?~dv :SE#c 9q84Q,/6(R@+#6c*{OzQ#1bhrE+wJ#5#@DEAfP#8EQ?B>aߞ ԏa4H h_>_Uu=I91" L?(?~2QfQNQоEL! <{$IJRE<ո̺4Ⱦ730~  D&lґ@Gp~}tWj!q$&\Z?^_m~~Wmx/_ +(j3䎑ZyMMo;GF?П ޠ񧟞w,ZPrUi'}[[ycjm5lZgG&gmլ5*3rN;=f^|yol=(5N% moqoس:O}A%ϯ|^^%)H{c}hCaw6unff? ~og`"A A@-? TNQоpCuW->:y {Oqkqɓ7Om5qQGI\ +_LJV*i::@VǟTkT0zh}'y"ӎ*0/b>~b_WbPjAR22d=_lQyߵ/=e$D`=1a fCM3̐7unfFQq&?BBEX?굩ё%d޸>_`m_ʠu+%OWd!1j5/RN~c4 *6iZn{6ǿ[j4 5'nQKyG T@ quW8竀Kۗj-TS.}ei8O}<2?TոtUZLlߨW#}5"sz=H|\sm ^9?P^j 3Mj멻<=97_3K({8n!@+UJ//h qt(GcT@)#GtY__-+\k2Gu,w|Fp.Am{C\쾱7& &K鏯:X=ǹL;5M?L)Y6/O `"AQ?~>QZQF[r/-9E0>N \뷛t0udvXx!GOɔcS ~eoQ~sϽ f-? T(>@)'R$_l>}~N܅K֗?F:;W! P~|5|e7 ͛/̺8K_VZ:OwGvI_^sݧKHŴPCY\Q0Aqŭ=:Qb ]C+KKh_TqvǺ [(69ip ~oPmu_Φ->ܟ?rS@5kSO:c?0Yj[C_XA|dj'v vi+Tɣzv?tK[7K{iQFIgbnE):3Z'Io=nM5J㾍QsIDATuT&8,kfffOB\@YҶB<@[ 7"hC`n9ա/F%EJɰo)_2ۃ*LFn)]0{]Q G)Kΰ1E?߽c- gQ<ҟ3>=sv㔿|u=a=-a 4y|97^34s= V`E~)d4T밭FyOJ]QJn{6Ѐj}|+ o敭RVߦ]%駧KI-ۮB iXB1\$Ih RRI-_3^tjJ+U'A>U6O#b ~j^}܊礚/_;'չ_k{xCEzBum,^BF#F@jd1ZRJrg;/kftfOB\fڥJI1ZH@C9~轣=FӐpƧCCrL mx?> ` i!NzWj#^p |6BrJ7Uk|Sյ%K "BщnLLL?Ntgbb"g Ur/tgbb"Hg WZ 6@.@7p&&&xv rMVޖ:Kk "BщnLLL?Ntgbb"g שPAY+JoZQnLLL?:6D*U(F7p&&&xv rM*KUZQnLLL?NBQ Qn!JV$Ym>>^Jn!@d#Vl)/7n!an!@VW*h c ײ٤&R45B\fJl2h cKv d WPAVIwM5‹B\->^nDl6Ok٤~/v dw'RuϽ\T傂F`殘2 $"`"(ۦՓ>ifeS|'L[4̝νsp/e9s{3w^}> tHDl Cԇù@ǑI~nCo W8SU sDޯU% ?H~P]J6~PEDk]`GvP*$ ?MF݇ԇG AɆ((0dhXd3G )P@e@C~a#@l(2GɆ((zl(2!C!CaksƆu&[0 EtIp)JSn9ndv]u v\5%lxu&(2$lRgB@J:z]jä6[gV˦VѓG~dTM&Q@T\л|;?ΰUo\ls=({T}{hJ~[o&TM&Q@TT ‰ښQIVGlK~$1 ?L(aР@J~XѵGג5-XE6bFzCN;$|fe"{g~YKKRv6~V@d@ ׇ=:tw4H"pSpg]ϲ ?yHޫ{z~,:u~P5DЂÔpC@ ֽ.L|bpbw-#Iܻ憓W#|¥Z-xOYvckS~}ȭ7s†W7(2ZD}74ֱǀpUwقkWNϿoHV ?b[X4R39쏜™DXHfm0ydK&Q@SE↑Fnp]K*3eLk h[t[U ?mvjٷgQ gQ@%2A˜ >خ3^l?b7#="_,1lac?ׅn=7a kڵD+ԇP_w4f,SEE+^/fSw`߰uצߐ~-cc˻Эg8tP7.K_:~_m! ?$ ?v% <8v$fL񞊊n‰=Uca]sɫNڑ[~%GYEۚ~vEYۥ6Β/i_>qm~ ?Lfaë55U1p"x#FVY۠}5{o|}cQ5?~-ђ1 ?Lf!\yϞ-h#q\+нWC+aQG-~>rE+l->.^ܞ%(ZQ_&n v4f,CŅyXqI &U6پ'Ӫf<:{3~P5DЊB>j0 :y~M;glTjj)&\:a~:fT~^9ndCJD0y ?LfܪRK~(l(2 ] } ۠dCUI~m<4R!4Go6(P@e@v#248~3AɆ((zl(2QɆ((ʰ_}}xǦ8J6~PEDPà!!CjGoV(P@e@MˑJ6~PEDͨdCUI*H &_!BwzEl(z2I%I<BtC!B!Da$ ?~M$ ?!B!~T7ffY$ ?!B!~T7#PEDP!B!f(( ?B!PQߌ@eBB! ?LC(B!BG~3UI~B!B(oF2C!BTQ&Q!~!Bz*$ ?!B!~T7#PEDP!B!2L>}<̧=6Ooo7]O+ Ib{C4.Nk5>Wq:{&絭{XI8I? _:{KNMCЭkgw9d;j|Gnϸ5p9IxwY{ ?! GS_ϳÝ_8#>¿+TQ&̅pqС{]Aaذرsy}h+ '4S.E[#͘1#\wu>l7-c= 㷢{Iܗ7IJ ;nrcgh?Zٽ_ iRy ?ʹO(0ƪu^K3ںUq"Xx-sJ柯cI)>ޝ~;߿>?|=,ٰ⡿ӯ~ic w4~J~3UI6cǭ- w?==G衳sK~_ٳg'|2&>cƌN8qI;oEWxc?FɶOQ⪶ ?Zv)H>I߸= W~(hVF~}zy^9fs SbP^vuϩߏ$aѹ={ƆGbX aGÜ"?/o_oZ_wO_ N_ߌ@eX1m }3or!}Vc-o~q_d/B(G\邏Ngcn?}- On\Xm[>/IUvLuqc+p_6 ?^A: ǧ1Ⲥ`uO5 ?Gw4=?qVxǧ~S߼>g_} Woa# ? ;O?z..f(l895sfoZ{ K8I']_vA+x0{^aذu:s 'ڼyvgnWB.B~C˜1Sg%}(gb[r10bgϾkaĉosgvKcۉTW7([t3nܻM7]?O1cFOn¯N8L&ӧE ?^Nx_MB. g_ z&[$ 9 I»NNڰ%?#o~#֙;qfw፯ݳ-hNu }2a`~V҇r-m8g9 }{%{$k.?= FZ?sz'aIW'vuG힄}LnG柙/^şHoixR ɼq_~! ǿ- }6׎}pqIx}Ns KHH>$|I۷:gG^Θ*g4{)ۋ磰??_IN3*1Bùc375bݕ\o/{,baJ# |k Wrr[þcMN3ia+imm"w77|H朘:c~[G7F~V.9x"_ a7 W_|'nêo9v~TJ~3UI6cGaȑ’%? 7^rJ ?bC,d{}[([nk1␒g0ԅm͚î_wiװ|S83Zo=k׮袋d2U8~7[G|W%'}gLָnLpmJcf.q9ƾ{5/I]ħj2pQ}ESw#H'k8&~X=qŢ* >>&T璆7gW7lX_& Wǽ1U:i>0woܯqP'ܝ;6_/&k'U(oZ@cgȽ=tyRnp$4X6}c Rۈ_Zݾ1Ra𡽅ў7 l{Խ!혬זXRx(}a=τKT~qU牿 |.y/o?>?úޮ*^oF2f,o4KS¤IWf A {:Cuu!=ڵ/6+ѿpi .|8[]LpiYEcǞ_ .9p_fͺ%U?+C9vag_3~,\HX#/gLjjjlʕτŋ p3ҥ㢋.?\QBT&z}2 ֐ȗ[?&}K־)չ4r 11> >n%aĆ'u$|o1֛&_fq}n_8kDať+8cbg$˷&74l+>ogz|JyLҎI?>Y҄14/*NɧB<q&^O4<<[.-&PtMk,^۪Y)6.jx0;&_QYvrT9 4.sewָ|%)H;*ӏ.q*|R],gP/=qc+(|Gܵ߅sovv=4+imm͞T\ IZ\+xd/Κgȭ)*cڿ1,5Xm}5u(N$,OAonސvLVkKGGi(i}?vFX{~LK /_ 7lǭy!}oa>}U;ߌB!v,4FˉT6ѧa# ׏ z6 #v^4EKLxyEҦm/9V~C9J:K,Z,E(ז-Mظн{ͺA"k}"_=Z*>}=\E eyQG=bj%0><&5˥ЫgӯcE _ky~Nޭ- .bBj[E/}7_*iPξd<>[\dShg($>&5ǤkI>}_- sNl)%ӞÊgE-~Y1cn}FLHc&=-HZȤ~=\/嬓s 䱍sqFX<JCχg֏'>?,gȽg(uT Pk-Oo#^b1DR0溯^I3., ?[sICU7FFbs47ޖ id5F·8wi(kpսφ),ÂCȣ/U5ݿ=|}_n=<ȿnmol35M4MӴDv/Tt0wa„w#@8Eq.m]Y< |V>oW_evm<݇4 ? O5Ͻg?-s boܯZNhi{9K9t] u;"}nKו=&en={Z8KJkGW@=FN#wkHjo'UZ+4+i-g 9y_%X1cgꨤ7rۉ3TroH3&5"+ia#g}%,{p^K|X?,?~{>cx}w ?wSΔii*1`bo{瓈> ?:hV M'\ZZ;ot}(gߚ~C+3ęWFzڱY2޴i7cԩT$7Ow?b6>:FL;}J^cb4$>=7\Tyt q҉;m}(gߚ&S:>c[JpUòY$[7u|1\?5=| ySX_o )v G }];iiΫ€8UWƌ9.0 $r/~,[(8uL*/_gŸB׮=g\l7iPξ]~voL6?;{:/V=inoc=kW9ۇ~L&JbcBǮmx|:u=1qqΉ ɪ1)z Iok}hGD\\uN:3n\[~wL:Q|tLۇr-&wָNTM{[KR񧊟R^txlJA 3 ر55[OLr֩sszQw,|j-;8d<qcwaQLarw{gi *6~M]S/i~?/o<+(2`Z^InK}{s+^|#[G-.بQIGplwo{C[_l\Tr_j.;y6|~^;V W?z% ן̄_uߩ ?\;>]yGNϼLߌii :jn8pp熩SmaN*Jb;߳|SE͞>̟PXTqujk[f̸1 *Yt1vX\1i҂pf X2ya֬5YIٷC==ΰao ^հr²e޴7°oͯ?j0}MVv8~]~#Fܮ?믿 ^6mڽLJLBɖ@QIϘ1Yc2o.h\77=Ze`hD$/>v.]tJscB肳pچyt[|zn`m|23֑!><15ٌߦ}̞ OU=[^J>7giͪ,)T?ӎJcVRȼqQ>aV$Y%iJžeI8/SĈxmƱ[r/9u|YT_gOj\%ޝ;zJ =qx%gH xyCg8kB^Ink} 68 gp{O̅Mq}CKcTZQ?01elò{_aG1ٞﯭQtY{_`Mx$z$ |iPj!VLC4M4MNŅbۊx1\}I''|\ydq]ޱXIzҎ< šc_ ?\;5W0;~= I۹OolJoDJ~/|iӦݗd~׫X,7->?>ſiHd{[^3'֯t5$ƙor3Gt8 O޵ 3qoٹ ~lmêHœ0h$ԾQ ult{l6]$aD}I«?Dᇦii:EdGdlyƍ!ϋWk,JDܾ#&fǛ{4O?CT ?wz&QiiNGуhcKE} Il'ڹYg$7''N$ LjJwtb!B!~ͨI$dIه~hO1jԆI &usᄥ'dc격Ř/g~lcZͱ?y\<: O^uoYtb!B!~ͨ)d靟~gNsn=(=z.|9|7. \qL6X=fsOG_ÔES}|ӄ7U@'A(B!BGߌ\ŵe d-پ~h ?%?ZczW:1EBB! ?JfԶjBǗ}7#q@76{ߺׅ7#zv50L|b77K2jHsP۵6]?>;ذͶ׆|?Xe&eS+G7W ?Zr_oE^ϠkW~(xz+r!a3+돿x|~υaǡ;__°{R,񒊎mvK>> sn]BݠqnMlΞx| L?4?NZ}, |jv,x0vpϯis3~.-3c>V76.{kk\;qͭ÷:/EBB! ?JfԶZGLҍ I$Fgk®Z2y]w ˟Z_OjtLNo߯ޗMn^L~NZHNۧsв_LϽ/&Llqd!oMn_vVeu뾻B䶵; 5jHǶT}^!y_o #Qri7LJGLǯcυ_sP0m]_iS ?ӱX=&}I{o,5.s+w\;bLx-NLуP!B!ҿV#2pSg^\ag_.}8[Y٧$G)5u[ Npg ^ԧ¤}jG~ r)e46bu9.:>Iܻ憓WYr97|»'O_9=?}owۡ[xϷߓ8E%ǶsqvXgc㜏y`vJs"c8&\y=/{g2+>J?xE g|0ya1 rMA?#4~lc{LXXj\ָL3}1on96:!EBB! ?JfԶZGL3^OϽv. ]w/_E ~,%_$36;+CL.e] IAq6ifxocVڮ͒+S~3K@a}1 \vee%b &T^ Isw5&O_n;g\[ ~WzmgUqVZϼ۲3mͼyf箭}.`kf܇|7k^m8N+qʵs>vNZ+8)c{cgilqvbv1po\t2!B!~ͨmjЭgly|JW%J}W{IUx;zK_e"礅ǶTqӌ9 by,4>ZN{fs)ieSq9;s8óm8Yre},Vr>fkZR㲜 dr}-2m;hyXѓc:TQOLw6 CViU(03ͱ jjjε$ ?y~M;glTjj'\:aek >qm4a8ٙۨcFgh}3)km?prGNeEۢG=q?qKsd4cQ~4I~hVI3kO}X4 ?eDKLM4M4I SLM4M4cRLii蠶Ʉ˓?)?A?o=#gK ކlw~%_0np]ɮ? zǎzm^q#ҭK0)aë~NK^p1um#&#ŷ-}zv++-J:R&-szV91[|uGz+~C1SƄϮ{3[%?^fbmL(|j6YG~?36\kZMн+>ϺcS5ŗ>4{)կ$&m5$޹wZMƭ]lXfۿk[NxKO?,VK<f o1\V+]{t-;1~e_sPp=o빒>9厓Ƕkǯk=XVۥk։Rt4e'1$Ҥܺk_vגNaS[ˆCF\ Ӫ4gchd ta1?R=#Ǎ8U#Q<&s[ݏ|nͯ_/tX0O??$=tߡV$ǂ:1q!oMdoڇYv]f prZtҜceJ8;NRv{_vX[ X4NᔕsIqfᜏy`);|e|ry|~L<#s=gl~yL=c_8Qs n]?}o8CgCo'q&k}pQ8#̻{^ų\ByvۚuPvx1\]qFx83ékO }x\*9'X'd G)5u[ Npg ^ԧ}jG~ r)Vm%Ǚ ub{91FO57s'\:!8#Hn;Ox+:iǀy]ϕ1q(w=~tc]nG?̭7s†W7(:71_뛭4vO\Eɕ~,mY rf<վDܺ1 \b-z|܇|7k^Ͷcrh=S1@^3!4Ws{풯\b>.{MM I\Wtlq;XkmJT̶` o1Z"[>`/+icFR0AK?zw9,&$rA׌y\ϕ1q(ZMvrJ ?:휏W\A\K-Nܯ '+:<Ȣ'{'o$S?px 1a]w.3 7&9d|\Irմ,N{ʽ+):M;SCϾ=U8`k*;1>uԵf gŴCJ$R2NO}(^NҞ5/)z)}"}vƅӛ%W#Q>5&Ou7n+?8:Vئ._~'c;綫zrs%}L{'lk;+RXA[nE6{;ZW=*(a{dD̮ݻ_ĵ%cv\=ٺ_oa`cvۡ[Ik6t%ϝ_?N g|v'ֶudxsKfz(ai7KIw#߭,J%S[kg2cDOȯ_NG߁}ú_1ߐ~;Gv߽wk}ѺmJƧy]ϕ1q(gT*j~tcێCw u겱-_/kҷ] ?͡$QGe=[I+~"%G<"d ~lh|߼̅[wͯ?v1u҂I{/>?3/f_wSˋ>>{0a# L=x1 3/41!gXk-(y~M1?Y,f8#Դ`s Npg ^㤜D0]y$Ǥ1ѵ'sN^urv&/9{=f3h|9iǀy^iXqhkTR]W|cE"bH[~t);w 04&?~5/ ^ri7L+ˠ}5['8#QCJ~NLFK+6qڒiIaAavn-&^׎g33$-$S{ō7I$:55U1pE\qKa{W6[w9mceJ83Nn~WU8Ra5?[D(:cM<ѧG62>;&Y'7]̛g=#oХ[P7.?(ZևSn=%yĞiqu>4;+AG"i>/ SM&n iӖJO{NqxİӮ;e7,\HڍIq~3eLvYaADabp>7Q|cD¢W{L>p wh:󖻞+c%ǡqvw_ ?:뱎3|?} ^[|\~ ?* Zu[axJҳm7%vaEƢI &UO 1i]Ɔk/ӪfǢQ{F"ŧǧ7 GFhmn6Ny`<&36 5 .~l|mcX"ni^:fT~v^͌R۵6?L^89lxuÑ iZo]l&hUZ"陚m3qw9xU{ri`kii蠤gji):(陚ii ?Jziiii蠤gji):(陚ii ?Jziiii蠤gji):(陚ii ?*!B!]aQ&{#eLIENDB`java-binary-block-parser-2.0.3/docs/jbbp_mm.png000066400000000000000000005505521377434312600213730ustar00rootroot00000000000000PNG  IHDR{IDATx^u7')$))z @0"HP& ])A"&R$/ P ]iygwv̙}ɜ1w󛝝={̔tdJ?_~:?~Y,_gǯ3Kי%uf ~:?~Y??Hf ~:?~Y,_gǯ3Kי%uf ~:Z#%uf ~:?~Y,_gǯ3Kי%Sjdǯ3Kי%uf ~:?~Y,_gǯ3KO?Y,_gǯ3Kי%uf ~:?~Y,_\>ǯ3KO?Y,_gǯ3Kי%̒ʇYwSWJ2_yҴ<|`M}EêV,_zM+%wgDɕAzw٣=nF{WM^%bWs.ldU|z3suo^)\d\3:FCLaMmt k3ܚn{垟xX#awJ:{~ڳ{\Z-qy˪yY6ǛrC^oJ-{י%,_gǯ3Kי%ufIg\}Kڴ<퓃BJ5݈Tg[ 敒*|S"iuFn+8W<(?A.ndʺ2qi5i}n 3)ɤx岏 7䵦Բ?~Yx~J-uf ~:?~Y,_gtv'x!IEGRKJ8aO)jֶ;UHr'M#m bqW˩J-{י%,_gǯ3Kי%ufIg\}Tt*&zns'v5Jq?6."AtySx޳]?8Э9#Ϙ=w{5h ;{Ӓ;MHr'ME^,4o32s\y#Zǯ3KO?Y,_gǯ3Kי%̒/d#Yjʚֽo %0ヽ[yFUk6j:RSDҽۦp>`1zt,iSZ^$wҴZ'r`k6wÎC=Sjdǯ3Kי%uf ~:d> +qVcr?}@e~:Z#%uf ~:?~Y,~9OBzG> 5>w_[n㻺 mp/7huڱ'?m׊G>nA $Yߍ1._R 9|76,ԫw+=)jEoҙ WeqҼqnɓmk%_-#/#/Zǯ3KO?Y,_gǯ3Kי%̒/@gR}_g/R $?~Y,_gǯ3Kי%OJ?~Yx~J-uf ~:?~Y,_gtv?tv(uf )G2Kי%uf ~:?~YZ١jי%,_gǯ3Kי%ufI_=Zww~ J-PjSjdǯ3Kי%uf ~:H~)GJ?~Yx~J-uf ~:?~Y,_gZYqPj@gR}_g/R $?~Y,_gǯ3Kי%Eű*Zӧׄy:{w7\}]S=iv覾Wd$FQk z+:3J<Ãj;6p.ldz{B>ǯ3KO?Y,_gǯ3Kי%̒"Zj|['}JJ+>9؆~ݤ'V-3]Rzo򇽪 "ZIۜU}VVm|oDDe~:Z#%uf ~:?~Y,)=ĽZ=5\?=ZvSLk7 ZQVI*,l]q582=1Qjك,_\ׁ=]=L{Qc;[ nQֵwok7-hor tM}\ߘfd44 Ϡ} ry3ѥVۣ`Rz9_:ۧR. =rU˥2f3e7Iq͑y|mN"d!_zz[w&'Sl6eL_3\w5ץgWo^y?ɶץl=|We|mFl_=x(_g/R $?~Y,_gǯ3Kי%E7vt̐+Cf7驹^#됓rՍ?VyTVN޺uvorÎF`Z'5Y%鷺LfopS@~nڟ'[C ))|GJ`[x'鲽^:g`7yQS22]{yE~w!n˥e߅ǻN]-GL jm#Dm}XooCJ}.ԥmS7Y4}\e7ytT O<3ݳjRq-<\8;gߍz^d}Zǯ3KO?Y,_gǯ3Kי%̒"]jhc7bUڷV"`۷r{*+,Oצf9˘ 5O]sUZS f>y}RlIYEGe~:Z#%uf ~:?~Y,)ѥV-&=֪>Obo]&WU{1B'C0=XWJ-{י%,_gǯ3Kי%ufI.pX+Pg^[d _r2;<&l?Zj_KWҾz|`zZٞ+ڞ$¥-iןڗۜvL3^>^䊱x2NC4㔿si[szK`-EpUJpHf ~:?~Y,_gǯ3KoZ"u䀹=iJZ'Ʌ>% 5N*v\_ێ4?[}|gVMSX^&%q ,G&f/BJ+?}}Zf=OܞΟil?jZBkO'6qۜeLsFVYU_ 9N:K&o]y6&X^ zRuf )G2Kי%uf ~:?~YR$#KY17n&? :JjYwSݳ$?fxl +23᝼֒˕;޻[Gsg8H-e=/{6{=+CzkibħRKb~wnXuEɈs6_qMzyBSW^tmfsLGՕH-=m~Ã\)Ɩ{fYw0 ٗr{:o{eɌsϻb'^e#9똦Op@yBKڒmM[$_4myLmK$W<+\^51ղGn38kS]{'ܥ\ٻ ֭  ^%g3ڲ=zfT˘X2N))dRcK٧z>M0]A$*wan (>?g_6j2&)*7ݻJLrFY\J?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gOJ?~Yx~J-uf ~:?~Y,_gɟPjك,_yZVי%,+Z_gǯ3Kי%묭PjdǯF@8Җ>yZVי%,+Z_gǯ3Kי%묭PjdǯF@8Җ>yZVי%,+Z_gwIh?17Iǿ,_g@~@r|uYUcy ؓ} ~:?~eR+=~.Jƃ??Hf ~:kZC]FE^?V uf ~: Vzי%ZVR $?~5J-Ƒ},_gYJ~:R jyZVי%,+Z_gSjZ~J-u(G2'Rc} ~:?~eR+=~J-C=XT>FJo)G2KY#hi JAA^?+uJǯ3K)ڏe!Z#%Pj4_:):iyǯ3KYV(҃,h?.SjdǯF@8~},_gYJ~:R H\ O?YA[JCO ?ʘ}/ox>|At~+?,i;\撛wItzg{KnӃJn?䚺\JnF%%weZJn6%wPuYrCǕܜJFMC/yK-|?27:%ڦ%׵oɍ:6:ʤ},_gYJ~:_K`C岐t[x~J-u>_/R';"WrP}ޢԺ꣒;rv}2Ͼ靑_N-Tr'?YrnVyJzM5RrM]K9/ܬ%wޫe%`}K_;寸vmnwv;マ}tA`_-aU^ṅ[Rwt:I>uf ~: Vzי%]Rh.SjdǯF ?z_%ByXuD-XZ>Y)yԬΈZSΤx֣~r8%ץLx>SYK/]v-ZȭX½kI:S} ps[]u*}PtfʲgBt:I>?~YβBuf x|'e!Z#%h}"v;|7puDVRK8aEyaH:#..4Qymzz-6޿VGV+>~HY6`?ąnq/wb.a]n9uDRKp}}tM7: ʤ}X}ǁ,_gYJ~:_K`C岐t[x~J-u>_Ɨܞ+'k]"ϋ C)#.SW Q..yz)*gG3ݜ>L ]\>JnI%O*Rk%>-,/#U[@[|eLk#ބ-[}Wt8ҔZ_z.fr\Zyݲe{QTIvYXwʘ뽪m5+tz4ݣ?m%eAXcgo oOyVKiJѧD+g-_jy-*.-kղ\r.w=-y-o{˼J-]>\^*<¯Ϸ?-Q3˹U?nT# gã5}3i]d,_ns[BK>v3į $ҔZk9fVRk]-kԧpN\pMY0:4>zbii:?-k9^Ov3dJgRhO{|9jwKn?NzǑY6?uf ~: Vzי%]Rh,Yޙ/tȴUU$Zƛ'iBo)G2KY#D|r8-[VRBOX2RKnO}+9[~.rԼF2͟ʼ KK߮L[gJ~mԵB8KWǔzƯ/uz}T amZr'*8A[GrrI'_==*Zk̝;׽몮?zζ $*&n))L1KSp>t^maBVO\)2# \_>_gǯPj?~Y?/%yF\u 6m6#9k#}ݪ\wcn nMاO7g^3Y;~[s}?nmq]w}ˑsϫx_o~]t]y[S%5<-;bؖC[nyj޶l[^- i~Bo)G2KY#D|RjH ѳo, ˤ-$Y3|JWY+8aE_?oKˌ-j6 .muir+Rls9Z + #\v!풫ɕU+>_X;^=MDY駟/>*w}[lAr˭[,IVj]_O!.^\r2}ySYj2ܰ* ӵ*HO ?̙n׃O|?~YβBuf x|'ڋO|ƏmDS6m#7h0o{-]lwpu5{E={vp7ܐ!ÃW_} p t˖N<͚;{d&W5ޘdzm\v]0q]ZsLݺuw_q^&篷$wc2Kzwg:q fݶ<[d) Z#%h}">y~^NJ*"&{W,U"$Gg-I%OTpukշOot /wF{/܎2Rk K?74ۜvqN#syv\%Ge^bq$q%7th'EVr->6گ22+}ze\[zO/7JGܴ+f6'ܺGsA S.~SΥ\˫𳳎 tZ9izciKAn;VCoQVOSO%OkmN3$gGڽ\xv?-8O^?)a8Sj-WT05uyP/{k[ʿ6Wϻ-ySRk[qrز2UiǑYW=+5O8k[xn齿t`D\#UyQeUO{n9ο=S/g)=zud|Q'u7ovK-f-/ߢ ,gRkc%7ei>eY9uNr>- *_%Wmu^:meޮ}ҌJG˾\c|q+Kn%wr%_Ėu\jɿd|[||{C+wi>8W^..w]V{_O=z w-?uG}]d]Y~:qZ_gwI}HLٰ;̙nU0.jRKʥp.e3~ ͘I}m^&ϛ 5 ϛwD.'cCƶ`k%b 0jۖxlse%Z#%h}"^# uܱߺ1|_g/R+=~.OGyUϿFEM%亐[Pѥ;~aD.,Y;ZH7uge?]egz- 4e!Z#%h}">ot_?nvofPZ$a'7ЊC`{Ҿ~Y\wE߫-#Os]'Yr?_Gvyw駻+V^{;I'>ȣu_f;7oAt ؓ}S.wg_ϸy{;t_?B,ǯ3KOuf x|'ڋZm4v3r_{nCc﹥K -˖e=\WI.Wy-kzt!+?tW7yA~W o9Q>}{EփA7l(;c{݅jLÇ?kbſܠAÂeN?Fpíy[jۖxls岐t[x~J-u>'/| ՟D(Y_5cܽu︳b-ܶu}34 ؓ}^? ~V>裏|Ewַ﬚Qz_g/R+=~.O )Tp7mF^q#JܨQܹ~ٛSW$%υZ-]}]>V[-9w̘q듫_^rzwՔwni]֘z>/N,~)k^#Y~_ 6~Mۖxlk岐t[x~(2ޗ'=`#.X~bqW޴3ywٗEXU9 7SOXD;Ϲ. uDG]5R $?~5J-ƑՕO1H>kÁ^={;?vy,Zǯ3KJǯ3K)cܬYE?1(?|qd:@iBo)G2KY#hYuSju Ҿigo}_; #Y?~Y???Vzי%Z!V Uww=J?< ^.SjdǯF@8~} Wn-6}c^k$~:~~Jǯ3K);ss.ppkjf{&ʕFhe!Z#%Pj4_:):iysw| Qtי%̒")҃,,ẏR H\>_rl\SSWo&Ld=Qy˪ltD.SjdǯF@8~}R ovx-ӯ#2-o$N'? 6Ϧ$\j _}{g纬&+OG$rYH:-_Zs.au|Wr?7SL_tlkn=_r=kn޼z{=zaF7q9~wsu 6ʻ5a>ݜ9{3nd;m3V{^;|x[ͭژQiu5x1e~XedռsT93S/$ϮɄyo35#K.ۭk{#CeZdi~Bo)G2KY#_> _|c# 4/w[4F,?qy,olJwI}=;ܥO+zLKe^p'xEY6sGycÂlҤO|ƏR2|OyE9mFnРa|=[٪uKky+$ٳ;E ̿k o-XVL[o-Ӯ ?Kk[_>+Id#˺X|}5;v7K\J]^ς,v}q$y|}`;/կ>镭;;x?_߾nܸi^'O[_}Y_岐t[x~J-ufpܖǯ3K[-yԢ_KFzH?= ~:~I~:?)/'8d_j*)\8?xٝw>d[m{C 5pe.tX 7:> 4hr}iywy~Ȳc~l[7|i0h3gs r)T˳S;'_}Y_岐t[x~J-uf8~:~}բǿO-zi,o]#ǟ?4d ~:~I~:?)/'8(·+\ܿ՛\1g]tqowkZK-}K.7X&|z}Q5w`gI']ӥ43nS-\׍7 _cV6"i}q6<nj~-g e[}Y_岐t[x~J-uf8~:~}բǿO-zi~wGWdZ?~Y??܉$g?~Yٔ䗓D{yZ36 Ͼ6oSlX̙nUº˥\)\>%Uik@871-TY s )h#5N8r/j!yOE`x7&ْ,KL,w,$/R $?~Y!?-N&,i_ohSFn=t/a-~OݹYV/m~:~I~:?)/'8JsK-kE\~W=G&iI\[_Xu)"޻FX/1K%KǎY%ԩ yVz'lʲ8<>$GJx{VkGV}_AM\ O?Yάȳܦ;^ǯ3K[-yԢ_KFzh_k^m~:~I~:?)/'8njU'Rjx/YwCVfwg⺥R[ R*J瞪ɶ\p1]yςϡcEu}{ry^R|AK)H<[W>|\6۱jLҼ/ Qj5)G2Kי%uf OvGϼVmuf q'ӟuf ~gS_NqA. nɪr-\ӭ[wo{-w 敢uO0+> m\HI5VAn=W#2\\y7ɱc#eSYW_~_e?n.rEX#v®'|[?ZOn7^7?7 wy_Aj,Sjdǯ3Kי%m/J%7y~:~I~:?)/'$6x$i]nrx{-]lUѰlʕ{rR]hOBݻ`ܭ>zGN9ڪG ">rK9)p7mF}coWkLr;| ._kҮKn'+cLEz~)|\6v39fY_I0~>GEgƎyRF/"cj,Sjdǯ3Kי%]}_<D^ǯ3K;qD,ǯ3K>rR]h/2瞻4(>sz:tlh}'WKi;"Z~J-uf ~:?~Y??O>>cwEDő? N'? 6Ϧ$QK opfmĠ8őбx䑷uYs'Ύ'Pj&Z#%uf ~:~ι޽^L#Oי%ǝ8N"Oי%mqMI"Zm Uww=7I?< :.w'ym]яǻݟBR' _7|<ȼ Jƃ??Hf ~:?~Y,~靼9D"oZי%ǝ8N"Oי%mqMIZw;w?~nƌMN\adxgL4w|9ek s?ۑj2-L,?qy,olJSjZ~J-uf ~:?~YqWH=ǯ3K;qD,ǯ3K>ZVR $?~Y,_go nv|OD4_N'? 6Ϧ$?@AxJ-ȇOƽ?>,2 /9BıY>(96GhpHf ~:?~Y,Xe]̹D ~:~!ӟuf ~gSR Jƃ??Hf ~:?~Y,X^z=s/F ~:~I~:?)O~Pj5)G2Kי%uf S?q_[4 N'? 6Ϧ$?@Ax,_gǯ3Kי%?um ~pd? uf q'ӟuf ~gSR jJ?oO?Y,_gǯ3Kr{s+y!,_gw8{Smǯ3K;qD,ǯ3K>qǯ3Kי%ESjdǯ3K_*}G{ufIg;g\_,?qy,olǁ,_gO?Y,iO]`Pj~:׹O?y/[5N'? 6&+ufIZ#%̒SjJ_g{W? ~:~I~:?~:?~YRD?Hf ~:=ZńR~י%_S>v,]aי%ǝ8N"Oי%mqMV8י%w`WK+*juYZZNPGQ.p A{ʞe7ϟ 9ONr|98ﻮMGh~ZjiI?BVp_4Ʉ} ϛȈ ˚&qG(iܤ~eM5MяP ~WM5M2G;5߽E&?~eMzs^C&f~eM{8#N_4_un{? ˚&G ˚&#N{}߽E&?~eM_n^"c~ 5Mu؏8Q~Y~׹I˚&/kd&/kdҏP+;A5M2>i6޳~ML˚&qG(iܤ~eM5MяP ~WM5M2G ˚&eSj6M2/kdƱq? :~s 5M_4F?B-]5M_4ɤVvP+8/kd_n[sw\mdj-_4&׍c?G~eMu^&?? G ˚&?2?" ˚&ص kg_4&׍c?G~eMu^&??N%VtяP ~WM5M2G ˚&^3-^U$iM~~˚&M~~ZT ԊN6ji?I&Z~Y$#Ё*߹ڴ3/kdƱq? :~s ~ZjiIPH=GSt=ChY4nb_]@5M2߶Sn6-2 ˚&qG(iGHBd&/kd4Ժaz>0tKBUvz~W3Ҕ5 i 6zZ}CГѴ Z]iP+8/kd^FOޣ\mZdr5Mu؏8Q~Y~׹ϏP >O<_Z\G#ԂU~eMLZoݞ\Ong4x2gaCˮ6ǮWɮ?kY'L>vVڢP+8/kdڿmN9yE~eMl{8#N_4_un#"G#ԂU~eMLb|ף| 6> :O;\o'j/k3DKVsյiM~~˚&M~~ZT";h~ZjiIܡV>;h𒥴zڵ:?46mjŻZ! UAZu'l]e]dfmL-ɲNj/k?F{mdz_4&׍c?G~eMu^&??B-@*o4?~?B-]5M_4ɤ?Pmy} |/Qi{=OÁV#^orjsdx*ã?ۼ{lv}<]&'#]K@~PZ?ijW9 2EsB-@*p8ZjiIܡV2^n섶Zc7m6η0m3k}AZr0?5`2S;xh}9XP:әec_4'^~&6xML?˚&z8Q~Y~׹ϏP 0[$~ ߏP ~WM5M2;q-ݰ6E0ZП}>]?:-w~iZ*KsVcCL ˚&iv?'WaeM5M{8#N_4_un#VtяP ~WM5M23Ժitmf6ծ$|xnAQכZMֲ'Ohw5tlDV-'-1[jwtIw{1cF~?rI&~eMh-[զAnY~eM8^75M{@1i~ҩA_~1vNzt%W'0\xTd%WtP+:G ˚&j.yLp§w 6EGB6zS&m!]NX/j9ck}NN Oju7G4iϤSt_4~Y$N׍c?G~eMu^&?^ Zh9 ]aw|)ڏ7y?W4z6zLp' ZF?B-]5M_4ɤ?P29qVmv=Yt{fJAgz(zS4*?t݇Yu2-N' \dy 4i{0vܰ ˚&q\O˚&M~~Z>G/ow|7ڏ'L9H't}GL]sOA$ji?I&qZw8Dw *D/vMW/kΠ3O:9v*CbYo2kuw9aM6L}]h'^=i/\K'˼@㟁~Y$7@ @nY~eM8Q'G~eMu^&??B-wpӲxzݺ4hZWȑ[3fA$ji?I&9 |,b^foX`㠧e 0rEƬXF]o2ZXZ eO 73Z~/euV:\O37eB6Kjeg_4 j6ֽFMnY~eM8Q'G~eMu^&?f5dߣ?ϔ?Itge]CO=US 7/ S0`UBsn~hѷLOd'y]Lw-SX9:,Jn%>/μkW?2OX*VlZO^KN7tƌn3ϡ38)O3$ oܸ]fZ>[VBF m7|/NmzvCw'hTRLe} K+2ǯsYIϜ[ Oر;]N'r%~ ߏP ~WM5M2e T O.;orA/zdg_4-7ӴyK]&~Y$N8Q~Y~׹(y;pcݘ m8rVe,PYO YpAT:C忱{OYu9=\J-M}Ĉ- %>1)S':?)$A<Νgĉϵ|s ,l!n?;ju8x]~w}FvsV+LcO903ۋ~ZjiI?c,'Fnb?ś١V]A E@OL ˚&?mRZ~=/kr=? /k:7y%o` OYw '7y^dy rBmG&ϿLn;LA(MnL?I(q? :~s% 9][$lO ]wkdlf_ղe6;ۚ7c~PVcvP%yuζ/n{ܮA&,v_|o|qy*Ġ]~/ԲdnvVNƁs4o׃Za?ۋP+,G ˚&?2?A/z9 ؓǣ<2߮(s.(yKWt[?Si?D(iț,>NB-Fj1Go_0=G<?=j%Ð*}2ԪYywz-w߾ i5PG ˚&?2?A/z9 ؓǣ<2u۾\PI7Wt[?Si?D(i 1a5l;mN:C $B w.YvKoQ9! kh]:=\:B0$쾆]YNwÆW=M /վ% xeݿZvv=H3O;d4?~?B-]5M_4ɤ18 z5)~hǞ<- ;sAN\}InL?I(q? :~s_ks睅%J|'3N&gӮԄV 跿=>v_Q֡tvm{}nÆV~C:B-s٦gk?YaC[d_~كg>]~\wds7'ݗg-i׃ZLo{5aw-;h~ZjiI?c,p"kS//Ѐ=y/'PyS25^{;Mwi+z+;uGn7Q&._.ow{ЪWȄ͚20^xv3AC0prA$ji?I& DOЋ,^Na@>^xFt/;vfܴ~eM8Q'G~eMu^&?8C^,L7p Gd_~Z롇3eOw"1V^m2q!Ot_-|= .ֳ!DW.h?}W[۶?uzE}CD>^8j}//"g=s7d4?~?B-]5M_4ɤ18 z5)~hطMk6Ҭ~vI'i3˚&q\O˚&M~~cr?2Ԓ 3|EW{e{6vji?I&Y^ A}L6)\'/kr=? /k:71 ,+oU RLMW{6vji?I&Y^ A}L6qPI'i3˚&q\O˚&M~~cr7#GnMx"esɾ =pşoV5D2d=Uμ+7py\.; zw4~Y$~ZIЋ,^N`dǾE&ϿLnw<&zw~"7&~Y$N8Q~Y~׹ϏqLp7@ K[{; ~Yt忡I6Ž94?~?B-]5M_4ɤVv"kS0"o/ͻ 5 ڹܴ~eM8Q'G~eMu3Nǚ{U& 1Ts::y~ZjiI?B$EVPA`dǾE&ϿLnni3˚&q\O˚&9 q aǜN^&/kdҏP+; z&T0"o/ޜv.rg5MG5M[74䄰cN'Z?/ߏP ~WM5M2Av"+M _^Szᇋ_Bl+MR4jԯ*Ĺq vji?Ins4+~3nBQ}z폩K'z~OSEQv v08tFfN8 i3wjFW\WvI]g#ZM6{av)jGFutۂZ^ϻG -gjMn޻._5Mɟz"q? :~Zu|xSfG{2Źq-jMne+M/]H6/kdz‹8Q~Y~?Bd:>( -PNs;\wɍs=h~ZjitxW{gnBS2b1Zhi'SгMX>`la~+Ml# euiР (B-C܄`6ͿpZt_~m_4&q? :~Zu|P rB纴;-On$3G#ԂU~eM8PNj;&bŊ֭[g-OSRa\p}1Sv#6oT5[J.::&kW?2~ իz@Ço1cRy9tg}=EוU3S$xb~sAլh*TYE֮ԄŊsmEՓ3hVK>Po9}x]5eʇ 2A} y&YLk6lvKү ˚&Ov=8Q~Y~:Cd{/G)vd ;:{g[Z6'c/xE2c;;bG6>~<>a69ُ-ƙ1"Os6^(v2dÑf,.XH#Sߝ~0c= }]-\c .=3z$ ṙ5̘3G#ԂU~eMdqWM;#oB[zpOhɟ3fC`1b {eA.4 02mC&D%+7qMү ˚&A5M3bup6~Ū[c&c /8<[O Yx5ϩÎmF0.?T .nWMme;wE*Oб[qǃvVv?I?G9 3 zw4~Y~eM yT4~x; ӹ{oz왢f=|'/56:7j:2d /Ikê]v]vy8R ZdЖj +a\FK}~ږjx =z^W 02m=Fi_5M/'? /kS Vݚ-1L*<ǚ'fL6_dm?ߠAժm fcA~2t;)΋Tcck"Ҫ1|l[FMBas=h~j@O z IZw… ӦMT!,[N|Ȭ/_2N?ز8ڜLcL]X _<[guE1O`՝a= ~>}_Io}QRcMu/)s"{guϥ*tUklL_e|-)/p_ލƿZ:Z?Z_ԶT|aÄsaߥe 2mM0_5Mɏ'?I^k?N=I yo 󻎏2 2r~V (튺^DYW}cdvl-C-:M'ff ż 3 zw4~Y~eMmް`/'Oj-],Y/_ ?/z}Q^> O+Ӷ폾Sw4j4ĮMWd5ۚŪCA8OOiZ_ԶTzO>ݏ:'\qUSzZ}z/Z}Lcrzf݆~eMl# ˚&yşB0~L׼cs׮sMuԮʹ]Gۄ$7fwŽ]QË(늲Oqw^y,Ž9jLÆz;1^V4яP ~WM5M_ִ7(U&;uZ/ž|>_ǪŰ 0`w]$b^fL9HpQ<$83#6c\sbOkEmKVgy=autO>f8ivZj=MA4v7|A&3V(n ~eMl ˚&s[9rxO'u:<wߠmWlםUuge]QÏ늲Oqw^yŒŽ:Ъ,Y)a}_~كg!1^V4яP ~WM5M_ִ7U^h畒oQ5\ږjdڵj/OY^*/S^}\2:KGO3k-!L[蘭<_ϽLgDoCwTWt iM~y=8Q~Y~:C-`2UP1Ios;,d" OU:?mc:a6QYQf,cm7 u{8ua\^S9?!?+>w 3 ;f Om n7Q&.6*r0|xYZF?B-]5M_4~Yu̳ҢEA~OJz^Y_ T|9s!?IK/ 6kW?JUܾ v2 ?|z˂y}EW9yx /m.ʮ믿L}3r wQJ-iAm 3 zw4~Y~eMd7TI/"mݺվaFzORR."O^e[?Ot- a^^UlБ0f,Xng=4}26xj֝^ص xH_nMUOiMd5M3ow<Î94?~?B-]5M_4~Y&>^|1[3f͡Ǟx}l></sGoKFj_X0{V jؾʐ*ܻoZ3u0>aq̿`jo5~Y$^~˚&jŒDZu1gG ˚&/kZ݄z\%z򩧩J*4~xڸq˿=ʖ{~1*.U<'9oYn^ۢ8kZ~a ;?~B9l !oW=$k?I6\O˚&jA1gG Z  y ?SRjq ]&2L)jl ` oTnjd~M_4&8Q~Y~?B-@:; zw4~YZ@Z vq|U[4=4mҤSjl[i ~7iM(q? :~Ztvji?7&@^%CEjMInU3uRAGWtl20ے̯ ˚&r=? /kGHaǜA?&/kqP h H@LPA`d /Yٲ}n:t0:ޮo:H0yW[$k?I6\O˚&jA1gG Z Ȋr* Lط8ڔzBPޣDFe$TSm?W[$k?I6\O˚&jA1gG Z Ȋr* Lط8_n[`ϲO\$Tq7:7iM(q? :~Ztvji?7&A/܄  S=-4~wD;vS^#]q_V s^hg-n5~Y$Q'G~eMu 3G#ԂU~e-njM^dE  Az[hH/yK`{:qB3EKVjb|݇LtM2&/kd?D(i s=h~Zj B-IЋ(7 A} A]C Bvx?@Ls˯˚&r=? /kGHaǜA?&/kqP h"+M `02EcBG{жij|/;vѨ)s]k?I6\O˚&jA1gG Z Ȋr* Lط8_n[ZV$U]acEzx~Y$Q'G~eMu 3G#ԂU~e-nrj}0xKqM?>s]}oi vIm,n8g%Z6EN76zY糝I[фUhޙG5VYWY]}z&T@r[h/[{Zb0.'79{^bu^Ϯөx];tx}zd{aXVoCZFYQnBEEns4?Xtq#TզpF9󖬢E?u˯˚&r=? /kj%C^[_?_4V y^xm;a1%z)kivA,S5qp}랙×-5}ouZL_CḲ=GֽK̥V3Vn-E(,zIړˮY2MMhxGB\Tcщߏ8~ъ6mN[~9}nW=nZiM(q? :~ZɁ~Y$[EO&/kq%z:ph= Z!4ꋱ>u?QI먛QSH%lN.~gW+h#qFܟ8=M]r+MġcӚsL;[h\=jt/`{C|= tZa1 qf@O9 [1?NJ;5rϓ]5M~<h_aGڻoJ]^xFt˯˚&r=? /k+zlXr4~Y$[EO&/kq%rN<3Di8y Z>n۰ؓ^/tmno[L_ple`$=×-168¬=ĿV{\3ԊAt:GN}dǽ tvS\T\I^ ˚&qwd>0慗If]a(x~Y$Q'Z!Q+Ԋ5M_4F?B-]5M_/e:OOx L`9ՠswjjߗGL<]ORYmk~d?1􃚡VM܄~id&r&u.zf@ߢpsn68k?I6jV~Y~eMtji?7aC-FL{rZ ZS #?P+6# c<|Ե m}Ymu~fG-'gG>f̘OPq5MM':p M&7<؜>m'^~-_4&?B-@\ J?/ki [ jx:7ng:M`eG.˧xzBg 1B'>5T߱i?ٮ<Y`FF/LZoiN"2nIhªtL.wҮSZ/z/:f/-:"P-|Yǟ@ÿ6/jyJ]͹m@I-]5M_&Lzj'WO.U 1B-_uE-20XY8zV}Eev d?sk]O';{Y[6~ʪ.W Oh{s.IB b4?_4VJMh-aӯfJBnEZiM( Zd' AZɁ~Y~eMtji?7ABۛ;3՛jsՇ\ZcްHm =u]g !~'jvG]v:pho9F JS>tcR{CI=r[yl?1AsGֱ}((?3'jST\k,p:329ir;xYTla=6Hj/k-zD݇LtYx5>?n'^~-_4&?B-@\ǤVr_4~Y$~ZjMP$GOW> _|7XhceKu_?YhV-=QeO' {Z4OS-2vO7,:ZXmj:ev8i;YIoB1xVmC/K:5! 73sCl3۳m q~֪} qڽw]{WwiO΃\mq~eMl#EqL*j%~eM5MG Z 0A5MC4dLW6&P]mq~eMl#EqL*j%~eM5MG Z  j~i#&ϡuZڼd;'^~-_4&?B-@\ǤVr_4~Y$~Zj B-I˚&y^VtA:afMn'˯˚&G0T J˚&/kÏP ~WMᗵA4 38~Y$GMKwjH{KT>LGLqʼn_ 5MɏP a1jm17~ǟ/kd&/kqG2x ]t0q?I^Wێ:D}FPs6ptW=NiM~Z3IEPKn; J?/kÏP ~WMᗵA4 38~Y$/YLv5?]{hٮz5~Y$qf Z ,˚&#ԂU~e-njM 5?_4K/an46Ԯ7Llj_5MɏP˟r4d̘~pT aA~_4I&/kqP hf0q?I^Z\oϿ4i"W=NiM~ZzX'CV&|Ǥ B?I:w4~YZ@0A5MmQN͟+B/sϯ˚&G#"Bf Z ,?_Z)~ǟ/kd&/kqP hf0q?I^U9?pf_b]U?/kdVjj!3IB-8?/? _4F?B-]5M_$Ÿ˚&y?ebڲm'Y#_h5z5~Y$R#C޽#3~Yt=~V$,Ӭh*T˩@ӹ^djMծ-a&1 i>/N>t)G~-G4bf{*VlZgϚib~{A:S|ާϹzu2ǵ,cRP % O5MяP ~WMᗵ#<.\k~i37xl?맟iz5~Y$R#Czْtݏڵk5a7aVÆi̘Ծ4p_)(7~njr|~C:Jp)RI4h-؄[f|r&T4pmH\xy-k% Xm<Jݺ7?q豈*ThΛoy6=\i&?kLR a+Wne>O:8&@X<;4~Y$w4~Y8"Ÿ˚&ygi=4r\_ԹKhͮz5~Y$R#C?nbS&s2Wʔ5;}9rʗƮ[w뮻îuIv?O mYm^{]ox(wIc~W]u]BdPVN8)3_)†ZĔO`j}2d}u:$gO9 sAZ:gt Z ,q^i~Zj _tb\~eM귦 |J]WŘh˶zdj-_4&?BȠemvۍ7ݮתɮ}oYՄ^<#u]~ju?xq~+=7ʹl#}w-tLXN6yTĻÏ>N?=c\vyA7x'~ǟ/kd&/kXIwzu y/?L/ke#̍jjcJr$iM~Z 8+绳v]OjMrLUuV{ɒھ˗7'~>9<^ƸΝgj#Flq}. :ު}vo>kogK(FOK[E @΢jQ[ֈK蛾n4j>ײaZc=އ|ciƍ朳v:е'/Q*< iW)~ǟ/kd&/kXIZ <,2u[iiMv}Fژ&A}~eMl#gܸ]VΠG4i~kݮWȄ>6!UR5u4AvV'^֞ Ђ)@M5|&jd8wfBN8eS٦Q~5Za?eNJO6u~oWϞMug-I?C7&x/R~ᅗ'.JS;KoϚkl Og o'9Q~<3i!:0}wXM'&ӈ>j+\MC.$o MqM|е g?I6ji?ujx"Sǿ˚&ycpڹ{~C1F5wE~eMl#_to󴍲=/PlzOhrd7uw ZK5jCP;tVƬM篣:#6ѧ-+TxćSO|BK4LEJwլh*T!ܞ{E֮ԄKWw%ݬzzj?I6ji? 0dqǟE 5MWߙ=jkki{ViM~ZxrVt꯼RQ^LMW{^zTfT|5huj@Նo:cwӰe;hTc#_T ׺` ~Wn-a)ߴZŋo~Jl~y?mi).S2$zThYr})p;q>uY4yO@.&=+fzN~\pef<(j=)_t=)@y6=\iv5&8sϋ j5MG ˚&/kif_{&/f}aFЇ_uE~eMl#;j1Ui54dzV ~ wu΢\a­?,W/*P۩TkF3r3OCٓZy$t*?tǞ)PΚ5; Rζ#&8 0 }z zn㧾)Mccr2N뜵d.5jt /޹lno OIj՟z]G˚&G ˚&/kiosھ2 /ki8UWhy˗7fo;/ MߵL^jTqf:Zzzj*s%j D m7vPqkT1>쳴i&; `2zУiѺ$;\w9dOYm|qptVO7z6S_8~rs '}=eL6_KOYzڀ7h_jP5MяP ~WM5M_4~YGNK[t}qTN /ki~~c7D@JTm.t&d/Ta=q+MIM좖3txB[t+VӁ젊/N ;O=mܲy?U:'E97{tua ߳•}BrA24Lߚ?Oi0^mkfk%8Pju250̬Azk]AB… G~R+R, znry"O9q8;\M'ÆvWH't OpfJB60P ޜl߾ A[Æ:B-_4F?B-]5M_4~Y~eM3~sqA m_m5Z..r˚&/kBagzoʛGϾ?Sv英ȝTck;{.ը+f3Fө߬PWm I&VP歲ߩ5j*}dVϵ{նv=LO/3jԯt>ެ{}n0as׮s7*Ӊ'淗]k9.}Mj :ww!~eMl#ԂU~eM5M_4?Ѯ={i m|cj5M_4?ơ+9o^t>_K9e0DY??\ PǎԍJN {Iz~}W}~lh3q>8|3~jGͧ>Q?o54h TѩAB_P*Oߥ"%yn;_nS2a]Q~;خ qx6ߞhexYW_t"vkP:?}fKĮ9Gy#J//koB-.,sJ]TfWpҨuWs_e|PLM4\|׺Ij7Q Cs4`*3~Sc}EWjYmŊ)S&c؝bڟz?f^PfgKZӧ9z ӡrʩ mZM=„qv 62ń {f^#=CNw_쓩SG?KϩuB*i \hV;o>=P!zi=7qzQ*SYtڵ39)(^vZ '׹M8xڬP; ]-[7:w^In׾6{Įv^B#1b4k6ʄo6} [\fXs"p'w4~Y~eM5M~~Jk= mUu17Z..rg 5M{= 87sMojZLÆmo)2a~~Zox;8,j'oU?z]|'P*&|>_ߘKm~Ki8`p?{%EOT(ŀ *TL b EEAAHId(D% Jr?ntOLO0sέzjzkwR-FMN:ӻ֧҇35~JZH5VȴԝTBǎ3Vn_\tT5Nc~''xU$z٪j5gXҬ ['}6EHg+']o8q>e;mT|->}-~9W^eMvT]>45jv̸q >+ ˘NGR ~[L'/c:~ ˘NO ԤOφezV>gَ Dy˘N;Mr21@R<Ѷ)ԤI<]tjuQƓ̌X:g4WpxuW~Z2>L)RmH&}cjݮJlEZW֍86}J&Z\G^E%Z:~I6Rk;.(/c: 4q߉YRJuԶR^d\%=vj ?y'H2eU6?,3^eA?^ZmeuʤV[xzjhj0dR*TZq},Q2m;K EsLDԩ;B~^5k65 (byv|8"9#lI~T;OTR^1q<ܵh$x,3sԚ2e;,YEm#guyY9+OmV~+ t駶"lӦ -[:_lي}Vhilr/t!ÉǦM?dSwk$Vܮ?$Hjo~eL'/c:~ y_:Ͽ4fY.#D/S 1w8v1'Jдh1ĜoР۝U $pS `G?3z*9` x8kMjqB,OTvu筼+UgpOgR4ĻOnDJ0tq~Qs͸ZW/ErFW 9׌vLXXj ~q≷C ds UxfLjq#V3fmd[B66қoMWERrJڍGm6_BW4*HNVs6mFs8]Av~eL'GR ~[L'/c:~ ˘NOiwt&NZ`;.(/c: 4q솟cNtۏ59r\bg0$9`׬D9+_81ڷ&#ת' /#ޤɀ7oA[ҥkq@x / &^)gԹ8/,pq>8cϛԭA8ɫ܌xZ-7ޘvbע5 w?|Z74vwc/t}Tp?>]|a1S,j^a<9pt?2d#-1~eL''ņm>WA#,S1w8vܹC&,˛ۤL̜Ǭ!CF3+-i_`ƭI-ކ/^e= ʫru{amz&9&}ogԹ!e3p~Ix[HN/XW_XbJ6kǏߨk䄧qcIj6Fň>BWPImcH;y9v~eL'GR ~[L'/c:~ ˘NOy+U[뺟 VA{K"~eL'A&:q[fY'ӥT/Du̸5ӭ43nMj]wM!z晗2~Ζ߾rr6l6[H>E"RWYWc=Ӷ: q%J.1^.y袋TxK7ޖx !ǻ]~Z\${1ۏ{+6_ pt?2d#-1~eL''jIQmf-S1w8vıLʴk7ƍކ"K&Jj]}~=lU)W@>|vϞ3͸[R1S$"%8YeuFsl#)Yoz1r~?{l8pzWSʗ^jvdȱnעߤX$46}i ?'ܮ?$Hjo~eL'/c:~ d9C~e?˹)"j?2 Nnx8g=Y'y7k9'8>lRܓZٳ_}2f2ֺ}~mē~1S$"%xCWEM,+Suzl}/']hԟmqIÆ=Cں⊫.]&qNx2v-+of(%TLԄWPImcH;y9v~eL'GR ~[L'/c:~ ˘NO?' T٬JqAH?5~I~c7L,9+te;Ot=͝{,ͲMFW^cQf&f\&9&}r-[pífyB9u%҈l.Zma<; .<';̘Kʖ}Cŗ-;A]Ɍ3nQ`T+gvSRS fbŷ6ܟ Nc~٧L.>Ɠ ?'ܮ?$Hjo~eL'/c:~ ?r8-\\ү1$i ?Ǽ=LD oMׯ|f2dҿhM,3qV eݔ9s63޻Z$͚ˌj5,8 ;7\hq_`ﺫXqw_v^f@9s^k+-[̕4>c9^yrؤI?mmj Njq?9oϯ⟝̞/S«$1Ì/^ތ31N"ǹT_B '/ܱ|{q>*ms}=ao<Tp?>Ņ0Opt?2d#-1~eL''OK3ү1$i SPdFt xZV>6jd$^e]eeʗ+$+4鯶cUWʕU+?Qƌʟ|5e'b9&}pK7O|9m3ib(ɓ_%xu'~~UUwskRN>{6m0ߏX3p9nUVxB>yT.s:vĈjU/^A+x!ױn7{ *46} !at!1y9v~eL'GR ~[L'/c:~ ˘NO,Uj-ɿOl~eL'w8v# Nc4o!TJ~sW*ȇ.Vr*UeuLs5yL~Iuk9m^}(>r[:5} j-IK4]pH~/HY>#'9޵T;Z?A]N]:~I2Ԃ ˘N_t?2?n2-lү1$i 8dRilrMґWq9rZA}C38#[za.Yr\mYZ㏿@_*U}!ۢIUUp(&x&.xLYCq*14pbEܖՋ7V"r'xqL=|hvpt?2d#-1~eL''jQ?O3Fu?2d;Mr2L*8Mn߱i„fXqd뮻|ik?NY?6c{z: XhI%pI%桇J_orL˖Cͤ>F ~,o~ylW=g~_;ݠ?'ܮ?$Hjo~eL'/c:~ oo4dz}h&qA!~I2&C9A&&4q63WT\wFJr2V8MK;[qxCYWi>3״isr~~w4pA]N]:~I2Ԃ ˘N_t?2_jgWk~7k7ێ ˘N4qOD246~&^q?ᐤA+ͺF23{>[I!C9l [V"ѯdӥ hvpt?2d#-1~eL''ft?4y*Яu?؎ ˘N4qOD246ьMNի74[e%kj׋0ɟ%KqK*]zizH*}6M+Dz_2ծh7{3gQh6pt?2d#-1~eL''c,~ņm:Aί1$i 8dRilrM˗d?~#ժ2$cl)a)Ì,ϕK?Nk$;`nBjSDz_,kyϞB$ss˘NяN_t?21xM??R9+NP~eL'w8v'd"Tp?cӗ_nK.T%nbʙINplԨoXר?ӪTWT&RUHFy,/Xr5C%x͚MC{t7m1[o"s牔.]:ss˘Nu Yy˘N_t?2pe~P,~ôvv[ ˘N4qcCPIm;6qј1hѢԖ9s^iɒ.'sҧLyQiذ'eΜ,8Jo 8qZi%?\ҬӢ4zzďWwV}2eO?RU[nzٶ[y_ҥyz_ oxYU"k#ԯ|Tz#;?'ܮ?$qKjm$ |M<q~eL'/c:~I8k鯓{6NP~eL'w8v?D246~&^w?V6*"N }/_OR}1'O~]^ƌ)oނԳLjlPH9ySOH_SQPq=zYoc`IjM7SUV <\}>F8VjٯT}ę - O/c:IF?ZxiyAu.XMmIw_/c:I&ı8#9mؔ s˘Nя/x~~ ˘N_t?|wZj#my![yP8u?2d;Mk Ay8Mnept?2d# 6_t?218unNnA;_/c:I&ısN<)\-$%kCa~vTuwu~Ӊ_+46 Jk͊1$I-<_?21ɿi/hͦ[yP8u?2d;M/?~5a*_^V 1˱+NcSIMܬ]:~I2UR+h_yœEi(eW>ƫcWA~ιN#a =/A?rJ4cZi̹Zx ^k$_t?218g/]GyVlF_/c:I&j7x2 NA[h;//c:cWƦpZix͹Yqt?2djRk†[}fijM}l PlQl屲` ՟5OJ':Jx1~eL'NG@VN~]$NnW6U|ݩTdl3V@ڴ1~eL'~rn19ix͹Yqt?2djRkiTGB0VYueϙ%nTdYu/M.ՓZ}f73gA+h𺍴df [,{u~:\lk /c:~ ˘N&/T@+7Hn+ '._tL~c7GC~Mj{%VDilr#לO/c:IF& ^SnD̨Mmi׬_U[c=S=ʼntS;VMWN{?R0ɯΜ߳pt[;^/x~~ ˘N_t=zeƟȱp~eL'w8v#H4Woُn_|U[=?1;Mn~$6sv~eL'OVʅo21/T/b·P.Ԋ@"$>V?ʂ⽔,jaZGZ[JY=}'Z&i46nEVu,gk /c:~ ˘N&mAGVN~]$NnOf翿]t:QG_t46Jk͊1$?Zj&b2\v)->r9YW7 7GC62F\EdK_BpYܬلC1-[OdS~z-Wv\ zN}l73fl@lWd3gMj~Dilr[@$snVܮ?$ 﫾9u %P^e$ dRAnr!k9Ie-zw>Y/YUBfO&:zӬϫƌߝO9-k[1kN7Lcߺ=#eW0焚&nwmH[n}eўZwQ^MM?lwÿѪ5k[iêcd;^lcE&m[x|ͺm$owh-{f5Y'$քfoַn?KR+SsN*2b'^{>HG?};vmJC<~1Ux_?21z>t Ѷ_џ'_/c:I&ıAf}νn֛Zk/RDils074!V^A?2d'\Ry詻Fxk;k@&xwZEmhٯR,do>DJ}ϓ:yBkJf8|63iLÚk2Q2KRxֶ8Ah=%>}'=9z7>c?u\qگڥ+OUrCQU1/pBvGZW,ݿ:\xexCR/c:~ ˘CGi_leA˘N4qFH3:Ō~eL'i46XM<˶NC~eL'OȤSrj; :Y"֤5ye}vV9pIuT[Z4R)νn⅐2^Y.EqOj1_~JלwF1n~I1kc$;ь[{77㩑Ԫ81Ny&OE|JsƟM_'TvF8w_t?21Ё#_/c:I&ıA%/LOYdѢ/=9rgh5 /c:I+~I 1$Nsww2>BEKT߯:V`ɯ=TkB~ *A؜d3I6ӌe"nkۼ̨s%TZ 3!%^E!*>&T)7g= U41BfϧIĉS-V/c:~ ˘N~EVn~$Nn<2i3De{1ؤ˘N_tTMjUHg޶3xwWuHiBʣ,S5nkۜ2ёʿZInoU!cC޺P^EfM{+X|Ve̜ C=?WYᄡq '83eɠnf[8rSY4ʼߨLSLҲUt$ё#'Uu_y֎/Hj˘N_t?2fvv:vO3p~eL'w8v#H螅[rGY  /c:I+~I 1$I-Yt_o(ɺQ_RQ4n2 }KZ~Ɇ-+UjPYժ>^$Ԋ/c:~ ˘o3gVn~$NnhJoq̉ ?2wt~ ˘NяVI6C_'j6.evlSsvwk]h~`J7ӏnk2~oѢCUu~@R+>~eL'/cW|G'>^n~$Nn/6f6ݱ{uVZzn1ؤ˘N_t~$zu;vn<i]-8Еz 5]Z~eL'/c:~3x-_ʃͯ1$i؍X^N9jʼ?_tVNc./c:~I2J`*вkZ=[x2is[՗|VJwq:ʚ=|6˴1Fj'5+u/c:IdWٯ7Z_/c:I&ı~~j<1ɏ?$&]~'_t?2d#N mVl?vRٲ<:~I2&݈s}zYpF# ˘NҊilw~eL'/c:IF?ZxI-@Vl?|B#EoŰŗn~$Nnoݏxu?2wt~ ˘Nя@R h /c:q0Ljn=Vn~$NnX5Z?l.4k)V4~eL'i46;?21$I-< @R+6_t5jz Z~eL'A&0z减mnՉ'Ah~IZ;MN~eL'GR  H4x\\'^$]M5Zh+7_tL~c'*wJnl.2y!:V/~eL'i46 FkI-@"u\r/_t'I _/c:I&q8@}V 4$&7?Y$)Hj x>߰n1ǏIsX $FC=A.7Wv8&oo[3iT0?m(՟;g;s(->֖K _zLjUVHjґr|x=3{Wj3l><&<=Zᐙ8~21kRGݩƻSj PѢEKS UBU<4wwg_O7J.+6&p~eL';Mn~$ uKN.dƙpI[v*Uбce"|Qz2TyGI7 yLY2Х/knBƬinl!:OW| 3lۍĢC:(W62\v)];)wRw鱞f3.˔2dLOyE5?*EK~aֳ957N۲QZSY*ܯr%ۯ.3Q;5^뽟w-WTs껶sgwP(s9n? [/oA5lmށJU)LٮȤc;?K^ϩkO4`Qⵏ3C7J9Qch4c|JYF5VoD|l /~;?2Duo7AϐԊ/c:q?72g+si '._t6r@D[RrO Um1yE^Ljټ7+_> -fzzd)[| m7"%XB% zMc6W\:ї/l'h†Vf;$xvQz{h.ucA`nu 'GeRx(Jup]N~pg*aűnIsvKp_3Z=2NDeFREVAɐsJ*ɍ;:Ur~ǽsD5m{'Zylq*s"~ӦJ}`rj35xmkGM{vzSEvQ3v7k^ϩkN{)r?׊>ʟ ݔwrvvM;ҁ'iʹ{8}vcd;^~N$<he," ˘N߬7hI\?;+gjjH8M0?~IZ;Mn~l#'@AXq}9ʜ9}aw[9O2tS>[d~}^[o_o~]Iqy7[r}7k69H#ܷX$ڒZN$hLj}Sj۶LϯkKj_Nh)ՠkez]Xѵx2Np.,v9dMj=fOǾes\UŸw iCb$J$kè73_~w>KΩk)~/}]T_i_h[{iuoon:\xA~~I;[q/leAo_t(۾Bxi؍Dy˘NҊilrg9 bgϙgڌ3Ax/ڵmLZС)J>lҿhĭ!s)YٷLl8ɬ eN>t}DԂI-m1$_&jSL~j *[I7v#RR+#WO \wcfeٌ]qUVZ{z=/톃W /"*ZOnwpwUnexI-^cK~ ok8a46|Ϸ +r~7ˬZZ~=[=3ƶ/ʿ]KnIhhXsN*2b'^{>HG?};vmJC<~1UxA~IDAT~I@Hj1`*gYEΦWı _t6rOvKw:k+9˜HͤE% }$RBdժ)Cf; Jo $MS[,sX$tl#?޾NNW|y:rȹ,ϯw'K<_Nh)[Ӻ -j6~Nof~eFW]Cu ?2D>[࣮#meAo_tZlG~ 4qFh~ zNc? P  ͞VIE,\I-/} $MSZ?2 oO?MOHjժThQ~<7+V\7v-I#v5zF[I==TRc8pY4~]˘ {ѫ yF'7xeP4 *-Gd?ܞ&??I&3nIH^%KL4RZN2C,B-2eʪovَפzUr?>K5ڊ^i?$ >_'o/}d9z}t4Wf::rq3geҤCz$~e,\qY5fwd<'hff\& * N6y sW2^O؆j,tܶ?7rW\i_ug$ҏ^B~r"jͩRֶz/.n&FͲ_~Я=Kj>͵ԊZG'*O*MOGvN6hɶ_雳wp]>FZPV(/c:w診oԊ/c Ǿtx~c7G˘NwFN:F-4 ~ϑj<'Z裾tUhmjx*ie͜9vjR'r]w+N\Dժ5/>UTWM~^}-b<Fu6c44O7VPM%Kc9ymD;B2FG]rɥgʓ9s^k)i~~n|9 a ȄwGʽIE<״{DGUyvo3΁ ~(3clWP[\Gnxǎ͸m oh} !uM|/\Z o>nƭI-^.dL4ג5l*3{NT ՘qLG9 ShϿҦ_qcd;^~y@KA~~I8&Gʂ ?$~~.c_~M%M5c޿U 4qFh~ zNc? P' ~־X3I+D9Ulk~$_VݺM i[πc~))̶&M˫dqr' ImNV^uWFzqވ*>dYƫHqӉ>J.$֮h]NX1oL͚Mx5x~WAq|ժBV;Z~{Dɩor[y,}=hc]LVckFR)2$tl~9yaԶm[U:T{儶nIU'I4aC+z;X+2ݝ k54\|I:$ ]+Wˉ">~oqFBWxr|ߐ*su79> iH ֟F׷T 3Nq^YŎU3ԭ,k3wt4J>uJ2^9[FdxM.P亙^>tAеs9 YP% ^TyɐLjy:絧J/to8 }9ZS.Ue,/NLrŠ?sx&LSӟ#Q6]hÞ?i۾s8qJJ_tɿsau"_bYoQu2A~c7G˘NwFN.~N<,o{̸1137o/@>[ֶuڲ)R,N(d^ai~_^c]DbMn^ݜسfҷ<ܹ¶e+#nlO槟~6l.',e~(acYufk"A7ߤԩ;̲ {W\>9kR+>}}8殻0⊫UsŒ{y Zb:I6mI*[m۶MŽ~-[*Qj|]7vC&xBZ[x(z&V+sUR0܎8NeEzm 'I*SLÉ Npk=Nw&9ړh+:Q)3bq%^|@ɘ9.~z.X 7dwX}gx"]NxpҏI^ v÷Ioʼߨսr}uK3qz ˘NݿcAu61V~eL'A@O3c/uV/A & /c:_ilrg9~3oe`fʖ=U??!/of-$Q2r2zŚDR4|JզzgLv[BcX'ϙA8un:{1ۏO'[U;?㉷e3ɞʐ~ލψ{MZĻ&xu[\ڗXIXhk"O8/3^QI-|9, -d<~q;z饗رc,^J| m7x":XWTϜ w՟%U5* AXI !ӻWEWlزRթ6z55-i%&//c:It]} I?2n0ct[Y$8M?_t6r4r•g ICx;/qBDRKN/'Woe=m^E᧞SI8ńK~g88)fݮzF[Bu9CG=gq/I 7~i#Xaӕeeѣש#0)2$tl~7L_z)⊭uRϗr57ж[lo& 6C_'j6>%»A|Ԝ]hn4pu-:؟RMs3njzVA4.=~eL'>u6뉤V<~DCm;)fF~z^ıA~eL';Mn~l#'@qN9+|&LN]m'wȖ 3a~PN/ϡ2ƏHj xiznsκ]~Q5|0ڌG>}~mַ>KBGsMGj?u*A^q'{yKN(:1b&/?r{y Zb:I61S~ ztjժ-[:/?KJooj儶c[6~vow(٬_U[}paI-پӺ }3Zp+ ,+,rz.<$^ p?2D^u69V~eL'^5=}t垃Tl,[=x{i؍ ?2&7?Nu|Ǜo)y"KΜp BS͚MiժC{2!ZZرߩxJ)5lě6h6췿K.UmZ+V<'֋Կ{ތ7lӌFHg̸[[~$B{1~&ɗ^3n=5k61^$FxR^Q]4}zMk#Xao)hw1˃Ȳ&bM1FRsyJI-  z8& YWJ>_-J<<*[*||rB ?E(W qr#QѵluϤm[OP{rg8e͞I?2==v H>x\Ӹ$[Qm^cleAo_t_|84V+^^q8v#H4~=~ϟmo]dʔUMΟO\[mfM~؝&>}'Z Ycy-kR+sltmw;|Bo3fr}|.I|^x}Tk?`ٲ* c۪0NO1^qWDG H%ߌ>5tx.~8)~~n '8ҧ?H _ ^i(p8kܯNkv>Tѣ׫u, e+qIjɉ7.tz8&zHA4.y˘Nݿ]Hj1J[6mMՋ'48M?_t6rԍSwPٲo <=Tjخ:+)Zsfg6yܥm*W~W%f>a-0xwR\(_J?8m0r'Gfí-^Ub7(^~[!>gl3^%}'9y1cfzQ^cx_15fɯ~7oALXvj?="q:ָ_ʭw,}acx,:fO=ZW3ҧrLݺc;W|]k=NNxp_$^ p?~eL'~KjA$~I8ɡSM88M?_t6rT72%D9, $^ p?~eL'_/mIx?oKҸj:i~& /c:_ilr#ɍ9, $^ p?~eL'v$?2q3%Mi؍ ?2&7j'.4y@q/_t_Rk," Xgd=t=_i;L^H[Ս7Ac7G˘Nw*_-3e[iu+Hj7^?WI'^$7V}Ԋ=}VEgЋJ~c7G˘N7ƦSSA >h%䵕Nj/^簼z9M;}:.y˘NݿԊ#'BjAuC_uҏ_tV I-HnayI-x ZxǛ^?WI'^$߬ݬv=(h" ˘_ .Xmzڼ^8B~$?2AϞdʅb.Gӧ#Gqt^\WR=`7z|]hҏhOhTnqtO+ڎ7EG2gMj:їnmrcFJZlAvJjn#-ٱ ^sl+7'bdpNϽӜ"QXϨ#,$_Ӹ/c:ItU}ɶ 7/c޲)}VDWҏ_tV|99 .lVP%vxԒ%iȵ']g͉^c] [Pq.Z9/8pj۸KZ*3Lj#FF[̙|ܐI kRkҤU -u4M7ν>Fu6cwyz3dHkW_*W{or꧗"wGʽIE<|WTR+H2!'`&ٸQUy향me:;=֤Xϐ35HD']|.yǽ7ʃߩp4S&ϙs{u.;bk >xڊZuQNWeRi[xy 64iKV|V9N0:%@0LHݷv{.eٹnj gx5V Rh>𜋜6|Еy]H]\㏿`'LlƋr '&[#)ԬČ.]ӌˤ>2۞2 dMj.o~ϑjK>͙?l;|2ʬ+Ȍرߙqy4VZ1D<΋SU3t 9,Jz˛.o9xo[f@R#ҧ>]*}]leAd^Jp0#-Y-n_BVKMﳵ>^N#ɉX?ٟhDtKmIپ^d&p!HˮB1A!r`ƥXƟ ~I|.æʂ ?vUm=|]`*>j^$H?Vj~eL'is 6l9Wy7q^Ud/"zj<[ y^zgRbt-W*3RbwPϩܒo=v[m^ʼn=I~OݝCq "o\pe3wRU S+2QԳfv>$C7J9Qch4c|JYF5VoD|lwR_D:4E)R)J{U GvH(]A ]8͞lI6;=ys3Nf7y+`;\xF?ތqB ہCK|x&%=ʢPpt@%7`}֧1;~itinK?а>k_?ܻ򂾠qSM->ӘLE>s@ .=׺uoaZtw.ͧ< L4ԚZ)ӧկm4aÖqji#˼[RL-JymNO y ;j"fv(T<:/70mglU7vW4UhOPm\]@ Ɯ¦}XiL&LM*>kҤ3|o{/b 2ht4ic&4pQf9rך1Nhf8jZytZuƴěDϣ>|&4ʳu<3Sk=:tuy̠ME$hj}1.Wݣ92hiӲI}Ե֢cj.j<ˉE M͗CcK[{ǩ 7&m}`dji jJ\kB9/4mjΤS{5՚Sg  o8|~,ٿN^\1_y0/Xc3fן]Xi  \{r-x]D@[%\%YF>ӘL]ֈ+tinK?а~x_{,&ggS>4&pwr\P&2{>gأƵľZVzB8q-(}8vjvO+cy>#L +K9rG9</vlj󈆠?"bgSq~V[%hL]*l$\Ng=)iDJZB>dήjW_K|W~UCƧmɖ;$HF-Z .ԦSMtFmY1y\QtYF]j0WLj-Vb3NWu4s- Ν޻ <W.n>sM2+`{\xF?ތp >Eo Wo BD@[!\%YF>ӘL]Ou;rJ]4^M{w篃&Ɖרа]^~7Բ>$\3h>RGUZg4ŎGzJGhɖ-SjEZ3nO*mp*Th#FDZf&.Ǩ[QoAXZ\yw^~;]7/*`^,CӞC=h9+XoF}مYܤ֐?V!CR $TʀYd٫tinK?а~xWPxkPgS>4&pwr\P&man(zs Eo$I2X괘]Ɗ)+mtL&8 6 cL?j2dZ:uFc}mD 4cƍ/pŋZDIqc>om?ZY蘈c:./1b "#CTIJf֬9ٳT]&=8S[yyٰ᪣v0~!aj!Ov"eץ#stSZZs.Ē06p&6f}ZCup[(2M-e,Ǿ+͎`I,T,9 ]O7^}[{Q02+`{Agg}sCgA›ìg.BncƇ͂o@)Kted!!KM XiL&j^qݎæV `8 M{D&,Th}_>Za}֧1|B21\.g"?~BH4%|A#1J<?~02jn+1 $e5Z'u{*P$HH.>t3\V\EG)겇F 5PtM1aDZD 5o omt^iZ2V5N;%ZPp)e-m}ZS %m}~\MeռQnϽqbSˍ~ZlH2E |J3Y~ЩUwPgy?u6-ǯ'o<z2SK]kn_va}֧@P}lHm@,w'B7BJ>ӘL]֘uinK?а~h뗛b=~ '{+E(鳩eg}I;g.(0絸"Mg_` S M7^TqjjiiךZ-_?ZQ1o]Q٣ܰEm!VlZ{ NR{}iM?w(ZhG͖%ԸbfԪohT]tߺKڍ;WOypȃy 7/<?>Xh>ZZ{@St YNn*eOc2 vZ߭ѥ/@_y'AcXAtַK(鳩eg}I;g.(0s*.޴iJ^mӥ3/~LM-dÅPl<;/ YAEϮ^4o,XX|8Yvl4L }eTcI%v),3yS'yrd# _؏K)pC4ځq}0(q|admK.D烢5%IL Ks{7=7Jbri4zBW젲וxe4l?Ew\;' 6k/Be0c41YG܂r}`Sg}Vwk P`nL?8>$oY\ե/@ތbF6̦ip[.Ϧ}XiL& M-[ Wu.@DLs*0vp37\ͣ YUǁbKETFĦczg d BҔ/=p?Z# 9h0,Fڗʠae+k v.YIʒf=pOLS(56l-<8S go=9זt@M% ཚߌռ߈6`3&sPq=i0`ضc7\ $uW$<ު zmqSp >Ʉ75N72Z-ld;>$Sku47hX?f뗚~J'yФgYW_BIM->ӘLE>s@ςGF֐5knx+8qRȝm?Νte n?[ S1Z4Y6jpEH?^5Gwa!pN_~%46)n_x n_va}֧1鿵u0ӗ,}'72`}֧1L ~Х/@1K?E])v&Բ>$\0 ڸlM k#=fCΐRn.o AS ?,A?#OgW(gg}7wjlTʀYdKD6og;y,VI(鳩eg}I;`Sa&q:ZAL惘Q;Gd˝NX8iBvueB /<?>ӘL藛 ަ@ KM XiL&ZKs_[?c [4Ϧ}XiL&M-aglj1ܾܾOc2G?φݐ]wYNn*eOc2 v?>5&/\Ks_KIH5xԜPt&hM_ lBIM->ӘLEZ 0b })}مYd{>}7R4&`_N6{~Y8(lBIM->ӘLEZ 0b })}مYd~ncn-:K EJ>ӘLk}&I3Xᅤ) [ѾeɢXnP}{3VgCIM->ӘLE~ 0L㼛b })}مYd>.-t. "KM XiL&߯b*z)u^Ɠ7ux9[7~B([?Yu[&gPgS>4&pwr0Lhu6n_x n_va}֧1_d7`Й<7 KM XiL&TV+vuY06y2E_@DoJ:;5LH}^%}6Oc2 }'lj1 Ä6n?[gSa,gg}IA]{!=4-ҷ,}'72`}֧1PTss/ҙ 4YiI^ ua5&S>Za}֧16aB0pSp >$PR%Zҷ,}'72`}֧1PXq?]rЋ90NH$JL?7i{<_pǪowOM->ӘLEZ 0b })}مYd"KAI(;uG\TʀYdB=W6<\'k&"Cܻ4@[gS>4&pwr0Lhu6n_x n_va}֧1[Nj#xe!KM XiL&T)0ZˮP~5h:A_XJ~I|Zw뻧Ϧ}XiL&3aglj1ܾܾOc2yV4S{ui2TʀYdB{ /ߏآp6r›Ԝպ4#w뻧Ϧ}XiL&}/dOc2a}֧1qlj1 4&2VBG`4znT >Ʉ곩~ǁ^p.C͡Fj˲?n鳩eg}IuACUY>ӘLXiL&FtܷZ cP O.4&g_wUIH2f.Oݺtg} gS c6ݽ?_4ހ) P)r.U 뻧Ϧ}XiL&}/FUeOc2a}֧1q1&qSp >$X /6K'znT >Ʉ<5$_j-xo w.f.)v뻧Ϧ}XiL&}/FUeOc2a}֧1qZޠ dOcV 2?3XiL&(bF@󻍬M۰>ӘL>ZL ? &VfVǠ|NhX=}6Oc2 }?j*oWjɄYdOc21ҧb}]L&1Ys'ȸ`}֧1~U?JKs#}qmXiL&TM-&81, ?|$ina/wOM->ӘLE?Oc2a}֧1BM-di?w g}IꗙuHw#}J>k_+Vc=u5d&_E?V/wOM->ӘLE?Oc2a}֧1B_Z 5h?g&4>z*>}>FqSi0OqQּ<_-71h $Ṛ| couد|~|mŏ{Pg4Řiŵi0̳?a@#eV} )^.&g} NqOc2)WԲ>$\;4&g}I Zf>ӘLXiL&s ΠiӘLϦb2a}֧1>Ә/u\we}'MwX\ r|ܹGz*^C muد1 ZπbI/wOM->ӘLE?Oc2a}֧1BM-dOc2a}֧1+v:T>[}ݠ$]+8շ TzY_ZUƇ0oZ3ϭp8},@We'z#Q"}OV뵂Ezz oEG_0`p v؋+c Zvޓj20aBz&LtsW,Z7l-ZlX?tԲ>$\;4&g}I b}]L&4&g} . ȹe.n[o*}-2n0h>hf-ՇyѴ)VB"b֓r~yQIF 7c,_Y̊ZZ8C ܹ6v$lj.&g} >٥iP`v`"]/зTzY_ZymhXö_шѦwHM@5'm~8uToQfhM-\j;^Yxurhi֨]W5 g_)GҵոD5\ih-\#|dq凳.2|mO-oO6:ljهYd.|g} >$lj.&g} > \ .եQ 볾LSE+6VӘLϦb2a}֧1>Ә~[.\/ץQ 볾5>dS|Z^o#aCpbO S4ѨwKa|+aَ5 hSqףj5L-lm~5/ոD5pYFm!1Յh^~Mf\M-֧1>X >ӘLXiL&gSu1>ӘLXi unc-иx]@S;ײL-\ O7ctQWꜿlG4{n߹ ޯWFZ+t<,^MĩdfjiӼZNޓO RJp=R`SZOc2a}֧@he &eOϦb2a}֧1>Ә۔_ҳWd#Nn*eOc2xB ZW/_Q{*TF RQ{5}jEӘLXi,T5u!3_YNn*eOc2L-!|j5Ht{<_wFZ/d*<_v/$L%4odA4{GZ}/lj1pZa}֧16 Vf6 b}]L&4&g} 4%=J ;>Ʉ2BH9RcW߀C =9]#x5WW"Ϸc.E]1\bryjC1g.*XFi_+(ڳW5W%m'# \ZO`j>ܛ B Z76:z¦}XiL&M-a6'lj } /8&vƟ@ Mv@nTʀYdBqS7PRعX͔Y߯->(֣-K)֫ 8ѣp?c0f<߲.QWʁ}OFhi#&֥+6QpZa}֧1da`gsf BQMa78Ͱ3o efT'BStnR4&囹U'S*&cO 0FϦV`0N`S>4&pwr0 Ou;a0dlj }6&`S+~9?OCcunR4&^VbI<ǝkSg/3ώdيCN}׍u}s #?XgS+0 'eg}I;`Sa`ōnf BQMϦÄlj`/?y1_^,OHӜR4&,׬Cw~=85mg WǏ;UC֢in|natLl곩Բ>$\0Lo7OϦV gSa 6K0Hu ԥ9M XiL&TS+KJ0cr8q,ns  ~<[RU 9ܺ n܄oNi 0ϦV`0N`S>4&pwr0?>Z1HM- /w^3?8UwaAI4&pwr0?>Z1HM- /4Xߦ~ѐYxk~} Nn*eOc2VM%ŞZ=$Ye[}AËpt}`wK!kʺ8ﯩUE iҥ GE/C|^ PdI_cgĦ}XiL&M-anv0dlj }6&p ;O `}{M{@?prS)g} շjj{_;M\Wx\*bhmqBM۶{?M_}!Sxx55 ']5{ū4[wq4 o#0O6V5 wǦ[K(W"t ~X.]$N3CིD>gĦ}XiL&#PVn=e?jΝx(Q26U~OM)73 6!lvc/?>Z1H/"Лaō/N3?sA0q.M XiL&T[jH¥(eixThVMKF 4'5zI&r{PW60O6VԐKA#Շlj=JuAXGx|E3bS>4&pwrc#}۶݁E#GR $ҕA0!ؼ7G=/n"73 6pj]/FHPoz;2E}6b>^D70_f_oH|YEM XiL&T[jHad 8 !T;U4ম{_fcȓ#q梺8]/} j׆k׮y\;F/G~V6OZa}֧1dظ]h 47bz^ije˫++`hC02rj3 og m!Zlj }CoaB78Ͱ3_IHh}UbBm'72`}֧1P}o!aCzz 5͗FLGw?a)Rv ?uݿn܄#)s@&|buP {_GMm foadaҵg[pjW|f.ky%gi3uiQ{Ş^k7nGzԙ.oYgD7vCզ֥ qdcUO 3 @ x}L97BF- ^7^l f/۠?A:;vHșt7#ߌ?|N+ōR , MZ~W&JҢT\ -z,XN,E{leg}I;>Hv _Ɣ)۠c!f9]YYC 7 >` vp3dS?BQM}z18kɑ:9+BI`xsfnuc"wF,W\0nBH'dHg),lF>ԕ Gk00!9O}FxG`? ;O `}wO%q'72`}֧1P}o!a7cgh҃r6ϣ0]@'Wֻ5iߧAYTFQ֝уF/]o?=D{x{H􅄮r1 7CU\}a&SJMԼj~#|fش}د ͥixΰkەX >ҥ qdcUO # #q-Ŏ 6R)Ԛ`00W)`̴ˆ˩^VZ I%io/ {lȷdȘ%aC'̂+6ٲuD[8nPr>$\H$1af([.I &|ĉ /VfY?Aro &}ҕ7.=}$I2H8iŊjʘW}NMjs^?\:uAڴE[ƍRE' PaÖ{ƍ})khGW@ow@z`Ӧ몶2fmyWOE?C;}~OumR> xMoOϦV Nj>{\>SNjK_14rlTԴ3 "۠e8T?wg9_Ȫk&|I{m5ݹ6_t4w}K -i=yu:#]` g% 5<}yz, c8Ͱ3w_%дGAIn*eOc28RC K ޺}=zаi+]9#s Y5j8 I۴]>Cz4bg- 1f7S%aejG{}s0t8ڣ6c A:Jz$LBg U^<…猠lXiL&/GCڸ>A}6ڶo:(Q#$ (i}}Vpֈeg>'6sm-ZtUU K鑆ǫW_v9GSz?I(&6Myo;v܇_V]Yho?q[[?>Z1H/" M-pUsWhQ-39bҘ3[lOk_K?X bPj2=;lٳv?~v< ?_<\ynsVsvƟ@կ3h tJ>ӘL>Ԑ0l%dيÙt\&OIbj#^jN1Cۨ(7c. =$sኆ.9H(.GG7m=q@SM9|q5j%Kԁq尼r猠lXiL&/G?Ц7M۩G}of(i8D5jZlI5Zv7-q։Qi`\m+{O^g%JTKy[vgUD,W~}F /QX jTqRƬ 4yк /SfllA(곩"ԢImb5(3ҜX?#iTZtg?~,a2H0䀓j܋K?Ka'+oAqQԡt>i2T>[~o2ScшNyR8"zHtO|B cʌ}Ea-kCB[.dJ ^ț6YZSTyrYj﨡Q_yv%O"'}ϔ֟#.M?sa銕rz0ba$2+`4X_rS PwTo>ɄxK 3L-dj:o&4qjj=Au'NU_h񙮌; ] oL%Cզ=z[C4?}Ŵf8KZ!UFQkW0SZZӨjMSn\ܧ 똻r3T^B! />dmt5jpti4$wxsMʘϼ>6sgZa;)SOeN4׬FW7odjᲇr|HM=)c}ne[L-lNkD(곩"pjj'\Sږl1aHg9_8p/ϵgS #4Q+I^bL;8v;j ߍ?}~ ~.hZ)ihLSh(3 ƒDܿKGbjaxie5)ģHj^yyRU'E/<ꫜ2Z>)06ytLCkr#P5?safZhz/d麸hp1%/\T}mx}f/ f}m*LG%^4SQNC+שp8{"\y~=|^Wo'y0/XvƟ@rʎwCkаo>ɄxK 3|;-U@F}ƕ_Y#&b}^!Ƙ2cʵ>2fzeq+eBqV wksepE+K{|а4ijyO6VԐI^CDI[]~ѿq,FA#=U-oWKI5u>kJkpy…?n*JhI{{j-]F쩵ȟ5V?@Oc2a}9F h\VauVEr0Ԑ0y!S5 ##sIka 5V5u~#&yϰ b*@2X5i6G=0{КWڽoZ8Ç"6x2 #T'c6?Ԋ~cF׳Zk7KAkj! [+TLҁJnu}{X3{x>vl|G"D140PnX—zA>}<գevPri0 ڼA7fώK‡1cZu#238}oT\U,[}N ϵ_n~O쫥?Whժ'ĉWgР^ۉfؔ) $I.SL-K :)c4+ݟ~B:[fcoPgj }Co3qJBJa~@ y=G(z>wbN)c=!Y5[FG M-Rce .[_`Z&R/ Ӿ .hNz>p3GN[isȆv~..ffP $ڮ7_}SolNOKU'،0zUv"D? \s'né? gO\y 7vƟ@r)7B;a߉22`}׾ވZ1 GSKQWE ܸl#h,7dM5 ,Cu(K1 g#~┈}d(=*KJ{?U_Z8k[ЬCwS]4^Q9Y`}zfCK9RcWu)jۅha]ɳ#f~]xfq[^Vpy2:V0O6VZ3ƌ~'# ˤH2ft;%4pRf8M ^~~4b@ajt"rWa򼕪ҁ aBadZ /|y(#&υ+6`[EmмھrSV]@͚5kqQ([hpg} H${CZG3zkfiٲE?cjٲ^f.*cfTv{sm:T-Zt^ xRzXFl&B_v~lTw; Eʘ,yк|NqJ6l*4#gYXoz;2E}6b>^DvM-jqCJztyRV1Oi;I@Ze 1j\Rjly+㦩3pfXbJP3~9| L[viE.+yR.1 ޻/8r/8j =h^DG8!/]\HXy2֕Q=v=܅WWT(]a{,Ts1v)PZu AsJ)3{1+]L7n\Aff"jL ޯX ^za%y1)#tlأAڐ-{NH,ȇYVR 4oZʘ9ȧ]6n~1`_>\vv=Zi/eg} H${c۶;⁶ҫ4;(T(z5$IY68s租nQ,ZtLPk„Mҵ84[دGɯ}PoTΝ`͚s={&ex oTƬ}f9ink;8F!CxC MNLǼEܴ麘}ԃuKFĺׯ_ϥYJ?T%,YrB۶}@<(m[ƨOh]V>C} 5Ш7Hu. 4<h٭Xoz;2E}6b>^DZH"=8qLJY#|343:|4tejRJS |1Gj )2X| ¥<gXᲅ3F۹\gяzH²Ve'PqYh,[w>xKnEL<z ڹf5-|ag /GԢ I._T_68RCa`dcUO Z˯`7CkרY 7DԲ>Ʉqler,'IS=p9AnuqfiFC at1C͛!j^pVeTƬ}f9inkE…߇ΝG̛wHF4kjn+ڋzh .]Q00Q; uӤɨ!J?Tif8eʔ]^hm2Fm~B[4˔)0P'k<҉N f BQM}mVb<@AFY#|343:|4tbjY]~pWո;S÷?9+ &erf;HGCmNj[boKٛkOʲMK:KC`%hl9~>8}CXclZsVlk_xK qdcUO EaкM[Y,YW}M})ТZa}֧1>Agϩ7;xӦ]mӥ?kbjc*N *'e܆u~lA(곩"͠ƉzN< vR_J}+>Yf}WMCsC1eU]{y/e麬B#덄$Q u<1b/׮S0g:9䄾UBlbYG{(Oi:t?V&i{Qhja]_?!L#xBP/YW]z>. >poWK!%K'|hTq54/P2j g@`8GvvYjv |jY{.;n?3A׈!|z8z?;w<Z^a}9FThxsAȻ~>%L%}-5$_iU}l?5d1N`S>4&7DZ>H~ClWֽa곰n &\*11I;)u tWן2uf6 ԊAxчfPSK\4 8g+NIh2aחŸ sKyUW.ڰna5cj櫩lp,# 5:ijh_)E֪,Fi1(^x:hPzof&^z"vh?}~Uvit>pu/Gb/ NYfd* &yp1\j95.geigo)3^?\>??K.&ғ$uW$<ު zmidZX_Q_hV2f.n}-5$_iU}l?5d1N`S>4&7DZ>H~,Xpjh Y_Lb='ܹ{@W&펉yϓaÖS&p??>Z1H/" 4$"4lMgGr.h8t;qVOXkgo~\i9f7|3d<XW/~q3֗o?7=޸9Х9Ŏ~ `}׾ /Fƪ>2 'eg} H$3 nv3dlj }_.݆˲)5'<;Ԋ1ujY1(7uyN<Z_8M->ӘLXXM-a7~2E}6b/SKYmzbI:ܧI1N"F3܋k~s3 {2Zz _~q3֗o?ѯ5xzȯ=wg}p1*4h f/GWGp?sA{($XXW뷅ÕkS08r:|]]^+4._&-_n 7I}ݴG. 볾5^OԐE(ƃ>&@`u>ͮoЕ30<=z u+UDϿ@eueV li7ܺ}GmljU}l?5d1N`S>4&7DZ>Hf f6 ԊAxч Ä.V8L _x nm(;m.7}N֚ 4&7gSa`ōnf BQM&idZcNplVBASuiFg}p1>-Tj^11Nޘ>Zf9j¦VG'*[KI0l6Oc2a}c}j3 v3dlj }CMaB_F/|?<7_)HtlIDAT_l ͻ`zJt(q]u:|k.7w>=6OgjiIՊ>_(W?~,k6lSJVqxѧ L>=VaS>4&ַOJ6ʄYdv? E}6b>?`faȴǜ4ZlL`zԏI28rx.7rD-gtPS7cմHe roB-ו,[q  >RSUJ~q?Oߏ~``ԧ*ljهYdX/_>ӘLX]}:^6ϦV  ^= 5?|}7>Fw9ưY2nBeL-l?xΆ2ÉZrc"Fa;az@,m8tLx){I]^JqߺKW_c4N'q<[M->ӘLXߞ>++g} 뻫O+_&ԊAxc0LxaȴbƝwO>+m.H7!$2ų Y#1~c!ѵtN0җ>^OԐ`1i?ͺҵ[b2 F}z¦}XiL&oO~Oc2a}wx ۄ>Z1\^Tvq >$&112-~9 7nT%KBJU\ŪPEGN vL[#oA2͋5]_? -it=eO4N7 5d.ˀKM-&ӴɆCGM->ӘLXߞ~}]xFLXiL&>|awsPgS+e8y&4&Oȴ@>Vm6tȋ?:u1W/O^t|#Z.cb? MxB-ti`U?P~p鳩q?ٰ~賩eg} >ɄYdOc¦V ק7vqPMXiL&1QiѡKOW>?~\G~; TM?(o*ZqO?3+C16a)Ũ/Vuo/GѥU@ϦidϦ}XiL&4&g} > Z1\(Cu7a}֧1D}: L{~ ׇk׮4_Ǖ+WJUNh ;VT <qT;8^A^iPRirJ{JJUz~rtQR(( 3kr$Mܑ{~}&3$?fFI] ˛?g~k*S5bA Y![lߗF^cC۱v=Q|]83􌪾qE+t3r%k@dHcl*v g-ӤYS~*FiOuHHHHHHHXkzZ5EBD07- Ã_jԒ‡qbDr-4&P'ސ P1:OO4}2CDBDBDBBL-DBD}޴t0p@zаG|)FA&U g6E5R `xU/K LOW絙'͓5O^k#ap*9ϑˣiHF,~eUW^,~}ȑ)},K_4L? ! ֧N?8ߢ$xnxg4MZ ҷ '/}O!AbthH;djYIIII ZI+iѾC'a1y/ZzaobcP`ór9US goIiw&܁CNҷ*?y9>݉%b?~`1mFv=3r|C ,vtQ9# qyS4֧N?8wjM]I Sv!䥏7$"T| {GL->1>1>1>PaA6B.]b1ߎJUzaog(Mn6iTϪusm=SJ-r_?-X-wm|\BzJ3mYe,PSY.ec7o޾Fh>/'| ]i(M'ސ P~g*A¶o<  «L-DBD+ҫzp]UYfPR% Wn*WŇqbĥ W6q *(Y5}xS &'}>&'}>&'}>*dj&&'}>&菞=X3.BSP5&FUxjK̔W4OQ9{e)ӣ;GP[bfjW·mI{sKeja;p_-)ϟ 9KNλB\G%y_+ZO׾['o; ZsK,q<A +2sdjYIIII ZI+ImwS MOwjҮ iRa}WMye#/ûˌ@.'0eڢ #X]JJw.[φr^4ߔifrzKy|))d< DD/g 8K ?``Y<)Ԫ6{ߞ_4qQF~Op.JXq1AvVEȘ%y!}蓩e'}>&'}>&'}>&'}>*dj&&'}>&{ ѣGY+&v7T -?Aӄ7R@jzФL($<16; ͟z _tҰÕ0T$F74u|ޯ>3rg -g "a SL,OWRn K&*YY[8 guԁimD3bęk"Oi XRnjT D@&hDT/AjJ#Yj K%ӥu| {GL->18wߏE>"}ML$O|L$^טZ t`hҤ dz` f:и}1B${XCbASjD{MH%H_ߨcH&-s h m P=4DC'S:O|L$N{ݻ ⡁L-c"!}铩EHHHgj!w"4 :ckP뭺Рe'Uy27dj٧Yɲ[B/giMR?BZM;@ M'K[KK£1C|9L-?>Z!}c"qRL-aL-c"!}铩EHHHodj!-:uA~ &&X^q&ԥTQ "dj%o {&'w7 "9}"qrفIċdj o| Gdj!GV6 <V]T|-R64lF |`ZH8KعrAS5y RLX g;x*Ox>1eibJo S^;E0}IاwԲ>^M8 n>O ً`tJސz-Nď5iN/tt(2hD9V ϟ|Ռ1flcĝ"ۤT}Sط#}2CD>Zو΃TMv }c">Z1AV SZ߲(5f,Ѥ9HR>X%Sط#}2CD>ZÆWprفIċdj ԧAXL-"fo nj>_~7a~Xnj$zA݇i)*tɧ82oTiAoطo9EE` -Dd׃OuHH'Sx0{N?;>1xQL- +EMZя< *ω֤}7IIhJq[R̺8hQЬGZp$믿`ČE4/}FifB'S:O|L$NꓩE֒nf'kYŬ[xY+ ]֢$uH9l~1MS>Բ>djf_g'}>&/ꓩ%Pa}djL2_{{54w/Wv%iǧIo@S14/'}>&/铩e'}>&'"6޿O|L$^'SK>= & f/JdjcW-a;}5y`W)_6o^᤾G͉fc밙4/'}>&/铩e'}>&'"6޿O|L$^'SK>= & f/Jdj>}xs`8 ~K@Qł~ffxw&)鋀IKdjYII}2 n>O ً>2R&fZ8ΠiºX[&qB?fbAqBw@6kĝ$HHOuHHwԚڻ7[_[M3]x*5y +Vl3n4əSsI<ص VCU1xIL->18tPG-5qmf/fMce2g̨JWrړyh&5xsۘ\M-d#mqIcOz`-v }c">ZE΃I =^21? ߬'2OqC ~ޓ ϩx(Xw@GNjNH_O|L$^'S:O|L$N{5 7n Di\J3br~gSbe^(^\M-$Za%gSkPvL,:|%y0g14[89@DE}2곋Az#S+ebړ 7@ E"97b&43G~_m21c&_(A g`_4q' /'}>&/铩e'}>&'ٽq@iF13`ùUTq2泐G4zr ;]۶Ͽ~[.6cn>oU]$()d<#<Y3d  mP˦I ,}۠L ܙpj 2dэGk>33ܟt?ꗮOxҥI4~ihZ[{LJ3.s)]&Ș6#U*]uw'ՔK<plT7M-"ycw,סyI4HC+cib/@߼K6ĝ$HHOuHH{40b)|dҦyd\J37 )ځT&MMr@{wuiT&14.Yag\r,O`xpxaNhR\[n[LDhY:;>7Ceyt^g8|%v͟kG 1Oah1~f.2 eib">}!dj#J?bXZ G~ MOcoDrj ?_~_+\xLM'INǯӠlN]V$F }c">Z!}c"qRyn͚13 g]߲| 3r4Ap2 *PJS+[Pо=YRD ض -P?tBG?+Yk+#!HX / 23cO=^4e to\Ն:^z :5n+40~6MV &lT8gj[>(K޷LPX1dž-~F\RL| |Flg.^ Cً0ΰf˫J{Ϋ\/{}ufVIih$)LȀ#:Uj\rL4nJ=YJ3 @DSUI1T4i>fdj%b{i>2E}B')?~r]aB_IroD (&$?a&Is#}Q>1xIL->18i`Ȯ]ap؜z4Ji@iF(M- Gy2W/S:v]\n\Ϟr-qvQ«Tva; %Wru✩UlY9mސ!r<ϿFWtզ+r4sL?g41NC'`ܮpp8v,ڷN] ;&/铩e'}>&'^7 ;s|_WF.R󯨃(͵@:vL֩70ANKq5mSRUvt"U1cY S T:m 駦ڰ<9 ^73O-_. D땧٧`+\:Nw?n}=!O<ˋez̀m4sN?;>1xQL-xZ"TSjK-B6|,2SHsj8ؿXZ]4iyp/)VdMWUeqD{*^",&z3&:wZ{FQ*Y "1\~d?%T~_`{~%!;Vrbǝ=cvfĥ#^¹ CYX9󳘴^_؈-{/%~خG3? u>Oyl9SW0f.Բ !SK䠟P4$7?߼,Ӥ$_H5ۚ鋂IKdjYIH^ xțF|۵%Kerdʖ#sȡ)NZ̡wj֔p-3:vL3T5KNS|ȖSupn*UhH)ӱ)m٨QreRJ%fڀ{U摨X8hTs[qÐث0x{'pu Z8 B:TzYgA W2h`8PCr"Zh(gY=qfL hZ<*hE"gr>4{0?*^d&~_>Mz9M2 W4t4ZpyDlSHe΄;(BN((3淙/}ߺuWW:PX%T36阆wc"ȚYѝƞ@'J|hp1)/\KO_o&.5V" (,j-.:^=l9SW0Mf.DZ9F:41xQ2Ij*M>vA4i"uM"gꍛ'ǚ.L߈䪿-:q6Nw#}Q>1xIL->1KsV^xțzʳ=[_N/.,)NZ2ٛoi⏕7u;V2pHۋ2#{XV h`0;KOAgg e㕄qqL<c[?Q>1xQL-xZªS3,=9єsdN4 xTieEM)4[-r?MgIq IzfR6u"`q4d#'7[ƩvAߓ-Jpj*N !\^pX0fjU}>^# Vi`ʣiHF,~eUW^,~},}cRb{i1s'rO>Q}P8;p*3kTqQF$W 1{_H_O|L$^'S:O|L$Fܜ[53{E4cgӮٳHSV7䴤Z~pMT h<))TY4p,ecUu㏰xpӦeB mBi2 SQIhy9wl%,Z>[^ ߟ\cy0/1 0 }c">Z?o|3rg Č (YNU?XwdNIqO*9^ިp¥fN\vOVZrҰqR8+Lʧ.hN[R<;9*}krߝ["X9~o_ƴu ]7h^sZ``{\eO>M 5y݀wc@?.äX˾47Krow"ܽw=ӜFO_$O|L$^'S:O|L$Fv^7 PWʽVׁ4I|3L(>b7lwkՒ =z5=8[|USmxxq:{n-ǕJi}۴Q-KH*iS gݥMSTN\M2eFOBBE7egzve8tϝ7Y̋ez̀m1|-Ɵ(HHO@}<a63JRV2CNvꕳfM-V(RAa&)T CLN}8%8C g5)D5s Luuչ/ΩҷܦJǙz8.WquCقer6\Pʇ/('Ǖfڲ4Ǥ:u*P|Y BD }1dj[Ȑ|BI4Si)qRQ_|6V#|vN/'}>&/铩e'}>&#}7M11wg΅eʱٲ+٬۶[ G-b@i- h{:2%JXV&;ҥcq܋Pʖ93; xcpDlr*Z;uzŊm[VU)S1a:[5n0bۻL*W(Aͧ۷2bZLuizX?q"ț-i X/ʕcMR> T? N@ by={&0Zsm󆓩e '}>&/ꓩ%POC@jj!5W8"i2*,=jIGp(e=zi$IJb&9mjm.-'V_l\MBxS Kg,Z<L֤9HHHOuHH4\\R)CcwΨڻ&`1L-B xZ Sի?Rֈ`4L~u8(M\>rOwiJ[6Y1,?F;ޫ.U[fSt+RGwV{ےkmj^sZ``{甆kǚAD\¿|yqHܬd2_k]wv=!ꇽ5k,Y NS'0bNx¿|Ax//˧i@Vsz ޝ %3fe!vO2GgyL _^ w e5AYh@}vѡڈPMo2CJ4p;e\ @F<2ʻ=p=.%U"RU4GYBi&k{EFOi6v6uZf)kiRѷ}:}W皔3”i/b"֥4{ghS:udyӃL-}6Q!;lffowAЯ4iVQ&z"!}c">Բ>3B4N\.u'V6_[2'Vay[ʖ?fY0 }c">ZE΃k#xCijݝx =Öɓ};-7!91EcpGס~4H{wi!m}d,dː?A0EodMc, 7,SfV@S @l ;> FFr_@u^uE:'ە5CVfdR?^r` b>4:8j&>]Ul2Gx&/ꓩ%P]t<6M|7t"yro}7'h3~;G8(DOY K"Wo؍GdxiWw`0ܾyӃ蓩î_5xg4M&ԧ\ȯʷ+ٌ->o("o}]s ^_4O|L$^ҷkj Za ohIċdj fjk~һdd/dj=1uzYX70 wΏ'ƞƞ91$˳narYܾyӃ蓩[I4 1Hi -MҤ9(~տ`|n?) }c"qRL-H:"k|#FOO|L$^'SK~0SKZRmg%p%DM~$~T<,h@n *{2ZH}''SK?ue%&.JWq4G4NU)߳1:rRM>HNǟ>18OA$dj5~ ' '}>&/ꓩ%P?{ (ׂWQ YB 09KɆ߉KlܾyӃ蓩qyqfhIH:`0>~$'7>4"9R@D>ZtEpF0Ɵ(HHO@`A/D }1dju?WA˾UCEd_A5FrG# -K" '}>&'"RDA*c4"Rn?>OAVpOOO>^ׯ5*~N/p.9AqrI>djDa^hE'1 }c">Z" n_djDa^hE'1 }c">Z" n_ZtعW3dj);cIƟ(HHO@}2}"z}2>z3Pnxom>9PgM\n~̾pЮ$M$c"!}c"qRL-H:ܫ21$FOO|L$^'SK>ZAX>?=H_>ZxEe в}{i)iod wISop}X6V&>~'}>&/铩EI{5O3fhIċdj 'S +}''SK/G 2]*,ڠI F]ـ){{p‰ns: IAR?>"ν'SH 3Nb4DADE}2ꓩEܾyӃ蓩ê1+̬gbSiCXM5;ERj s1)<~IKdjDa^hE'1 }c">Z" n_S{ sR4IuO|L$^wKW_[h)nşz 2e(зMM^a^h5aq' '}>&/ꓩ%POCk #ܾyӃej7(@B%o t&M$IuO|L$^wԺmYHu'S˛l+;~2OMz ֨!Gpmfx^=]&cֻWkhhдNzPR%V]۰)Tqi҃oOٰFHH߇_;>1xQL-B5AF7JN!wJ dje>@+!}c"!}4J5 ӧƭ]7lC h{ {7կ ccvoBv:2@OnHHO@}2ލ^ ?=H_>Z Hƭ%E>1sdjN`JWkߥ'4i=ƥ_CAN$SK >&NځIċdj W^A͋>?=H_>Z|djiT8}"\&t6DADB6~_-73[6ȑ5+[b2L0RL ]ڴ?OrÆi{[?ۅ˱>bgcP(_>Vo,Y/ґ#5mga"ҥyB!S'.y1~fJM[-^lXfl"LMM)#g[C…V8[D¿?iՊ͒ M/Ymse-P@Gz{ILSWڰJM;lt֡{_hڴ)dz1uxzиL-1~8k'}>&/ꓩz'u>1u} I}#}1djB𑩥Z'wS=o> ;/z駡MPWK<*ӭY3ffE ׷l=|L ̋f |7cb f ԩRsU6:Vb[K/1kz߾r1ݻkگ]PX1M1lF\Rq L-\jiqdf@Il3CEQ/S܉]# ~LC…{',)S{r]~uj&|Ouޣ)(?+;N?sYYĠn܃ u+mMVvA3}?A[NZbfHH߇_;>1xQL$ַPIHHHߺ> >}2!$VƪP_WZ#g-S> ;/Ԓf߹S6eFv ;tP3g`QL{gq={qYT?W-[V7o9_ہfիUu+A3E*ϛ#hl"GI+Zz_F޾]n#2W8)RgIq; O8v40mXpkA7ʷ!)6Y^al,Z}Ch#L`8y> > ;OVy$O|L$o]?BRH_>Z}c|j 9 NZ3j> ;/:tVdI9lؙ3yqUeR%J&c9sYJ [M=+DE|<4woH.,SK1MiY-sq:v)3!Έg{c6WL-ʷ!)6N &&Ey/Z%'}>&'}>&/ꓩvj; > [!?O!PCP_?%׍[pmo>qS O|L$dj=y= 7F&õ͛ ?/*fNu۵c.e{orR9rh4F.ifo ]6e6}T2 3~FKÎY伡Z䞲 {f-\QYi"[Neq;ĥ:MN`ztV_gϝ7kׁug/׌s'qc'}>&'}>&w^L$[SV$O|L$o]?BRH_>Z}Mֽ`X> 7kM 7@DBKF&Ҵ38R.'سE T{6ʞ]ӸzuU42ea鼩L<_4z$ԟT!h0}Dw'߻wYf͠RJ0|a9,/uqc'}>&'}>&w^L-DBDBD"JL-B>A7oh;Jo$B~p̟]I ;y!vAseF2L~+pO(i?]gq=SkŘ1r:N,sr:lGر#,>&'}>&QdjBRhxe>$*Nw8d )}>1sc/fF2L-y;k|ֽ;R<#>XiJ[嗐*UK& ;&eArhaLlr128&hI)}R^{MC㿥xW_まC 7Ac7lwk ۍTFlǭeMz"SK O|L$O|L$^'S51>1>1'S¿v$%_UP_fwkoauOaM>#B'}>&wNMSƶml+pd":*S r3&"R|f~\Z˙-Y͜Ruo\Γ&ujy)@ \^21#~D(7/[?%hjaٰrXnĖȓ4Jk//gF߾Ӝ9CLo6M.4e A:@_'i)]|XZ'd @+Z=pz ;[V٠y߽p24x6ߓQ8Ͷ1}{/h;n,<v dceаGdj IIċdj&&'}>&'}>&QdjB.AU䤪BŮ~rmx`߯ǡع|O|L$.˦4$p6ҸhX +Kٕ+U퉕;G7΀󣡣CCJ>|{KY`8 /st:4Moچh6<N-_ʏժYc3f*eʰ=kHDvp>b% f:HSj)\I8^[x 3 'Z}z&ζ+P.Lj[X1X-Ĩn~27ڪ({>ݮS,uh5nl^P~8z(f_1ZX-hh2ADBDE}2H_ > >(}2!`7wO_Β7>;*V#g-G{lf֏C1s4ybE HHH9}7M-Aŧa |P^4ilL{]5kׁKdjIIċdj&&'}>&'}>&QL-ߡ [ 5 jԂJ*׉6dfSxQ wvmeѣD(𦖒wN݀4i"13Gg ^Mh/mD.f݄I'S+`ǠSH>>t&Mڷ z.4hIdjIIċ" ›D\n/V;P=&t ^Xc)3@*X>,{:?j6p>/r3"#uܿo+ʜY5oO<'ԦAVٰF*>ÈhLe{Ҧ%1u[}FM 1pZs߶>27@S ")aFv¿|xq#,:T}p?[Q-C$/}4yjU>bbbkkq&hסTV]smݵc  -4S51>1>17U^o(&]bTY[It #:v_Ѣjjm3&N‘#K_ /hӽFZffjIHyIlTz&G3`7+a=p9 gdm+L: )ƿO|L$>J W_˲Q4yC)C$/}pmAxD3xlo7*u"tWA3S;eo'@DE}2H_ > >(}=S띎AFo^/ǘ% 17@S+"|/xҢ6_ٲ1B}#=wN5"SP{kjm"/B3>1Okb"!}c"!}c"ϛZCԭtZl T %Rۦ鉆K t)1Ϯ]˘|W66oU0d_t˟eީSu!ek|9s/G^\NiݿK2J)Թ3^,j{ڶM\c=qqۈz=(5K=f;Fe߉ܦi`|P_|Jf[(_\ c`yкw.1:~Q>1sdjDa1dj1o' '}>&/ꓩEHHHHHDV{f_Dh [a& FH͛~H2Z K̃nI"ÛZݺ h]={W?/48SI{R'ZG/*Jk6}Ab góqߴdRFRʉ2ew玽cٴ |ݻ[*U_N˚|uoG` _tiZB~=4or˛W{lfY{ckNRݻa_-cmNgaw;`ÎIfRl+xqXeLY^M / '}>&>ZtXA?Za uhIċdj&&ҷzII/JwϛZ}۸kWs~Xcn!Z>JGhܿ˗pQ59|cs$GGO2Ҿ61 gLIuƖ.˓'1>yrbP@ Ќk0yihl=:fAdL)ɠZa'l+; Ng{\]r]3JXi7\> |f,(5 }c"!}蓩EIGk#?Za uhIċdj&&ҷ?m P/.CNRe7qW1 ~0ro~nQ'}>&>ZtX"S zDA*`' '}>&/ꓩEHH߾>&A /JwFUzu{*֬Y3T+_߸ +Wa2St #RZ{9R_}.!eW?wnu~ djaRZ su4p2-᳔p-)걜;mG S guc>3$jbSa3!Ț0o{X> fP57 *,ڐ=ZgQ'}>&>ZtX"S XhIċdj&&ҷ?@">K+ =n ygfj! \wVb2Mij)+i={_Ha]Jm dj)6>@rz_|4.\Ж ͦ_u$r& vIOA$V}OAcga4DADE}2H_ vd `BNR^?l{O%W9rp̔Fڻ71TII^\R܊(-SND SjI|t,\3>k7v̥Pda6cO]} }c"!}f5 XA?Za>"}ML$o_y D}0CNR^d1Bz: 1#~M4nQ7zub܊(-S 9p}oZI)SqEBi] w6e ,^3>k7N߬W-Z[)}>1wx3`Hu^ FL-0XhIċdj&&ҷ?@" 2)El £GÇ!Ѭf~3Μ_V[7$|&>o<_ khEkz?Q>1xQL-DB7B$^\ =56m ,- k'~sZޮaCm~ypyr)rW=u6`( gP)& l^c-RĿ7VQՇM$e'7me%K̜|KoJ1ס_ۆXѢk0OeـLXo9KI};>1w~チk!Aֱz2;cM' '}>&/ꓩEHH߾>&A$ԋP}^{G_2c+،-Lo줪)cndxSO2nqBDB='gz2;cM' '}>&/ꓩEHH߾>ZDRe( RjqR cy/߿?ԮSW5CKL-2}>p76ꎟ/wTM-}>1w~!N2V}L{k-[.<\H&/ꓩEHH߾>&A$ԋP}^ǑSQЮ}hذTT 4fރ?a7[߂7 ºuK׋X<DAΟ5i"ADB7>va41>1'Cu3&,Z2W$*P6oBﴩ亟̓G0՜)LM-d#mqIcOz`>' '}>&/ꓩEHH߾>&A$ԋP}^곛-HɴkYL׋X<в(b+TL&B?O|L$}@o7qT>s~l?$9ZH<'Ҹ fɓdj&zXx'cSkPvL,:|]u0,撦3X0hIċdj&&ҷ?@" 2)EWf t "eb|jEP16{y+ }c"!}BO?ۦB0HcGo!djهL泐G4zr ;]۶Ͽ~[.6cF?Q>1xQL-DB7hk'OΒ0Aڬ>^?Բ Ko<&sC:Pv[PځISfe]hA[ 2)EWlrx>١I u.^_6I>1w~ϟmS!O|Iţ7MS@DX~IȒ)KbZ?q(Z>z4<[kQ46[M#~KW;hظ1ʗifϒ*2,9RV#?kJ. 2dCҪ\ްA·|[.Ym!W}*UXmӧC2e sƌ5Xf@\qS̱mCڵ.N AyF٩F[I@G` z6\X|X ,~1"`^,clձ>"}ML$o_yA3>@}QSg| ch a6-[/OVM1;r)V&@m`Ⱦ c[WA4?P..CNRxol)P]p-%_ 6IuO|L$o| #:F:41>1'C65k t}7̐:N@iF(Ml33ch@Ƌ/2%JX};`kl(cʆ .<Z )r+xŐGYz^b}2cw״珄Dh ?~YA{',)S{Ϩ3N3 %'c4iX4reX̶tb, \RL| |Flg.^ Cً0 יy*,ڠIwp|L$O|L$^7v }c"InZ#v:bsP244N0~C^2M80v|=&qZ>o9_s(}tp~jM9%+V׳Kq$(JxkKֹPBzn-m]M-+ a"I3$ANi4q ~? cg®}ū3!#G,2|=f6Z0hIċdj&&ҷ?PM-K JMS`=u[5_s 2)EWll)M "ec|j I޸yЪwo4|&$O*HHo'@D\69SW㏫xCIS g )ʕӔ.(qyk78.]tr@.l+.m(=0&~#<"];a=ΜlW#_+M- ͓Ӱu>{é|h:s/^K_ܩ?7G'gy _V30 }c">Z;o2"TSǪ1e 5TYXE"|fAt 1~Be/>ȐuZdDŽ:KW>(YެyeA>Ժ^ k[e( RPY "b|j =곖A4iN/'}>&wN7>NہI$K֜[ٿ̀^#p)Aْ%v9fUG;GU~qgSl)Ӥ%ΐG8H=mM*2Km1CwǬYrDLgtn*UaXK-JqeXgmaP摨X8hTs[qÐث0x{i(:g;o-lU?2;ǻ?/s*TQ{dp¿\yo׮o$E-CyVmI _vOseWe OJM'mm[)n-am܇2r/O/s*F?Zr*?e E-]`tp<ş˅QKy|ޞŕjzxdmײ]EҺ(W4& M\:7m01/[9;:o.^tL\;Mx׏M@ |.^_B7ovO̩Nn˜J2Q+R{dkgJ~\ƙyE-yU5_ng{}x1+Slg?5҄>nE-.~YuС 6M}Iע fn?(-cZuf-jݖT?:Ne#ϊuzFX{[qa+~ t)  wy>HK/+ԋZ]?okK/(~S 9dɰ*xkjZO4Fm ֢]J Szog#a)OݻykQ-ց+UϚou2\$Yi;+ʛ-]3e/4mz`Th}i7*p_^F^?39dE-m9oߢ|FU}?>η܋bY t\ r4\GrhTXRmb̿bcYpQ;~-'Ul<^ГׁxJ@B+?p:i+]~l]46=%ItɴEԭ0[_~eN%wzw#L_TM~KN'Bc|ӿ??a5SQ"mׯyrZ?iRgϦLnmkEI>jB ?GoHf5iʕ׼ه}XSti/ԩzboʕӋ?,#F UcN ;NocUNp}={ic.9RW}Y6d3cS\|} }ex\7"xV׉&A)~v4n3`͜>'ڰ-/ᾭQԲT?2((jo˩~~cE7-j18F,ua|퇫fseL\F>.κGr%ehWiɢv:}ݛxkڵ }qaz sA&_1anؤ$ h>lsAͷQ~^%?L_Tx~x7~eN%翽|joDž!~_#?UλIM['=jUzO8E~?ڷ׋*rY.xwhkc&<0Y~eիϦ27Θ_Wŵk~1b…UT5<\Dc8[70>smc򳵮8|}w {-,c/ex{]{~\pѓmB]ϢYlk[5ӧ#I+V_vw7}oomx '|~eN%GQ ~[N%,2FQz1bY'.=KʚVyE~+ڗ#{P\99,8i45V{[qJ̢*ӄ^t͟;?[PM46Q1濯}r,u|%[/˜JF ̩$V`W6?W,/.NЧkߧ9ޠQZbgッz|\w*x9É0_A~SI6QԂߖS . n[FNRI~Y7.g-^Yդ [XT5r2\ߎȝ/)瀥[ zok9xq,BQ+U~ /?:Qgƛ9 |`0oLvO;0?2;ǻ?/s*&?%'}K$<_AOΟK'̠MMseKi[ 9& }OqV~p"__T~Tp?"nEШӖ-2Gr|git%5j0!e ^m9.qXھ CWNs]ocxU}\uq%|uߎ* Y]wnht !D{[qK4oTϝcgIFFmm9_V܎?oԫ Qq" :̩Nn˜Jϟi *Q-%b4x yT ˜JяJ7vYdpCϴ2 3NtFȳ4:V~f~oG:1-WhT"JӨQsJ*9F vkTWlL.U[ѨiS6';ncWq_^*kthͼAm-?.QJ*ӄ셫ğRGFܐǃ**n_V܎?D?Mt;Ɉ9=ލ0A~SI6?.]-WP5> ;:0_A~SI6e.;}M?Ƴ簾+^E ?JFͺY1թ>+-=}~l?cT郏eU?]_&߈bf??29rlu/9dE-m9oJqeQKS.Tjh:z˷-kQ75Eox?]_Tv+t_ѹoV̩(j~eN%aQyT ˜JяJ }PxEhHVj1u*'2 Zu§̾t~SIq~i*u~4p*c̩$(j~eN%aQyT ˜JяJ }PxEhEz ?ZВE-R3t~SIq/!*~MŽ/s*&?Z~SI~@u!a?2l-s}B?^DQ+̢VEI+?xt̩NCnЋ6̩$(j~eN%aQyT ˜JяJ }PxEhoscTuۤ}M K1zun}Oduxd?2L3>17 _5/s*&?Z~SI~@u!a?2l-s}B?^DQ+xJ/X@'BuSñ3c/i+./s*4fҡ{H(\ ^ӄW?]_Tn}5M44hu8/(Q?2l;ǻ?/s*&? W7a?2l-]Nr\ ^ӄW?]_TrYO~SI69d_~6i֖S \P_~SI6QԂߖS >EwJw ˜J4?_SA9d=ލ0A~SIgIE E^ 9dE-m9ܟZTAQ+ t̩@|-c[?]_TM~x7~eN% $a?2y{!_*_T~Tp0Ł?.zQ5TK4jξA_4:`Q,WDY*1u5}F ?R;ϸ>v t̩@Z|{-t?2l;ǻ?/s*vl ᗹc QR?2l-7TGE><&TE:vիGMm:RwrLkQ Mht=Rc4rRevz8F4:X{5jFШN_n{~eN%aք͞l;//s*&{a?2lm+uwT?2y{!_*_T~T~9(~?d܊Zk0]Ӳ% 0 iΝz_?}}&&^Z|Q`*[%>*o+u=>.SĢg/s* ӟoU=ާ6'~eN%wzw#L_T?29̩$ ?Zr*?\NEE ?}~yL2NE{:tH6l۝>Sjڢ%띰|E-Bh;N,/7iTBvsZ?2]z: eba~a?2l;ǻ?/s*~S ˜J_TE-m9_Np"><&dE|:vH{5Wn_w&ͮv7a:n3orI<Ϸl}drZ?2(~J\f /s*&{a?29̩~eN%QQԂߖS }S=1ȢVV[BK~YZ=QR>7UXb*=5u>g:/s*_|y'GO˜JF ̩~eN%/s*~SI~T?2eQ*?_7j8aC*TϷ~W%-5;>gPPJ9[J2_TM~x7~eN%/s*~S ˜J-9/Z7Mf7Sq")ja)jNlkH/\=˿3((j˜JT?2?9d̩$Nn˜J_T?29DGQ ~[N%/s*_q +> Q֭i׮]fצ[Aq.j\UK.}AS;hTĢ\&3x|*.J]䄗߿/s*e#˩clm(g_TE-̩~WZ+LIDATeN%/s*~KT?2ejذ! Ν;Szlp믿W *km]wim}~+/L9Ӂ\vmn_rK 9䲿ө/5N8~SI~?29̩~e.]PԂߖS ˜JWJ/7mJ#Q oĦe V<͟hF71\eLxQ"˜Jry濻<53'J{_TE-̩~eN%/s*~KT?23z+3^y}J$OϺ7Z%Jij}no[xFON2Gj-{> 9GQ?/s*&?Z?A kyB~Ti\$(j/(j˜Jdu^O3>H/s* ӏ_TM~x 0AJ2͟EC(j%~eN% T~ߏTcG67~eN%aQ˜Jɏ@8a?QQԂߖSIs}?UxcE̩$1l˻?/s* ӏ_TM~x 0AJ2͟_/^V4xcE̩$[]AGo꿖ok+/s* ӏ_TM~x 0AJ2͟1Zu9d阍[lmV{~eN%aQ˜Jɏ@8a?QQԂߖSIs}?x}3FQ+?2\=0Z:~1!˜J9dE-^q&-L$9~fV4x(j%~eN%o2M* yU~'_TE-̩$(j!:6aE-m9d?'7cGQ+9/s*%R2_TE-̩$(je\s5'x0;o~d/cO膾72_xѯܿ6K+˶ody ((jo˩$|rx}3FQ+?2\_6}~e.((j~eN%"r]d<~p(aͱ*_Vx_ igSubRn2%x|1E%.f j-_ֶo>.{M DGQ ~[N%瓋8$1Zu9䪿kiyEMqm\PP˜JϟEd-"bx! 0RUlR.2׉IUɄX*:!:-=pB~^*qp 95l{ &Kȉc@r ^?ZɁ~S 1 {c/DIlqQ˜JϟEd-"" 0RUlRA 7^ythc4w\[q%e'l<! ?Zr*4?\q2}?IJ83u4*UAu.[FQ5|רR-jҨ:>e_uz8mwPnܯщMF<ϻߐ>AaQTgNs3ՙԝ>䷠{o7(ZVr_TtSl}$S?{(j~eN%"r6Y${ʹ/Q \r3t v-?,7Î<*VH'7rHjԾ,rRKQ媕+k^IcYUOJOzN厯Ln@_\:e-}5o4U8r)Ԣ{ 7~_m#!Q^c}Lg=^q&-LEN'?4E+'%x/֨˷}κ'^X7,=cOF5 Q5k!䢖N?th0FmRmwP+Y>w[O'+#s|VVnkg-̱-Ky6xGUJ\74jk˩$  ZɁ~S Τ歠~#lmaW\PP˜Jϟdm.wwI\}ygPu6s5ν\jLƎ}q:Y+껯˓|eE/&c,.V4 F5i A#˚_F;nOsQ6k]T*Ud7ܗ@c n^Η,UR_?#r)g?r1')x΅?|&W\PP˜JϟdSȉfh[ym׌ i1+ǘ}96k!oYfm%f"GXcW}WM|vAh۷<+auhU?ۿ3־~؇̼,D%0rOMmCR5˞&qYzrLǍ˞sNμ5v5W\&WiTxT"up3nJ:po]h3>znVl\n[ȷrk6͞VYGN|&9V)}y9x=(r0(j%~eN%{W޼]rKū?*_悂_T[?? Zph␑vNX['ķT顛:g +vkg7rX Q| 1k[M6~6-Y}5w܄vjDz19}gޏdm+qp kokȅk R :Vl 2SyFNVcsX Į]YYLjgK^:}v c/e'lJ2'9qFТ_mķ[m5{Eo;LooIx~FFGqu>:VbQuH<gFQŅE-B⶞4.JqGcŪrˢ{y{z/_L|l>yl3{ hNEB_HCV|G[MBK[wo>Z1mnm=Yԇ2r/zyy3VYj mٖSI~??2?25>U\/sAAQ?/s*TyN4᧨œf]OHxaGRr cY_}eĒ}K=]g0Y"_"| :~ޓCͼ,D% cOnŦ 9''󾜎 X)9Um %G۝,xY/hatWE~Ti~>ȉ{7y|U '戚o_.eP]UϋK?*rC*T˵8.lzRgb\?m}E-dETfs5@xlXױSZkz.|̾M~MgDϮM}e+^6=Z>3ڸ;elx˛ʢrŏVr_Tov 0~ Z~SIkg-]N4᧨{9~qǙy9i-9d}ۿ?yG=TR2|:~Ɛusyrz1>?~=2VVl 2Syn?8l03o|+a땉 ?v:FnckiS9 r\ɂ26NUDGQ ~[N%瓋woQQJuQ}}ͲFauL]/q__Ǣ>jr7$Nju˂[Qv*j[^=;L񼵨ŷ.bYȳ#-f{fdfz=kq.ڶ 㷟/?~CCQz_^F^//o(j%GE̩~ӅRmm~ ᗹ9dYPN:D3url bbL`OGޟ\;VuW|MR%~0I~-Ҍ\~=?1 *QY qN =C1u{)jq+~J(TS;5g=Syx]Ҥ6\}m\5 m7>pFK^Yd8v?DZ8~0;Yr^((jo˩$|rn-j"Q}?>WDQb:nr.夓o?X2ާjQ:.|'>,ظ:m+\=3[e3ko/y[8ufj0~ 0{;=r/={b/=>;YG}y9xnc%$_?ZɁ~S O])5xu-A~ Z~SI,j-y9MZ?)Ϧ腠-Tby3ϓo6:լfO ,_ ‚5? TA3W|(vV45f5iuw,봯Jcx\X}??|5?Wmi/W^XkX0PE=1# |cg>KC 1e7r cObE26~ϧ pxNeyN>No?>{N8=wIgƕ1c w x\B zhCz?c~ck?MF83_ynzqv;/V8-LENܻnQ[1fY'R-D۸t=Eo4%aJZIP,NV2ń^ԲnE-}btݝfl擷RI[i[y;E{[ vpᾼ ^k׮MK~H?E̩HGnI7u-a~e.((j~eN%ꏲwT߄ϵۺYI|[|e պ)W?pYV|(o-DU=|!.6>Y-;Y:s/?׵\`7]ҺgkyV%-ol_}yj_\g^c te˗†ҽk[.2XK~ jgVoǞgWןtҟ'sEc!܍tтtWE~Ti~>ȉ{7dQL+ߺ&:,μUYo'#r|\Ԫ+V(펵U(vu|.Q4:FmlTXu46૵]Q#5*YE뻶h]I#f8v1OrUm=k_z[Ȏ5:F=1Qc#.t|{f?QdFRUW]E]Mz![8^uJXT+~?2_TʭcgT݀~ Jp"_ߩp:dkQMmvɾ"̱D7ҙG _QQԂߖSI"'h TdTσ컩k銗GOcmg)릘,dާٜ Wy%7cSMQJ˜JiH:{*[ /sA ZԒ, ~-w*?ZB LtтtWE~TiTE-M ?_|ŷP r05}*5]}ݶ4(F7|s,&m:M*1?f,_?ZɁ~S FL>9XV_悂@x9PB 5NUDGQ ~[N%GQ f,_?ZɁ~S o;jl([*?{(j!A>9PB 5NUDGQ ~[N%GQ f,_?ZɁ~S ?o~lˇ=~܍ Ŝ?(j!r*-Lw}3?E̩3%]0g-^QQB r7|s:@ j"ȹ; J2͏IXT+~?2< VQ13l  GQ Y@ 0p:" ?Zr*4?Zx'7cSMQJ˜J?]?OCkk _=~܍ Ŝ?(j!r*-Lw}3?E̩ϟo8usñ3#l&g?ʢOܮǥ~쏕WRݦu|twX}S@gΥSԧW\Z6Y?~atWE~Ti~>ȉ{@r}3?E̩1Ty!]=(Z^NS>0 Vf+^6'lk?l/["zG<3g4jFZ+ -~>w/L6p,wӔS.SPziԢMG߼. AZɐ#Ө-9sig-Lw>HyET+~?2|P̟; iDt?{Q8]VFŜTqq%԰WZW:Ϭ6žY%}_隖-iTXXH;w}λMMPR˜J\J_q>=5㩄up wT_ۺ ݩڙըL2tHCZjnpB_; Eg;Sldq'j~^-s7TugTx9}mM5f[@+֬W'AޜJ_OᾼKzݏ:vH{5Wn_w&ͮv7>9~y.0 B~Tr &LNj*~Kq/JrŏVr_Tv=1Nt2DOW]:өUVt՗3;#awu )C ?,W}U/"p_.5ܯBc݄'$܆.k~~uϧ-{^W ,ǔ S_\zmI?2:nBN?LUO=XlE~-Ǽ-T4dwݴ-yu_v=?ГE}//# M}U)В_k֮+j?c+VRyg[N%/s*~3( DGQ ~[N%_NڇIE?̥|%GQ+9/s*?^NQ/n6&j*ֿG[ϯ*L`̝OIJgf_.RY~Ͷ{̳ Uhȴ!u1=ۿp_^F%x]}T놛Am3V*<~eN%/s*F?Zr*9r>L(*~e.] /(?ZɁ~S {`o-e2Vw.j8l;Ιf>Ƭc=cڸPf<p:#h4oǼxqًfgU7FO~mj]T+]fw頃2F>󾝧SsvOnQ rT-;Ⱥ7%6s]۷Џ{wѶ_L_2r/ϟn*?_ߜN[CQK/s*~SI~Tr }DQT\׏^^Q+~?2stҵOƮR=~/h/Dw YXoverovC7E/%?'og=mh[qI&s/-)a=V|Ɩdݬp~XoȌ}gZbF-݊9~3xm_Q+ȺJm[ufj0~ 0{;=r/={b/=>;YG}y9u_|]v*?_[m&Msf3a//s*~SI6QԂߖSɁ: {~e.] /(?ZɁ~S 9yS|7Z~x"\N;a\V'N]OHxaGf4l0a.t)WƯˢJ$g(ɱn~?2A/nŜ 66Yw8p[ F\7c35NJMJA/S}'ZÄ۸e8^םZ 6?#u޹sgW [x9^&__T?2l-9̩DE̩W3u~[ {F =[,'x"\N;kfYW\`̼,p<3㎦jlgeYc蜡 ˿']ZAY6y 3^o?d;pڦ 4Gnd83? 9eZ~FNG >FK7eEu[}//#+ZRkmRK!/s*~SI~T?29򣨕9 @Xl˫NnDw kܦ b^FT8VJMCHsvݳz$=1 *QkQ+Ⱥ\R3{<_r%f>pU[{!5ԘɏnxOݶﺻ"z]&?f6e>Y~6s=0q*X>ڴcϿ}//#>\z)xB_T?2((jo˩~eN%/s*QwRw~eN%/s*ڋPU)}@Ux~x7x"\N'?/ןoeL3S>B+R j ^L(P/.cӄ'Pu\#*R|*]><_=gCq{ Bւ] hĂT*m 1[ ;;8=|md:ܱ'Kmn J=+6z|zvoចI?N޼m~֍7s/?7e>Yn.Agl~Oƍ>̹ o~ bz:͢?K4 U~zJmE-E̩~eN%QQԂߖS ˜J_T4˜J_T"fQ+I(~= ɂof->O֢A6p.?&_XB F_a#ׇcƖH%Kǯ}qm7y=nA?2ƺq{Tf5PÜr)nI;|%S3QҥlT44?v[F?ۙjYt|{f?Qڜ>}*=TbhWQzׯ򿵍%xqQ-[ц b{*\lԘ{E-E̩~eN%QQԂߖS ˜J_T4˜J_T_7lH;ǻrr@\M^H_[L_^@k?}&Af7ТuL}ާ%tϜZURQbzuЁkܾvIW7mNnO?/9̩$ ?Zr*~S ˜JT&T~S ˜Jya;~nk 7 ZD4[wgӨ`c"Ȏ2OZw~. K=48&{F #~tAQ @ DGQ ~[N%/s*)?Z 4<~u* ˜J_TRZKޥ#>ԖWIqn?M~x70@dGx,`@(jʼnJ_TR~@hx 4Uǿ9̩;bөl}?Nn/8rJ۴.Xz.[;m/q'X2 tRL2:W;zzI&x0Ra{ā4ZQԂߖSI~f cQA'탬ӄWqV_T?2LWU޼'J2i0Nn$˿'Ę)._T_%^L5ʖ/kkҔϦ$)X 9ayˆ:^e?'-ٷD/qA%L//cU>=c[Cz{L[L E(jo˩$j~R PY$N^q[~S ˜J2i(j;vڗ L L{(zR?/s*/_ےd&[Z\|)qp }]kS']dN3BNtb՟萲η iI۾psEt Qa־7Bz{L[L E(jo˩$j~R PY$N^q[~S ˜J2Ta>:OlmaWE6HODIԓ~SI~ږ${3Z]8r6'Rr֑#f!*̱TcF\o/i)8?E-m9DOr"6Pz M\ߴi'.wvO2V$n]ʗ'QҺu:Wj\DCQ+_T?27eߢ&aWA6HODIԓ~SI~ږ${3QvVRKQ2 U.ޏN;gq=C+Χfl+UMt>4l/sRu}}۸ .Z|;^6򤻵m,6я\Ƥ=ӤK϶;zxiw(0t7-0A{Mu<9>m^dGF~|e}^36|\~^Gnc׫cEY7U]_%g9x^EQ+F~T_?ɉ`󛊯cW`mKEcgo lҫAW "U*Mz_bC) ذL&f.7{sfvf˼|bfӰa%JPE gߟ"O?M$s60}˘I}3IPͺS9+,K!~Xd~kaЗ1@?~&M-;0k8/LNLV􊘹CqL6Rsoٟˬ124y2`;iV[g*n{%U=OVoMy.ՁqiD%Z,sMe$ZK$gRq}>zy5cV;Wq3s̸uw"Q~_'}3).=1hq\>dSi<,czvݹƕuuQ'Ї}G$ַN@!E1 |r[\,CvEn%~%tu1Q8Ƒ3X8_yJ>NUҝ6"5jP31Pk(ҹs!M])rѲH~̟9>]Di&P{(R0E=6:Ϩסwo.M3ϤH1m ~MϛW5 #0З1@2fzO{8 aMɮnC_L:yYD6&Z7Fڰ<|K\qvݰX%zW?L}]^ËF}{ \y.z4>.n^?$;"0$ H6C<>&+źG?"+VDlTN_qf̈k?5Ə#V\}uԄb(22~g b#)?_3n\tn/NrbjsnD7x>"E0^uU,^E-_8yyXǧ\y%Ex"f SNt\]\zp^6ǚe^w]t=ޥKt4^G6*T}v^JC_L}˘Iѯa`JG8"0&dx7/c&~b}ejylM,ػ>_N9\1{ FmJnjI/R;^4qwqJ Yʌn{%U=OV4tӣ<҈J)+f)/ْlJKS/]V^ܶ?`M2S I2odC08㋊c7.zpĽt5 j yrk-.22Ts|ʟ?qZ J_$SӦu,^{-۠Çe$Ї$,ۇMHL47J6'ƻ~}3 ?ަ'V*YD6&ZĚGHz#/GGџM(㸎)}I߷c<_gAvO[/&,ȑG.?5z ^JG"b@a{uqy2WS7V4v3`,דrӮ>KnuiPƧN9<<죞&5W~+wS Z}˘I}3IXu[&̧kkÑO~PI?5ލ0}˘I>?D 82ߑV7Vs4S-0~)}IGoygƿni<Pj^ ~mO#U}r.b;|x Se\ SObsKek廼d>sKKfj՛?[*Ħ7ю'Z|wSm 92C&ajA3Il6y|Q tLT^p|7K[7C½P ix5wpqOjǧ)KyTܭniWnzqF(RxtE{ 6 C^R#~et16!7Lt>e$Ї$SwB}CJ2lOvw#L @2f臧"7 Ӱ[ã<8^j!^L W؂VC]s眞t٧ZÌq>4rMc׍Y;gٺp<][ heխ䝱G Pnjq+-mBGjTz(nLkۓ-:nטWT2B8ΟߦN;ٟ7sgX?:C_se 67o8qSp![^m%K[2kީb/v2R՛u:33i}{ rsO)8/~n{nmԿx=~13l-ٷmFEoXCT :a }ZwLi}$M_TO=s7sΉJİY,G/%S1 %5utS3xHE)S X ѣcP_EY5"GzHdD`j/c&>e$/o딽2lOvw#L @2f臧"7=v]veVC4͍lL49. UoZoa3֬O궫r8pi\ Xꔳzr--֎DZ^=ْg]pkxRχuX^x:lb퐋߲|J{@)^Vu(jɶQ1ȋ%>d{o)יoG0AȄ>L-;b&ɴu &/*Qȸq)r1m8bq3ؘ->џ%{Dv[,-Lc歹XAM)R@,Һg>D`j/c&>e$r2kB=nJ?٤FA>e$OEd.,X`-zM^?yB&ajA3Ic7Ja0܃(b<(E-V~qEN="*Qdx1< 7ڗ*5#\>ժQdP:XZJΧQp|9o,/D¹{+=l ]U\J +Sztc}fN^8#u܂{js8x1_HÇcsRl٨YU"6rar;tˏ KvW^얖+}w옠SJ|7tJV:@2fC_LbJP]?FA>e$OEd.,X`-zM:a?Ї}G$ַeCp" 7F|3U}Nxu=7 jNLۿ?EJ٨<8~ۦMeEe7fLtغu8` [o$<1=sG?Six"EfJԺukɡ;wZ ߿+tOTlk:yR-[Sdd8‘cCqNg!AŽxH=Nj{b<Fk={FW>etҿGFm$/V"55,yŨAYjEag G4O{BV:@2fC_LbRvU {; 6lOvw#L @2f臧" ,X&hcXyB&ajA3IcKaIT*]VQ=Yl4ɥtC{|]xa,ծ6^~|zZ~K5H--?L~GwJ[vL[JK~y8/#D$sPZhV#BڵkbT^Ikt6_g7nLoxUWq{4qEw)Z4~Rl\~9EZ 5uE6έer Ej֌Ս+e1#*syЉ2?/iU̴ȑLt>e$Ї$OzsΚi&ɫWFA>e$OEd.,X`-zM0 LԂ#fL[ Dz!؍tM-UM5Ĩ5lnY4~=sX}hza6:vx =&OAss=!}W4_PE;,t ={Uݻ|TqL/NЂ >>Lv,/c&>e$y|Ѷ~ֱ^\&ɫWFA>e$OEd.,X`-?M dB1dZ߷%̺D_WC؍7Ư^/jDf8îq4y"\/OJEz~EoVј -3kvVzǻ4o/}nr< vQiԲ;U'zhGJwS֞cN Y ~^XC_L}˘IfЩQ93 AHvw#L @2f臧" ,XĖ폙 2S I2@jjq/+>P]w_eRi<4'Oߖ{u9T>*cr~#ej[llejA:h^Fm xl*guY~3}7~ˏsr< vQiZGjӦfWy5| `$8!d~?"gM֭0}˘I}3Ɂ_yw~m Gz9_ƂFA>e$OEd.,X`-A3A dB1dZ߷y^!nV16ڶuԚ3"*GkmWY?R8#2L-&OLYSkyOī ~6:bY>[17n+/#D$EǚPNNUy~;V?gGÀroTyA;ajЗ1@2fU>ۇNs&)Ovw#L @2f臧" ,XĖ폙 2S I2@kj|:"Qci7@)S22{6JHޱAM3ό Zlۆlv~ݷ믊qr<['[vx3}ǥܵkUy~mܼ,]&1 @2cY![fOP6xe C_L}˘Iԯݦu]7y#-l$L}Z>e$٤" ,X>OԊ }ZwLi}_SKmsC-u!f;t+ Zu&m2÷=öu6uy8/#DnV^wMW\N:TT)_N~Gcde$Ї7n Ke$٤" ,X>OԊ }ZwLi}BS\|cǚY7Ivt5yh4fjKKԺ&6\+wш~=4r͏6y8/#D|O/7|Z |c٭j:hr1T@2fC_Lպ HK/$L}Z>e$٤" ,X>OԊ }ZwLi}BVŊeafDe$٤" ,XĖ폙 2oZd+ tMyXK3n2.k4>}J\{m,~3ci3zΝKe)2uT =yyY'"~)QTJUZ~W|YNw.K^<-vlڱZ>e$Ї`ԯӺ7|#/A$L}/c&&}~ X`%ma }ZwLi}49$lpoE)RE7n(6ڴȭR䓣a}tpZ5ڻJZwE㎋=RƕI:l)bC/&n9{鉹鷿Q#y ="GE ȫM+NS16y{=7{"͋J_5k챵򃏨LT^~F' Rt騁;3dce$٤" ,XĖt$2S I2@ٓ"%KSSAf๽xE8Vze$٤o?HJa?Ї}G$ַN"!XchW_wᅱ/;󃃟.*L-@2flח&W@2fC)2}OE&ajA3IS'R|yqFWh L<&2ފIJĀJ[yL>e$Ї$[0YL-@2flח&Q9>e$?0y>"0$Yc }ƛZl$.]ZHFЗ1@ЗMSdLԂ#fLgV8HSkXoy.4V.EF7uL eZ}˘I}3ɡoAtS7F0}˘I]_>HFЗ1@ЗMSdLԂ#fL[ڲ!hF@^\T昔KMߏO<1jJq|Р&l"S+}3 /c&9/p-]: S}3I|5WnX3 /c&>e,]`jA3I@0ҤwoydtoSSVT:萀KR$''jLV(2lEJ [$sfM;p5 ȼy&`j/c&>e$od\wH3AS?ЇЗ1@2fC_1dZ? 0…ֺ}4E uQbbĈh~Nˢy?"QcGg^bQH)Z4:oWZi}˘I}3ɡ{?P%9L/c&>e$ЇЗt}G$:L--J C_L}˘I߈Az׎~og0}3 /c&>e$Ї L-;b&ɴ> `j/c&>e$Џ׿tty3OZ>e$ЇЗ1@2.0$0Z [ЇЗ1@ߩ_XծNގe,]`jA3Ia 0@S+-}3 /c&}R}P3iaJ0}˘I}3 /c&>e,]`jA3Ia 0@S+-}3 /c&~}QiaJ0}˘I}3 /c&>e,]`jA3Ia 0@S+-}3 /c&?}s/6RCiAL-@2fC_L}˘I}KZwLi}@@S dڱ S?ЇЗ1@ߟ~gн;҂W Z>e$ЇЗ1@2.0$/`ej?01I`jЀЇЗ1@߿~=x/:j#W'}3 /c&>e$Ї L-;b& DF^C_# Zi}˘I}3 ӿf:|TK bj xGS?ЇЗt}G$y+`c$X7Ҝ Z}˘I}3 ׯcگqIF*>Ї٧S IB?W@ЗH&BVZ@2fC_Lp/]!7ƛ>L-0L>e$>}ZwLvGޛL-;b&>e$yS L>e$Їտr2*j oݑƄS S+=}3 O1@2fЇBVZ@2fC_Lꗘ.p#Sϟ5i<Ǻ?ЇЗt}G$Ї$/ajЀЇЗ1@߬m#gRcasx}JF @2fC_1@2fЇBCL@2fC_L}swhԇ0A>e$Ї L-;b&>e$yoZ*Z>e$ЇfzjgTEG sx}JЗ1@2.0З1(%K눙cC_L}˘IwuZ|?BEWqVz@2fg>L-;b&>e$16E&L?@?|}y10}˘I}3 V7ޡ]߷kL-0L>e$>}w@cyU'i;J{n'# W%ϟ^dԂ#fC_Lr_{oe$__c zj%З1@2f8e$aj%З1@2fע=p#V޳g5hP&Mb[7լY99 ϟ:?Vlժqe&'PZ '4xޞM&;#ITW_{s=5k駅ԨQ5: SJҖ-SL2S}ٶm=x :3;khΎ%O>Y˱6mq':ʸKOG:txҳd~>y$9\g.,<+]z:l<3VfYV>| 䧳>z&qy8N9S+4ŋP裏bŊK/է~p=ajЗ1@?ajA3 /c&>e$aj%З1@2f7LOL5ۿgϴaD:SuA|Ĉ׭|n(/>u<Ӧụ>b5fܐ=@TR/5hr˕Ծ16Xd$0sƕ5n\n+]z^ Vcpq&mv㍗[[Fͳ/gIJ]mۨf !*ids ׏yv?92k9gE7v|>+jիIȆ:ϛ2TM.2](Rk-F,#u矕TDQ+F#_fkA^>?s~u1Y328矏?{y{ uC_LӇ}G$ЇЗ1҇C_L}˘I0Ŗ|lϴFj\n졸kgju mX\C볉I3_0SO>^"dYwύ7m 'Jw;M Z&r%Çl(q{t}݌u$w:lr}lˈ{yMGժa#Nc %?ufcK/O#4#u`FS=ly{-3Q|'R}&~T:Ƿn vWqɖ,/ۛp}`jЗ1@?ajA3 /c&>e$aj%З1@2fg۫Z!riFYPy7=5c=eqm}1rطDȲT(?Sq<ϝL?]-o GCyyy-k]qEӳˎ:A4f_DuGf:k-{6aց{a,y8=9TBw"gv<*OQZlYz*yo,/ۛp}`jЗ1@?ajA3 /c&>e$aj%З1@2fg_næSk~q .dڈJ,n [=x3'_6hCtv|ذXdȲ#lzCn*=6R<W#8Fxru܌?\Nc#Pȋrz:wfJy2QLJW˗]f:zNLϩʓuTߵqqDc.쓰,/ۛp}`jЗ1@?ajA3 /c&>e$aj%З1@2fg~l;tQV6&B6RsH.B]qtqqm݈۠VS+Rw^t^!fܸv^ir0p dz[ynut~PmA/Ϸ~Ho^^VnXL@2fg>L-;b&>e$ЇĔ>L@2fC_LїV6z!vz5_eeCm2JnX~qi͛?l z#>GόQ=F6٠VS+hݺ+UjVS-ƕ5vl:SZYq'Z1.vֺ~4kwqU|/]YOE}κ|p++7*޻+vʗHL=Oժz^LUd dԷ4dz[y\O:Vpy;x27qmЪ i ]=e*Z~ -]ر_>0}˘I}0B/w&d]8w"`j%З1@2fg4tAM>.66LWyxF~&P…!~-jW'?FwYҎqĽS~u1Y32fݺˬń;)q=U۶MbŊwmk5.";Ά~xx:{lAs>"Nn o=¾rerjvt,{idx}Fw߾[te=>z>uftS/Bzۯ| :y[x*s'5?21w3a<$#םrЛEim;h³g7OIk{1U44lNzcOv4_h욽gGuC_LӇ>_+{lA.S+1ЇЗ1@?{L-涑3Lq1sk5vsϧn2|ޚۈ{ }4s)7+WQ/Eatؐ{o)'JX2>x G7b 6 ~{{[Cĥ{/^PD4ujlx;/z).,ϺdۮrRgF7n?sՑj]66m`}OK.9^z~1,/֥۫]q)۩=ԭlVzzznt֯e/=&l*.0}˘I}0B狛wE}oU-%7`j%З1@2fg~*SK~t[p/ z%ÐsEײi̸ڴbvzaNzv.zu4z4y4f~33W`j/c&~ IxS+=}3 /c&~1x/f Z@Ȱ/ _QipPIDAT4tʌr>4yogh3y,/JC_LӇ>L-Vz@2fC_Lcj)p/ fx#߰M-Érd^15K#?kf=uWzg4GY^Ї٧S+$}< |5+ֱS&wd wI!wVb}3 /c&~U -+Mϻgv3Z֓c7ӣ#7s6Јջa.p7xX'z{2LЗ1@?aj,}twd ^`j%З1@2fg2f/ĺ{lMKSwЋS 'KNFMőYo/z.^8F#z{GY^0A>e$Ї Lp7`j^`j%З1@2fg2p/ MAo.'j>s75^5^=_M1рOF}IQoRi:dy/c&>e,]`jS pPS+1ЇЗ1@?{l]ܒ={a@0kdj1a-u?՜b֏o/ʔ uyv#6w5,/x}JF @2fC_VHx80U0}˘I}3 G?5 "j!s`EI-Z4t=Ty}%x%̲ޟъ/Rs1P׏-Z|5|ej<+BfFh 齥WӼ5W2<З1@2.0Bހ@8x}J/(ЇЗ1@ЯӺq`\v>Y,ϭfQ#U [FϦͺ[YY˨LvM>7a\C+wL:mV׼+k S=?Q=e$Ї$lƎe4QeШ;l|1tѪϩ^c?æo1#jhIk/g&̧cvDu[~lkf,!k_yջga,ύ9sthRn/6]8.8K!WGe";wβ?0֬(3mZЌ0˂u`vtb65:&z~v5r% "yez~3۱~*>%#_?@2fC_VHx80U0}˘I}?=qϛlGKvdWøxO3z+,3.^>/5i~]7y6[4?~hvZe|*l)}DZZGs"o{\z^0A>e$Ї L>|Xŋvat+/6mR~yt٧_%zW_}#o*V7mB[~[|\k[_p$?YCI.\v}֑*Eii;u:ړ( 6SV҄ FsO[\xV_(yyKLuP+_P}3 >DF7P`}շ54c ޲̟Zb;LLH9k7Voqs-'ΐ S[MzOjy⋝yOLYo޾}I$McK7!jhyB}wGYmi3~nd _?{ ><+ gz{b+V^zKIǽgϗ*5 ΢ɓҥ^@糌'L`AoY5_1uتUrӎ3GI'g^gװss Mܶmϰw1tĉ۸xq?kP6 !m1g?=d-~R>[0[wpĽŸdy/c&>e,]`j7yK_--[ȑo̟ߛo駜rcc0vMWXCdrݜt%81n4~h޼^6ɴ|hq_M+Y^DQ+Ʀץ3vsb\ԦMLA7N*od5I r749OEOx3 SK]^ZUTJ*E+TJkQO[re6~ ~hv5 io k^#R;A_2CUA>e$٬+@su)6%جP6-lPo= >9{FHRo; gڰR<#2dj2*O)s< y4sᄃ+g/߫aEvMmaG7x8gt^q^?z rz3C&?/@?_ ٰʆ̣RJ9d>O;\*oZ9΍Wez2iq֍#8>dcIպoF3؁pɘI=ɮn9>܋IŃ?(>d㏥֏1UW:;?Cm>WСmlZinժaȳ}'t4L xqUWVfQ\ T҈JFT~yyJF @2fC_VH|q<7 _S{qL[mq4`@+Zzh\>=l4!T7|׏o.U?P/Ŵh0no.aL ֩k QQEI W~HKWJ~d }.x)}fj{+o:og:/Eׯ'_Pz_C>쁳oVdD5/y /wJ5x8e5q=A(!ܪwb=˼{dl\lL |&"=Cm!G1hk<בxzeUVTBe8N80" A(ۯeG_Iq!P~n_=!w;N(LOC *t}} 9d~zA_u?CU7޳6W\Wa]r(F6l&(}ƴaߤ)ȸvZ5(ֳLq 15&ɝXCO޺3[x i0s2o?/a6_{>r<8ؒNGcJ?lY%fp=gnNy/c&~x0~OzQSO4~Ui<<6aقSyO>rd2YZ_G =x)Iq/FTвTvn_C_L}XXH AM-e* 7* }ے5{coz j==zDgq _?z25=7Wu7nY/Tύa e*Uns\~Rdfn6PӖ ЦӶ;hO5ӶwѶ?Q<בxf>%\Ǩjժ){h;;k '~c{nJkxG׎)s㏇ uϞ;a?KTǫWtd?^Le}Ϛ'1S؍-a3_Ժhh+)UItڱ}nze͍^Zux܎]ڥ7]Ӭ'[P#T'EFoDo.>SvY s_Ѿ;f3v鼱SŻ'KŎUzCk3{ًvcStK~`mz!]ڱ̦b=Kؖd}&*zXT18s]x?ӛs?S}Hgri2-| )@pdS 뮻ԎB]Axp}a'M-%|/(}sSK#*A L 偃살e I/n_SBeVЇĺڵkիWso%9m7܃JFdǿuTf\yՃo6W޿tA~X^#zY_;F̣?$uE<2}3近^Zv}e\6}#X>]6|:/pzf zz|}WHߗ/Xϑ'鎫ig%] ڳ>ƏFTǫWtd+^췰O^VtU^t ora3__=sSM }طS[=^}Z=!݊|D7cBGr :6<31'qsOӭbN}z_:nyX=Q{{C現=RZp\=OP}3 /c& S=gnbA}.mU<Ks%U~}#3FZ#VjYrM(gܸygώC}*/V'{ 偃S+8Ї٧S+$} ~M-M=>hʽT7 a 1X 6v"mKhgm /(n@ ;׼\[Pqҹ1ޗK+Kqǹ<ǫw?IDuW4n`7~n g}+_i[_~ͳr bj5~ P1lAM-[c=+aބ\S,NSN>L?nǫtD +7&WLFGXIիPԺrryl: c.sC9̨|-]C}-G>E-sI/{Lk3p{;?z_dnra]}˘I}3 Ӈu`]߭vW2en[Gɐmo<ӻ+V\7Q9f̈4WRՍ-9Uoacnbrj؄z6m`ǽZA tJF],Mv0}˘I}0B狛!oHSZrn+6eAT(Q4#*CٸqbRSDo[Fc9z?\NuͩјQxޫ֭aTz…,#o>P y/Oǫw?IDuIDɛΡ[)Pe?Rۥ{{%{}n+ud9^z=ٲŃx*UFv Н~0!Hn ;_8&J!.9F gq;^$:Gُ^Iw)d6?Oˡ׭kކϧ]v!cqϵ^[1a_ he [HsrT9ˮt뭲xxmoa9YGqʜ,fΜөr<* e]t=O7ú j_qǵt/,L_A-ԨqY`}:JvAItJ~Sy~绤L-54`X׿@2fC_L}XP`j8= \rK~ԧOE+˔mGq`=6px~e2'U\vvUvy-L&/xnajկ_1 .zA/;Q1U'Xcs{/ڼ6:0 +_-==})/F[On:n U-޼zIs>?ڲyyY^{j}qZLv{ްy%0cnWԚ2%6?({3q~u#GߘO^2wI!7S/6ҕ/(ЇЗ1@2ZԒ=Z>e$>}Z!}8H.e^xbnZ+%j4fͿ2Ҁ_A%K׭\uƷ=+P9:w]v:ׇKynyR~rqqP)۩o_~QoҌyK鳍?КooVrR7~zc}˘I}3 G֡L-</c&~ IÁ=x|矏t)'X1&p*#3.#!ܻMa^uv} 39xUnTV}6{DpC|Տ?a<yvv: ӽ;,,U}d:z]QuFjnpjMg6mWSOr/ ^A+kŊA־a^Rz/0yN+_Żw1Os=hyǎ^Ka?W=o"#G/2y~J@2fC_Lчu?>9 >t>+=}3 OVH~i&_M7rϐ+(j2|2Ⱥ^nO%zyc[/u 6]]$+vG9^:1[TjUZ~UeeK]}W<ܽiG=2ä;>eO0^86_Tl`>~Rn=Gxݏ^ee}~J@2fC_LчuШQ5%3iRG^@fWz@2fg>L>f #{W`WR)s{shڏiڌ4a 1-#u_|li8t9m+E FЗc/￟C^^;v2+R1 UTPЇЗ1@?{aj9Ї٧S+$TLS=egWiteڦVϞ/;kj1lA+UVZQNNܹ/XHM|,h,;we$}Z`</c&~ I?RnykBWGc M7]ACu bx j5P TRT\*[Uo=$we$}Z`</c&~ Ix#,S+(lU}˘I}3 G_}˘I}0Bހ@8*=}3 /c&~s+=}3 OVHx80e$}Z`</c&~ Ixဇ>e$Ї٣S ́>e$>}Z!o PЇЗ1@?{aj9Ї٧S+$}< ZЗ1@2fg>L-0З1@?ajS pCUz@2fC_LчWz@2fg>Lp7`jxJC_L}˘I=0xJC_LӇ>L-U}˘I}3 G? x /c&~ I/nD@pPЇЗ1@?{ZD&Q(ۤ%7xk{C_LԍD_@2fC_VHh(0Z}˘I}3 G 6n2\[G z}3I6ο&>e$Ї LP7`j0З1@2fg>L-\)*x |;G z}3I6ο&>e$Ї L82PC_L}˘I=0ij/XsT3o&8_L}˘I2/ϧn$:З1@2.02//vEЇD)_+< ЇЗ1@2L6MpL"KEmH[usR4Ї$ԍD_@2fC_Vecc"2ѨC_LSyחǸyЗ1@2fC_ƂeQBQu[KM}˘I}3 /c&~02/FM?@2fZ+< ЇЗ1@2Zޙ@QkHB6BB# E!"k aY qAoTDd_EE[tOzIjySS]=w~oǣ:hVO_9O_3^llYsPk8w5:j>}uNW>}u텡V?b{_~GC:'sB:'gՁCOBϑ>}uN*}oE=.,7ёW sB:'k/ ~p ɯz>}uNW>}uN7P~9O_9O_9O_2jO-x4^79O_9~6凱gΆZ韺 :.*|t*ܩEЧ }Ч }ЧIܩU?K՟vY 1wR}Ч }o|sB:'sB:'sB:'e3Ԫ=snz_zs3ZB:'sB:'gEЧ }Ч }ЧI ꧑θ"zvl7m-~{O_9O_3Ԣ_pNW>}uNW>}uNWL}Z}c~VB:'sB:'gEЧ }Ч }ЧI #έ/;~¶ ٯ9O_9>C->}uNW>}uNW>}uNoPkV=Ч }꜔P~9O_9O_9O_2;Ժ-P>}/_sB:'sB:'sB:'e3ZW~ 9O_9O_9)S,/0ԢO:'gEЧ }Ч }ЧI `y}9_>C->}uNW>}uNW>}uNg o{|f}h=Ч }tDsB:'sB:'sB:'e3g=>}nR}h=Ч }tDsB:'sB:'sB:'e3ZW~ 9O_9O_9)S,/0ԢO:'gEЧ }Ч }ЧI `y}9_>C->}uNW>}uNW>}uNg ӧ j/8'sB:'sB:'sR>C-X^`E>}uN藯P~9O_9O_9O_2jC-sB|}Z }Ч }Ч }꜔Pkwgx^g|/OiqдcգgC6Kamge8UjѧO_3Ԣ_pNW>}uNW>}uNWL}Z9*;}W.l_rԅ{ oLFv6>g|ja{K,+DZP>}/_sB:'sB:'sB:'e3ZXJ9^+l_^gʽ{_k_Φ罽äe8UjѧO_3Ԣ_pNW>}uNW>}uNWL}Zg}sq&-/\ڔ|[ k5yޗfDzC-sB|}Z }Ч }Ч }꜔j{ab=#ტGw@Xk[Ļh6 Cs t;>ko28SvOe k24I|d;wHczha}⦿ȶ~ֹ?d] mcFEb]׏wẇ[a~ߠ>IvRא WգW4s8[|O: S `Bqs)췚ZSZ~j|bhaMW^$qMZpd AN.}J~\}a{gV9涵yz~jW>}uN7P~9O_9O_9O_2;zu11ph5VY9?$\α1hUcW<;> C6qr\) !,h?緣w nqljn-f0vv緎-!sӧeív]\'^x3}l3~&Ǒ^F~=zmK? A?o7,|juiz}-߱5gt^l [93L6ޫu6 Ҁ7zݙk1geۀ5W{7w>|G=@:&Z9O_3Ԣ_pNW>}uNW>}uNWLjcAvnh2JwTO>{ŧ;v*^qwM̷F_W÷]ó7m|iF̥;Ƕn hҶ4tM}%& ZzOC-img͑)mz~XI-]S}iUrn~gf8}״nl8\;t< sB:'gEЧ }Ч }ЧI9Jy[qmӣcӿ_Ҥ&k?pL.{ŧ5o~TtgOW?.0=7j&UI]3lnݑK$JTǑOKwUjdkPu~ZZn}tjXIwH隖Zխlx-Vm Zm|G{>&C-Ч j/8'sB:'sB:'sR~G{K5ڪG&{k몷!^~ۈ[}zM˪-=-}QsǝUXڧ5w-6!Vv\{}Uh2Ԫ֎=kg}uNW~ 9O_9O_9)SCZr<]`5-^t?!̷wpgq MF|p]CJ> oL}'T5}>jm_4'=~j=,CZ>֎Cc-kg}uNW~ 9O_9O_9)S#ZW1qjhD= m? =>2ߖ'w~hH=}WV~,}uN7P~9O_9O_9O_2;rFdAw=v.#򵟹^sߞ[yF Xs|{A}{5YwW46Ŋ+XP_ztUm8M/iF[TU-ښVŸ|_oOj5Jw}tćߛh8ZqƹO͉9/ y%~k1^}o}R;=.mݯg6ȺI1OǠavnʾҰ&4QqSGUkĖ(h;tzZZ:ϧ5^=J^g!/~L網4pkwbӲAQz}#M{[Ҷ>϶ϵ5.ͱŗKbg?R_=?lC:&Z9O_gC-X~{lZ"]X֋ͭi4to?,D^LX{Z'dX}9ݑ~ۃ6iI+*z48s[fw;';= dmbvòcKJ+ >zVq ZWKj}VSyPUKRrޫZ=LK]6{]9|͘|OGYVqϳsm˞0/ z&}1_Y<<cn_tL^|Ly8OU 齥V5 VS~9O_9O_9O_2;N-j̤\?r\ q_{+~gc=wVboߊ={¾j2NwA:'[Zj/8'sB:'sB:'sR>C-a2c2|bgb^}uNW>}uNWL}Z- [j&uDamWd̽sccfí_&N11ѫ1~9f՘GߍNV{O_9>C->}uNW>}uNW>}uNԯ n^HP i57b=/Ļ%&,x.Gƹ/f}'&T) .+jѧO_3Ԣ_pNW>}uNW>}uNWLP1ĩw_?1Wc}^)>F\m>q/"t)-Z`E>}uN藯P~9O_9O_9O_2G5p'V[0ę?:+N㉘y'x͘15}G\҇o\qU ӧ j/8'sB:'sB:'s Z_Z 6]~yyߚVX7E>?w}'";qoǼ49nq+W(auhamP>}/_sB:'sB:'sB:']Pun3bǘm29ƼO/_ߦǜF3\_\-=v\(E_?z1 l Wl`z~ڂ}9_>C->}uNW>}uNW>}uNzVm|3M=.]S{m C-9?̿'.Zq b׆ǩۭggb;wfRmP>}/_sB:'sB:'sB:']P6jAYq_^=%}8\cC+{?׽:Z`E>}uN藯P~9O_9O_9O_gUm f}ȡ! ^W^5s_m 7}F}]!ԼZ{G}Eݣ^ƃccwkmU\sԶ1dղ=zuA=7ٽu+C->}uNW>}uNW>}uNzVm5gِg£7y?91dp6 TqٺUg}s~VR\Ĩ|?i緎-W>7eץH6IC4:b&Pb̶TZ{SnX6g]ҧO~Nk7elu59{9φ}uNW~ 9O_9O_9taY/6CZ\WvG7OCm÷];v>t7фoޮ=6_sдl;jjs<?yJ=h&Iåk]b6מv!F̥;C:0Ԣ }o|sB:'sB:'sB:'].,f(P9~|o6jOXup߸ݙղH\qLZpd׾29{,bֆZܧV~ N_\qM^_>G>o[w䒻ֺO8j]#=Bmk't`E_9>C->}uNW>}uNW>}uNz?]X֋PVT?Npw̶OT>m& z/:iTWji՞፩& WӡVz_\g׼O}#} sB:'gEЧ }Ч }ЧIW z6:'>}S$'^=woOxu?4d\bN&ۯyiR(YÖZM}Uٿ<9kt_[>=u黲k£ 8,jպ׃0Ԣ }o|sB:'sB:'sB:'].,f(PzSRS;: n#={osc5Nݹɶ2jiUsԭGlսm8_PK.=:>9c&XqV=!} sB:'gEЧ }Ч }ЧIW z6ߦG! F]`\Ĩl rw`%wUw;ku_#feK߁5Oewhz3Fp`ƷgU^<5:O:GɥZZwSG!VyjZץKLLw 뚗'Ō>ճOP:'sB}Z }Ч }Ч }t~Ҡz{ ˾}b#GfwU&S4lrn{1i6tѣWӿWaـ&=QHɱId0,9knZ|?Y i@KkqqwջO~OjW>}uN7P~9O_9O_9O_Ob3|QM6wT@0ԢO:'gEЧ }Ч }ЧIW3j8X]O>JP ӧ j/8'sB:'sB:'s H]S/:)lH>:n>P ӧ j/8'sB:'sB:'s '}oGoW;o>ꇡ}9_>C->}uNW>}uNW>}uNzt ӧ j/8'sB:'sB:'s 3`E>}uN藯P~9O_9O_9O_gC-sB|}Z }Ч }Ч }t>C- jѧO_3Ԣ_pNW>}uNW>}uNW礫j@gP>}/_sB:'sB:'sB:']P :ZW~P C- *Cj@psB:'sB:'sB:']P :ԢO:'gEЧ }Ч }ЧIW3΀}9_>C->}uNW>}uNW>}uNzt ӧ j/8'sB:'sB:'s 3`E>}uN藯P~9O_9O_9O_gC-sB|}Z }Ч }Ч }t~ 0Ԣ }o|sB:'sB:'sB:'^vC:'jsB:'[Zj/8'sB:'sB:'sB ^vC:'jsB:'[Zj/8'sB:'sB:'sB ^vC:'jsB:'[Zj/8'sB:'sB:'sB ^vC:'jsB:'[Zj/8'sB:'sB:'sB?3m>}uN7ߢNW>}uN:P~9O_9O_9O_9O_9O_3Ԣ_pNW>}uNW>}uNW>}uNW>}uNW~ 9O_9O_9O_9O_9_>C->}uNW>}uNW>}uNW>}uNW>}uN藯P~9O_9O_9O_9O_9O_3Ԣ_pNW>}uNW>}uNW>}uNW>}uNW~ 9O_9O_9O_9O_9_>C->}uNW>}uNW>}uNW>}uNW>}uN藯P~9O_9O_9O_9O_9O_3Ԣ_pNW>}uNW>}uNW>}uNW>}uNW~ 9O_9O_9O_9O_9_>C->}uNW>}uNW>}uNW>}uNW>}uN藯P~9O_9O_9O_9O_9O_3Ԣ_pNW>}uNW>}uNW>}uNW>}uNW~ 9O_9O_9O_9O_9_sIoIENDB`java-binary-block-parser-2.0.3/docs/jbbp_primitives.png000066400000000000000000003312341377434312600231470ustar00rootroot00000000000000PNG  IHDRO6IDATx |!\"-&H!#P@ФxT[O<@jZUSE<*h=fdaٙ{AQ1QC?cnEUҢ VSEli1w(E hBxL=D8J| ||R&!C7 1w(E hBxL=D8J| ||R&!C7 1w(E hBxL=D8J| ||R&"gmZVmۿ#=osڵ;u]eee4qăyyy'0ϙ{kL1ۦ"ݤy\cWSRSիV7YJg /B-6*#cZf:FW^UF]gbic@^+W2),ի {𭔊۴iӟUaaڻw*--UB+v,?=bĈC {(qE*mjz]l_LB-ب%}||.9P7@lh}u;Uj\6ܗ0|zSp\\Mt߇l;}a Rׯ__JJJ/>;;C5y.|S>רM6oJ>8u`s[o8fvi~N9.:=:y}FZ ^z*L؏|?\ZyJnFCtgg%H2^YgK.QF*j/lx褣.=4sԠ!׍ |hUը_&ljҤZ>?.7ndă]EEE*^=Tw׆]U|\mݶ_[m8$v.?9ꧫ~2寚$Hw>6V <LFl;h;~(!O^_| @ H*¡jڴ-*'g1۹|>EI3W^[p+fƍTӦ ƬpɃ+k׮=TqnQ.Ye8v.mu~u_v\m寘寗u0!3˟Hy\~Eך˗_dqKZMԆK7G?嘬2[f Nys׾?}uV}8/r5qd_NҘ>A6w cm='ߡ^7oʣ+nsvuYYu<\=L<3+2/UN#3ȏt𝕕uѣA߇>11hqUt-3}su-[DWt0!t.juBkcY*Ռ~^̓6>rDs % &<ع@c ~a ꊎW~Muz ޹v=^ p5﭂ ueݍ7;՚WtCtRY-W}Din Ҟէugz ofsyvlsgG|TVNfχ[0\޽Qw\pm65 uqmLe]oǹW*Zf _h6P7@k|7mA,5nFYso-լY2ש8lw=q!ݥKY3U;vVHKK<Ҏt]?<:ƒo C_KM*>.Q-du;Q)票 AvZ6EzWh=8n)3.ˬRҎe%Հfc)>Gv-S:ahC=2uǀ;<%-Uj^f6nu#sC tR]#&.7"F (E 5oG8J| btR]#&.7"F (E 5oG8J| btR]#&.7"F (E ѵ o*&"q)~1x @5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5o@ 5oEE)b|pd08( ']t+"7 P7WD0 oA R5o` @k\0 7JM"a|3 o|pE f0*5]#A`0TjF  `|t+"7 P7WD0 oA R5o` @k\8~UUQr&0R~OpQjŖ ~k׮ݑ<''甮*++막'+t;2ӋpȘ! #F t+AGዷֿm{2}mk?gʻ%Ou_5k:F^1R-}ti>2*ئFjQ,ٶFO5jԬZo];NTR׾q ծk;s_h)6mlUXXݫJKKʿO1!CƖ=̹:x^M]<ըKfIa9|pU.we^;!_|?'mO5ՋٚjVfhfh)_\URR|ߧR^tذs0wQE*u(<ƃ湩]6usK7H5o%|diMe{īN:eB~=`Yx5`nu aㆩFM ~Oc1~GEtϞ{cRrUPһWb2jWXߵoװZ7o~NBcǎ);uINN]t*V+_ 9=[s9%YF U$!3\tE.3>63|ݜ5sʬƬp|OCuهpo"C^e%m li}Z7*y8۝-ڿoioo?r춣p_.a{>F[BhP —/_N=;˟=o{˥smy<Yz $~a=gj(uC:RW; (L_o|!_l~Zia0z};_8f\3"ȱ9womF2Iv3dut?WPΑ{QjU} Ŝ9C/| F Uă~)m5|ءOִ33szƑڜH`^!OKk=BV>{BsS ZfUsgiwE!ZŻT0 u t*I&.}?3MyC 0Yj\Z y: x%t[|fk>HXJ,ᱷg]u%M"3ݷ/v&cs/|'yB C9GvE }X' tskY(A$,Odŗ^l, <Ӹ}[h].^$m;K_}%%*((N\]vuHӡ`oѩj7ww g~aa)Z'Lry%/z<aZ u~G?޷\^vM㡵r1iE}P[?ڪ.ϻ\.}֡C7<[̇:,egwstU=ׁs'VΓ\fP_ Է۽9w/ʗ-2\Bn5/.ť|(oF IJ|R g,atĥn.(7`΄Teo`CpmoswotYo .k,!B N=z4'&& ~}Oz %¹NH2\M6ƿ/~a,Y8H[ O}%}j̑U]F+icMe.6k[p2,/?$,3=V;N|"F.wq}Zo4i8ٹ7eDc{[ʴu" t8~`q׎SNp)lS]DgZCc BmE/O,7I'2#3i|>!Zi؟m{`u\|q@؇ORN<hO0A^dC.U0@U0ضn#\7|!36@RN. )uz|!#x5;~N6I/ncNzmݟX휻]wJw55WV)._Zo|PLT13>[+ii1  GnibGBx3#|V/|_2{v߹/5ٮd3("wVoQ _E,4S[I?`Gfy?燫 >G5as{u[FRz2{s!}ZSL4[lߡeMNj;zTca~|ͪW:N;ΚV'ۃ:}[kyg/%mL܏^zFm\ Ns',3=ΩV'`EKV_*K( #|:lډO4k1\ι~ȶ[+o~/_u+K]M,9|hڲNkVCzU;?Ydf6 l|{HԩS">] \UTmכּve4Gf!+K"i $D~p^\gKHyK`-<RZH+]Gwy|pc~c2[گs'\+%/`"|9vn9{/y2k^΋|)tM5.%PYC13[yk$XtXIMi }3d䚇>=nٚԘ=36 >N%%%K>ȟ qc-Gq}a֪߸^BV6LӃm^Qf f,3ޝet_g̱9w0Z/Ss}C9GvE9_}K C13:pظa^_'x5 &`Zwn5¦ګ44f_lsyȥTt{ہm4e[gV啖;OU;z[r- 5]$'' "o2׹]O}QyΪF|iXuJNV_m<}܃_|/6*cɒaٗ6S,V`~JMC ocǎ=}1euKMM<99ytvz&M|i ;UܹFmd.j֩P0)UV-sO?~*>~{V; W0?@kAkqL\w'Bo!̨v~z͚pasyո834.%߫%/i;LW0?@k|ϟyYo/vUFRզMokT\\ ոq\nݐ^nTTT1eʔ}ylg6?o K?%<فt\:zg08 oPguLJXr:o_Un]WjڡJ3G>rv}{iifBUꙙ|QX0_SݢEWUF|Ij͚mV>xmf\\M5c֠_{ڴiGAؾ}u|337WsQK0 \%55-v5m믣2oT=n߰U+A>PiԨ2^)8PZ9R^8TME2f};<ܦo\syݺ QT.Zayg𝙙ѣG &KKK%%%}u (H[\lwjj:yK8azV?OIII?p]AA5O>ZvqsԭWws1چ8e-\-ǡ^yߞStik\4_᧟ݹNf{ϺWժܼ?7ka Ct_'g ULݭe.nU}y p?\Q7ץ 4_0` ky6۴M-A;tibgַ5ve.n|z8UY.~Zp]˯;- յk9l-[ 8M-3N]5i\7gΪv^KfGCyƌow Alu4V'S>ni]r\74I/@_໨aGLM q6gڽ{˺-[z :i4V'.=˻Չ|oi{k?$Lgr 5<>rРYnY^35i}*?Y*pK.m,(JJJmCErr!]nC#Gu;w͛rm=m?r04k{.]^]'#|K?Lۗ_4jyemJ1Cb鑞^ᖏ=[5oe-nڴYrmPZjժRբEe3fl … ;cl8d &Gpl zKժ[gOi~zq\ϣGR:u*}ƍ#|qy{ȧD7hPf Z0_F L2tkF5U˗^f;iSDn˘.L5j /~'ׇ;]wCcǎ=of.;ts- Uf4'MnVjjL΃RO8 Sw\x7v\cZj;.))fBBF~PVV 0Yږ=RQs챵[}W]4bqR\~0atg߱Z[0)g9B%$,--5HߧΝYJJJiRRҨ0\UrT/]#Y;LʣG9''#Fo5lذSSS_qzzgUk8|1.Gga$] +flh^q&0)jsœ+iŖ ~k׮ݑ<''甮*++막'+t;2ӋpȘ! #7XTQFZRjԬ=\5l0ʼve_TwJ}jP;f%'=XZm_kT-۷T^*}k8j׵q,װZJM6);;[{R%o#F8>dȐa:Y+fAكT]AYy5uTf,A$a|@|M*Am-M}+EeĿ0>q0+ՋzZ5Sl+͍ΜE/ K~rssUIIEgggrKq@+ha5a6UPgySNmb0 ;4-oъm{f7M6~aO& \I eB9w1* UjUsyrfrĂO,sl7g3s|߳sUTQSn *T]zw1JUf_P@kI}1e.==?ɣC:^"#t5|UaX%l@t#bSo{v_s ݷ d4j|OC|z^]zVMN3?s._hBbz};_8f\򅆴F"ȱ9womF!2Im='_:ܟsI(νWU R7^W=9/rH &|M*I=b҈rlmms"a=0I 5c| '?e/hmǔ;;L/ )*..UTTQPPAQ|7igJw~gz_cyჅ.k垕Lp.힎G^ 2pQ6yG }^ %<k7ol6wzvΝu$9]w((acb7t-%/QK]jYLfS˵Ƿ[nvݏ:3;[\i].^'uYӲ|<@;ype0vuP6JjzhPÇOLL<}=YQ}:{,}ʲ6ژ?_/eֳ;x<>~:,_8CT v5t}u6ǖ۠n m$,3=V;N|$FYr}Zoi<@΁t=B=GvEi$Mdl{lyoM*;o ZUQvβ g^72{vqrΨ5nfsKOٞ$؟mǷ]L C RSSɓ'] &(]˔,webb ~Ԣ8m.bs<\{aMxWv^S?/}ؾFί~{s{1'LtyOVsvΝEPߡ#bԬ2_ZU| h}7ʌm r]loQ3F `VUvvºݟ>zyPf@19TIYgǷJaGoΰ,@+##d38Y,v0Sv/KF/*~Cŋx3#|˽g$R%C.qy=omX;JsFs+yLsy*<{/w 뭳ȝU@L nϯ,3_9@gIh e! ġϘܫv߾ݨ]Gw%!<\acʙchM2`{|oٲe[,5g6/K[i#}_gy;_Uk%<3񰶁pR5gmՉcwaeVu6<n@QUu^o 7w|3pYam{N!yXݕ Ttwνň{N~|dʛg36mlb Nq>LZ7K|Ц>,nۥ}ȃil]wk@ιֲ~&Ω ٽ8|-7hG ]2~w..walI\yݠIr |o;͘q+ TϬ66,-kwʝkOY|999?*(--}Mɣ랬l{K+>t㤰,x! Yvrl#[y/m={9enj%3X~e?[77Zq:fi#l' bX;9q׍SZ53=:WA9#;앳ե#.5{T>C~O@4 .m,+v' Zo'=\僵7q֐!C䔯&{RBoGd:Ҍ7(C —//eU >rR^@+))iTJJʑ3g{ݟTJtСz홙K{y$7wD8 [vT~RR.?szzsO2  $0-*|sF=!A`0Tj0 7J;"7 P|pG f0*5op @F `|#a|3 o7wD8 oA R#A`0Tj0 7J;"7 P|p(CqORͿ'7 |b 7 |b 7 |b 7 |b 7 |b 7 |b 7 |b 7 |bJ0TEST |}~-- oaPY]7, )1S )1S )1S )1S )1S )1%wzz3R}9~ڵkw9iĉ N51rmnj6}Zmz6?ǎAf&کjիVV-&YK/B-6*#cIJM6);;[{R%o#F8>dȐa>D%;FYqc)9YM~rḆd wbmE XT0ߣf:|+_$77W(_d}vv))) B?T%4iR&;UܹFmd.֩ STZm?ZoD0ߛ7o~NBcǎ);uINN5ʌj繪׬v~v;W3=]^w,Ua񪯾rfV=7­\T;6_qF듓gOI㲯O 3ϛOo? 2_R~_UV]WC5mxի|߿77SRRJCm;<{ynO`._.MzApqm6k;n_(~k[>'Ԉ չݺ[wmWIӧ e׭Gzjի.PEP}_s11Š/,s~p܇+a_}Xqp܇}xH{{Zy\=33Ւ> (,Y@~Y`G-ZtU5jė ޚ4i֬xmg.[aOnU˾wjժ5lJ-]z0໸xWQQ FAA{y m3{͍̌vɁ˻c<}l^f;K0?nu6}x=.OUj֭.\cq~:7|_umVPv#u^;կ\>}Ӗ|QֿZMX~K[k/NfKX\dɁשּׁG *>|G#'T-w8{݀+90벖/w / Ƿ<9\f_cF;?h7ڣ8=@'v^xn$!}e͛/s孤Ƿs1=ǡ~o# ueuԝw0uFYVJUu3gx@\?W~Aߩɓ. eJ?211Qs-MmZaN(vf}[n]Wݮ ~;ujQZ5c;0q_ᮜ>eZ|_8l6.\'ep>1K; g1{O? 7qOim7Xb.965d۟|׮]k]s͓A'}cƷp)˺&ڙfH3{ʔ){˖-ʣǷv벮a˖^CqN6 Չ,= >TZLK.qvW=z h:| ނH GLDr _{v{zAlۿ2w ʼn҃.]e=ʙ}?ew5{jύ\oS>CjuqHԩS">] pȑy8}{uΝla湜Ck/g6-͚7,^zw\×_68oW/C/充.d]uK{yb/pK;T>|E#g0ecA=2;cp~oSnwӦYjܸZhmoe^;ߧYNe^[Ncs_oGb9Po]Nz۷oc/e pp[oZu<ӦpKiUWõm㸶G up 7{_<{{y0Hn j-ۃE"ǽ}K|ÆLz@~YO}Q7QW5j=5f^{۾Mw-^%Eլ16+FV 0޻Z^{Qs )iiisrr~UQZZGp  enF,mEA2*ӧ%5ǵ6>77טl'\Z; +WF0WydF/ˍ}ȗҌe23޹)vVV, 0Γ8S8={\^OKJ2yVwk?,'we %. 6 [RRrW{}=)wRR!^ÐzbS_ZHa,Q|oxJgZHERRҨ#3gݻ?/--=СC>S333&0\C0nK=svղG3ݢ.?2}_[HnG'%%r3G)wArYgv'oTQ|WRU5jZy뮟+]]>Ps6}}ūũZ/vy@'E =+CiE BMMQ)E BMMQ)E BMMQ)E BMMQ)E BMMQ)E TM(oX|1S1S1S1S1S1S1S1%[QaT}݊ (AQT9F P )")E|oob 77EQ1(oBMMQ7@L!&( ||SE S )")E|oobJEΠH3 ~k׮ݑ<''甮*++막'+t;!L׽B"˵3&bTƊ٦woL~[q4kjҮVmjznO0❕_Fed,[)iӦ?eggBw^UZZWX~zĈLJ 26 =n'ռsgU#>$4n:%'߯6>]/pb2,!|f6"@{7YNJׯ/U%%%Y}JJʂO?U M %G,xnN:wQy6auT<NjLioUa=7g 7oǎSv<:n;sUY3#,#v.g59ւX@U_}EJwEͬzo)wb434JN.pY׬Y's]~^T;6_qF듓gOI7V g7oj~ enn*;{:V*.jڴ>W 9޿oioo?vh7xl0\]Λh4xl v8@?|G4}YPಮY'}^c=FiR^=qW|˾dFcm=el=c{Vj}T3fW#G{JV𝟟Rb޽G4M{s(RZ/fis 7m59xԟXe鳾gk?gN7v|[KNZ{j6hؾ}-gs|Brlv9f֣&C| mۦn{)6/?c>3G$cu.M[O< 8.wU'js9sNj/Q{='XB Pw[(4?Rچğ[fpKg^;>kѢ}޼gMA8c{VAݰZ=%&} ?4>;>+)9l9:_YYi)>~QFUܜaܧVw;ۈmm*e^4KWXY[Zb|C߾gs|X1]f޹SoQ/~|ű=ǜ6?c_H}Cc  ~pqݲc꯿8.O%-_l^z|7{\k!$V;% _Nǘ[H=O[[ȶ Ugh_< -nl~  SRRٳgӧ+MӪlw]rԨQ|nnq=`rC9@gַ9x7~glF9kdFcusT 5gklܨ]cW~J/k۟. ~Odq3t̞k!If|Kedoq3f'VM[xs|gdd:_Čoi!?>kݭO=wV'^4#Mۺi汴ϣ[f\VC@"7mc|37|?1~gFe}gM;:iWu*TDDo= `K^VӾKWY@Ҵ|Ϛ5K=wڵ&z|Kj}t,cGV'mVՉz|D^fg_ s;SjZ**Ԥ/>{1Ǎ%Չ?cum"3ݍ_P F_c %̙\lаᆥ*;{mWmг_ 6ʬڤ*XqiSԢEQ.koA^e^niUNWW+3grs{4M˷xqt|e}ŋDzߋ[vq.8 }CL K5z/n[>*k*E/"}_y_';]}_[;aL{ z+m׿ӱ BiiAH+1{:O0ni-H\Cnۅ11}m0fq+>>AmPY*.Y۱{;Yxs96/e 5MIKK<{->wC_E5o>H@nmVVq0>8km0\O[ofeaFw<㾺sO cb;}Kt\~m5|:fO | ! mEbj8JEG3Wʯ- -ճ4{j:Uy_~U+,{gdffUڛ~Ax6p[{5E 0 ,yJnJy¤g^Jj $Mgv'o\Vw UVՍ7.W[ZD Y*@% /obciN RMe#(o 77EQa(oBMMQ7@X!&( ||SE V )"+E|oo 77EQa(ob)ROQ;%J @E +B +B +B +B +B +B +B +VoE\OQgY EY:|ۇAQTe# )"+E|oo 77EQa(oBMMQ7@X!&( ||SE V )"+E|ooJ ӋA2driӦNM8i*++7|$77 =z4ށɓCvLmVƵ|x℺~Bպ[7ըqc؆X9k!.z7*-PJ۷9;;[} %O}ScƌgPؗfd[Ν!^*gFmݲTq;+njQ}ƃ؟ca^%%! uK-Y1fA RʧN˕7yvv $6GU'uUjj;_~Y蕻}};جYbO)ΝSc=㏪ԩoB ycǎH} /==III9x}] S#oEM۶ME ?ik޷VwiZgOTNhXo~QzdUO pi iC?eC~IhըQcNu:L%&Sk|RW]"#cTTT4([Ysm`B%$ h~o?:}l;Pݻ8kY߷|CْÇULU{שEAÆCecq~P8VGTVW@<o{˖3A߂;-!,t{j֬GYBi~SS#f:m_qСS͛QӧۭI1l w]M7mQs>&Oޤxࠚ1aƍ~(G;+w~~IYҊ{4%8y F~{T߱cmv鳾ݝ](As|ڟoA9c z-̗HI!,c{xumO=4%ϗtԠA? 1bC#ss2kܱ:wl`-['P Cx*5眎+۽ t̞k!?a/+ݶBD&gT˗~[zklng[w5>3 -NN:ؖ_lrREG4>?Vibő[2++|eeGU3r~^! tq ,Zx,-[fK; g'f +[lt4럽sN_?i=c{cK0"gz36 Ec{:;>ec_q\JZؼvwoB ^$VuͩrlÆ~99p]ݻh[LM߶lNczԌϒ=z\v̡ SRRٳgӧ+MӪlw]rԨQʳ8wocV;]:>͎2JJ =otM҇;usܼ鳵s6nqocW~Sw8cz\{şmm>w@Ҷ2`߁y}-|yƷ݉+؎\qI;|o J=dHNg>|WuuƷ#qsu9}'9l=ԛ[fO)-{UGtޮ\5ܵz[Qb l6Dnڢǚu25foX!ujҤF խpkG >;Ϥp z|+5k֗V{|ڵkw]-!cݺy .juҶmZHolǷ{ ޮ7Ç{oubǭ?n/?N ]ۆLsOA~(ڃՉ1V4'YeeRJJ>WV}&L(]o-,Lw▕~f N-uxjsYY\\|B}.33siq@a/5g2vaRco~?7d }6n56陽7/\3,^q9ٟ )TZeo_{M_tQrO(*rZv-IdE@|K 6ޱu /;on͟cUܹ҂Vo+ct`^ 7@x iݬYJ-3۷jsk׫ʹZ|kSyyy@wُ=*[|}-/g1Ng\zcw7VU6TV95Ϟ_ftw :v Z-Bco{ؿkϤ@='B ^B|ggUW]TddeLLk}ٳw:k%v5(Q;{rKV:#fC~8.c)iii&N,V鏊4M;Sg[jĉz O|p[3ƥЩSR鳷#cb T=|c%ŽGgE0a~?-M֦{-z|Zb6b~Heq&u*xU#d\RO]ZK,KOOgyy9oMމUW5e?_e` {/Ƚe#k8[`gRg;3<'33D 3&Ĭ^2Xm ٣:`>7o!=핗WR煿UwEY+eZCZrp|ߵ| BZ0dW\G꽧 \E|oo 77EQa(oBMMQ7@X!&( ||SE V )"+E|oo 77EQaRM\OQgY aVaVaVaVaVaVaVaJ(,BX:|ۇAQT-7W1"E|S1"E|S1"E|S1"E|S1"E|S1"E|S1"E|S1"E|S1"E|S1"E|S1"E|S:NO/v@jȐa|8p`M^;5qs37|"c^ԛ0Lccu& l\G똍'N.TuS7֏mبQPXo|VFꕑQ[)}?ggg"o>UQQ)o?zj̘1S^eY֪sgyH=6ٸQu1BEl""#U\ǎ1cԬG{7XXWFI w=AZc͂|+O:U+oW(0|kU p{U! |eRPW%cf>tB98*8f޳ϪM?JO !QB|ر/z8qBCKOOGRRRN5ދBÆܢmۦ|ΟWG4r5zxo;^zIM޴Ie^]㳚- 4,^NH)=y̪'ߣFfx`iok߿&''WT 0];w>Z_A =z\KM7-L| s|}u]w]^n8 ^}UJJRMUTl>FfƛOe~wsC<DZc~wj@zѿ UCY+cu^Vu:TE5o"4Qm{Pwݥ]Ꟗfk`f*bw-N31 dk!~Vۼ2](ݡC_ռj3{ׯNo#""Ϲm{A_~UHҽʊ5M˷^41s6oF5nTgOW^Ϡu׮j\`AwU =; eKV1[W9V]\N>oǺT=Ukő#3լU;1{:O Ggeՠo[\=5k֣jInwM)SJՈ3/[v8fЩۨU>)v-an#>-jܧɛT3fi,.yΝz;_~ӧ(=f/UÈ?c_H?4_~Lf;Wqy*ibݽ t^ 7@$V;%V']75lHǝQ߽{v:ɴim˖}t^Mn>gOJPo=u{ SRRٳgӧ+MӪlw]rԨQ8wocV;]:>^ʎ2JJ =otM&9k/۶ۯQ#}vƍz8-ܕ|/p^|3e|&Dfi[W0@<o9m!=cfZ ݉+؎\qI;|o J=dHNg>|WuuƷ#qsu9}'9l=ԟ{\N̽-ni>FzQ֭N3Gtr[ ekܴE 5dku#>1~gCG\c9qHi3k1"[fg|PT K0o3$뢣 MflؿWƝ{׿ӱ BiiAH+1{:O0 p! z*!TULLkwf^{Cl+ o߾GDD;<,w\KuoMb~*//(޿.GxE@!\/y_t景6E DDdd'$ ~~s9ir.4[ôŒ#!x\ǎA Kxmlsw[h;1{:O0 p! m@bj8Jo%2l4jW^*U'}x;m%v5(Q;{rKV:#fC~8.c)iii&N,V鏊4M;S"Swՠ%˟2o5xTOMgK/S3v = gUG|;Z\1GE^W~-no󑚪2F%Gg{E0a~}?-M֦{|mYb6b<Heq&u*xU#d\Rҷ]Z^-RRR&ZB^XMՏv|mE #n+z|I-XW^y늊J3ǎgٓWio" \ ^2Xm ٣:`>7@}C]Oo!=핗WR煿UwgLeZCZrp+z| 00d޿\G꽧 #ooϘp)ooϘp)ooϘp)ooϘp)ooϘp)ooϘp)ooϘp)ooϘp)ooϘp)ooϘp)ooϘp)K7U#Uſ:VaVaVaVaVaVaVaV*6).8K}@mEQ>|BL"@|BL"@|BL"@|BL"@|BL"@|BL"@|BL"@|BL"@|BL"@|BL"@|BL"@4NO/vTjȐ">pM6wjĉ4MSYYYgn#Eכ1'O1=Z 2G:f UnTƍc6j{cx`iok߿&''WTw&OrrY1qe{[ 8~ 57ݤ0v9qSZ h\Z`U߿}.}OMՠAcexࠚ1ac{MwaTi)w~~IYҊ{4%>]nq}ҥj믿<ղCةeeR}ǎUڥ&[vlE3?LJMϱiS}f=Ֆ3g9>!Ae[C|ƱY|m<%ϗ?Ȣ2{zgi=.ˬsv oڲE~ 0dqO>Q{;^|Qrݛ@<oXs{i~VPۨ͛6>Сٜ9OwVVJKO52{||nU/^W۴a8>Zi6PjoY1\fKsi}ӧ(=f/z{ ]C_ƌ|i^Ndnٱ_{/6/=ݛ@=| UoqNqٴiB|g:ӧOWU)Q{h|{~[-Zy-mWf_17~g5IPR͙S6lHq{ wޙ]N^6wq㜎q}ݶM߶~^me\ Z`#>jRf[C|gdd:_oגV%{"u^~/\ק[fO)-{UGlY%\kk5x_nu"/(<ܧ1|.sߟpLf{֬Y_Zk׮5{ngY2nGgOF oOoNڶVm3N/3'_o[mnqK@Oo׶!2S huk̴:@]Vo+8ySE^WV=gΜ-233hou'9Yh4G>}hsIhliqq.n)}+<2s|u}!E_Ujk.J_ EEN 2AN=,Zҟ[jشi_xA~eyw+m|;HO Y[YJme̞k!&j׮W͚W+4-&--R{TxKXuo8os)*́ԆJvDZcMLLt87p #%KEF|oɔ)SΟ8qBþ߿RRRNJJʹp oFM]ŋedd5G}iG/&.a~~=ӾjլhPlv*7lv}TCDDxo޼;u2Y~Wۻn?oެfg]QQwhVݕ[}xrֿ>XKJ Gvn޷IIe,oۦ.o|n-\Wt&*.:Zt5zr~z!,5U?w9oFTX5kW5/1Q[Z˷++f͚_Ӵ|7pI;|,7kX+A?7m|K*GFD=e< YNL^6u#aF=80ݱg'k b-m7ׯ]/ݽ>{zn 7"{=hױL߷vw{yGӴl%`K3f3%=fzwu}F1'r=uP?OWTJ>vamkl߳`mu9/ZoϱcV;g nl2sZfoilO?꫍cbԖnRO̞f{>~/7mXqFwo>umwml_J-7N{TVۏr$|ox!Lr ry7gW'R]QQq,1176ZK  'uKի];սM=]Hsm?YZpl9{|2:7߬o_6ǎlώ}_h!ᲴplJ+a%%Ncq-_^~HٿS~v[8>_$8>kߢ{p~>iob0]L`NIIQgϞu ǮiZfNRFp Y77| -dve˜ڇZٳJwMv:%_3g=o_"c+MmA\[=>1Gꋕ+(^lAݺh"3\J8~qӌM'8Veeۥ5GM] ._A.W$XY0SݢiSܹ|Kdd-F}!Q'v̕W:}ڢE>o_1th&ZY{ܹV{|o޼d730~ +mٽYV;vmu9ǙVih|Ϥ,o>:|^siJՉŶz|{گb:}Nۅ%8,b|os V'48nݪ|o{Y sss[Ӵ|=B|qӬ^Vl7ء?ZcMX▕q\fNÆ9mV!2s|]LH_n]eqGYXs遺c,nR@S8%뾮"oiq>}Y^{ Y2\+E&8-Y{Ŋ<6'$%%4-F 56נΝ|>ٮ%?iL 23u1G) ]IBtBJf;//.vCf,j[y1Ns7kVeキ~[-3 Q^}NV[,[)S?q_}_ƌ#w΅S|aB0w&'-J=cG ڂ|;CzZK.}%ܪ9brwo͆kO{Ǝu{ϛ7:Xe-yXi|P^uOj|~YrM2Zc|K嗻J8.D\kރK.72]Gf[ ]oqw>6eʔsf~'??xbb} 0B|S[P[Bjt}6[-.**ReEEv˟/((*99"11qˡVoE> @h6*YDTE'{^hոnK"_jMɹoĉRSSi7n)))o;BOoWo^9U'o0( YʌoY|[DMMQ Fw ^oQu+oE @lT=-79bR7&"E @l &(o„f#77E|&47E- )0)oAMMQ FM| oo" Lh6o[||S7aB|S߂"! ӋUCL$ ܴiӋyyy&NxN4uo>[d{̥|ރɓCvLmVƵ|x℺~Bպ[7ըqc؆X9k!||SoTԾ}TEEݾ|jj1cL{?U#Tt˖*"2Ru쨮3FzQ.سxR{7ߨB2JJC577a|ST`!Bj\VVV>uTU^^ϳO'''_gPطQqUPYb/F;_~Y蕻}};l,damΩQQ1}VmGUzTP 7pD{ǎĉ_zz?r?i<.\㥗MTgCC|{ é }/9JO3 N|ւS*#cJH"#cTDD~S>zJRMDX5hPZh/..W:þS;9j,M|v3j;۽۸NW_/״E uA =z\GũknIoa+5:~I.[:맚DG뽫c۵S] S橵ǎ۟t{cbUS ^IIxłwך&{ZePռhDC%uZwU_ǪZ1 ݵ8qTD&1{;O0p)l߀{T}<ljܞEAUCN]RrXĴ_ll;)hwiibeE~~ǚ[|v~}#f4WBf i7m3'ǭ =tr{Lm _y>] 1sSlr_la{{^u3n6l1T6i@cZq䈱?L5k*q;t̞k!\47 j%:ty6jPyyU>)vaU۹rҥُjР}ٲw elo,^MMvSNmAO•Vݻi/Y|v~}2oo繧vsrmn۵KM\Nn BT3~.oar8'$!2_{ž'MR efޫx{^=Ldq?{=p8f[ ߯xklYeֹc7m٢f?J2U'=t/_Mct`] 7R^y|T56Ϟ䤊ni|vr 7o8Cgsfj՗aÈYYY+++-Ǐ?=jԨs3)0h{37iW!Zv`F-3ͥ3-n3͆Z-AcsN_?i=^=/UD>O$!~KLf;Wqy*ibݽ t^ 7R˖}tիq:gO,9*w=z\k|&e;}ʕ_8Ӷm)))ٳNӕiUJ.r9j(e/8UbϤ㳌#`g# ƾ=otM҇;usT 5gklܨ] ʝޫ/p:^z tLkMme|;,m+ f='B h6o@|dD߮dJoo|oշ}RcwFFY3>UM">드l|6{m+gyUjj eRWut˖U%sJP"2ݺm, zO?]'o_c& 447 ^Y81AmuhOgTtt|K+mnqc d֬Y_Zk׮5[]lpy۶ju"mVOGP֯7mpN6ǝՉ?ki)E{`:5fZ5|.nN͘{^;eqJKԠAN} ONsYY\\|B}.33siAUT[XϬ^&=jõN5=.ӷB/n9n-_gXsM-+S^]_H8dv{֪Ujk.J_ EEN 2AN=,躀huoaӦ;ﯷWƝ{׿ӱ BiiAH+1{:O0pl߀{ݺ վ}#""<໤j,ʹ%[ӴJ߿~Q9 (쓠<[ڼP FoiU!]6TVի}Cyn=},<u[+̱s׾%:.NIo;1{:O0pl߀$#Du_[_W uݬ2RI.Uɼwʕfk(nlVz ?5WzǐJ*W>Z mk"Xvh^'i"2gʴijK/9l?!d\.d{ķct:7\h@.lt.߲%KT<6 *Թ{ȑ#g=7y7wNJJS@It\e҂EDU FBHڵۙLDn4iY%'w\ȳ)))'M^x3J;׷o'lW"XIUKǕ]k(Jt vN U|O.c$+))yo(7? 1%Wի-9No@BH*19exx`FSk֜(бgHbJtqնW\zGW`N7aN(jt f47!o!77@ko& "@@|# A|߈oB!!77@ko& "@@|# A|߈oB!!77@ko& "@H|DoBy 4t "ko "@@|!!7@ko "@@|!!7@ko "@@|!!7@ko "@@|!!wh!y!wh6+E 03t C%!o57m B;A^ ]C|yI. t C%!o57m B;A^ ]C|yI. t "+ 7\@@H|s! A|!߈oBB]C|owIE1m >=撚6msq}>Iuɪrݺ*T)sB6'sA|@(kooUǫ[VV5Ef2eTzT-wo ɪr:tٲTt֨J2Et Vflj:EO%}ROOj> 1HF?wޱU\dۿg/9l\{ZwZ믅zmkov-Հի՘;]T3Txd%[O|GĨNGQ۶q=.:A~_6lXWO+W_JLU=.Tt ^fK"6IIW%#OlTo8 yӁqD\U 1[x:uX~˿qw1wJhl"R^T;o$e9Zm~1]O?q+e%:w6 P+7 2VT۵S˫ҥUUʴij_'q׬GjqbKä$Ɵ1{:Na B ]C|oķHˊ/*TnIDgeYԻrw|Ӧ՜"=/ޤ?W1+l[W}Q| w+%IjX嶖|m*W_*l !7;_\햊7߬o ־7n[|Kq;wVU߶2i')zc- :2[jW,hJn2wV.R>ARVZؖSC7m2W"kķyy }}ktԶ\gY,gl˥jܶTۖrj#ĶmݎU>&g1\O1;Na B ]C|oķ̙jjlKzI; U0l-_P M[&lUK:vow=mۭ}Ctw5֍}Q"_Zؖ#/߶N*НuZ/[riy;f_qA +L ]C|owI M;7oWe.ztnU4܍$ͻusXw҇;[rر^#"jkךrܓܕkgүr8ּf]:S+uR=.~ϯYOČ#ڷRIwS1]C|AgWEG#ԩRX;o\nܳ3]<}l9sn 1 ]C|%R|ϾÔT{lm}X2~Yl{ܸqe"@طo;?b_i!-9No8F|;ko!h'KM9ɾg^}&m s3~.~s'V 2G53 o7@Γ[VVSUZZs-OCXnj*,,̥ 0ٸsԛo ^dI;9r䈿mNܹ1]cJ|S%_*:Z{ku?p6y)EEh;^w愗>9Z|VΝSML[x4 <v~S8̴m޴wttbaj$spTTd>['yoaܹfff;yOt.]D|,?o[kօt)mpjݩSjw9Z|V7}!6{&o9Y|_5M|x͵*6r^%Ux^WMowI߶&o0'_F~t[moU)[~hZf[-HIibb ,na{ffYo߇[n'%%)?I9Oڷ/~p_~qX>ooT) 2k%%<2ܮvvo@|7,{uk;wX7[] Y|մE+k豕_쎭qtҪZ|3i]Op.SV)/xmWҤyK}fw'Դ_Z.l)A㦪Lٲ*2QK0Tm97$xcɩ"={c…J&6ek|nߤd'%% (?$_U1GFEkI|{(oDT"%5S]fPkNc\9vl1Hk )O?ibKä$[o%KTb6**&.9R?ta[[S'ӷ:'| jp啪z^eKsi3b͍;Q֭3[=,[l0ZZ&o@|gI%4$I[g]3)?e bWȷ2{MVlZ-^=ru4r{{VSw?]bw#.S[]Qh|߿=zlvcjj ;`mDzO{_k֯O>󯿬z㍼D^})f/_Lس'l6Y%rlV+͚e޿[gd8C0U)ֺ["M߀v%~F%wn;IcCZ5~l5{ b-JqW헗pYEݽ>ϣ^5q\kyiXbq7>{q~5~yk-ܥ\*J~C{TJ^KZE| Klƍg+~\(_||c}moowUǬ]jѣT㫯v]{jjU>lޖ9L}5Wխ~Q|2|*jؖ-*k^$55X j͏?=^mֶT2l^ėswvm5;]Jn{m_yT}//2VT ˗}M@ʾϷHp7 >Ln)Uͻ;4*}ѧp{7[f.wTؼ lzvs29*!,,\ݳV?s9aGEmyh/]ŷOKkx7zz|O~)uFUF̖%1h;pL6Z_}ec~m쫞ؑBX'-ӗ[E| C>z]sT^#oT._ު]w)?.]E/L=Yw4B eڴ,A+pygߟ;ysk;oVxV#M߀%E͚fu|ܳ4έD|/jiquAߒ.}&=ߡyK݊bF ߈#% Y|=yҜж\aZN[Z89xWZ{.+=~q@[Rŷ*z-m}#UZoV;?7 W^322>޽I'--Drr^&AB#E|On.qIkݚ]E\z pV'29۲$ F| }k=_ܺ[&- &׭Ŷ p ik@V':xϵf׷:XNS``_$ocƌгg,Xp^SJfԩ_$''4B߾H:o}o9nqSg6`?$vI)ݬ6PWnP>Ϝk9oXGdڵ322/U{kb|@|#[f6{'Re/TƊ*,,̣$OMA_yHU-,f=j8AWh#g@Aɟ/pl-[7g9pZbz*t鼿j=~ao,^l Y---D*]|8bw`+hbb U|;VR5Bcٱ}9MJ8sСt]?j>c&^G߁ 𫒻*V*$Uf.4MΏ9o߾W_޽{KU7"jw"m%l[[++שH$$عW;fPh,XEÆ snԨQo>_iuyR\4y/6GA<IfLa~']V5?E5hԜM!k2+:Մnܨhe׬\voכ^A皉urPeϾ1[ ߡ.V/U?3)))ٹ_"߈oYqY ,rJ2E7e4+)~n+:-ƍ;.=V!c߾}WZg)o޲)+'SZ|zp > mRj5sB9RMXpB-\DK7;u'&NIo7ۇ|741'%D !|'N$%%%}篴oХ%#LI|K2TzZ-pRg`k]zmVyҭVmKu:5sRS_7qWafj̹9TSjEvyUY'NӗxR F|(rVTպS oۛvsc?"L0sD4U'8TgM9,Yo}a H|>]`orNyȋvf,72\##r]˩l+K2$ާr|L@!y oMq|*\^uUZM 7Ǐ?h'|@z|R 5/>t)[ߒu[bF{axќҶ|̤f幬>ve#rt'ai vM'aynٚ{JR Սt4aDz'/νߟ1FKA'/7}9h~M7@7? / A|@0XhC& sijgoN@*Uwmse,ֵWKAS)m];k mk^ŕeo[wswQPNkrS <UūfWa}Ur}5h|oo41*-_F!\˩%-!rTgQK՛Yeׅ(KRm{4KU7MA|@gff&)>[nݾOJJڶ5e|S'֩.kƖ~2ɥöoeJټ"d?9Ts;cOFOA5n."2RV3wC۷8l/]zs7iR)omߺf/qoZZ0DUZlR^C5cZed[-o2Fj\UܪLi~Z˙y(}G|Ӈ_T=~ۺn*& deeMٳ .Tқ5;;۔ӧ4Ua A4 2)T~r&ϔ>ԱȯdDD1(IP_7AgzAEsU+sw%"\lK㧘RlYݒs_g~q(Ǖ/*VR 4WzWz<ڧOS޾T}vR F|BP8ȟ8pNggյkSSS_5e.J-1-GadSZloem2\ɲ;Zj&q\%-bߖj }u4r; y8vd/IGeiJJ)S|/}zرvLFFŗ!$ B|_F #ry3yf(QȋHC[QgεWdܥ"md;iMdF&߫kqNě1dY|ýJ}:w0T$2m%ښۊ| (-kvYA.H!;L)]]E[|ۭټݣXך>Uū^mA|VXF|1 ]C|ojh|ŝ-!CE\#턮!7=I|:R|WWdu]Z1q6cķTPۜzO9׈oq8F|;ko x5&>A!;!A]g[^VC]mfMUcy"m>Ljo't !UBH 65jK[mRm.fkķF|Gz]ݬ!EC|sP-jR ͚Ҷ՚7, uFecǞ8~><Ljo't !U$#!oB| >ek`rl卷}~@G#57^W ڨR&$+ԉxե0>K|Vof暉7#KtBJ't !U$#!o)zIZؖլ"`^u? 9d✥%e @@|C  A|@!| ^d@w:,eU:˯_|ZyּivU_g_QԯjTnwzբ[O~י3f]xTո4 !7 H *2ؖT?waB"ߕ$v)_۾4Sx-p%w9ZwAzsd$ AzTf{ڼ^ǞKwטU>zǟ|Z~Ǔ*k5x,uCc۩gϚ˾)kڦ V#&ߢ{uIot !  Ԭ*%$Qˌgը::0+J]f[-`m/b?81u;mJl ˺:ŭԙ3ߥj1ۛت3hJ)؞57@@2Z5U5>AW?*oZiC(/M|nʾia/U-w廝>ki/r `ko!HHqd$ EED_iz _+~qM/t-USnY^~:|%9@H|sb& .\'eZoV'jվ"='V'RՌm|C7@߈@@2`V[Y?:x)e#;ߡ?xq=撚6msq}>Iuɪrݺ*T)sB6'sA|_̊Z|Wm_~sqw]OYK}y #r57F|*okת;+Ȩ([jڥm}'=Yb˿Nϛgň b9gͩ>.\|Kd_i1Ɓ~Ҷ'I&lޮ:g\q.3Կ{Eof|~LJ|߸pu^&j_}ˬDz#!D57F|*nѣf˖n'G9qBU^H =RO737urv 7=JEG[\{ZwZ믅zmP X74߈_Km:wN۷77PS}V ZN[32ehŷ+W×T# ׈o@@|#z,qw% k]z>;0+sE"o:p@5NN6uX~7)j/]87c\м*]ٻBjeW+I.:rD[EĨ ԥ}E[)|/ulA/"Uk+UvTt*tiU~}2mZ}x|jnU߾jg%]8aRkϘ=0 }0C\#\koU]#,Y(Yb*UT2eL8|V-+S:~o7EV@ߴ)|5'ZN*m"ޤ?W1+WzlZ^E*,<ܭT&'UU+cZ\J~ŷcvw:7 qp!7;WwRy)Syn9Rr@sYaoI45nN*\I[?P kjփU/K8%&[o~]sb6yzSC7mR{̡Y mSnw*,,Z>}: ך];57͊NG[˥jܶTۖrj#ĶmݎU>&g1\O1;Na `F|@`ڵa3]gv~=kiW!*&$X؅!\aIoqͮڛ Ƿ|[TٱliOow=mT۶_*<2ҫv>uc}\&_Zؖ#/߶N*НuZ/[riy;f_qA ~0C\#\koU]ƍ-YX.mm]ŋ-eV#OyikͻusXwUpC|K;6_{xDY=pZS{"V0Ƿw]:es?k-YO޽Igv"99eY_K܉lH֭\z)[|Z@N7f eUvzu:xnub۾u?0/N|mCܝF{hum̴:  iow6y"NjM7hPz]3f̄={`V3SN"99*Xn߫Z,YbVfXa-+pN+Tn.ܲkW[^=yWY/5xF5W̉C-[f9pÄnuFCniO"/:O Z-lPky-̖3"~eY{*}6m۷;;v4!d ;1;Na `F|@;wVV̳GQ8|𩴴IIIs *ND}~Fw)ҪB;!.1Q9q'Y\Q|:FPn/ݾL8"ck,4-B䱧5mxjR66֬׊@|;fw)sA|0$"/E^$fU<%.~ٙjf>{Μ97eff;yc?SSSܹ*FD˿R<筷|ڿ0ķYYe.=\*}yQ11~+,"_e_RQ89٬(~j٫y on.RG-VV,u;v4J{3e45y~2.Se}Qo8u.o"{^<@.o"J y]WJ~S" ߈={>}V̡Cuhz~#Ϧs-,"mgkA| $~S ]ҸxJ7;KTzzK*T(m> _6%~KG85rێ[Ǚ81a]FխuOVbxgJNnʕRԐ!w߮Y|r ծ]U|9i;d,ZH¨Qu}";#XI-[ZA> &󈈊Gj*r$v uӴ_KYD@| rUR|mԪUjժR|(n$m H.I)~MTٲN:Uԏ'6x?Ajvr_~*({ܸqe"@طo;?Wv;GzHKM)jlSUmO\'^ZX%! ߁>}Z[ruꑇWmvp{ˬUW[6T{LQ]2S-%iiDzԪ̪oWs'd-7ߘkV}s&A}ĉcIII!C;ӧĶm;+=Btyt7 S"*^|MXokLV=~"Q o E|K|Y*Wmu~FEF߿ώGu+ݽeT>-E"ץUmeu= ki"mMl?6kNߣʔɫ$.SSSՙ3gaÔ"9}_IIIq;< }39#kmF>rՌ@  Y|_O:՗Klg qIn{=}zWwn-Mcnx=v%?V;v Kr$#!o7A|߄9b@B*kjN|:"?V'VE%ǏՉ}]TmvX{'s#:u:QMM$rU%a0񪍑Tk۲ʉ*ޮ ݹ yXTN1Gڊl-a-hkm]V\;U+ZO>^ZHۓ`^hCFߣG~K~NW-3Y:K3/)m+m<6lvРo֞<^>D J*ҷ-U{)eJnr0}m#"7T+4E|Kv䖓'{rмv!-ZRO?52L9aK/^dI;9r䈿mNܹ1]cJ|Bh=}?!.帚SP˿ΜHTgbl:wN7m3@zjӈo7A|4mZ 5 KHPki2\x @|>2 )mmv%X4Ip[:**Ry|"qZnvo[Z1OL'a-ۈ<ÖcC| sν533ɓ'}vt"{`Aހ#uaߛΞ5wuNw{*Wv7l{{/MAF0r(^T OcoߒC=Z(3S[iSۥY|[j:Ѭ'_F~t[x|uUTtt)SK7_irKϯv?lf?n:m$UUc2/2Qzl̼[gffV}ߺu}RRҜ~NGu4:I!/`/ 2W-%<2 !Fz2E66kJVFV.i P8lR-deeMٳ .T2evv)_sӧO&%%%;))i@a|NGƕ3idTT6-g&MTJL1(0xe k[n;ƕc~[|jbbTt U߾f_sO[l%Kmd?.9R?ta۫ƍyDu5e=[}ӁqryeccU!CVoo2V4ltx֯RMSʼn- .KWe˪-[Hmnܿ_Ѭ<\nO?UafيeÆ?p{A3"]lp$L o7;7N׃\.B|qc}moowUǬќ嫯v [{9jj].>re5;՘GQm:n7x|*jؖ-*k^$5Z.6)m/E+%Ը;UU*eWs'; n-oiVw=Zިsgib?h[Vf{-}cG^cٰoi-lm-_nxSI|v#>^*Ej>Xy߁\]Uۋo9f.n?Ɯ9Z߶| /ǷA|aΜ979:^`m05%7m? /2iH#{&rnob$Byvf|yTA F|O{]wmM|zR lZzOkյN2%mۦ]8 Q?ﮊ~{g./>pǹvC]'w~ݺ9#}`ķx=߁\ֿ i/ss'4on-*@3<`m+.={vl>}xvСC_~Ԯ_kԼդoUq R|x!}GjZkg\|e>$"D⻆f[&CE o7:gIjii[ak o2kLh+nj֍~ān+{S.sEn#wqowΓ[ږK#UZ~#3ׯw[Տe,2cذan׍5M]'T|?ֺUj-O7IC ֯~դyK߬~Cr#O1E{eUdd)_y;/Q|uY.siݒܵp}5+:wQoߢhUz 6cjJxe˖S6?ǣ||Gr*k{oF32H H|} /!A͏OP8V ߈oķ[&~'ukŷLg[aMH)iշ[1-5 `[ox]F[ [zXkq9⍯ڡIժD&uu-'-V'\w=Ƀ!yHOpͮǷkt}8D q,!c߾}-,}Uf-m. 7[c=n[u5|}AmvVSw?s,8JV7/^R8{XW1+;VJ\oǗqg⻜ڹB7d#<)m#k9=/7Rz ( 4zz~Yϲ@|#ȸؚ5sdVYK5/P+V-=i{iiKԩiٻw-wyrK'- (5IsoȾҵ7cEyܲkW[^=y-Z|֢Ӧؗ .eI֥H|r]R|򘑞Hb~A-n#31HPh3ku6Z3- kfͲ9^^֬ɷ=s90Y!6֬v%Weg&M~v4>Lس^0pZnĊķ\c:ڝHmDx{'*1c߃ m}%ϥK  ̙3ڛvsc?C|kmߜEd*UT3w>soz#ukT{塽[{šRz漥ŔMqK>,cp*9۬u>OEDD߁q(rږ7s%-_k>N~ކZ#7J4-MI5~ˋG|_|fVٖ%$˫P@|ķ,sfלtHm>9)7ܠbk2g͖-MQ>pZbz9jor\U:.CEĘڲ,Mv}/6T`KJ#Fy iફThSKoirK^ݔ˖5qu,$KW;x+o '^*1#cnb>.܉o\G|8Q {•%kMk_-m>K~jYѬh^aE8羝O:o~i|sK.w;r8=;^ڒ8@8bs'-$ǥ{FdJ&so!]^1rvmͥ7 ݥӵZNmy~u,9_sי3*x3嶑pb'O[=%>ߡy-vkݐQTA׸l.[.mGzjPķ/m!:%YSՈWiTQ1Ոoeѣfծ~-*yUCR5%Z߽L}n,Z!#ns{{F)Nam5'rQWOw=ök<[@x'ɍ9܆;p%1 Չ/mow0VCªz ՟ovW9u ܲdɒ8iwrw;w>zLAŷL9oz7vܕo={7=̷fYj麻(mR@_2!{N~MnY19iծ?{Dw63Y""""be.X@Tjbb b,'w6TT+;QNvM3$<LS7yhgȷz[OqNN!{r*={˯?pw{{ ߹( eig%{ D߄7_f̘1uܹ,++e}Od\?h+M-lU ]{X 1" 3Mʦ7@ވo$#!oDZ?̱cǮ6lײ~}3dȐӻ5-U|_$VqC+6>tF|C0r^|844- 26ףPH izōWmsaG! J(,' +.elNA"@A|C$#!oķ}g2Xy`ΈoNiʅ%ˎlR!"@A|C$#!oK)(ݹX!Ka9 Y$b 7N@2?h?Xt6-#dɵlb !u7@^A%wV{8,PXƙ\ѮlfD 7@ HFBy-S%LKzflv@@|(otd$ "~ <F|+%w mLK5Mno  Z/Ꮷ^o76-yݲd3[7)*--Kvwn.To 9⻚ĴDo 9#Yr,;ӨLA1 rw)*V%j'I N^1 r wл4uoYriɇ{R%b 71-%F|B! eIOw=B\֑ ߈o7!BGi@| ߈oB!  eIhdZbt*@|#߄BA|@! ˛g9Ê F|# !79%Xt@| I|ϛ;ڹH%/"oʕ.\Ԍ3~;vHDFaє)SF_F|׏7/|[ ԥkj{lн4jĞnF8972[B=w?^GvocMSus?  mgWy獰31K%K<:f;wKRVV&7 6짡CNM}wWsݯ&?k5n,>mQQ9oQi^}3%ҴeK[b 9xqrMY| ?<;#GX7/;H>kS|'_ Qz >zlq̈o7 ʱ,ִdiʙow]Wu^Ufӧ'gg⩵.1E^ɢEL8Q֬Y#njKii9̹Ou1#W^OYVs3ȐO3eɒKds?Ie䚟~39p IGӔyrò[Ql@| ;16l/x#FnsY|B.TH\5S*7q"O˭b騄W12wm oظq[uwm.vߵ˿*ؾk}R(o7 1MiWFi@|?t̙sl}XZl*M42p`w6mj{=:c7^ e7" kmi9cW#>;xp/m۶1cv/>:yb1noȝ5Hӻ֣jUVMj面便ewr%3ջwK}?>>&Ň_ ~ֵ?,k.Sbd4l:{n~*jGVP< gҭ_ޘ.nH6mqӦҩG)=$3N&I.6%% ?"{uy[t*y[5"5z`SOvS+qs^Ff^Ҫ}{AaE{/{l|v1LQϞf@|@ "mBr:\[Ӓ%]o/QIh+GNHw^SY|ҰaJ))陰k?T:th]i<;q}5,̙3ߎD"3ߩ3*Z|Bvp=gx!Ck|gGu ߯,omnYf*jsR}Mbtr v:ծX A|t>VY_~9JUg,^qss{߳fy~5swd>u[ ޟ7)%LK$AowU]%>Wϗr7@Vy=ϓ?29~qԢESyq1qKeMnE}Kg«,5b/M7! THo̍f ɱ{ڻ&̙3WaE" Y|~e4ha݂G,RQҢ~ǟl=lcI)?es׿f@AkRnG0ܽ~eٮ^q0@Xnٸǔ)¹6I.}[ܹ?sSO˭ 81^e7ˌrk++&+[[>nGՉG*~|n;ݷeNm֫ʶ{E|Ջ{W3%[I7゚sߺ(2:殻;A|W/ ]K_D|$ˏ/$oVwMs￷-6m,}xyq҉ȯwp{nZ/A||4nܰѣ7_>/4hz5aaJkNxT$Ctm79?P>E(ovwۭ*YYo/Oբыet]to)>3'n5Y z_u/3!o@|C!LK^1ò5 /ŷxSF*}Qv髮h{=rt{$|F弧uJzrQd׮VjZ|2D6nܘ 'M$HRtxC. $A "&zܪpBͦϹdS!jKći*$ /MߏЖC U09­uSen}IYlοr^mm\OJ|'\[座M$VVG|>6+ӊk[|teYuС>*uwP^ķA5sw%g>^7^ꞃ]wbW76;Ji)CMKD bTtu1䚅}goyJM .X|kW%Aws޻a]ȑ#7mvOkŷJWɵwU[ovM_>ۡ{|Sw'*?bw <',,'-4]dimiev zۿN[Mo7-e+[lYMr>A瑫A`]8mMywrڍk wb}Ůvt3SV|w\ڮNC^5swd>=;PMKVò3CW19uu>>HjʾF|B"h]m>Hv W ^]L<}|/]۹vn n!´.n}iGۥ^ryY⊊nSv5?Xa}[ؽmܦ;&BB3<[uk NcHmo\OM]p$F?ζ־S^B|k͆'CjZB[@5ʗ3ӢqFA5swd>^:Wtuu%HcӒOC܉⻴t;x8sg_j=u/~]w<3esD|kƌ9_p;Ɏ;v[N:uy65jY$Y[^{Ute`p9v?ن[ն$׉Qpo.JK}Mx83EYTDՅ֨2\}2k`2Gn|Ikj˺Uv=v|GR/bW-8l{:}4ݖ=~)[|g>^ne9oQw- eIhΏf3L[o[l:cnɬ3TiF]\rTqDZ>5k+VX*KqMrF|kK=ch^$3M'YuӨ+(K*\ݭ߽JVN׿Ix*?3Sl]L?ouZ'$S[[⚽z>0rCO'UlAz^~ǭ*gygSti'_bڟ$5fƒ w_{Nl'p~6~>/7JbEn_7 ^,(l}٥˰a}k-uf-;th-쳝}״J~B [; 䠃ԦMs>=l*=wOO9 VK|+Ç?tر*PVVH$v㫱_0/ֆz *3^o= m>ilY]C0eVI1e*ɘ.>Æ"Y qPmKwߕd/ -\u @#mU-ū[ᾧK|WvNw$%ubIn.-ݓ{NI.ԧ&ķmy.]d#=o +5~(y_ХE]vI0Ov;ےUˡ#vx4H4onKQmY|񧟦|ec]T7?~6AǜR~an~ "!).)'Yo߅MZivˢ߹ʹN;ٳT[|+C 3bĈ׬YGMVXqJ3_TK(K*!]grg}bCT2;5r6&gJ|O) D%br%k~ȵ+*=v9.Uo￿"k޽Xݶ^<[)))WZZY{g?+++mݺuFvoeabEx*RUZnhF&cBIC8,PXiEE ˸[v''LZ=c# O7eM|'Hu43JJJG4_ϨFވzw+z:ubKKyv0gJjBiӔ/ ˜|~7n(m۶y獐__߭Z5}<R@!aYiɚPjv@|#߄BA| BeZjtXc7&ByiʙfXvKpX"߈o7!BPX (bF)ݠiʡo7M!7 ˒.!K.E9LS e7&B46M´57&B˿S (*>%{8ekӔ@|#߄BA| ,9MoɈo7M!7˿V%w2F|B! o|͚>д]˒K]76!BK]y qѼ I7RJT|B!7;1D)o7iYrΰΔ u-a´W˒( oy11Rd$bxߖ%B\ IkضP  A|x#bxߩ0òԴK[J yrG|ߡ刊! yr 7"F5w2%%w&< 9#[ d@ymrerC(@F"Fw2%7M' 9ŷRT$Bi%r7<M)21jA|,bZiJ/gXҖMu r 7"F-TaeɣlC 1\|K#3,,9--j7<}C)21rw2%3MK4e/6oyވ9(a4ewY֕M5rG|9().%5MM5r 7"Fo4Ql$:ve36#d7<G)21D|'cYri/Y O]"BoLo [Ѿ]qȒg,K&Y!oR]%B[ S nekӒͰ_&BP Pw+kݖ%֡aSM!7@ӫ`JPw"!KN1-=HdM!7@>o;bcZ"%F|B! ol@|'cY UK8 ߄BA|H)ߙ0-y=ϢehF|# !ezB)ߙ,)]H;3,gw(!v7M!7@. PB?@|#߄BA|>R =eiz}iȒTF|B! Pl@|gɿ56-y'dɝF|B! P("D)Ypp oU(,E҇ E|B!om}@|gӔC-KM6wòA|ʕ+-\3f4v?"=;)S̍nӔmcl1.F&}Q{s 1|ASܪYB7~Okq} Pl@|i:Ӕ)ŷ4^dɣcƌsK/$eee::|Ӱa~:tz+?\92w~~-J!G8G]Iu/k܅F|>R ki%a;UT$ݩ;ŷ4Yhњ'ʚ5k$1c~)--=^_Zr17Xbl)ψqɱeInHL;ֹ 1A)uLK6Bҍj 4x1{w<{Æ oĈ |ibkY>o< ̙3ߎD"3k\|vI_ *Qc2o maG|4ҍ׬?Z|ܷ;9ؾ<\&ŷ|qg1j_N#6"1{m'&xͼ;A<6~?;dqt? UO_~gɶ3l|=s: nowaY{4]biAu X|' 9ŭ5ߜ+51nS^~Ee+ƿR@ӵ^/=Up1;sAX|GHGLXoyڗC׻cSYW-U25lXÄ!m3Nc㾾*͜ih!^ӕE 7GC*k>G(ŷF[V[lkw~~߉,{*̺NѤ+k Cmq-R1<2j}Ȋ:srU%kjxqR1.<ւ7վf^}I]Ryom~ϱg [g}F[;ﺷb*_tI-(差~Tꜻ;=~ \-oߓ);W YiɧMЈjxZq9v|lدѩSl >NZ@k2%^'O'^omnѣGoZ~} _2hР>鿶z뾶s+2i-Thh ̟iV~O(q޻[u*x{*?eu?luTTH%k7Ll*֬M?IwTLbd T 8煮tӮ˼U`vwSHto?$qzݿ;K e܂ vCa=zH3*S+iTtu1[s1ے-6$.._\1\oOuqn ^ّ͋#Gn {ڵxocq:qZkb}ƺ[ǵŷ[ht?CPSuP{G@|y'pUT^ϱu\ˑ [3w~s/=1~/uE|׬ι W(J/q4 WfX,y= jʏl,_Vri}3o7[[UvKj^N<}|/];vˮ<LGkb;5ovwUtOЮ]{ڝ@.[ۄt-tݕDMl/2N-Չyh㖤\XuW'~kƺg:w9j/{}Zk#`Igۧ뮤do]k]U6:ι t.">R ӒfXvF:VT&wt(sշk =mMR14U=u{1jԨeHdfoxxYLNP<;VWw{XK{m nΛ1g\3=k=R=LcC9s.Uxh>;ؓ11u6XtQAnzD6B9Ճ g^3T|r[?ǟusrds 89dR3+(差~low*>s  /(F| ˒%R<*,Y\B"ٝ~;ժD"5k|Y+V,~ |oFMן}m,Vŷ_u7+܉Nr^駧b\*iTdOUO?kRw-!u5 HL~Խnf^kf9cϸ~#g} /Wwūϕ~jd[W?Nŷygqdֹ ˿:e%׶o/m]2|HqN/Ny.y}XrgY-oeᇎ;W}XD1~@=x:@|bt2- |F|B! t*"A)`{FEEye2۲ΝŢ6o7!BA/♔ QmZJt7tu$Eo7B!oܥC,J)fXA|#g߄BA| ߐ'ͮbZ\g3,g8,ňo7B!o`/ٔ Yiɏ%-߈o7!BuO/)QT,-KvfXN #&BP 0DxXiT7M! ټDJ>;Jkil˒ݨ⛋Y7!e/(jӒѼXF|B! -">R FdΝr^) d⛋Y7! ]ڔB)P{X4-Xzdx߅߄BoD/S)@F",_9A7@(@F"jmhw.YtαEE|@ʈ )@F"j3,'n? 9OK Z&%Gw0ӔEEҩ 9>HM # uLbV# inY # 9@(,d됔̎kUKgS D 7 %6-IYKD|@Ĉ s(@F"riX\,;8z%[XC3D|@؈ 9 # y@Q1-ҲdJPiY  idy # y6KMS֭4h+@S D 7))sMKt+.p! /PqeH@|C`Yi'8M̥ o(i-EE34NRo feH@|CcM˒y*FE|@^{4)@F" PHJLSF9-K:D_ߥK yh. oGX2-迥Io! /- # i],h|ÿ)u !BH\L(.%js+AO ɐ\lB! ɯ\B/M6,:'i&Bo45~2 .}i:H͎5RM!h  G|x=zH3Ӓ,K.E" !B ꚟ2  ,K:a)gXb!U߄B!u\Iߩ,&Bo4 jȶe7*B!.. mTE|B!72 ߈oB!(Z@|# !BPX"@|# !BP(@|I+W=Nzz='wK}Ml;7@.)SP^\E*rer?'>$~}6fJD/Y1cܹs套^2Q_}iذa? :tBm;6ƥs\ c:<;{9^@,7ε/ly4P7;) U<9w?>Pf>:|;rC_ɢEA?E4Yhњ'ʚ5k$1c~)--=6g5^M4K>Ax?ns3v?}r+5i*o.ҫ٬ Oao/xOY]B٨z/$_i7@Y4GX&޲ZrKko2/dȜ+W.7.⧿S|)W}]F/TzoذA1i=_Y5r*[oa x{9b6_w6S{W -g>c'_w}kLdC|^To|m P{q/^0[}ѓCn_Ki/ 5/̷[=Eݻ>i̾C~{r岧ߗA.[y[_o,ON<\e?ȸjUVMj面便eqjXCIƟ^e퓇9 o4nR:v+V=*uΤc5aرa;43a7b#5YgF׹i˶cd_Z]$oL?v :2)f4mF5n*[={\#t8\hmc=Fɔ?Kr|q;~uq~B[]No]ŗ-U&{ϸΞw>[7ۀeWkv rݏOY:G趽} i[+жi2uu;fuAn~Ϲ69!zrݫҥ>:iM|]3#feKup WxAz7k!G;N<;>OnӒm'myud{9PIA]JG᷼U:gNE|?>ϔ P[/ W!7 /"nnr|ҵre#o dy\zo_E.zZU\syIfΜv$Y^qtt w?$n/G h:l'1˓:jРaZ)]{}ڂ9t윰Mg߷ϫ ix\zNPj͜miǧѨI3{\~G\N}*}2?Ӻiw?Qڹ2qw?{<ʴn*|혭Zgz񻍳uNt\uΖ-b Ǐ5ay]n4`#J>oA5kysjvnR 3n}TŻ?4<NE|?>vK|q]5 z-zW_rU7\/L[(ݸRn/ܺCFw?~yAe)=1醕^k*ڪP%F/xN7rnWzx| TA-2{;%>\[U[>%:Ns"_E.݅1 qZj]a?}kӯ(izI91v>9j7ΕA3I#TT>W[W5m?1[3 uO{=<'$WsʠۭSM[;Çso|NNK}Mҵ{9'a%պ 8 ŴݦSVws==OLqeKםćkKnGNE|?F2 |kˏ/|q1[.\=ܼSы?Nh H_/W|r_˙e_i})=Nn{e\wr?E+~[^qr׻"c{ZUG޴~@/eРA}l@H۩cƷr녶_ 2j饏YJ}=S 5Yg[Su3{Ϯ#CߩSng7ZޔM;Yp3O;>Y#-&Uo-w v q˔뫵t֯'S?lEՐ!Cdƍ B{ҤID*E'?rРAdG/$Vb#ڗ Yo+jbdOb?.>o3}؍85M5mq7Y~$LuV΃;U82X[|&tn}gѪu;=^kJY~7\v{I1; tU{nlΧO\HtbHNVx[r@ξ=j ]T@u끉"q-{ݪN ~1?zIüxH|"k2 <0ܨ$b@kbw[r-߷!evQL^~_r Or??er;FE]y,}~t.F1hk~Z[-cVf=rg@п>.mWd >2u~CΈf6KķJ}Qޚqӗnqo{zǁO ϶:\;W=΂G~맭T5lҿWt ~wU8wK~`O#M#^ķ}}QmG;c^Ndc~ƭ s+x7(_VLRiؼ C Wk"w+{`ӗ~+x^.\/^-oy} yzMD`عrK d{d]jɟ{ҥVG8R~j!}>fS˄3õR>%jr 5ՉޮtnwjDŽ_Y3vkP;c -g\8 )q5{ꫫL1[{Y=~qMvu]xOm݈8  ^ or^W}6]7]..*:Iudse[W'$,çF{A|ގ'}{J0VsvK*Ì#.{1MfݻV.ͯ.=[-FxϰsjCn]eُ5o}!EԩS̛7oSǨQE"%\[%o'WY8Ȧ>J{"**<-A x/$ꈡ) [އƇkڝ)}>hm\-wQAv"9ȃ4,sy`wc\kB|PxdB;?~覶{ G *gj\8 =m-`umMxx_YK?Nor񻍳yNH~vu#gWlBA3=~[$ Cwu[nt_C/ny~1?zyUݷ+p{#f)e@|o08rS"._ېG]\y'{2tDWsr㆜dC͓|QDZ>5k+VXߚ;RvlIt:іna6'K -U*8Lma]t&ߺ&-r@TW'LG[:,;gˑtQ}צOUWW2qw?{<1i^g;f~^r񻍳yNpvnI#Iݙp˕x=[8o;33ZuΏxw8̴n^.cϸU[+4ttC5znE|2 rE|w zVFh?J/:v+yR.;.*^Sqw2둟^?9!C^u>|cǎUV酲D"_h_Bev:]mI֤EWo$6Jc+jzɶRl؏>ڷv|UɬUi.{t-rak/^^ŷ-u'hkR|k.+YP˩;'܉-?fqjTі3neTadt}ut" 9oU<@")/M 证|Q5dȐ9#F~͚5ddŊ.))9۵!R8Wz G e@| s碪d\ii׳fzglnݺeFz\7]BEz>ر]6w6moi&oh own]Ti())YhD-=}z!r+x99ͣ UlB!J|$@Nxw7A|(Fe@| ߄B! P(+)&B!oBaE4OR7M!B?y2 ߈oB!(^Y@|# !BPx>Q7M!Bso7!BA|ɋ F|B!7@{% ߈oB!(fe@| ߄B! P(<Ϳ(&B!oBaY4R7@Mo)u* !BHh^ o(f%eH@p,@@4(@F"cYM21} 7۔ #yѼK21} 7{ #y|@21} 7,CX/|L21} 7g #y-|A21} 7W #yѬ >[Xaa4SD d WFeH@p, B!9.XID~A!A|@rI4 >7!B l >7!B D} oB!7 R dB! y'Ɣ&BA|@!pN @p, !BPU~jI)} oB!7ZS dB! )'(!&BA|@qR =@|Ԅbp!&B/6Ǘ:P 77!B '"J@|{޹ݳVhq4o(>_~jT65gOu9&߄23%@| ) z{,VRxOؖ^e_HsOq ӛ:weܪf|*o"fϳm22Ji 2ll {x]os Zh,D9罳O9>O=>7~?1^T m5fV1WvҾ]siܸ zj/OzTw.gN~n\w {Z:oݮf?<ԨZ9~^|gR '@| ?[F&'R/U|f{qyݱC [j8WQ켧˚vҠAiX "_ߎisqk}o[UOUuķC/⻶%nۘ2o3*!{ LJ{mއ_{*ˠLoNJ?TӚ~kdAtޗ= nu}N~˾GnÆ <.Ҍ˫c"8|e碴/[w ToJ@|R?ķJ Gr$ӋgmEK?ʸ=da,>Bhj!  A0ТH (E"$$(VPz+*vDE.UD$r2y9gi~|ٝy,|mQ' 翦(zϭ}xa CFG}?&{?qw>0B4'>p޸wo?lB]brvw6/~߼luN=ya_%N(FUV:T| 6x{㧕 ϫ+ץc{>rx|;fGzTsߕ a܈Q_4(MXW^_ۅGT8QC>4+2yˢǜe-׾{{U#gMo{.}۔NT1T.3.d<pyxawsÈ0^Y}pC|-~][_C §6]m~>J& ^Zo|+aa4h~d}v_8sІӣ/ #iC5ip2ܾp~>P #2?O.!_B(5Z23?haC췏ס;4<>h'] K(|wպTz`'L/,w%Bu(Y^k+ *yaD7Ξ';?~?򁶃^>;}Ei(]m/kFۛuO,.zELQ{ٺYO%ۍ|GaDt&)v6Jzzg?ڰe5ֲ-d|} $7p: Li(07h*_PtsffwS,:-;:/#gkGCe aIq5C%wi??Lnz0gl0r\𽢂໫֥#̳,=}E=G%]ɺPzېGQntߓG"˽/+CiJCj{Y}4^6u}tO@ۮZCg^2? _L/ڷF7"Y^GOmQekٖTjVJm8wv dpMaWP\y@a./{@+ : Z7dP~N^P-чJ_'=3:}v:Iלb]jD]Q}W.^'= Gw/L/j!=ҳwg wJaլC鹾4~YPm]cl,eԒ=gN/OrʶQaF>'n6e [G@_0':@+@ o*ۑd2Ï9ّGBٳ>}/ ໴=$7h^(5mS2ȇ|W=>(-,'ޅӁۯؿө5=y"}2̱U8BRvZ^ZׅJ{?JGaFNuY/9I%}-Jt']v*W|mFQSÃzNurw)Xz޲lK}m@ouDn5U+@ o:ߑ[t9G}W֭x;ӧ?rbOgL.;J/  \}E/LilJp+SBpp(52? Gw57rgBBHFrn= Yt{1*}@ck^C.y-8|wպTzఞףd]$XQW8hcۅcށ'h sGߺ|r|gd˲3,:g3,[s| ԡZo wEv.QÎNyv"Ϲ\e?YE;H`ny硅m_] u8*{svsN}hVarȨu ;a&@* ?Y s4ѕ?I-B-/r{\sy.mt\6iхe&:2U9Do[3zK+uwGnٲe[`+?xܑ{g~增|ѡ9v9_6:{\aZX'ڻ',,_k]c7NXoaOwmSP2;6\סwXnFX`j]JaFwB%R|Q˦cxNF׺\(]m/kFվ۫jw:=yy!"7{=QchF]kkG7|zs AJ{fЬj_y~Ev(JBP˵վ]tE/X^x!D͜9ӦMWuyXkZ*%fUZvC8Fg?l݈Ezjc:4!, !B0za[~~[e 7'g__:סw~C:26 AS-j]*}=yٹkC8֨ףd]4X? ES,7eL#?\}*̝K7JCj{Y}4QUÜv+kX{7+7=hh^>F> O=pl~_]ͯvZs|jV3; CQ'! #ŽCYmTkDsi #0h:0 2P!dR?N_,>|Qns'`+_vN: ~q~0r໖rۦ7}!_vy}a8&ңŽ\Q }*}LY's. mzG'xWڢ?-sمS:<\$Q-3A˄\5,幄O?So90:J+@ |v_ |e'? scdW7?urD9̇ӆpEjF =IxNaJ_?>Qq_H_ a@]y)7@ Á F{72.wP46;FP cdg[#a{E_%w0HTf0%IO=t2}W瘊S4utG{矯)^nݳ---O {NP=H(>So@ or􁔚|C$UZa4qaqsCN^CQ6Mص Lݺp09\Uwd7/z᱔]-'=FT|:V#7s^* O98ɬW_|?---}S{H)RJ PlD7T^9;>}@4Ym3O}xvp҃E~G4uw>_Wߡvo(/|zNE(Wo=xf{ Kx|:vňG}4>Z?oRJ j{nP+@ ,jhZ3s';݃Kn5fhE_V47Ր9#K+2~ 3㑛 .ﻫ?1W|_h0?\럟R(Jױ:Z{:uÑJ)|t ahT/twM3itmn'#g+\9,ӏUtӿ~}Pa.!4ϟJBjSXʕ+?TM%KdP_{wL7Rp3%@oSnX+@ |skGaG-]sPM{{N=gN/\/w_~~S'ds[V^9Λfs͕ī: >vΎ^/O_n~]£OxlwA*u4utGnժUt'?xӜ<=mڴg3pwÚǤ# B ׫u^)||sZo;s[g٦9c?YT4j\x.XV6p0z_)7U?qvt^\Q^jv.,X /PQ,YB=CzXJzU-zM}Բ zswPGf0ZzȐ nB/`?>8;x=Few\0zaszw>d{ &c[lަ ڕfnDXfCZ>p?O{tqQz_jw/_~ׂ ^lc=#8---u{HXwe[O {r_3 }w=p$64[6Po |CO;r˖-;裏W_^[.v&+1cƺ{{Gߝ 6G?ih U&:t@=?e1f;uU5SL~>_<0>qn1?܄c_? ?;[}m߿)٣;'?ﬧapܢ( =ן[X׳ ;L5vMTsh  1-;&>{]\Ӳ>JV´>g?&M]W?YTXw6tp|ʂ]*Uڳ]_Yc@TڗJ>^p^q+X2q<㣳?^ղ-i{Ts_n-mu~TjzVgF#I}>V|ojVJU#?E'p/:꨿%~9s淓/cN}3: B@?.-cB~6Z:-l]t)ـtsݶÛ|0$-=f_hwqVm5fhFVV7Nw>{ Qz{|&}vi-! j9VҷJp!.krmxNo:{J໻ qB&VwDֲ-i{T{_n-VTm?*q5=7G~>V|InuVJn}?wOˎ Dw׬>}vd<=za*w%͊L޲ 5 )|c #nE~kќ~(p@a^5X WLΎov 7/ۻ0nDGaa:-'N??(̇Naڨ(uP0>='ggw]wpM=0'! <: SX.[soߡ¨p҃&|Hv9gQt~zt~] K|O_d:( (ȟ%u:]Q-ےfvۨmi#oKWӳZ>3[=mK m. |[)wim! SP%^.A@z^0:Y`a|aL=jaȏat!!,_G^Wq8BBhFNxO3zea emZa$bwcjSTÈs(74<0eYXI* mp) Hxsȟ/-|: Ks1=y\¿';໖mIR}Um䶴[%R-=]gVDžG|>V-} eZo|+%NFr3/jչL~Vf3۶;]J1 :ATf#w(,vLV>lM_'6K_V-} UZo|+% Ϝ^4J0Ӡ1=R;J{ Lz^LV-QǑNz'n :}v:IǜVaZ/]{gz4Ѵ ޮ/Wߞ|W.z\gVwߕ|T-} }&u6J FGwN =I4Jڈ'n7}PJ±zPobN(}~r|ܬ-, SNurwi =$;J6oLV+[WO_ GwJ>FGyKvhT'zoKUST~d]ϬZ?3Mj/g{*hVkA;_y`.: 栢q|:vΎ-laun[qPǸcT  _Uoƴ!iizNs٫Ⱗ ka.pΞ}ASso\o]>93seٙ{0S]Fye~/ۿ v,[s)wXoa%o}suz޸w!s-ےfK5UzoKҳz>3MjﳧmKWR7jA໽#]_;a?]T'a\XMXS:or¨zc.B0u0oGTN:$P ':FxGM mo}-k5V{JFQnk{໚T1_˦zce[Ҍv{}6r[Zu~TGJzVgF#I}T-|n|[)wixmgwscܨ;#?\ux:bָyp/̎*wO켳g[}ao~鸪ñ_[6fmˆjku͕dp{m݈Ee{R|>"́² ] k}ׯ:(Z*asx,e}=%Op[CφaS-Kg|oXCo(b)ckٖ4rWFoKyU.UۏJz\Kh6ϞjoԻ VJWJYuIPuIG@ B;J8JY+No)%V!e}Tʺ@_|N))R%%_'1m7|+RJ Iݣ fvJ|+RJ ^gI}V@ @jRJ jä| VJ)|}ŃI}E@ [)RJ $ m7oRJ)7W|#~oJ)RoO/k|+RJ IH@ [)RJ MoJ)Ro'ǴRJ)@_~ J)Rz'6@2z%6@2z;6@2z[6@2z6@2zkzI]H QkO+L$^岨5PV&|@5D+L$^ey (+ WYzcDoUΈZCqZee"7*ZChЫzDoUNZCjЫziЫzkH5B}V@YH =Q|G?Eo!/ԳDo@}>Z+L$H kR <2z"7Ю/'k vBX+L$H k곴Do@}~V@YH =hצ/h v }T+L$H _ J2z"7С$u6@YH =&u6@YH =Q+͇񔓦`|RkDo艺%^xs5oNee"7D˿oFXt]ޥp٢z0O:h4Q7NNanen|@oɤ~ xF+G}vKRs|wmYɘMu/|@7+'#4[U[U4~|]-7#4[p6jX򵯮- |J7t[6@vv[.0cBo⚤^Mj#; .l #>o͹1Zv-Q+8=@/U,R侈P8^ut/ӵy+J|@}=F+qd7tjхZu% S;澈P8[Q:5:EpI= P8n W?:sŜlͽfn fJ)mwV.x抙ZrۋP6z|oz[6@;@?o%| }Memw=I+"uڮ񠡃! 'ϛ_uwsaru/\˫>8{_k /ѥ6vx~e'n:V|7V_~U=O7=%/Cy{ӱhx|o۳As6IeT6澌 kGhmCeė;N7ڨ-'egǧyj/7w8^;bx|OxܔqE#|3^U?|qaf~U|2:^+q:sr>s+#Fo´uϡ}n_>e:;H~]ģw ΣT`_6igW|^ܗ} 27ګFXt]ޥp٢~'Qty^$*|Ϲ|N}0sBUtpKzr_Fkd"74[>%)흿l|yaMHn:0]_8UjM7>/kd"74[>>tKǎRsh7s0v0W3IeC_-NmY oz]r_Fkd"74[ 텷-Ͽ/q}r_/C2?IDAT'VWttH6nt ;e"74[ ) 2*{بa>n6t94tewe~ԝ=josk+R @2(e L$fUwcŒ ძ ){g8byW6oX>#x{ok|w(doz'NmL$fkjT)LgJLNmL$f|ӄuۉt_%ImLT}.J)|oH @7ARid;TTO㇕Rj,I= | RJ$TRi߂o|; +- | RJ$>߂oRJ 羐 | J)%v|BV[-VJ)`A Zo[)Noٹ/j߂o|;  }!=V+@-|+I @_k߂o|;  }!] | RJ$/ԫI] w&~1Ǭ7oώ<gϞӿ{Y\R k|^Ym-]|fM|a;mE<Na~5K.=裏UW]wxݺuqty=1}ZZZNM ߈sDNDQn=ilmIjZng mZ7?ץc[&c\K P%u6@V&jr}Ҕ)}5~z9suܹ -[ y>{gZZZ.s +';|Q|u9|C0=lֲZjڔgȠ  aR_DM _Y62p`!g7'.\ .x q%o̙6m:֏. ^AqG?zW+NiW]!oovG5kuQ݀mh6@V&jb 7&=!LoV};wK2,#o|$_-֫΍7.>xplX<_l=qg͊²Sw޹Ã]8Z﫮+WƵXxL5 @Q|AQ<k?~(';Puݣx(40w.߲(ʃ_sml2,ſlԽ;>ُ?o:M_Շx-.×niCx&Q|S>j[o_(>l(~Qou_!?i#>|:?Hv7iWĝE_+^>LÓ:~ǭ[է[f##/_;Lu~|7h,.oY?Ps߻le`fpV y'=,0 o73S8Z-އ2Wj\?j:_A(~ݑaY!2ovmۍ}^歷5)aiG|2K>t9^~k ]֭[lKK5UE?;{ك/h^OEgo,-}g:O=04lv~!Yp97vE/ZSv/~\yYqSOO߯mOny'!ԅowovާ|k/n,sb?!?x ]B0"ZGTYo)|gZ]ܴ5 {]Ϻ*n6X 4ݲ܎XBܿ+g9ÿ#?tieae} Ô(!.tn㟷ܒ=?3ȞڵM6)tiWax1cG>-2s|Ϝ93~饗”SN9%d2U8?_OKKx}_* #F=_~ jgrc4 S_ #ECМ췟kv(80FvѨ͘;~P;w,a[D_KGڧrFX(>š֟0H0uIa[vm7?:Żk}Brw$L+<(^^6O^^CW<ԟ^_]6_+z+4w{W4˂܎ǞZ໪jD.+ի?m3 eg{_z~=޺p=gY8*Nov]hG}4>Z^jt8xPAA]˟~5N1^)05D;L鑟#v !o)ޑSч_^bҧ*atCZoOw"B0,/_{mQE{v=Fg]upo?,3vjQ= Nn/>)ϯ~b*d.~a͚,燩Tӕ\{[Ӄ:Z痽{:uQG|u{BEG\n:u=7u9m]ufA2ӟ>tP[rqNSszNj=ˆ0O>Z_K𽡯?ߡ.<&;ֳptH# |]IIÂ&̝ üa7<>_\qEѲ]|Ƕ|ƌx#!n5|(Uӧ0}J~"=UFwSKY~rS9{⏭5oi~Y{wzڗm@x"j C뜻Z|˖-;裏W_[.&+1cƺZ?Ag C{r精@UJk=_w%M=}  . Pc*;Ο?N8Guߒ??Ϝ9ɿ1Fa*۷mCEڷ08@.-뀪/Kץg]#az`7%>Oj C|wk-d[BYUz[}x/ԆV߱` Τ| 5aVw~QVQ|ՙQ%nOomUR/%O+@-|+I @_ | RJ$/X9Q+@-|+I @_0=%0[Ro'7}/ ߂oRJ [d | RJ$/I] | RJ$x6i߂o|; +~Խo[)NoI'o[)No&6[-VJ)⪤^IjV@uaZ+T75>v居<)MNW|lghn4Q W?:sŜlͽfnA?=x#?xΣN= G%79&Esz\+ffkK _Ņ'̘#-%>겣:4tPvt&c6wǸg/j{  HVjMefCۨ$b-|c7x7VělIayk.F -M}@uYjp yz?<>S 燃R9BJC0vTQG^rd|˷;ިFG hK~xIË#֯oz$em-Ie*Gķeǯ>p٦c7tJ^i8-[ם߳.m=y}emaDy$O~/v7I= 4Q>j 8b[n3c݈໳ nooƅ۝=yr!aT}φn @oWh9QT'#{J]zZx4NyNܼ 2>Bq^cEZ@dr93U\NoJ0u i>ּW"z*~;O,\ov*|m6Nto7m~Z@d. CMSs_ߢ4@&!ɩS$ O%am2I @~ 4@&|; NꥤhuDo'7=/juDo'7=/ghuDo'7=ĺꔉNoze"Ⲥ^NjVPL$v|C}3@2QcW?)'Miu9]kdozM|֧*{lk ;"VPLKO\1'[s[sωdo{ۂy͋'ϛoj痕]~k㱻.3zm/&O"yVPLÂ_]\13[K^R8..ak b|mp>cCi1T|NTX^Ӕנ> hr_& j(S:hzoMӜ.kI.[7nyzaSwjS^7mrPgOϼNȎ8Ο܎vy|ꝧo_Pqs8/%#WnU|3׵>l毙wztnkRk5Dsj G[ eݴ9+{ǧ}lG1//!xΣ[ o{<^ҺW4$>c Ӕ45訏o$6PLTExZ=أ買>֢ۼ執4$nE뇃\7Z9> 3't{)Y?en @O{{JEW Ymx k_~M'O9ڶQ᠐}umzZ|z}emC7/(,;-3Yr_(O @mSlYT'a(5wSo/\]'a,m'7l]s|RX6A8mf_\![>Oh][^{Xw8dœ~|V|PƑێl +ݼ/kn;^Īxu6ڨ)wOϜ -ǑY8nXz!/8%=ŝI9Z@|o˖֛zԸQiNဓ6jXŁm~*Pae.ܷ頹wQu2/_<:B?>3iA%+R @81j C-:(xӍas厂0Ƅ|_kǓM.,=cxv#{ok;@_K~t$s|m6O. @7L-LwO-nG0*طkY)bxVt%6d:ͷ<{ƣ.;<0w`}弦ׄU..Lդ WN]w4˳&: R%%mDNoԵIԦZ@tts_.hM"u|Х6JꙤ>4HI @9&5B+h.w@ Z@tt_'%m DNoIDNon/j &u|m~: 6o}ќ4HI @4&uV@RU4[RõuRIRi}YQ`hRJZ@_qUR$5^+ F'j}ECV&zVW|8$5F+ vN?Iݤw$Im!wR 5IԞZ@_02uI}C++&'uVKᤞJjvԫI IZ@_0"'eRCనuʓwj}mITV KѤIj+wAV"EEDbШEbպmrUTPAeIѪ#xIx PEnjEE4Zbc;Ӻ$jJؚ99s.mw^rpZOHLOQP.IտrG@9HՏ;,ɟz#C%9I~@9dWqPNI~@9/$~",@8%U?$uK;N]$st&c[5_uN&U_nHңS8 ܘ$N2 Q8`$'Yc+P7p@$_$$|(Re${HҳXŗ&C%5IP7pdm~I|$P7nL#U_*IM~Ux>(v$Kú$ڭ3<;K)ŷ(2pt[SJ]+gߘ-Wz&~.[ly>+>s#{o̝h,\ZzEs=q-˷-Zh=cƌjjj1N@*p\V{7/,(f}̋1]ϕwig 7|GfutGȬ>dOd3i5?t8woN;3W/8'ok'|=-PsrK(>7d&;n~Mϥ%C/XrKXnsa8/]V AXUqMy&\>X>\ ~CkT|_ve[>o(o%qvUZ3?~W#z4:4:n5[|\Zz5wl)/GmNݪ6Ν;'[/<ǑsKZUzQuu'Ol׮]%uuu_5j=Vz+{%u=T^3{Ǐ=t{vܹ!ޕw@S|lnE}X"̊˩*++TUU}<_l߾_v>q-&Lx-7AV5_8Z%DřIUVVnK$!L8;}aӶw A1Z#)7P(P7PVʊBU@YQ(+ UQ|eE*o(TE (P7PVH;  KaV<IENDB`java-binary-block-parser-2.0.3/docs/jbbp_special_fields.png000066400000000000000000003034701377434312600237230ustar00rootroot00000000000000PNG  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.3/docs/jmh_results.ods000066400000000000000000000346751377434312600223300ustar00rootroot00000000000000PKttOl9..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPKttOmeta.xmlM +zڪJE6LD6^Lm0P%ܹoR@0䭆Quwe   S>S"Jڣ ۷/*¿;0~ ./[wəXziS.}tf_.9-A>PKNPKttOObject 1/content.xmlێ۸_ahQ EeKxEEQl}+hDv(#fLxC9?j:{\֬ nrVZ;nV$ǫ7Y#P7bo]>kqcH<+? HNGQ=~q~H"Ⱅ(Ee= WX>oq\שzZ\|r ?+{lsʵ͊!AĪA5+[mVC9QR+!mSǧ{8Op! bH+aV#œ=Ϝ%Jp/s8C9QĈSTb++>^S"1ŊC/-6UN}l);EW}(ٮv3j:puKmnA<%2z@IzgR>i\]QCt3SI5P*W=j㖝Dr!b9_> 檂Ӻ',/J{N MK'5Ro>PvyEFqf]= yISvx|0`NgJǵ@uO5itmU@WH?>#< ӗmt% =#kXv PĘKLY 8 `-v ᖸ84fjIym R{U|8:.j*U6ƀTy ,`|0ne=vPs z Ň|񛬖[1DrTer&MNwv(pRHQѣm2KtqʣH1znZI߅X'EL]^ELT ~;(h):έB5/,X@sw*m݀fhK~P ;[..;Cx~Lt bAi5 ͮ!)䂊Tس=VYv 2 nYp_%3Q}/{dJvQXotv˻G͐N.{1_1e:=NUBڛ!{ߚP,Q!qE]._Gf FIc6ewiM.M' }}7'rYޥ+w]W'-W^Ծ&χXިk6;JeHɝHێ3.#`#gBKtO{x{aűqx|0 ?P 0Ml"ԩ~u lv<>D5+۞s¾ nχFH"UThwƑ'yxW1zIh?c xƳLgDCC}r-ep5cTdy/n.MwQ0i D+|?8?nfhB 3蔿ՍczQ zqbmboF!˼I۵!\.⹷4^4A y)Y4O.,Heҷ0> heŀx2^̳$JpfQ/Y,,vJ7e"Z.] $$˲p-` "@r0jqD)Nڙ{QoyrD]J*l NНbfp 8hWtTo4LT\R@]J1V9cV$any2z%Mx ऀuCYĩ}oY0 _qW,;xgѷf Ykeߥ}vUvqv3/ZNjԲ@5}4CT a(Tp/=q35p21qc$cF›/kc|qOzr'(R_;=|pY8, Fօ۔FWIPhKD9$^'5T5_[5\p3)=$PP|g}s|IZ{[?g|K|PKԔOؓ(PKttO[ Thumbnails/thumbnail.pngPNG  IHDRPLTE)+-:2+;2<;/1-9.&41,679::H3;P?I8BL=IT@6E<L;E;6A:@JMFPQMTRR\^T[\MCQdVi]bYmaWk]addjjcloctf{oui~th{kqstz~t||IHKFITHRWTLHTMVWQJXUWN[eK_xUZeY^vYahZftfWIdYUqZFe]bga[xfMxgXffhelsks{rkh{sjqrrwz|xup|yt{||\hdkhvm~t|lt}kzx~wq}~n^r^vf|s|jr{kux·ʶȽƩƧгŭظʸ׺ɴǷƽл¬ȸϺѹ.ܻcIDATxpWp:JUP %h-бEP.ư!9hTHZҢmժ jV:Kw?$@ $нKwߌo*# 1#&o޻{np[GL#?ο~N^#EWJڈwYcb^&żʓcV|N}Ej?Wb_9Ϫ5>+;›BƷn֌{maW~'ܿasㄩFC_Dv |mչb|! [_yi᝕Op)³ύssi?!x+1EݮB ?Kg':4ߝȨkb]|e]QmmQD3C\D=wKvlTշ=l 25Ut:әlq^;0G D/-/d74nQ s 訵bswQ;m.̑ZI DOu <ےGlYrEn$9{`_k׭յXY[7DFqLBJAlڗm.D-";ŧL#,洕ļz ZmNE֡rj4 GuIMsF~UIZG:PXCNEɴX+7Qv!! ?69Tnsy$37dG'xQBD;2䕐>Ӧɹ#|11E $1MC9E_?gBHo<~Ngipӓd*#KL9\:{hTB[g7xf*QK9@t;y%N~vk~0Mbu7NPqZ3_IE]q%6ΒA95y)b N[5愶$;Q]bژ3/Epwnwfٳ׮r7l6]p \w,d/OYPy.gTUN7ho)H |? Hr>U(tD!z''RwҰ5X}%ϯo{p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p 'p pLk8hIENDB`PKttO settings.xmlZr8}߯H6 )s 067B\!_lgS3U-n(~[{bG/J پZVfȁU:P9_WJfHNRA^NU?ƶw]a!_-0P6/J?Ń5E*nGW:{ر }_ݽ Eݎ{ I3D CCDD3|G$h|Sz2|"u]ſ =UHư1=p &)PEP?iz dD  6)Qhg\'Iȕl%x.< (@N^t-uG @)Ѳ=si9D=j A xJgJ2:ns5`SrF mR ) `ALp-^e_14|sll)n~,Mir>KJږ(6` \Ч:2PӲ`xd 1f~w :-6(,.Z}]ɲ# ;Rb$N$2_!#l,FbwdBfg*Nd|@oRpIu[`N[rITaj7!|8~.Z!J-F|sv>y퍀M9A`=%Βېl!ny61\ -RhlH<k7w~`dg \CKJpWmq4J'ѱܱ=[H _ wm&t(y,O8" XC 82F{A%kxZ_Yu?FWUC~R*gt+840y9JEA\l ʋfwy\_gʈ,'>1S\e ;HeۀU6EhZ ҴT7þ.o{\r 㾭LA0V㇊M'֓]zdlvU+D`4|뙭;%Th6b2~$s9S2D3u禾Sz\Nu4MWCG[cr9ɹ7P.~ͪ~jM[^NgXz6;6VzjC};s]oMSQ13þNƊ3Z չ_cRW{ЮWvu88>dQHsߏuk"lP}*?F?PK]k"PKttO content.xmlYo6_a^JlRaK銽%Dѿ~GQ%>>~;㫷6T*&k/ވD,O>GsW"IX2FDt VXWJJ/VL-sQd) ;SeeS[k],`ۉ/dD$Hk6n_9Zg'%bT b=F,Wj!3v1" nE "z-w۵PiۖͰ^xfqלLP[m.x$nj$ۖUz`掓C qpv0pjJf1_ a2C 䩦R=IVLʩQk ^ckB4].Ov\"$Ӧ$c[Nmizo((PlL 鶺gce2MmiI{dxA.rrd+S1JSz? $-ī7bn"JM)~=d.ty\G<J9b'g WgTLA+[v4tk-ޞE"ߨ!$0#bJm#6x;0z9tt/(eE5&W46C;?v$Lt = 7YWh$'-PIF` UPaэw+k~~"Qn:yuFN}Jլ J}U'L ;A vp!{Uo>ه ۮB^ %~c|HpXA;#s Xw=fO4+qp̟ÀX+~?e(`2h`-e`,, aY>pȈpe<F? cp6Ű0:gMqPMcDwG3ӱT 5̰nu?PK0ϗFPKttO styles.xml[ms6~BtڙHJcv p Zv-"Ӳ*g]>\ܥlrK<3:!Y#%ӟ?CӛxӐ#)9LsA$ bed"[s,\g8%r5IXmY֎'"tT~?/f\$^EXatK۩#XVuO{DP r *IG-CAs5 K]7%"˭i)V.0izcekiZpo}H,싨Ϋh4mׄh5q$XfM۰8 QF4Y9ZcW*SZL3X zK>;jJYdŮNr"-ZЌıIbBֶwL saRѩ (fkuqwf_WVqrۤG}w!_btB^ɹPU20B) ==tUa閈i`h!faYy2OͶnHhF"=M3PLӍ9cfC"2XVջH1F'AHStQB2{*e"* jUVF3F{H BzMS\qc?5v$â>g닻r6pΪĸ`e[$ C!als,p"pC9w"^N5H9T8"Wu j5{?k ̒ Jv?@Ppvq$L)MwFWD;z,OsS:aj5^N'TVX5fؠQ綏8%D$ ֮{7鍉P\X ;0̋,T hVӌ  ]ࣆgiC03I*]\˃纬B,^@(!xP%J0מf("2U~| V:jEչSTekHrGD)ihBB2+a!ﻖ O3"[uO9mhkѰcؑ`)f(g` &! kh5״/M.z>|ZLMA E'yYD~qYAD}]sVa9*v6զGaG@@֪%7'sJ[:QDNW`ﳿ|6x,{Ewm .F I^I 36zHϔڇiHeF{^;\зU{BhI=_/zmH{Ԡ'<6&.;lϸԶ?dmJ^li$F8,B4E 6-CJhl2ҚS0]i\D ˟Yj"9եs:Oˉs=v,=:5A{i{FtΑ fa5~FGhLi_iERBIaUse8N]uJ1JB@) PV/ţD10u xP~SU.^"$H[9mV8۬%8PvJ,HP,<6=5JN;i<G_ l-2|$rIՠ?{zxԚb9-otէE@[џzcRIp4D`ާ)0M8 x6`AV> v`Ac,c'jݕJH`&栰G[vݛ~J]:6Z^{l:\zG[u:=_T(h HeA$6z8jUNXV"VjICgyQ+м-wX͕wbcdґN`~BA$?_j 78ӇEkb^^΂b9 æ}_/6Ǽ:yY vLuW?lPKܒ0PKttOMETA-INF/manifest.xmlTn +,ɩBqrk/T#J؊Win,Q>Hk*Ϩm-MS[Z3#HEϙp +aZ; xxgk&U qXbg RH KT5YhM~|/PK.`-PKttOl9..mimetypePKttOj?hTmeta.xmlPKttO=Configurations2/progressbar/PKttOwConfigurations2/toolbar/PKttOConfigurations2/popupmenu/PKttOConfigurations2/images/Bitmaps/PKttO"Configurations2/statusbar/PKttOZConfigurations2/menubar/PKttOConfigurations2/floater/PKttO'Configurations2/accelerator/current.xmlPKttOConfigurations2/toolpanel/PKttOh Umanifest.rdfPKttOQv6gObject 1/meta.xmlPKttON Object 1/styles.xmlPKttOԔOؓ() Object 1/content.xmlPKttO[ Thumbnails/thumbnail.pngPKttO]k" settings.xmlPKttO0ϗF #content.xmlPKttOܒ0 *styles.xmlPKttO.`-2META-INF/manifest.xmlPK2u4java-binary-block-parser-2.0.3/docs/jmh_results.png000066400000000000000000001134521377434312600223160ustar00rootroot00000000000000PNG  IHDR]9PIDATx@S/ {#*=uW[jkj[[W]eVkkhEmOpԽ8&s!B !^jrs$$_sN ,KGBDDDDTYDDDDDe7 1p7 1p7 1p7 1p7 1p7 1p7 1p7 1p7 1p7 1p7 1p7 1p7 1pd\fD9pl\#&xh:?G^h&I}Xs>V$i1FJŹQ 8^M"*^ T%5FXk(`U7ߟ%fÉaPLX VB*T^%%j'`0_t\miw'[tZۇ[tZ, N:3&~|&6]<؂=:!z-PNASFz s롮3J,~*m&54HsGNcoIdi42 }zw?5grRio}N{mAWæ6aK[ubl Ds*9°"} ę' WWa3z?S[gS=@˱uheJgTi /5  l˅kqJ 4O1k|пrix Gҭ&M@O?+z\Golė?C7C)¾w硕ZP!TC-,~bKHfHha:O7×A+V0M vDz4YONy-0q#pK`o6ƱVkvafǾp!B,xZ:|$^ h'aRQ!_ѷl^}&6&HǪ8e~%36uF//aqXEئuRqs_GgW8㌗{|AbE:5YBAd( c;1˱I_Jv:xv} _?c(aбr֟ہ~YC~ܻ4)p+K늨`&nv?O.j7~'Rj$+nDIqc[vwC1C\z5Tg4K/%tΉ+5й](h{/+uЫ#!D\kR{`>Ϭچ>o 8&C%TvP11۪h!Cqf22pc05 yI\Jq\*/eR"v^^WDe dN zk6>y׵3RӊNKA Đ<Q);m,N> `Jxt}OlņMh\w<$^,ܞ ˜) ?STbZh(\=uy-Bu+<~Z](*m[Ki̋xS! =Y]>E!sc.Z8Gf۱~tmJJ7P<8{9?r\xm6lrᏇafk.+gGމJ\W^WDe7˴xnWrk=Đ9Q(3>|˨W-į6;Spnv/<1 Ҕo{xx8ZC_c䢼O˓['nįGdWGDY+z {,=l zc͜H Kc_?k~&lvxv~Jï:m<}\#(-CK!!KF?<Ei[(BKo26~E>< T^WDe7Q.σWs$+?Vx[2W~a^2%?((ZEey s:8gNEG1 \p\6v:7;xaЗb~,yХ!& {ֿhTgX |a;} U4SO;68Q\{8p 9m=)pjwt˺ܔO _ǿEy+1U46q/1a1rq PԢwY\qBgh5t ~< <69CQYM'%*?Yw_Bp`OdΊgW`X r$uJ}._91}K<9i7e".m^Sa8-E;E`8E̔rdBu6<^݋?{8ISg;OJ!yfJm7/z?;6V9R yy|l 阣_mZ瑥/_qf*oXO='b[cLıObTm]K^ws =֣+g#]~;Č8VѩHliy͐chtvF<<%lqkg-ӆ.c ѦTLчfkke z] Dg◭cWKɌ7/mo}CI)=m:'Q'AaQƒpgcJ>ӯ+}~#!d_unOW򥛱tMvaP/EJB?Tq7/oŧݤixA> 7ØU7xu3a~:ӛD,W%Ԩh)VF7;a+k˘!ꟘzHn~җ {<3&܅5S~Ο~ m$v~= bا˳ЩB>O[C,w\y(ѯ0}صBFD2 etPm">8͐\lQ^WDe7Q>,/QcX8| *x=?C;܋]Qb3C Y opڥV羸JxubcKX^"WM[5^#F a g@:?#,*. O'_]\3G-bZK?O^XPj2ݗGog{~auGAoX> S.a_pMc6P_0gU Z݋a*!ۖcQ ðdv'+ʕaϛc2ʈǕ_^GƦ_=jYZ,^#⨎ yK({ARk#m/9+(_VB| >;>;Y|HZcZ76̟!fGøॆNy/pE;~,-݄6ImƣUD؇>Fg =xy^Xoc.9PdM_1_C,#E=A疜%hfcԇ 4e}v^^<,`Կg4\HW:3TLu Y?Wk~ h{5yS|)|x?x}u~_-\?)'4EŇSEC7CP<2}36u ?3˧jj"&{t>C/Q U^WDe7k;MwX10-,=nO ]MgB73RC} KM ^yso79Vs 9yh֘wG\DV>ǰAJWWՃZ6gQ&0M~t) `t)־6ytoE+t=fC7)7*Q8<\_I7UKAlC¼5vZu?zG12n"""""b&""""2!n"""""b&""""2!n""*rl ȄLȄLȄLȄLȄLȄLȄLȄLʤ8qW^3t:V4"""*ᬬe T}1vvvVR4/]QM~n*_J ۵k lڴ$""rJ-Qr :u*3h/sH3;n*UDAڵk8~E([|!"""*ITA[Aj6} Nۊpm@J߆Uw/֙X$._~s<ke,}&W-Ǫ۱kz'TI0EDH%iii~gΜ%ŠMDdWF 08ͤۤE#YITgU9z.[h 9!N-{|tEkw*}|k-qxj!z˃fĮ[ PT1pى1!EoX&A>\t;2?#PxFF,5O_fQ|7tMQqrAoxaUGg#k a,ƙQF|?to{S1O1v4`&A;99OBB>P®Rd!ſ` nµS_ϖxnUCrf)LunCO OۂMzj#c0TevSIMNblѫ)GX6BDT(']jjCkmDϼeWA\g g|O:_h5ݚ,und~ۀvsq) #:T0pS"h'$$`bܣMDD%wC)4z%%65/߉K1:ͻqOM\wAAbh$7~UxF򑇡[.]xxtJ>n2)E$mqDDNȇ;b8[ [V⁕ 0IG!O6E, ߭kڢR34E[8{5nv _8][O8;KJ%ڴil""zH8ueh'p>#Х& OщE5t tSWRX0fxKiv܊!_XZյ-t8MŎJ`bZ?1R8"^-[2hѣt 4N6Cz!#NKٟFۑ(Mن٫c[̲ kCb#q#=-s'I/k5[̕(!kԨl""2+OԩQQ"KKiZ tJ8KVAXީ'tms6Iu+]h}s7,k}ٳg-JF~&""Ci"aCv/cm$<%bApQ.x~:WG"AKo, E@nKCܝx&tu;>Zu`98“լ)sIh7+n*0Q'O|[jmۖA '4f# cs{S# ~oWFg꣢_e$ގ̘{>lp˾D6VZl;ӧm &iaӄaqGܟ>gwFϢ;٤ՙt -*Tc|$O'B&"*4_vE"R`=vч4l(1& xC4>8V~ѵ4`ǙȇטzU(|u&FmOƂ>w=熴'p9 Ԗ◥2h_wᎵ]a+ĉ#V<A]еpi<֪>-:i7v ܔ'{-fe#b XMDT^qo+y}K:v:RBl3}{` cz+ǶU 2NaVdק17$㬐8+60i b&+̱ֆF#/ف5!Y7WWV觶(LclD}~ ۹>Z7w Ruŷ30g:9u"{+PQ;zML5PJO/3ӲUDEBh%8 qԬl"59 ]HA:Yldi$^Zv ^?~]* *hs0ƽ?vc7bkylB?Ņ(D`y?1ۈ!+Ud&QF.Ca[b'(_ JkvcX}"Zy9̂V7K'O=\gut.ma1Ytzmz\Чhݠl`cc#p*:rN`}M:uJ]\\ЫW/s7(W?_'7ae)DRyv* .F;íBsL`.D!E})NT#G}Eigf̻U0'U4:]$,Gѯ}w/rF"=JDDTb!ѣ}8+eR\PDVd\_BEfi^Y]lF%Ν;@HgQI%vrJ2^:"0 nY& y֛$mUSfv ܂,^ kOSFYQ84];s^.eC;f|9G𲩘o~):0fC[Cd#A[L'B;wNb3zmGDDd Rr?p qiA!P[l:m2r5Qȁ^6_tEOΣoj@c8o Ϊah7t_5`}c# .{2j[k qI_}->}ZiDc}6H&`-,Ñb.l9P+C!)[-S0=?VLDϱJ>|9lz)|VP gSA|@7rgu[p_`ڞX!4u9hDNHHv|LDDT-zoݽgc=8e%m[|.9d Ūn,ęiyKr[š-~<#4CpRvk\>vm3cǪ`wnm`Wעt… r<R\XMDD%_w_'c~~ܴlA;OڋygeT\y<-hXAiπdJŵp1 wM)~}O[_4 ׉JۀCEoudݙٮhڴ)6HsK?9Uk&}{Q`ϭTuL [܆"z'8%~ca\+h6#p1Qw{pN(S3 p[NWKʁDDTReΖcŝCoX[ (걽9ngfvImT3=,@sKwvWJ9.j].S0bu>J$Eov؝[r)p$X<.ǕJHK6y'NGO GBD|M< LO8俎φ-`&"Fg'&&N,H8P*H}NhֱG$c.]\g7}L%w4H<tYxFUGU^]10u:ȏ;aWP^c[FDDT4iN{o#ց! q MlIp=> ?' gCy]9{T*߁[tؕz&RuJ3Xow 1Z6A8io1ҿm^lG )߁ Lj ZÐl X[9j%:.=hJ;)N٣]F1pQ?uAHv*:HR e2 )MkK# Le 7ȱq6E 6tb. ѻ-JJ,U>e7>`3"qnE DDDDD&MDDDDdB DDDDD&MDDDDdB DDDDD&MDDDDdB DDDDD&MDDDDdB DDDDD&MDDDDdB DDDDD&MDDDDdB DDDDD&MDDDDdB DDDDD&MeNZkZܻw-""*R6w+8EFEB2w3J=n*DNJJBTT\R)_mfZ(Y* *FN(vVVVHMM5LA;11ŋX[[#%%|*M>KJ*TA*/FLL bccqyDGGgA-STxR"+t"XTzw/os7F7j"hp-zϜ9F#( TV ͛7Gdd$6mdQ9MA;<<r) vfPF 3(7*"hWƍrmvttDVn&eM%~ ;u---"pwؑAJ,n*A[̝}…́b؝;w6wM%(gA;@H"""҂J Q'GDOp $f dv"h\vM="hs $ dlqBCCC촴48;;3hQMJ턄ܽ{Wuڢ>[,X"BQMBjQ6"N~ҥ́٢E 34ɤA[ Zp $ d3Bgfd}67 dT)))r8͛7)Df&""򊁛L?Rm2Bzyyq $k Th}yQBR8(7Lm1RׯgN'6BeM F#/g}6Q)_ryj?8BDDDT0 ܔSB́DDDDMA~bm1DDDDc&(ZG!E@H""DgP ߗKiDf}6q0pSG[8DDDD&]Έ}=y 7--M^3Bw9!B1ȭ[etDfs $0p 8x^x7=OYG𲩘o~):0fC[7q1dqD!6m5kgC>pk⇑1ymr]wV Cl l1}[tQݓQX޽XܸqCNNN&"""*f3W9 8uY3䲆 4}'|Tsm3cǪ`5:G!B{ũmA ҥK!HDDDDR7`!mqE)a'p[ʬw7oeDg8ݙPyQjR8);?pn95*n/+gu5v&C9@H9da.IrTcS0f}6Q 'q9jN4,=v51=`iu)߁[mov.\lm½5=kSo``Cq`TO4:DDDDT֔: w=[T0!"""2|n"""""c&""""2!n"""""b&""""2!n"""""b&""""2!n"""""b&""""2!n"""""b&""""2!n"""""b&""""2!n"""""b&""""2!n"""""b&""""2!n"""""b&""""2!n"""""b&""""2!n*t:gϞ5sJxhm *N^AtdQ1pSj0ܼy/ӧOeDD}E< s7,߲j1pS"zܹ[nɗ$T*{{{s7ԲWAՙTmaoegf;Z7nEoQN"zq]9t?3n*Qui"XC *F]wQJ-䒑p|D͚5?"##i&3+n*uD}vttk}5ED5MDDDT0pS!譾r\6"꫔J|XܣbfeM%Ǿ~\6"BE}nժ[HDDD7n*DyHbb\6"jEԪU 7s+M%~ hР볉TaAP>;66V*UM>J%n2+Ee# DDDTf0pYbJ?Q-ʼnk1v͚5YMDDDe7}}8#qDh뗋rYMDDDe 7bljۛ!r`e"W\E϶>he#&"""7=>[jmgr%Om1RXZZjժ]66Q1pS_}f}6Q1p ߿/YMDDDTt ڢ7[ {|[\XMDDDd< 吾>[fGGGCV~~~&"""22rDeA"hEو(a}6i0pp\6YMDDDdz r}= NMTPx .˦baXl|rd̚=mT):FtE>;&&P(T볉w~t5._6cY*{ ' ֗/wV Cl l1}[tQݓQZSu\Q-)Gݻ'Fgs $y0p'8~3 7`p˪ϖ?b4>OnHʝmzi{Fcy\fl1[OTl""""aΏBK)+̺bBͭXsh~OdV(f(:/ǎU 8:扞#22R?;--DDDD%w~͑v S%u9]ܑzn}kd E#oߠˈ8(0Qwl DDDD%w;;vEăcሇ=s |T:^7>qq 2+++GDDDD%w~tq88-.U`qO]r{r΍YRu F <[Qq`·گx˓9N +A}4i`~/bWo`O8"7#<^.gaGckm3(^E`[5}[ ~E(h:#h$:N^=k\8)Ј0pòbuTD .^NX}pg S͑ojsv%{` :f;J""""2|XI]k $0?"Oc'c=6>x 8X`ԌC|>v51k9 Q0.1\ 1sI6b,}l^ oeE:};t3F-,:DDDDT0pұ~A UTA D""""0sUn ,hMHuÀg|7Tm(( - ,1oNw 5=_ Cb` ~+.=3R*!CCCGQ-ʼnj8*C< o)P'^W@%KkTxYڏk`.!٢|DFj<1sIBە/IŵqǧXjhЦ3kPLDŋ!E[g]jUm"""*7m ̟=^Ŭٗ3eԳwꆵJ* #d5ʼnj8#nH,3I__6-t\ڼzuK4у}ilD ԟbŊ YMDDD噙KJT{BATxխ O |X pʕG'e# MDDDd5]HW\<ð*n够qD̟u @:uXMDDD6 翋nV Sτ` z X :)rOmQ}Yf}6QSpnVO{ Z Z"p*+}'߻EaޖW"hlqѳ-BTՈ ͼ; ~- Q~ۈ ްJ}1?q&;:؛֧N3]\\lgyw}G^6Ħr}TLގ8ub 8#ڂ~ $볉 gmQUobpL U; 9;p4%&'m UTa&"""*$nF;x-  :5 v_5py(ڙeVn!ʼnj8x߄^Fcm҃/vA eYs0o>h9sF'џDDDDeKE|{/|sOǭX5\*vlY O}ow wV Cl l1}[tQݓQZSu^ϟ?/iD5ժUjywr0,؏uW?St{ל;A'෤ƹ!;u* :is?ķĺy#+wVE kB_sZ*ӧmћmaa777$"""2#nXl&+V O/F79lcӯ@{'2D6C~9v F\|I֦P3?~\NMM{2OTPc'""""+1ӀRpU܉zM_IJtc53Xlj ~^k We֍m}72"B_'N+n% Lb#⺳\]jUm"""6|g8&=˕~<,|xIul"uo_ap:_&B{tt\\ċ<Ŕ~j@H"""́;tyJ-mS NH+W}4~*֩SL_?kiT69;dsGlIKKC.]XMDDDT7p'+cԊX33[n߁#0S~ -lDDOƨsڲrGt;|i}*;["5&Bi+`=zf;['6 YjQ.tx;8Ľ[YcNA80$f@ye!"""*e!6N۱irMJFY-%U>`&6I<d""""*SEs|-t?4kBtu D1s"bX9;ky^2k`^Ϝ*J?g$'.,*[`0T{98|Vl(Q7p'_0 ?lsTk[xq- DDDDT*7pe+a 51@Rm 'Upf1^y<7g[@%GRo"h2L|3FAcsMDDDDT Gv+'qoE vp,s*`xyU9}SaZ!CDDDDk54$]"""""#2S ğoI=N?"`? _;Q)eCΉh'\9}JFZ cusw,^❈05a{<\o)lB5BWMJxu, 6=] ?_/ojJI,)Z{~? O뭜{UzGX ۆKRv(zꊆl.{pkN6/jfYl +{+V@(jOEg#9nq`Q)U۶::WNjv!Sum:aDUmC"""" =ܾxI߾85!8~% +wekobohsQ)eig!l'1f$}~eZrCDDDDd dY %7UFQU17ȘJfnQѕMd$ZuF 6EDD= *N{QJ!$Z+W 88IIIwttFDTj%**HeFlBInFBj񎛁qY\v 7TrN5jDDR݌2d=U|P(v.]2LHII#Gpmw[J'x-%"*vxeRAz6w37QQ[ӧO#"""s-QfMTPr&"""2n*uD(9w[m TT VHDDD'n*re#l???_"""J1WիW3EɈ>&""RJQ*r9l}}vŊlgQiMf%NHsG@ʕ+>J=n*vDDg:uDDDT1pSbJ+WA[Qm9`WRn&Q1pBGrADDDTV1pɈ:tnݒCږb $QYMF%JDDɓ'%ϧ-ɽD5&""򄁛BkQ}l,٬&""򈁛Dj12k}899Sl"""*PĜV-BKFD667 g}-lQ6"f@H""")_ygEȮ^:볉My=آ>[2٬&"""*8nz>p#SWTAI&z###qqDGGgggg3Ba˹A;k}vwwg}6Q1pSl1Hjjjfو8Q#,!"""*:rF}ݻق)f}6q1pl$$$aaamE}vʕg<z.D"sr][d-j ȀgA./wV Cl l1}[tQݓQۺ >ĉe#L&"""*> FnŻ×i8;RB0?ouFWn L3;N7"q޽r(P\mccSADDDD]PHl}g8,y[}k\>! N˱cU0Nb6+DӢH}|< DΏCC`{SN&\ѷ YE#oߠˈ8ݙ͛7g϶\R%""""a.QJ!|kć#p1Qw{_׉׈o"Q-`&""*Otn凁;?Hlyg8~wy>hyw ![g&"""*yK-1b>$r#!"^M<=<:>Z2l#""""*V ܏p7-7s9= bᮨ_r> ,IaWϚ:Nb;0""""*& ܏eֳًHaBp?wCjPdn|UGU𝋰+^/|EKaÁ`K)U5:==M*Q9_(K~XGKYmUy+y@=wCM8[Z/vT՛CxhiYm(K {ߺh}vPZ}4+bԌC|>v51,k&[&wBCK!X..QxQI| 1R!1|՝˵uf+/#׾_wƯjR0<".?IKM݆(pVVةS70G!80cXt2 Yö{#ti ͵2EMQQ E#Y?*ݻUpH&o)\m]:sBٝ8|#-}%zo_<1YfNDDD`.pZ7!B-M%OY ߬Bvi8~#4eh[]ߖJ: ctxYk {ǖh c$wOg'iXڿO_u˯8yNenh38kPQ =C?@ئ7n7p*"2:GY銖_%np9J T!!KxV:CBlµS_5ۖxna[Crf)L&w<܇WFpaWASxp'N#Bln"""cb;\!).3o[V).{Oz!#[prRm:w0| 1cLnkO&֬>kq?&O“Y S 8E PJc6ynt~uޏD܍!^YF,*ڞX$eهFtZeI)R.69W!""aR.G?{o;WeI1s88qlnox].:?k0`Vԃ]Z÷noμS6ĄY`:UFm)lGmBoY?_P|t+{mltON ݎ-r8bٝȸT\I]ɲ$Y:j5Ō%8 w{ ~+b~ѵ4`Ǚ,%(5&^aik6Jht_P+ĨrǍ}PzhS i7O蕇'فs}1[5} tB;R+O?~U\U 3IDDd\ TE^lҷq!E8+z0i }Ғ?˳XxQ= H;Š_aTUBnCDDDOW*n]k Y{N@ Tn^zAM[̙cz  SCϠvx콀sף gy*yNFTSԋU{M߂6iqF4sDFѵhf;)6TZU6FZ"""cRͦt\M:شiT*^|־xr&%%Ν5bW9נPDS1oGZI@\DGge'WY/9|[#[F%HۘvW]M ϡ[(""Bc&*Ϟ=ӧK׏I!*ZY\ 77_4mƼ"""*$nBxY۷FsY פ/)t@HZn""* H ,QQ{@xZ w`afՇm*U ""(p Ο߆迡TޒBv"BVV#""2"n<^殹Ue+`5 D9cϞsg/KG}"ka3n*DuTTDjXS']CR iBdHQsn?^c;"*.W#ܭev8YڙNL%,20pSܹs/,999ǚCV_cs(HyAtazJu.442T$knFb&ʋ!!!EOn'%%j*sOΝ>#XZƔ$lch4NυcѰHefP1j\*СCF7 "PٳRxV#^:,,,p1yyJVVWox9,2l.@mm Ec-QTw+1 DD{.-*U...B_ NbժeR>[{V6R|nA:2j>n*uDо}k/|||r͛qR^*ixg>T(;n*5j<R\uAj-z+V(r٧NBPR<%sGZ~T4Y{ J< `~r+,--;#<|QX[Ja;A4-lgA`޽;@Hы]jU6[\ܹm/)`6 *UB 8ּzs DDDM%Jn!E=ժU\-ֻy[ֹ/mgpBL7*.jm""c!=*IN 볝堭{ߕB.TtVV,) SS_ĩEO޽DDDdn2+Q-)jgtڢ>[lQ6"sns)Oc2؉@ttzNKc&""2n2 E#dD'UK^p2%@tzAZloB“Y#pX`׮]!E鈾>[+CGh4P(B|%֢[Mc Ù3g2Bl???T\YͶ3w3LLF^Gimmysg5 dt[ ''iذ\6"C2hQyMF@H!*n*4رC"lp-'볉\cBb ӧ堭P(-jm""""n2Frr\mcc#g;::5ڬ&"""z Dse)v:uPB9hѣD!ڢ6[!WWWWtMRؾ}{UV=ڬ&"""*nʤ)z-NT+ADDDDa̠}!y -B6n... DDDD]`-BՈb0dJlQ-zȌZ5q?YZrA@>6ctM+ ݿ0_"z'{ 2XGK;_%EGc_&4mή[-ұH˔=wCb.AԩSr}PbEy ikkkR.cɐxe51|o9VMގ];2]@ ^ @Ba6L æ^i[^S!1|՝˵uf+/#׾_7?;W9"fٶm#!EAHŹ~з"P-Dr~Yj}3ЭHF?*]Ϫjs8f4 x ds¶uVX gw\[aF#/DD%w9?#~ |Q-JGXMT$Ng޴l3gODmF9н1&MnDOV'?ǻ.v/> ;o$~ D,5O_cîqGgmj<_7Ͼ{"M{z\"اbiz>>}-чQ)](.ޝ{KoJ2Sf{t^6? /Bgブ&cqh%TuH!E}3nݺrf}6Qɥ{o٢הa[Q_W⾸}wFdMº8 77#pv6$d J7gY J=x" N}Wl[m^k !sD"2I 8J|%]h2N۟bx;p=tj ]WgcM`#X؈Bj힌֚cl1 !k֬)mQ:BD%&>捄-ܯ!g4wnE;'OEZZ59l>Qٻa|Ah(yqYCbpf@J_Jum g,S[H qhO3jX{jPC@˟mcWa<.m6axeEE5t tS` Q`*3{>)A XA)񰇛}Oş?:Sр-f{E=DTOb殘s1}Iāe} nh817T_mǟN=F(`_o8>ugt 8h9~;ź)0{u ~+EH={uŵH:kbIKWAR1-]*svr,:lMj*Zj%Q٢EO>Ĭ?\J*Nien yxk]~CD7_n)QxcfS M_ta%$!p[FPG` haNcxU Qb.(mnw[Č>xTp!DG gq,ijMTv)]cM%r]J|sW2kqfʶMժ|571[u*ATR WVR$>8KV]AXީ_SJ>hyt{i,uk*!磠eꬤ8xq∨ҥ!U\xCvCԁ8Oέd5k"aCv/cm$<%Ap. ѻCWuz? vXzs7xeS(E31:I DT0pKēADZjTGFɫb@=`u9 TU-w.®d z]L1r_*7x܁&Μ۽khdQe\K4;;+)o+Dxg^p4 1a$4JNcq8?cgJI…_`VZHq|c%꾍cYUƧZשQ3>w_ikvĔҧe!"T^zWX Q9ꂮ5K;v AzVGm(EPg”:,~cATsC8zwupHD w>b8R3{-1v aH *rw *x\+a1Xuxuj5y(g_|fN_:rWe0~0-4j->JT<ݮF4ÖɮOcnH`ؕc[FrzT`&҇;Vu?EE3Ii""F}UNEk7\p}⭏&:& /"1>\ mq}$L,UV:ŋO{UlkW{1ph#*0c 6+w@"wu4n=MGq` ؈_"*AJ*+t}RtrC~8c nލ׺b: mz it˲m8pv1b%>-1 ~G-s^rKͳn/;?ܫ9JVUdp4m""3c&"*n\#BĮu$XQ1="U]mIPEDMDT\>+~EPFޅerB:iCȸ&_tI=wg&*ʁ9r[ԠPE܂eJ؅ӥ?|=bp]fWCb\[GD%7Q+@wzIJKy ܮgmEe^DT0pQ1G\8߿r(o_*1ʴs7T{X6N1ʐ偐;AvI Ph3Y6R(Pغˉ #[[au6ɉR֘YRβAP DDTbb> : ,"n&5ET$2"`&"*e}V.(`N#E[-+DDMDTJDDqo2Xܾ Bd&ϻy: 1;*ʼnjS}1(1h0pP΅#XE߅}jà.|e/뿏\g&"c&"*AD}vБCUmإωjb\s6n"@/ ݰNIRV:f)iI=m"*n DDfCEĚsvrN}a_(ur'ϣ~0pڇׯF_a{\2 (tٹ@{1p6#tp2`AP"@iuHwtIѤlgd&"3c&"*FgΉC B% -P)nVɗw)kKa[8QZa0 DT)X|a;7`w`=8[*QR%KH MHJ t]{_ )3ߋڻHu0w+8߾*s7YZZ"--hc&"Q ;bIJuVk+*у]ҿX=(3dThTAժReZ5j5KS' .aw`. &9)el my:)P(6j4耺he&O_6>qP\e$rE'vu(Lt:'ԫ 4\RbL DDDDT.YZUBV]P0M4f&"2lQhpZ߿J3pSh#GCTh DDO;;;XYY"B6l 6{b o 6RY6RtRPB'A9:A[>t5kA |U,RCޚuFJSPAk4%> JG+KII͛y12**TrFmuw0hSiswN]HA17AJV48rHyZXVB6]lћR= dRKKEDgkԍ-AY7{*ߴ>4l mʀeЦң୵[Zv&MZfX] dtG:$j1ZXqvH1:YGgS!}kyM:ЊF~SyHvAg+u٥W{|:shStq"f?`:mbe剨h|]O ڵt9:B]&4@ZAJ̠H:{XJ@HũK[}vA0pSp[W"#>A[Vii-_ dЦAD#6VGXM%]ViNhvBu-lto dO٢7,m=n2XZZ6nܘqFHJ%,gBg6Z]]z@HgS ^o6l <6볩ң-vy di;Qn֬ufV/ T`YB>#d\=C"h&7^ub  T*Hj/oթMem؛M%_FvҪ&S] ]WЫẂ!h1pc0-Bu4"e9#tHNn$HKߞYzYihsx<ӟjkTGrRKi9볍Ekk.#h3.*=)u#U8bZ5"&MYd\F櫖.1-S<5䁐D۶_%>m=nGvލw:m"/K;%/NXOD|c*1LI!}qDRb9lk4`}qi*!JbބMj*4 XR!Q+ɕР *ckTj[TNHdRP1.4S@YAKK |4VЕ4oFiiiY A{RJRvfpVkg;l)]xj )ɮHNFjj%HB)R٩#6>,0xJB=*!ՍAۄģ- ո`X)v+Kc]Baˢcvh;m~Pttt{s]Ґ۷oRNJ&כHKׯaREwCbBҀFD>GtǃRUt3^|30J+H |0]l֬|FȲ<0p#~ypB)pGg,!;7oS,TdC"2پ\B6lM9]-N,G /} $-q䁐jj.$⤋G𲩘o~):0fC[79FCBB`gk%-MDDDn޼TRNTcl F;uu66>-tFɨmm$ukQ.qZ <+U\lc..)!?ķĺy#+whE kݜc֮]1R+DDDTBvD-Ym D}k\># N˱cU0]!-B۷O ))2ˌ#DDDDѠaGMMo d..#N+n+WO*v|DDDDyDQZ볋w1ć#p18Rwqq DڑH-&""MIŃtYO+tSO=U1p'qwh.ʭ=l9xƯW=2 KDճ&X>EDDDT0pWW L]7G*#EEؕA/ԃIDDDDF]\j`ԌC|>v51%;E F ^ oeE:};t3FGDDDD]>`&6I""""*LȄLȄLȄLȄLȄJ$_Ox[(~m"sn:|lͣ=%=T1pS1A>\S¾R 4jOy nWU79ѥ%1 {kӗ۸j65L5Y6E:;qݽ/#TBhpy}wP +&h:i2,ݠb$y5lV"ci*eIS;w7ԏޙk7Iعw:*Ebq!ibLQBr*;QQ1pT JPDt_y&kN-B?q[aZ|γ3s{p_x `6>]NwY}JA쑙a[Qs(f|=x^+gcg:Q @|߆ےҊl>DEMfR# >x)w~ë@`π*xemboGoM%971sŐ'zgx*&=*7q*YankehҗXJTc 䵚|_ }]!h0'Lx1/|9 噌| ƞzv`VIiO|705m?_~ u qiNuPM\CקJC`l}3FIC<܁~ـCGW{n}~ݮ&>-}%QU≯V2c#]4o;$`11)x7{SWs[ 'pH+2cOnwsXSxgT5oM_᩾8w]Զzc{p _$e[x5C ׳t 9is>FcNP$6.'q)&g~~oc_MYm v}]Y-D~ ^/>O=|`x,ăEd.plfb,h`Q w_hqr[Pw_y1kX1uEĆWѤ 6qqJ?aۑеSyM%m^A? OX OqF&lϨ0LTQkTigEM[øƋ{/-ők—>.q>>=ć:_up CxOi!s1ÔNY4/ݍw x~t)gym]|=j3ޯjۦ I8e`C':)' N ~oS0)-I+U*Ego_-b1cc^LƶѸs/i*RaꃗB8F}\`H F]KȰ@g'DDjx-  x6uuj ɷb!w*PU'x^?o^*nfl4]?FYyԔ>\ܛW5*By^gR|*PT=7ZwYxw<,mht E|j>YYnAU͓K#0 Mde5N-9,:VUеCE)p-'Wq.2B5}2>z?aA}h?z=_”6R -ZAN]~eO7x?cp,%S-ٿHPO {lOgr&oyhb>b^"c`Qziїqd?XgXr,AZr:úNQ XcF{?4mtV6o~Ģ8I\aWoϷ񄳲SyM%w7V'g't]-ǏF_*9jT0~`Ues?` ضooE<6?㉞kO4+2o"yڥJs^O3C=}7SA?=DQL2VVC*Tz?~!k!]ZD wo\J MQXF 1>%ruQ MEGul)$Ί߱4s$m%,T-Edt9|5m)jhe"\g }, >y d6gcipay&\׿~ޮl}3Uxw? ]̄'^-,R1ە1\O1825|9(*a7㤏yN|$㛉 kśhb3a־r CΨSLʼny_qŕUxoҖ̚/wGnߧ M84vXVKfwjں{imJ[ent&0&bt&FT$s>c8LYMLQ6"HIsk8I-XtoI(˜dgL~P5)۠AqGCB*7Lϸr0Y3zu"ڒ(,5-Ҏq|^JCиst*w|@gn?Eb/o\$>=pSsel|j'P~ߐ:ü79i jT23WB+:1/~}{RcG8!la)c9݄e %*>52a^%Q4@i>O|aX=mB,w[l9H90vo#w9΍k;&cu1t䆇A8pz..t=.4Lu2D->fvD]X0-Qk/VL 3 nn[-ZC1'm+QMU LpA@D4Sf!}bMN2wCt%RqP(ձmLcgƈiX xxtknPy8ђ\^/IfY'5n˽ t1o{}|,t1&۠[>k ݒa/8sq&o?WDûP]\R k!ʕcMA"RaVEbњEH%=cN݄/ ߞœl-/mԎ|sԘL"2 706cuƼj嶱4 T1Q(ODdpYn"""""+bMDDDDdEL 71&"""""&DDDDDVĄȊpY}`QoRzIENDB`java-binary-block-parser-2.0.3/jbbp-plugins/000077500000000000000000000000001377434312600207075ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/000077500000000000000000000000001377434312600230605ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/build.gradle000066400000000000000000000031641377434312600253430ustar00rootroot00000000000000def getProp(name, dflt) { if (project.hasProperty(name)) { return project.getProperty(name) } else { logger.warn('Can not find property "' + name + '" is not found') return dflt } } def jbbpVersion = getProp('jbbp_plugin_version', '2.0.3') def metaLibVersion = getProp('meta_lib_version', '1.1.2') group = 'com.igormaznitsa' version = jbbpVersion apply plugin: 'groovy' apply plugin: 'maven' apply plugin: "com.gradle.plugin-publish" sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { compile gradleApi() compile localGroovy() compile "commons-io:commons-io:2.8.0" compile "com.igormaznitsa:jbbp:" + jbbpVersion compile "com.igormaznitsa:meta-annotations:" + metaLibVersion compile "com.igormaznitsa:meta-utils:" + metaLibVersion testCompile 'junit:junit:4.13.1' } repositories { mavenCentral() mavenLocal() } buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath "com.gradle.publish:plugin-publish-plugin:0.9.7" } } sourceSets { 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 = 'JBBP script translator!' tags = ['jbbp', 'converter'] plugins { JBBPPlugin { id = 'com.igormaznitsa.gradle.jbbp' displayName = 'JBBP Sources Generator plugin' } } }java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/pom.xml000066400000000000000000000113501377434312600243750ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-plugin-pom 2.0.3 jbbp-gradle-plugin pom ${project.groupId} jbbp-plugin-common ${project.version} provided 1.8 1.8 gradle install 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 gradle compile ${gradle.executable} ${gradleGoal} -i -S --no-daemon exec java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/settings.gradle000066400000000000000000000000471377434312600261010ustar00rootroot00000000000000rootProject.name = 'jbbp-gradle-plugin'java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/000077500000000000000000000000001377434312600236475ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/000077500000000000000000000000001377434312600245735ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/java/000077500000000000000000000000001377434312600255145ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/java/com/000077500000000000000000000000001377434312600262725ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/000077500000000000000000000000001377434312600310015ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/000077500000000000000000000000001377434312600317165ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/plugin/000077500000000000000000000000001377434312600332145ustar00rootroot00000000000000gradle/000077500000000000000000000000001377434312600343735ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/java/com/igormaznitsa/jbbp/pluginAbstractJBBPTask.java000066400000000000000000000037411377434312600402670ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000023531377434312600375440ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000071441377434312600376560ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000151221377434312600402520ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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 @Optional protected boolean addSource = true; @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.java000066400000000000000000000007541377434312600371400ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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) { project.getExtensions().create(JBBPExtension.EXT_NAME, JBBPExtension.class, project); project.getTasks().create("jbbpGenerate", JBBPGenerateTask.class); project.getTasks().create("jbbpClean", JBBPCleanTask.class); } }java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/resources/000077500000000000000000000000001377434312600266055ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/resources/META-INF/000077500000000000000000000000001377434312600277455ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/resources/META-INF/gradle-plugins/000077500000000000000000000000001377434312600326625ustar00rootroot00000000000000com.igormaznitsa.gradle.jbbp.properties000066400000000000000000000001031377434312600423500ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/main/resources/META-INF/gradle-pluginsimplementation-class=com.igormaznitsa.jbbp.plugin.gradle.JBBPPluginjava-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/test/000077500000000000000000000000001377434312600246265ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/test/groovy/000077500000000000000000000000001377434312600261535ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/test/groovy/com/000077500000000000000000000000001377434312600267315ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/test/groovy/com/igormaznitsa/000077500000000000000000000000001377434312600314405ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/test/groovy/com/igormaznitsa/jbbp/000077500000000000000000000000001377434312600323555ustar00rootroot00000000000000plugin/000077500000000000000000000000001377434312600335745ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/test/groovy/com/igormaznitsa/jbbpgradle/000077500000000000000000000000001377434312600350325ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-gradle/src/test/groovy/com/igormaznitsa/jbbp/pluginJBBPPluginTest.groovy000066400000000000000000000007421377434312600410400ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.assertTrue class JBBPPluginTest { @Test void demo_plugin_should_add_task_to_project() { Project project = ProjectBuilder.builder().build() project.getPlugins().apply 'jbbp' assertTrue(project.tasks.jbbpGenerate instanceof JBBPGenerateTask) assertTrue(project.tasks.jbbpClean instanceof JBBPCleanTask) } }java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/000077500000000000000000000000001377434312600227305ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/000077500000000000000000000000001377434312600274055ustar00rootroot00000000000000jbbp-mvn-test-customvars/000077500000000000000000000000001377434312600342225ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000024701377434312600355420ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600350115ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvarsjbbp/000077500000000000000000000000001377434312600357265ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/srccom.igormaznitsa.mvn.test.jbbp.VarCustom.jbbp000066400000000000000000000002331377434312600464620ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600357705ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/srcjava/000077500000000000000000000000001377434312600367115ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/testcom/000077500000000000000000000000001377434312600374675ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/javaigormaznitsa/000077500000000000000000000000001377434312600421765ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/java/commvn/000077500000000000000000000000001377434312600427765ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/java/com/igormaznitsatest/000077500000000000000000000000001377434312600437555ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/java/com/igormaznitsa/mvnjbbp/000077500000000000000000000000001377434312600446725ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-customvars/src/test/java/com/igormaznitsa/mvn/testVarCustomTest.java000066400000000000000000000030651377434312600503240ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600370435ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000027371377434312600403710ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600376325ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstaticjbbp/000077500000000000000000000000001377434312600405475ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/srccom.igormaznitsa.mvn.test.jbbp.GenAnnotationsNonStatic.jbbp000066400000000000000000000001311377434312600541270ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600406115ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/srcjava/000077500000000000000000000000001377434312600415325ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/testcom/000077500000000000000000000000001377434312600423105ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/javaigormaznitsa/000077500000000000000000000000001377434312600450175ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/java/commvn/000077500000000000000000000000001377434312600456175ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/java/com/igormaznitsatest/000077500000000000000000000000001377434312600465765ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/java/com/igormaznitsa/mvntest/jbbp/000077500000000000000000000000001377434312600475135ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations-nonstatic/src/test/java/com/igormaznitsa/mvntest/jbbp/GenAnnotationsNonStaticTest.java000066400000000000000000000040321377434312600557670ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600350435ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000026111377434312600363600ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600356325ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotationsjbbp/000077500000000000000000000000001377434312600365475ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/srccom.igormaznitsa.mvn.test.jbbp.GenAnnotations.jbbp000066400000000000000000000001311377434312600503040ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/jbbpubyte len; some1 { bit:4 [len] someField; ubyte len; some2 { byte [len] field; } }test/000077500000000000000000000000001377434312600366115ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/srcjava/000077500000000000000000000000001377434312600375325ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/testcom/000077500000000000000000000000001377434312600403105ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/javaigormaznitsa/000077500000000000000000000000001377434312600430175ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/java/commvn/000077500000000000000000000000001377434312600436175ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/java/com/igormaznitsatest/000077500000000000000000000000001377434312600445765ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/java/com/igormaznitsa/mvnjbbp/000077500000000000000000000000001377434312600455135ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-genannotations/src/test/java/com/igormaznitsa/mvn/testGenAnnotationsTest.java000066400000000000000000000037541377434312600521560ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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) 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;" + "some1 {" + " bit:4 [len] someField;" + " ubyte len;" + " some2 {" + " byte [len] field;" + " }" + "}"; final GenAnnotations instance = JBBPParser.prepare(script).parse(testData).mapTo(new GenAnnotations()); 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/000077500000000000000000000000001377434312600351035ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000030761377434312600364260ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600356725ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssettersjbbp/000077500000000000000000000000001377434312600366075ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/srccom.igormaznitsa.mvn.test.jbbp.VarCustom.jbbp000066400000000000000000000003001377434312600473360ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600366515ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/srcjava/000077500000000000000000000000001377434312600375725ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/testcom/000077500000000000000000000000001377434312600403505ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/javaigormaznitsa/000077500000000000000000000000001377434312600430575ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/java/commvn/000077500000000000000000000000001377434312600436575ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/java/com/igormaznitsatest/000077500000000000000000000000001377434312600446365ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/java/com/igormaznitsa/mvnjbbp/000077500000000000000000000000001377434312600455535ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-getterssetters/src/test/java/com/igormaznitsa/mvn/testVarCustomTest.java000066400000000000000000000032641377434312600512060ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600342075ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-testspom.xml000066400000000000000000000025121377434312600355240ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600347765ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitivesjbbp/000077500000000000000000000000001377434312600357135ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/srccom.igormaznitsa.mvn.test.jbbp.WholeStreamByteArray.jbbp000066400000000000000000000000601377434312600505770ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/jbbp// some easy JBBP script to test byte [_] array;test/000077500000000000000000000000001377434312600357555ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/srcjava/000077500000000000000000000000001377434312600366765ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/testcom/000077500000000000000000000000001377434312600374545ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/javaigormaznitsa/000077500000000000000000000000001377434312600421635ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/java/commvn/000077500000000000000000000000001377434312600427635ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/java/com/igormaznitsatest/000077500000000000000000000000001377434312600437425ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/java/com/igormaznitsa/mvnjbbp/000077500000000000000000000000001377434312600446575ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/jbbp-mvn-test-primitives/src/test/java/com/igormaznitsa/mvn/testWholeStreamByteArrayTest.java000066400000000000000000000034151377434312600524420ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin-tests/pom.xml000066400000000000000000000025641377434312600307310ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-maven-plugin-pom 2.0.3 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.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/000077500000000000000000000000001377434312600262455ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/pom.xml000066400000000000000000000220441377434312600275640ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-maven-plugin-pom 2.0.3 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.1
parent
modules
profiles/profile/modules
true
upom
org.codehaus.mojo animal-sniffer-maven-plugin 1.17 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.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/000077500000000000000000000000001377434312600270345ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/assemble/000077500000000000000000000000001377434312600306275ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/assemble/bundle.xml000066400000000000000000000020111377434312600326140ustar00rootroot00000000000000 bundle false false jar ${project.build.directory} / *.jar.asc *.jar *.pom *.pom.asc original*.* *.zip distribution.xml000066400000000000000000000032051377434312600340110ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/000077500000000000000000000000001377434312600277605ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/000077500000000000000000000000001377434312600307015ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/000077500000000000000000000000001377434312600314575ustar00rootroot00000000000000igormaznitsa/000077500000000000000000000000001377434312600341075ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/comjbbp/000077500000000000000000000000001377434312600350245ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/igormaznitsaplugin/000077500000000000000000000000001377434312600363225ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/igormaznitsa/jbbpmvn/000077500000000000000000000000001377434312600371225ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/main/java/com/igormaznitsa/jbbp/pluginAbstractJBBPMojo.java000066400000000000000000000146561377434312600430270ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000054721377434312600423020ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000354741377434312600430170ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/000077500000000000000000000000001377434312600300135ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/000077500000000000000000000000001377434312600307345ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/000077500000000000000000000000001377434312600315125ustar00rootroot00000000000000igormaznitsa/000077500000000000000000000000001377434312600341425ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/comjbbp/000077500000000000000000000000001377434312600350575ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsaplugin/000077500000000000000000000000001377434312600363555ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbpcommon/000077500000000000000000000000001377434312600376455ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbp/pluginconverters/000077500000000000000000000000001377434312600420375ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbp/plugin/commonJavaConverterTest.java000066400000000000000000000043061377434312600463160ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600371555ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/java/com/igormaznitsa/jbbp/pluginJBBPGenerateMojoTest.java000066400000000000000000000115731377434312600437040ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/000077500000000000000000000000001377434312600320255ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/com/000077500000000000000000000000001377434312600326035ustar00rootroot00000000000000igormaznitsa/000077500000000000000000000000001377434312600352335ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/comjbbp/000077500000000000000000000000001377434312600361505ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/com/igormaznitsaplugin/000077500000000000000000000000001377434312600374465ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/com/igormaznitsa/jbbpmvn/000077500000000000000000000000001377434312600402465ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-maven/jbbp-maven-plugin/src/test/resources/com/igormaznitsa/jbbp/pluginmojoConfig.xml000066400000000000000000000065301377434312600430660ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3-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.3/jbbp-plugins/jbbp-maven/pom.xml000066400000000000000000000021451377434312600242470ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-plugin-pom 2.0.3 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.3/jbbp-plugins/jbbp-plugin-common/000077500000000000000000000000001377434312600244065ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/pom.xml000066400000000000000000000014761377434312600257330ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-plugin-pom 2.0.3 jbbp-plugin-common jar Contains common code for plugins ${project.groupId} jbbp ${jbbp.version} java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/000077500000000000000000000000001377434312600251755ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/main/000077500000000000000000000000001377434312600261215ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/main/java/000077500000000000000000000000001377434312600270425ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/main/java/com/000077500000000000000000000000001377434312600276205ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/000077500000000000000000000000001377434312600323275ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/000077500000000000000000000000001377434312600332445ustar00rootroot00000000000000plugin/000077500000000000000000000000001377434312600344635ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbpcommon/000077500000000000000000000000001377434312600357535ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/pluginconverters/000077500000000000000000000000001377434312600401455ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/plugin/commonJBBPScriptTranslator.java000066400000000000000000000220741377434312600447710ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000066771377434312600436010ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000015501377434312600432220ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000006351377434312600422420ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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/000077500000000000000000000000001377434312600371135ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/main/java/com/igormaznitsa/jbbp/plugin/commonCommonUtils.java000066400000000000000000000074661377434312600422440ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp-plugins/jbbp-plugin-common/src/test/000077500000000000000000000000001377434312600261545ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/test/java/000077500000000000000000000000001377434312600270755ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/test/java/com/000077500000000000000000000000001377434312600276535ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/000077500000000000000000000000001377434312600323625ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/jbbp/000077500000000000000000000000001377434312600332775ustar00rootroot00000000000000plugin/000077500000000000000000000000001377434312600345165ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/jbbpcommon/000077500000000000000000000000001377434312600360065ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/jbbp/pluginutils/000077500000000000000000000000001377434312600371465ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp-plugins/jbbp-plugin-common/src/test/java/com/igormaznitsa/jbbp/plugin/commonCommonUtilsTest.java000066400000000000000000000031101377434312600431150ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp-plugins/pom.xml000066400000000000000000000046221377434312600222300ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-pom 2.0.3 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 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.3/jbbp/000077500000000000000000000000001377434312600172305ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/pom.xml000066400000000000000000000260221377434312600205470ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-pom 2.0.3 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.9 test net.minidev json-smart 2.3 test commons-io commons-io 2.6 test commons-codec commons-codec 1.13 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 java -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.3/jbbp/src/000077500000000000000000000000001377434312600200175ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/assemble/000077500000000000000000000000001377434312600216125ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/assemble/bundle.xml000066400000000000000000000031231377434312600236040ustar00rootroot00000000000000 bundle false false jar ${project.build.directory} ${file.separator} *.jar.asc *.jar *.pom *.pom.asc *.zip java-binary-block-parser-2.0.3/jbbp/src/main/000077500000000000000000000000001377434312600207435ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/main/java/000077500000000000000000000000001377434312600216645ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/main/java/com/000077500000000000000000000000001377434312600224425ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/main/java/com/igormaznitsa/000077500000000000000000000000001377434312600251515ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/main/java/com/igormaznitsa/jbbp/000077500000000000000000000000001377434312600260665ustar00rootroot00000000000000JBBPCustomFieldTypeProcessor.java000066400000000000000000000063311377434312600342730ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000024771377434312600336140ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000234141377434312600332650ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/JBBPParser.java000066400000000000000000001100751377434312600306270ustar00rootroot00000000000000/* * 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.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.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 = new JBBPFieldByte(name, (byte) inStream.readByte()); } else { structureFields.add(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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/JBBPVarFieldProcessor.java000066400000000000000000000074041377434312600327700ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/ResultSrcItem.java000066400000000000000000000022761377434312600315050ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/000077500000000000000000000000001377434312600277005ustar00rootroot00000000000000JBBPCompiledBlock.java000066400000000000000000000216251377434312600336370ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/JBBPCompiler.java000066400000000000000000000666611377434312600327720ustar00rootroot00000000000000/* * 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.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); } } private static void assertTokenDoesntHaveExtraData(final String fieldType, final JBBPToken token) { if (token.getFieldTypeParameters().getExtraData() != null) { throw new JBBPCompilationException('\'' + fieldType + "\' has 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); assertTokenDoesntHaveExtraData("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(); } /** * 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": 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 shoukd 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.java000066400000000000000000000106361377434312600337230ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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[] splittedFieldPath = 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 < splittedFieldPath.length - 1; i++) { if (fieldPath.length() != 0) { fieldPath.append('.'); } fieldPath.append(splittedFieldPath[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.java000066400000000000000000000060121377434312600337250ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/conversion/000077500000000000000000000000001377434312600320655ustar00rootroot00000000000000CompiledBlockVisitor.java000066400000000000000000000306551377434312600367510ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000056131377434312600402600ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000033411377434312600372730ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000002313421377434312600363700ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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)"), 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/tokenizer/000077500000000000000000000000001377434312600317125ustar00rootroot00000000000000JBBPFieldTypeParameterContainer.java000066400000000000000000000077441377434312600405410ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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 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' or 'value'). * * @return true if the type is a special one * @see JBBPFieldFloat#TYPE_NAME * @see JBBPFieldDouble#TYPE_NAME * @see JBBPFieldString#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("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.java000066400000000000000000000117031377434312600342160ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000017671377434312600350710ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000274421377434312600351170ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.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("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; } @SuppressWarnings("NullableProblems") @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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/compiler/varlen/000077500000000000000000000000001377434312600311675ustar00rootroot00000000000000JBBPEvaluatorFactory.java000066400000000000000000000056111377434312600357060ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000617271377434312600364500ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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 substr = expression.substring(lastFound, matcher.start()); if (substr.trim().length() != 0) { throw new JBBPCompilationException( "Can't recognize part of expression '" + substr + "' [" + 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.java000066400000000000000000000040061377434312600366660ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000066771377434312600362010ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/exceptions/000077500000000000000000000000001377434312600302475ustar00rootroot00000000000000JBBPCompilationException.java000066400000000000000000000037771377434312600356440ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000036161377434312600342450ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000023321377434312600334270ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000040531377434312600345610ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000020011377434312600336500ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000020651377434312600364270ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000055641377434312600346060ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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() + ']'; } } JBBPParsingException.java000066400000000000000000000024061377434312600347550ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000107071377434312600353270ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000035271377434312600370700ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/io/000077500000000000000000000000001377434312600264755ustar00rootroot00000000000000AbstractMappedClassFieldObserver.java000066400000000000000000000601311377434312600356160ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.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.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 ) { JBBPUtils.assertNotNull(obj, "Object must not be null"); final List orderedFields = JBBPMapper.findAffectedFields(obj); final Bin clazzAnno = obj.getClass().getAnnotation(Bin.class); final Bin fieldAnno = field == null ? null : field.getAnnotation(Bin.class); this.onStructStart(obj, field, clazzAnno == null ? fieldAnno : clazzAnno); for (final MappedFieldRecord rec : orderedFields) { final Bin binAnno = binAnnotationWrapper == null ? rec.binAnnotation : binAnnotationWrapper.setWrapped(rec.binAnnotation); if (binAnno.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, binAnno, customFieldProcessor); } this.onStructEnd(obj, field, clazzAnno == null ? fieldAnno : clazzAnno); } /** * 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.2 */ protected void processObjectField( final Object obj, final MappedFieldRecord fieldRecord, final Bin annotation, final Object customFieldProcessor ) { 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 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, 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 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, 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 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitInputStream.java000066400000000000000000001007771377434312600327240ustar00rootroot00000000000000/* * 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; } 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 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; } /** * 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 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 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 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; } } /** * 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 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; } @Override public synchronized void mark(final int readlimit) { in.mark(readlimit); this.markedBitBuffer = this.bitBuffer; this.markedByteCounter = this.byteCounter; this.markedBitsInBuffer = this.bitsInBuffer; } /** * 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 the next stream byte into bit buffer. * * @return the read byte or -1 if the endo 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; } /** * 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; } @SuppressWarnings("NullableProblems") @Override public int read(final byte[] array, final int offset, final int length) throws IOException { if (this.bitsInBuffer == 0) { int readBytes = 0; int tmpoffset = offset; int tmplen = length; while (tmplen > 0) { int read = this.in.read(array, tmpoffset, tmplen); if (read < 0) { readBytes = readBytes == 0 ? read : readBytes; break; } tmplen -= read; tmpoffset += 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; } } /** * 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; } @SuppressWarnings("NullableProblems") @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 srings 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitNumber.java000066400000000000000000000042051377434312600316660ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitOrder.java000066400000000000000000000020631377434312600315110ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPBitOutputStream.java000066400000000000000000000313341377434312600331150ustar00rootroot00000000000000/* * 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 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(); } @SuppressWarnings("NullableProblems") @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; } } @SuppressWarnings("NullableProblems") @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 accum = value; int i = bitNumber.getBitNumber(); while (i > 0) { this.bitBuffer = this.bitBuffer | ((accum & 1) == 0 ? 0 : mask); accum >>= 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPByteOrder.java000066400000000000000000000022271377434312600317000ustar00rootroot00000000000000/* * 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.java000066400000000000000000000027551377434312600334570ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000036051377434312600333360ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPOut.java000066400000000000000000001144571377434312600305610ustar00rootroot00000000000000/* * 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.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; } /** * 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); } /** * 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 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) { if (this.processCommands) { this.processObject(object, null, binAnnotationWrapper, 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; } } @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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/io/JBBPOutVarProcessor.java000066400000000000000000000025271377434312600331240ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/000077500000000000000000000000001377434312600273525ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/Bin.java000066400000000000000000000121641377434312600307310ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/BinType.java000066400000000000000000000175541377434312600316030ustar00rootroot00000000000000/* * 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.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.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 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 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 isarray; /** * The Field class for the value. * * @param fieldClass class for the type */ BinType(final Class fieldClass, final boolean array) { this.fieldClass = fieldClass; this.isarray = 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.isarray; } /** * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/mapper/JBBPMapper.java000066400000000000000000000463001377434312600321020ustar00rootroot00000000000000/* * 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.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; 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 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, 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) { 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)) { processFieldOfMappedClass( record, rootStructure, instance, customFieldProcessor, flags, 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 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); } 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.java000066400000000000000000000030341377434312600360570ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000630431377434312600334550ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.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, 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, instantiators)); } else { Array.set(valueArray, i, JBBPMapper.map(structArray.getElementAt(i), curInstance, customFieldProcessor)); } } 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, 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)); } else { try { JBBPMapper.map((JBBPFieldStruct) binField, record.instanceMaker.invoke(instance)); } catch (Exception ex) { throw new JBBPMapperException( "Can't map field which member generatet 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 thetype = BinType.findCompatible(mappingField.getType()); if (thetype == null) { throw new IllegalStateException("Can't find compatible mapped type for field"); } else if (this.mappedBitNumber.getBitNumber() < 8 && !(thetype == BinType.STRUCT || thetype == BinType.STRUCT_ARRAY)) { thetype = thetype.isArray() ? BinType.BIT_ARRAY : BinType.BIT; } this.fieldType = thetype; } 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 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 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); } } @SuppressWarnings("TryWithIdenticalCatches") 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 multicatch 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, Function, Object>... instantiators ); } } java-binary-block-parser-2.0.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/000077500000000000000000000000001377434312600271665ustar00rootroot00000000000000AbstractFieldByteArray.java000066400000000000000000000027461377434312600343150ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/BitEntity.java000066400000000000000000000017121377434312600317450ustar00rootroot00000000000000/* * 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.java000066400000000000000000000050121377434312600341140ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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 */ @SuppressWarnings("NullableProblems") @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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPAbstractField.java000066400000000000000000000055251377434312600332050ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldArrayBit.java000066400000000000000000000063671377434312600331640ustar00rootroot00000000000000/* * 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.java000066400000000000000000000045061377434312600337370ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000043601377434312600332610ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000053571377434312600335770ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000053061377434312600334240ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldArrayInt.java000066400000000000000000000050131377434312600331630ustar00rootroot00000000000000/* * 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.java000066400000000000000000000050071377434312600332540ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000050321377434312600334520ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000043371377434312600336300ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000037521377434312600336460ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000042221377434312600334030ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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 + ']'; } } JBBPFieldArrayUShort.java000066400000000000000000000050741377434312600336050ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldBit.java000066400000000000000000000056331377434312600321600ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldBoolean.java000066400000000000000000000040311377434312600330100ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldByte.java000066400000000000000000000037741377434312600323510ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldDouble.java000066400000000000000000000035531377434312600326530ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldFloat.java000066400000000000000000000035061377434312600325040ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldInt.java000066400000000000000000000044631377434312600321740ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldLong.java000066400000000000000000000053051377434312600323350ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldShort.java000066400000000000000000000042251377434312600325350ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldString.java000066400000000000000000000037171377434312600327110ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldStruct.java000066400000000000000000000327761377434312600327360ustar00rootroot00000000000000/* * 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 com.igormaznitsa.jbbp.utils.JBBPUtils.ARRAY_FIELD_EMPTY; import com.igormaznitsa.jbbp.compiler.JBBPNamedFieldInfo; import com.igormaznitsa.jbbp.exceptions.JBBPFinderException; import com.igormaznitsa.jbbp.exceptions.JBBPTooManyFieldsFoundException; 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; /** * 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, null, 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, null, flags, 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 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); } @Override public String getTypeAsString() { return "{}"; } } java-binary-block-parser-2.0.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldUByte.java000066400000000000000000000040551377434312600324670ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPFieldUShort.java000066400000000000000000000043101377434312600326550ustar00rootroot00000000000000/* * 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.java000066400000000000000000000016211377434312600341360ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPNamedField.java000066400000000000000000000017061377434312600324630ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPNumericArray.java000066400000000000000000000023531377434312600330730ustar00rootroot00000000000000/* * 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/JBBPNumericField.java000066400000000000000000000027271377434312600330450ustar00rootroot00000000000000/* * 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; /** * 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 */ int getAsInt(); /** * Get the field value as double. * * @return the field value as double * @since 1.4.0 */ double getAsDouble(); /** * Get the field value as float. * * @return the field value as float * @since 1.4.0 */ float getAsFloat(); /** * Get the field value as long * * @return the field 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 */ boolean getAsBool(); } java-binary-block-parser-2.0.3/jbbp/src/main/java/com/igormaznitsa/jbbp/model/finder/000077500000000000000000000000001377434312600304355ustar00rootroot00000000000000JBBPFieldFinder.java000066400000000000000000000067401377434312600340410ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/000077500000000000000000000000001377434312600272265ustar00rootroot00000000000000BinAnnotationWrapper.java000066400000000000000000000101271377434312600341170ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/Function.java000066400000000000000000000015651377434312600316650ustar00rootroot00000000000000/* * 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.java000066400000000000000000000061651377434312600374430ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPDslBuilder.java000066400000000000000000002121251377434312600325630ustar00rootroot00000000000000package 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 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 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 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 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 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 defautBin = annotatedClass.getAnnotation(Bin.class); final BinFieldContainer result; if (defautBin != null) { result = new BinFieldContainer(annotatedClass, defautBin, true, null); } else { result = new BinFieldContainer(annotatedClass, null); } final Class superclazz = annotatedClass.getSuperclass(); if (superclazz != null && superclazz != Object.class) { final BinFieldContainer parentFields = collectAnnotatedFields(superclazz); if (!parentFields.fields.isEmpty()) { result.addAllFromContainer(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 || defautBin != null) { validateAnnotatedField(defautBin, 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 { if (defautBin != null) { result.addBinField(defautBin, 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 iter; Pair(final BinFieldContainer container) { this.container = container; this.iter = 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.iter.hasNext()) { final BinField field = pair.iter.next(); if (field instanceof BinFieldContainer) { final BinFieldContainer conty = (BinFieldContainer) field; if (conty == BinFieldContainer.END_STRUCT) { this.CloseStruct(); } else { if (field.isArrayField()) { this.StructArray(conty.getName(), conty.bin.arraySizeExpr()); } else { this.Struct(conty.getName()); } stack.add(0, pair); stack.add(0, new Pair(conty)); 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 addAllFromContainer(final BinFieldContainer container) { this.fields.addAll(container.fields); } 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPIntCounter.java000066400000000000000000000060411377434312600326220ustar00rootroot00000000000000/* * 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.java000066400000000000000000000060231377434312600335020ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPTextWriter.java000066400000000000000000001674251377434312600326670ustar00rootroot00000000000000/* * 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.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.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 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 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; } /** * 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[] splitted = c.split("\\n", -1); for (final String s : splitted) { 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 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 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; } @SuppressWarnings("NullableProblems") @Override public void write(final String str) throws IOException { this.write(str, 0, str.length()); } @SuppressWarnings("NullableProblems") @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 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 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.java000066400000000000000000000050361377434312600351020ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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 { } } java-binary-block-parser-2.0.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JBBPUtils.java000066400000000000000000001043651377434312600316400ustar00rootroot00000000000000/* * 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 delimeter 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 maxlen = Integer.toString(0xFF, radix).length(); final String zero = "00000000"; final String normDelim = delimiter == null ? " " : delimiter; final String normPrefix = prefix == null ? "" : prefix; final StringBuilder result = new StringBuilder(array.length * 4); if (brackets) { result.append('['); } boolean nofirst = false; for (final byte b : array) { if (nofirst) { result.append(normDelim); } else { nofirst = true; } result.append(normPrefix); final String v = Integer.toString(b & 0xFF, radix); if (v.length() < maxlen) { result.append(zero, 0, maxlen - 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 nofirst = false; for (final byte b : values) { if (separateBytes) { if (nofirst) { result.append(' '); } else { nofirst = 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 delimeter. * * @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 bulder = 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++] = bulder.toString(); bulder.setLength(0); } else { bulder.append(chr); } } if (position < result.length) { result[position] = bulder.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 tmpvalue = value; if (valueInLittleEndian) { for (int i = 0; i < 4; i++) { result[i] = (byte) tmpvalue; tmpvalue >>>= 8; } } else { for (int i = 3; i >= 0; i--) { result[i] = (byte) tmpvalue; tmpvalue >>>= 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 tmpvalue = value; if (valueInLittleEndian) { for (int i = 0; i < 8; i++) { result[i] = (byte) tmpvalue; tmpvalue >>>= 8; } } else { for (int i = 7; i >= 0; i--) { result[i] = (byte) tmpvalue; tmpvalue >>>= 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 thebyte = value & 0xFF; value >>>= 8; result |= (thebyte << 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 arrindex = array.length; while (--index >= 0) { if (array[--arrindex] != 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/JavaSrcTextBuffer.java000066400000000000000000000125551377434312600334310ustar00rootroot00000000000000/* * 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[] splitted = text.split("\n", -1); for (final String aSplitted : splitted) { this.indent().println(aSplitted); } 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[] splitted = text.split("\n", -1); for (final String aSplitted : splitted) { this.indent().print("// ").println(aSplitted); } 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[] splitted = text.split("\n", -1); this.indent().println("/*"); for (final String aSplitted : splitted) { this.indent().print(" * ").println(aSplitted); } 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[] splitted = text.split("\n", -1); this.indent().println("/**"); for (final String aSplitted : splitted) { this.indent().print(" * ").println(aSplitted); } this.indent().println(" */"); return this; } @Override public String toString() { return this.buffer.toString(); } } java-binary-block-parser-2.0.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/NullableTriple.java000066400000000000000000000010601377434312600330040ustar00rootroot00000000000000package 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/ReflectUtils.java000066400000000000000000000060161377434312600325010ustar00rootroot00000000000000package 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.3/jbbp/src/main/java/com/igormaznitsa/jbbp/utils/TargetSources.java000066400000000000000000000015521377434312600326660ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/000077500000000000000000000000001377434312600207765ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/java/000077500000000000000000000000001377434312600217175ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/java/com/000077500000000000000000000000001377434312600224755ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/java/com/igormaznitsa/000077500000000000000000000000001377434312600252045ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/java/com/igormaznitsa/jbbp/000077500000000000000000000000001377434312600261215ustar00rootroot00000000000000JBBPCustomFieldTypeProcessorTest.java000066400000000000000000000244371377434312600351750ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/JBBPParserTest.java000066400000000000000000003263621377434312600315320ustar00rootroot00000000000000/* * 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.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.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.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); } } java-binary-block-parser-2.0.3/jbbp/src/test/java/com/igormaznitsa/jbbp/TestUtils.java000066400000000000000000000103561377434312600307310ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/000077500000000000000000000000001377434312600277335ustar00rootroot00000000000000JBBPCompilerTest.java000066400000000000000000000620551377434312600335770ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/conversion/000077500000000000000000000000001377434312600321205ustar00rootroot00000000000000JBBPToJBBPToJavaConverterCompilationTest.java000066400000000000000000000510371377434312600424240ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000001440551377434312600410760ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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;" + "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, 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(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_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_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).floatValue(), 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 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.java000066400000000000000000000034721377434312600433450ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000273321377434312600356240ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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(); } 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(27); 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: { // DOUBLE builder.Double(generateNames ? makeRndName() : null); counterStack.get(0).add(64); fieldsTotal++; } break; case 23: { // DOUBLE_ARRAY final int arrayLen = makeArrayLengthNumber(); builder.DoubleArray(generateNames ? makeRndName() : null, String.valueOf(arrayLen)); counterStack.get(0).add(arrayLen * 64); fieldsTotal++; } break; case 24: { // STRUCT END if (activeStructCounter > 0) { i--; activeStructCounter--; builder.CloseStruct(); final StructLen len = counterStack.remove(0); counterStack.get(0).add(len.make()); } } break; case 25: { // COMMENT builder.Comment(genRandomString(this.RND.nextInt(32))); } break; case 26: { // 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 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; } @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(0x7FFFFFFL, generatedFields, "All field types must be presented"); } 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/tokenizer/000077500000000000000000000000001377434312600317455ustar00rootroot00000000000000JBBPFieldTypeParameterContainerTest.java000066400000000000000000000036221377434312600414230ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/compiler/varlen/000077500000000000000000000000001377434312600312225ustar00rootroot00000000000000JBBPExpressionEvaluatorTest.java000066400000000000000000000520531377434312600373330ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000105741377434312600370630ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/exceptions/000077500000000000000000000000001377434312600303025ustar00rootroot00000000000000JBBPMapperExceptionTest.java000066400000000000000000000042751377434312600354770ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000033741377434312600362240ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/io/000077500000000000000000000000001377434312600265305ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/java/com/igormaznitsa/jbbp/io/BitIOCommonTest.java000066400000000000000000000151761377434312600323640ustar00rootroot00000000000000/* * 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 writenBits = 0; for (int i = 0; i < LEN; i++) { writenBits += 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 (writenBits % 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.java000066400000000000000000001446551377434312600335430ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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 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("Unconvertable 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 { assertEquals(null, 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 { assertEquals(null, 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++) { assertTrue(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.java000066400000000000000000000514041377434312600337310ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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); assertTrue(out.getBufferedBitsNumber() == 0); assertEquals(1L, out.getCounter()); out.resetCounter(); assertTrue(out.getBufferedBitsNumber() == 0); 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(); assertTrue(out.getBufferedBitsNumber() == 0); 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 outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteBytes_BIG_ENDIAN_zeroArray() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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], outBiuffer.toByteArray()); } @Test public void testWriteBytes_BIG_ENDIAN_threeItemsOfArray() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteBytes_LITTLE_ENDIAN_threeItemsOfArray() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteBytes_LITTLE_ENDIAN_zeroArray() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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], outBiuffer.toByteArray()); } @Test public void testWriteBytes_LITTLE_ENDIAN_wholeArray() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteByte() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); assertEquals(0, out.getCounter()); out.write(0x12); assertEquals(1, out.getCounter()); out.flush(); assertEquals(1, out.getCounter()); assertArrayEquals(new byte[] {0x12}, outBiuffer.toByteArray()); } @Test public void testWriteByte_MSB0() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer, JBBPBitOrder.MSB0); assertEquals(0, out.getCounter()); out.write(0x12); assertEquals(1, out.getCounter()); out.flush(); assertEquals(1, out.getCounter()); assertArrayEquals(new byte[] {0x48}, outBiuffer.toByteArray()); } @Test public void testWriteShort_BigEndian() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteShort_BigEndian_MSB0() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer, 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}, outBiuffer.toByteArray()); } @Test public void testWriteShort_LittleEndian() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteShort_LittleEndian_MSB0() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer, 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}, outBiuffer.toByteArray()); } @Test public void testWriteInt_BigEndian() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteFloat_BigEndian() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteInt_LittleEndian() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteFloat_LittleEndian() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.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 outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteDouble_BigEndian() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteLong_LittleEndian() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.toByteArray()); } @Test public void testWriteDouble_LittleEndian() throws Exception { final ByteArrayOutputStream outBiuffer = new ByteArrayOutputStream(); final JBBPBitOutputStream out = new JBBPBitOutputStream(outBiuffer); 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}, outBiuffer.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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/io/JBBPOutTest.java000066400000000000000000001371001377434312600314420ustar00rootroot00000000000000/* * 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 testIntArray() throws Exception { assertArrayEquals(new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, BeginBin().Int(0x01020304, 0x05060708).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 testInt_LittleEndian() throws Exception { assertArrayEquals(new byte[] {0x04, 0x03, 0x02, 0x01}, BeginBin().ByteOrder(JBBPByteOrder.LITTLE_ENDIAN).Int(0x01020304).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_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_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).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_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()); assertTrue(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; } } } java-binary-block-parser-2.0.3/jbbp/src/test/java/com/igormaznitsa/jbbp/it/000077500000000000000000000000001377434312600265355ustar00rootroot00000000000000AbstractParserIntegrationTest.java000066400000000000000000000041231377434312600353050ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000607061377434312600350220ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/hexengine.clazz000066400000000000000000000515641377434312600326600ustar00rootroot000000000000001>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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/indexedcolor.tga000066400000000000000000004610351377434312600330130ustar00rootroot00000000000000 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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/logo.tga000066400000000000000000003443421377434312600312750ustar00rootroot00000000000000 ? ('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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/picture.png000066400000000000000000000074071377434312600320170ustar00rootroot00000000000000PNG  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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/tcppacket.bin000066400000000000000000000002551377434312600323000ustar00rootroot00000000000000`g &DtfExZ@9}dzERAqaoqP4W` [ u~È^桋I >zG#=b^V`F^~a^%u C)MlܻtD_A0?A{~)YpibOjava-binary-block-parser-2.0.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/test.clazz000066400000000000000000000014771377434312600316630ustar00rootroot0000000000000039 !" #$ % & ' ()* # +,-.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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/test.tap000066400000000000000000002565021377434312600313250ustar00rootroot00000000000000ACEofACES  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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/test.z80000066400000000000000000000302151377434312600311510ustar00rootroot00000000000000~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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/zexall.sna000066400000000000000000001400331377434312600316310ustar00rootroot00000000000000?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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils/testwriter.txt000066400000000000000000000007131377434312600333300ustar00rootroot00000000000000;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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils/testwriter2.txt000066400000000000000000000014101377434312600334050ustar00rootroot0000000000000000000000 .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.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils/testwriter3.txt000066400000000000000000000021531377434312600334130ustar00rootroot00000000000000.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.txt000066400000000000000000000041111377434312600340170ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.txt000066400000000000000000000745431377434312600340400ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.txt000066400000000000000000000754371377434312600342050ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.txt000066400000000000000000000001251377434312600340220ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~--- ;START : TestClass ~--- .00000000;a .testb;b ~--- ;END : TestClass ~--- testwriterbin4.txt000066400000000000000000000002201377434312600340170ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/utils~--- ;START : TestClass ~--- .00000000;a ~--- .See on array;Line one ;Line two ~--- ~--- ;END : TestClass ~--- testwriterbin5.txt000066400000000000000000000022471377434312600340330ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.txt000066400000000000000000000024261377434312600361660ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.txt000066400000000000000000000010341377434312600342020ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.txt000066400000000000000000000012241377434312600342040ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.txt000066400000000000000000000014611377434312600342100ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/logo.png000066400000000000000000010612771377434312600177770ustar00rootroot00000000000000PNG  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.3/logo_sn.svg000066400000000000000000076047161377434312600205210ustar00rootroot00000000000000 image/svg+xml java-binary-block-parser-2.0.3/pom.xml000066400000000000000000000226151377434312600176360ustar00rootroot00000000000000 4.0.0 com.igormaznitsa jbbp-main-pom 2.0.3 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.3 ${jbbp.version} 1.8 1.8 5.5.1 1.21 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.6 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.3/logo.svg000066400000000000000000075770311377434312600200200ustar00rootroot00000000000000 image/svg+xml java-binary-block-parser-2.0.3/logo_sn.png000066400000000000000000012144201377434312600204650ustar00rootroot00000000000000PNG  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 testMutlithredUsageOfParser() 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); } } java-binary-block-parser-2.0.3/jbbp/src/test/java/com/igormaznitsa/jbbp/it/ClassParsingTest.java000066400000000000000000000311371377434312600326360ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/it/ConvertToJSONTest.java000066400000000000000000000145451377434312600326660ustar00rootroot00000000000000/* * 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.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 ? "nonamed" : 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); assertTrue(jsonText.length() == 13917); 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.java000066400000000000000000000213661377434312600354370ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/it/NetPacketParsingTest.java000066400000000000000000000305521377434312600334470ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/it/PNGParsingTest.java000066400000000000000000000205111377434312600322070ustar00rootroot00000000000000/* * 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.java000066400000000000000000000115341377434312600340240ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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(" 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/it/TAP_ParsingTest.java000066400000000000000000000113051377434312600323470ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/it/TGAParsingTest.java000066400000000000000000000120731377434312600322020ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/it/WAVParsingTest.java000066400000000000000000000111331377434312600322200ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/it/Z80_v1_ParsingTest.java000066400000000000000000000366211377434312600327220ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/000077500000000000000000000000001377434312600274055ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/BinTypeTest.java000066400000000000000000000050621377434312600324650ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/ClassWithPFGSG.java000066400000000000000000000022101377434312600327330ustar00rootroot00000000000000package 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.java000066400000000000000000000012741377434312600345600ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000015021377434312600375370ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/mapper/JBBPMapperTest.java000066400000000000000000001023371377434312600330000ustar00rootroot00000000000000/* * 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.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.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 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_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_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_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_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_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 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_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_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_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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/model/000077500000000000000000000000001377434312600272215ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/java/com/igormaznitsa/jbbp/model/FakePayload.java000066400000000000000000000014271377434312600322500ustar00rootroot00000000000000/* * 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.java000066400000000000000000000076731377434312600340010ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000072201377434312600346260ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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++) { assertTrue(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) { assertTrue(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++) { assertTrue(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.java000066400000000000000000000063421377434312600341560ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000064301377434312600340030ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000072231377434312600341510ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000064461377434312600343570ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000041011377434312600345100ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000047071377434312600345420ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000063521377434312600343040ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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]); } } } JBBPFieldArrayUShortTest.java000066400000000000000000000057421377434312600345020ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/model/JBBPFieldBitTest.java000066400000000000000000000070601377434312600330470ustar00rootroot00000000000000/* * 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.java000066400000000000000000000050551377434312600336330ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/model/JBBPFieldByteTest.java000066400000000000000000000055031377434312600332340ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/model/JBBPFieldIntTest.java000066400000000000000000000054121377434312600330620ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/model/JBBPFieldLongTest.java000066400000000000000000000051251377434312600332300ustar00rootroot00000000000000/* * 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.java000066400000000000000000000055721377434312600333570ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000026621377434312600335230ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000301531377434312600335350ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000055331377434312600333050ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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()); } } JBBPFieldUShortTest.java000066400000000000000000000070561377434312600335030ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/testaux/000077500000000000000000000000001377434312600276165ustar00rootroot00000000000000AbstractJBBPToJavaConverterTest.java000066400000000000000000000255151377434312600364500ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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... mapvalue) { final Map result = new HashMap<>(); int i = 0; while (i < mapvalue.length) { result.put(mapvalue[i++], mapvalue[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 cloader = saveAndCompile(new JavaClassContent(PACKAGE_NAME + '.' + CLASS_NAME, classBody)); return ReflectUtils.newInstance(cloader.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); } protected Object compileAndMakeInstance(final String instanceClassName, final String script, final int parserFlags, final JBBPCustomFieldTypeProcessor customFieldProcessor, final JavaClassContent... extraClasses) throws Exception { final List klazzes = new ArrayList<>(Arrays.asList(extraClasses)); final JavaClassContent klazzContent = 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(klazzContent.classText); } klazzes.add(0, klazzContent); final ClassLoader cloader = saveAndCompile(klazzes.toArray(new JavaClassContent[0])); return ReflectUtils.newInstance(cloader.loadClass(instanceClassName)); } public ClassLoader saveAndCompile(final JavaClassContent... klasses) throws IOException { return this.saveAndCompile(null, klasses); } public ClassLoader saveAndCompile(final ClassLoader classLoader, final JavaClassContent... klasses) throws IOException { final File folder = tempFolder.newFolder(); final List classFiles = new ArrayList<>(); for (final JavaClassContent c : klasses) { 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 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/000077500000000000000000000000001377434312600272615ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/DynamicIntBuffer.java000066400000000000000000000054511377434312600333220ustar00rootroot00000000000000/* * 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.java000066400000000000000000000257551377434312600403440ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000000545021377434312600334020ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.java000066400000000000000000001006511377434312600334670ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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 testInt_Array() throws Exception { final JBBPTextWriter writer = makeWriter(); writer.Int(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 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 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/JBBPUtilsTest.java000066400000000000000000000642341377434312600325330ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/java/com/igormaznitsa/jbbp/utils/SpecialTestUtils.java000066400000000000000000000020011377434312600333560ustar00rootroot00000000000000/* * 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.3/jbbp/src/test/jbbp/000077500000000000000000000000001377434312600217135ustar00rootroot00000000000000com.igormaznitsa.jbbp.benchmarks.JBBPBenchmarkParser.jbbp000066400000000000000000000001401377434312600345060ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/jmh/000077500000000000000000000000001377434312600215545ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/jmh/com/000077500000000000000000000000001377434312600223325ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/jmh/com/igormaznitsa/000077500000000000000000000000001377434312600250415ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/jmh/com/igormaznitsa/jbbp/000077500000000000000000000000001377434312600257565ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/jmh/com/igormaznitsa/jbbp/benchmarks/000077500000000000000000000000001377434312600300735ustar00rootroot00000000000000JBBP_Benchmark.java000066400000000000000000000045021377434312600333470ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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.3/jbbp/src/test/resources/000077500000000000000000000000001377434312600230105ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/resources/com/000077500000000000000000000000001377434312600235665ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/resources/com/igormaznitsa/000077500000000000000000000000001377434312600262755ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/000077500000000000000000000000001377434312600272125ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/000077500000000000000000000000001377434312600276265ustar00rootroot00000000000000java-binary-block-parser-2.0.3/jbbp/src/test/resources/com/igormaznitsa/jbbp/it/11kgsm.wav000066400000000000000000000744301377434312600314600ustar00rootroot00000000000000RIFFyWAVEfmt 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.wav000066400000000000000000013365321377434312600333070ustar00rootroot00000000000000java-binary-block-parser-2.0.3/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