pax_global_header 0000666 0000000 0000000 00000000064 13262154162 0014514 g ustar 00root root 0000000 0000000 52 comment=406f6cfcf87e8b03b50ab249fe4c4a5df36a7b2c
vinnie-2.0.2/ 0000775 0000000 0000000 00000000000 13262154162 0013005 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/.gitignore 0000664 0000000 0000000 00000000053 13262154162 0014773 0 ustar 00root root 0000000 0000000 /.settings/
/target/
/.classpath
/.project
vinnie-2.0.2/.travis.yml 0000664 0000000 0000000 00000000146 13262154162 0015117 0 ustar 00root root 0000000 0000000 language: java
sudo: false # faster builds
after_success:
- bash <(curl -s https://codecov.io/bash) vinnie-2.0.2/LICENSES 0000664 0000000 0000000 00000030320 13262154162 0014133 0 ustar 00root root 0000000 0000000 vinnie======================
MIT License
Copyright (c) 2016 Michael Angstadt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
commons-codec===================
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.
vinnie-2.0.2/README.md 0000664 0000000 0000000 00000010372 13262154162 0014267 0 ustar 00root root 0000000 0000000 # vinnie
| | |
| --- | --- |
| Continuous Integration: | [](https://travis-ci.org/mangstadt/vinnie) |
| Code Coverage: | [](http://codecov.io/github/mangstadt/vinnie?branch=master) |
| Maven Central: | [](https://maven-badges.herokuapp.com/maven-central/com.github.mangstadt/vinnie) |
| Chat Room: | [](https://gitter.im/mangstadt/vinnie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) |
| License: | [](https://github.com/mangstadt/vinnie/blob/master/LICENSES) |
vinnie is a lightweight Java library that reads and writes "vobject" data (vCard and iCalendar). It is used by the [ez-vcard](https://github.com/mangstadt/ez-vcard) and [biweekly](https://github.com/mangstadt/biweekly) projects.
# Examples
## Parsing
**Code**
```java
String str =
"BEGIN:VCARD\r\n" +
"VERSION:2.1\r\n" +
"FN:John Doe\r\n" +
"NOTE;QUOTED-PRINTABLE;CHARSET=UTF-8:=C2=A1Hola, mundo!\r\n" +
"END:VCARD\r\n";
Reader reader = new StringReader(str);
SyntaxRules rules = SyntaxRules.vcard();
VObjectReader vobjectReader = new VObjectReader(reader, rules);
vobjectReader.parse(new VObjectDataAdapter() {
private boolean inVCard = false;
public void onComponentBegin(String name, Context context) {
if (context.getParentComponents().isEmpty() && "VCARD".equals(name)){
inVCard = true;
}
}
public void onComponentEnd(String name, Context context) {
if (context.getParentComponents().isEmpty()) {
//end of vCard, stop parsing
context.stop();
}
}
public void onProperty(VObjectProperty property, Context context) {
if (inVCard) {
System.out.println(property.getName() + " = " + property.getValue());
}
}
});
vobjectReader.close();
```
**Output**
```
FN = John Doe
NOTE = ¡Hola, mundo!
```
## Writing
**Code**
```java
Writer writer = new OutputStreamWriter(System.out);
VObjectWriter vobjectWriter = new VObjectWriter(writer, SyntaxStyle.OLD);
vobjectWriter.writeBeginComponent("VCARD");
vobjectWriter.writeVersion("2.1");
vobjectWriter.writeProperty("FN", "John Doe");
VObjectProperty note = new VObjectProperty("NOTE", "¡Hola, mundo!");
note.getParameters().put(null, "QUOTED-PRINTABLE");
vobjectWriter.writeProperty(note);
vobjectWriter.writeEndComponent("VCARD");
vobjectWriter.close();
```
**Output**
```
BEGIN:VCARD
VERSION:2.1
FN:John Doe
NOTE;QUOTED-PRINTABLE;CHARSET=UTF-8:=C2=A1Hola, mundo!
END:VCARD
```
# Features
* Full ABNF compliance with vCard (versions 2.1, 3.0, and 4.0) and iCalendar (versions 1.0 and 2.0) specifications.
* Automatic decoding/encoding of quoted-printable data.
* Streaming API.
* Extensive unit test coverage.
* Low Java version requirement (1.5 or above).
* No dependencies on external libraries.
# Maven/Gradle
**Maven**
```xml
com.github.mangstadtvinnie2.0.1
```
**Gradle**
```
compile 'com.github.mangstadt:vinnie:2.0.1'
```
# Build Instructions
vinnie uses [Maven](http://maven.apache.org/) as its build tool, and adheres to its conventions.
To build the project: `mvn compile`
To run the unit tests: `mvn test`
To build a JAR: `mvn package`
# Questions / Feedback
You have some options:
* Post an [issue](https://github.com/mangstadt/vinnie/issues)
* [Gitter chat room](https://gitter.im/mangstadt/vinnie)
* Email me directly: [mike.angstadt@gmail.com](mailto:mike.angstadt@gmail.com)
[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=8CEN7MPKRBKU6&lc=US&item_name=Michael%20Angstadt&item_number=vinnie¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
vinnie-2.0.2/codecov.yml 0000664 0000000 0000000 00000000235 13262154162 0015152 0 ustar 00root root 0000000 0000000 comment:
layout: header, changes, diff
coverage:
ignore:
- src/main/java/com/github/mangstadt/vinnie/codec/.*
status:
patch: false
precision: 0 vinnie-2.0.2/pom.xml 0000664 0000000 0000000 00000016421 13262154162 0014326 0 ustar 00root root 0000000 0000000 4.0.0com.github.mangstadtvinniebundle2.0.2vinniehttp://github.com/mangstadt/vinnie2016Michael AngstadtA lightweight Java library that reads and writes "vobject" data (vCard and iCalendar).org.sonatype.ossoss-parent7MIT Licensehttps://opensource.org/licenses/MITMichael Angstadtmike.angstadt@gmail.comGithub Issue Trackerhttps://github.com/mangstadt/vinnie/issuesscm:git:https://github.com/mangstadt/vinnie.gitscm:git:https://github.com/mangstadt/vinnie.githttps://github.com/mangstadt/vinnie/commits/master1.5junitjunit4.12testorg.mockitomockito-all1.10.19testsrc/main/resourcesorg.apache.felixmaven-bundle-plugin2.4.0truemaven-resources-plugin2.4.3UTF-8maven-compiler-plugin2.3.2UTF-8${java.version}${java.version}org.apache.maven.pluginsmaven-source-plugin2.1.2jarorg.apache.maven.pluginsmaven-javadoc-plugin3.0.0UTF-8truetrue--allow-script-in-commentscom.github.mangstadt.vinnie.codec
]]>truesrc/main/javadoc/syntaxhighlighter.cssattach-javadocsjarorg.codehaus.mojofindbugs-maven-plugin3.0.4org.apache.maven.pluginsmaven-pmd-plugin3.6${java.version}org.pitestpitest-maven1.1.10
com.github.mangstadt.vinnie.*
com.github.mangstadt.vinnie.codec.*
com.github.mangstadt.vinnie.*
org.jacocojacoco-maven-plugin0.7.7.201606060606prepare-agentreporttestreportrelease-sign-artifactsreleasetrueorg.apache.maven.pluginsmaven-gpg-plugin1.4sign-artifactspackagesign
vinnie-2.0.2/src/ 0000775 0000000 0000000 00000000000 13262154162 0013574 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/src/main/ 0000775 0000000 0000000 00000000000 13262154162 0014520 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/src/main/java/ 0000775 0000000 0000000 00000000000 13262154162 0015441 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/src/main/java/com/ 0000775 0000000 0000000 00000000000 13262154162 0016217 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/src/main/java/com/github/ 0000775 0000000 0000000 00000000000 13262154162 0017501 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/src/main/java/com/github/mangstadt/ 0000775 0000000 0000000 00000000000 13262154162 0021463 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/ 0000775 0000000 0000000 00000000000 13262154162 0022753 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/SyntaxStyle.java 0000664 0000000 0000000 00000002736 13262154162 0026135 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie;
/**
* Defines the various syntax styles a vobject data stream can have. They are
* similar to each other, but not identical.
* @author Michael Angstadt
*/
public enum SyntaxStyle {
/**
* "Old style" syntax (vCard 2.1 and vCal 1.0).
*/
OLD,
/**
* "New style" syntax (vCard 3.0/4.0 and iCal 2.0).
*/
NEW
} vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/Utils.java 0000664 0000000 0000000 00000005576 13262154162 0024733 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie;
/**
* Contains miscellaneous utility methods.
* @author Michael Angstadt
*/
public final class Utils {
/**
* Trims the whitespace off the left side of a string.
* @param string the string to trim
* @return the trimmed string
*/
public static String ltrim(String string) {
int i;
for (i = 0; i < string.length() && Character.isWhitespace(string.charAt(i)); i++) {
//do nothing
}
return string.substring(i);
}
/**
* Trims the whitespace off the right side of a string.
* @param string the string to trim
* @return the trimmed string
*/
public static String rtrim(String string) {
int i;
for (i = string.length() - 1; i >= 0 && Character.isWhitespace(string.charAt(i)); i--) {
//do nothing
}
return string.substring(0, i + 1);
}
/**
*
* Escapes all newline sequences in a string with "\n".
*
*
* This method is 3x faster than a regex when the string has newlines to
* escape and 6x faster when it doesn't have newlines to escape.
*
* @param string the string
* @return the escaped string
*/
public static String escapeNewlines(String string) {
StringBuilder sb = null;
char prev = 0;
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
if (c == '\r' || c == '\n') {
if (sb == null) {
sb = new StringBuilder(string.length() * 2);
sb.append(string, 0, i);
}
if (c == '\n' && prev == '\r') {
/*
* Do not write a second newline escape sequence if the
* newline sequence is "\r\n".
*/
} else {
sb.append("\\n");
}
} else if (sb != null) {
sb.append(c);
}
prev = c;
}
return (sb == null) ? string : sb.toString();
}
private Utils() {
//hide default constructor
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/VObjectParameters.java 0000664 0000000 0000000 00000017603 13262154162 0027205 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* A simple multimap implementation for holding the parameters of a
* {@link VObjectProperty}. Enforces case-insensitivity of parameter names by
* converting them to uppercase.
* @author Michael Angstadt
*/
public class VObjectParameters implements Iterable>> {
private final Map> multimap;
/**
* Creates an empty list of parameters.
*/
public VObjectParameters() {
multimap = new LinkedHashMap>(); //preserve insertion order of keys
}
/**
*
* Creates a list of parameters backed by the given map. Any changes made to
* the given map will effect the parameter list and vice versa.
*
*
* If the given map is not empty, care should be taken to ensure that all of
* its keys are in uppercase before passing it into this constructor.
*
* @param map the map
*/
public VObjectParameters(Map> map) {
multimap = map;
}
/**
* Copies an existing list of parameters.
* @param original the existing list
*/
public VObjectParameters(VObjectParameters original) {
this();
for (Map.Entry> entry : original) {
String name = entry.getKey();
List values = entry.getValue();
multimap.put(name, new ArrayList(values));
}
}
/**
* Gets the values that are assigned to a key.
* @param key the key
* @return the values or null if the key does not exist
*/
public List get(String key) {
key = sanitizeKey(key);
return _get(key);
}
/**
* @param key assumed to already be in uppercase
*/
private List _get(String key) {
return multimap.get(key);
}
/**
* Inserts a value.
* @param key the key
* @param value the value to add
*/
public void put(String key, String value) {
key = sanitizeKey(key);
_put(key, value);
}
/**
* @param key assumed to already be in uppercase
* @param value the value to add
*/
private void _put(String key, String value) {
List list = _get(key);
if (list == null) {
list = new ArrayList();
multimap.put(key, list);
}
list.add(value);
}
/**
* Inserts multiple values.
* @param key the key
* @param values the values to add
*/
public void putAll(String key, String... values) {
if (values.length == 0) {
return;
}
key = sanitizeKey(key);
_putAll(key, values);
}
/**
* @param key assumed to already be in uppercase
* @param values the values to add
*/
private void _putAll(String key, String... values) {
List list = _get(key);
if (list == null) {
list = new ArrayList();
multimap.put(key, list);
}
list.addAll(Arrays.asList(values));
}
/**
* Replaces all the values of the given key with the given value.
* @param key the key
* @param value the value
* @return the replaced values or null if the key didn't exist
*/
public List replace(String key, String value) {
key = sanitizeKey(key);
List replaced = _removeAll(key);
_put(key, value);
return replaced;
}
/**
* Replaces all the values of the given key with the given values.
* @param key the key
* @param values the values
* @return the replaced values or null if the key didn't exist
*/
public List replaceAll(String key, String... values) {
key = sanitizeKey(key);
List replaced = _removeAll(key);
if (values.length > 0) {
_putAll(key, values);
}
return replaced;
}
/**
* Removes a value.
* @param key the key
* @param value the value to remove
* @return true if the value was found, false if not
*/
public boolean remove(String key, String value) {
List values = get(key);
return (values == null) ? false : values.remove(value);
}
/**
* Removes all values associated with a key, along with the key itself.
* @param key the key
* @return the removed values or null if the key didn't exist
*/
public List removeAll(String key) {
key = sanitizeKey(key);
return _removeAll(key);
}
/**
* @param key assumed to already be in uppercase
*/
private List _removeAll(String key) {
return multimap.remove(key);
}
/**
* Clears the multimap.
*/
public void clear() {
multimap.clear();
}
/**
* Gets the first value assigned to the given key.
* @param key the key
* @return the value or null if the given key does not have any values
*/
public String first(String key) {
List values = get(key);
return (values == null || values.isEmpty()) ? null : values.get(0);
}
/**
* Determines if a "quoted-printable encoding" parameter exists.
* @return true if the parameter exists, false if not
*/
public boolean isQuotedPrintable() {
for (String key : new String[] { "ENCODING", null }) {
List values = _get(key);
if (values == null) {
continue;
}
for (String value : values) {
if ("QUOTED-PRINTABLE".equalsIgnoreCase(value)) {
return true;
}
}
}
return false;
}
/**
* Gets the CHARSET parameter.
* @return the character set or null if a character set is not defined
* @throws IllegalCharsetNameException if the character set name contains
* illegal characters
* @throws UnsupportedCharsetException if the local JVM does not recognized
* the character set
*/
public Charset getCharset() throws IllegalCharsetNameException, UnsupportedCharsetException {
String charsetStr = first("CHARSET");
return (charsetStr == null) ? null : Charset.forName(charsetStr);
}
/**
* Gets the map that backs this parameters list.
* @return the map
*/
public Map> getMap() {
return multimap;
}
/**
* Creates an iterator over all the parameters (for use in foreach loops).
* @return the iterator
*/
public Iterator>> iterator() {
return multimap.entrySet().iterator();
}
/**
* Converts the given key to uppercase. Call this method before passing a
* key to the multimap.
* @param key the key
* @return the sanitized key
*/
private String sanitizeKey(String key) {
return (key == null) ? null : key.toUpperCase();
}
@Override
public int hashCode() {
return multimap.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
VObjectParameters other = (VObjectParameters) obj;
return multimap.equals(other.multimap);
}
@Override
public String toString() {
return multimap.toString();
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/VObjectProperty.java 0000664 0000000 0000000 00000012455 13262154162 0026726 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie;
/**
* Represents a vobject property.
* @author Michael Angstadt
*/
public class VObjectProperty {
private String group;
private String name;
private VObjectParameters parameters;
private String value;
/**
* Creates an empty property.
*/
public VObjectProperty() {
this(null, null);
}
/**
* Create a new property.
* @param name the property name (should contain only letters, numbers, and
* dashes; letters should be uppercase by convention)
* @param value the property value
*/
public VObjectProperty(String name, String value) {
this(null, name, value);
}
/**
* Creates a new property
* @param group the group name (should contain only letters, numbers, and
* dashes; can be null)
* @param name the property name (should contain only letters, numbers, and
* dashes; letters should be uppercase by convention)
* @param value the property value
*/
public VObjectProperty(String group, String name, String value) {
this(group, name, new VObjectParameters(), value);
}
/**
* Creates a new property
* @param group the group name (should contain only letters, numbers, and
* dashes; can be null)
* @param name the property name (should contain only letters, numbers, and
* dashes; letters should be uppercase by convention)
* @param parameters the property parameters (cannot be null)
* @param value the property value
*/
public VObjectProperty(String group, String name, VObjectParameters parameters, String value) {
this.group = group;
this.name = name;
this.parameters = parameters;
this.value = value;
}
/**
* Gets the group name (note: iCalendar properties do not use group names).
* @return the group name or null if the property doesn't have one
*/
public String getGroup() {
return group;
}
/**
* Sets the group name (note: iCalendar properties do not use group names).
* @param group the group name or null to remove (should contain only
* letters, numbers, and dashes)
*/
public void setGroup(String group) {
this.group = group;
}
/**
* Gets the property name.
* @return the property name
*/
public String getName() {
return name;
}
/**
* Sets the property name.
* @param name the property name (should contain only letters, numbers, and
* dashes; letters should be uppercase by convention)
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the parameters.
* @return the parameters
*/
public VObjectParameters getParameters() {
return parameters;
}
/**
* Sets the parameters.
* @param parameters the parameters (cannot be null)
*/
public void setParameters(VObjectParameters parameters) {
this.parameters = parameters;
}
/**
* Gets the property value.
* @return the property value
*/
public String getValue() {
return value;
}
/**
* Sets the property value.
* @param value the property value
*/
public void setValue(String value) {
this.value = value;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((group == null) ? 0 : group.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((parameters == null) ? 0 : parameters.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
VObjectProperty other = (VObjectProperty) obj;
if (group == null) {
if (other.group != null) return false;
} else if (!group.equals(other.group)) return false;
if (name == null) {
if (other.name != null) return false;
} else if (!name.equals(other.name)) return false;
if (parameters == null) {
if (other.parameters != null) return false;
} else if (!parameters.equals(other.parameters)) return false;
if (value == null) {
if (other.value != null) return false;
} else if (!value.equals(other.value)) return false;
return true;
}
@Override
public String toString() {
return "VObjectProperty [group=" + group + ", name=" + name + ", parameters=" + parameters + ", value=" + value + "]";
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/codec/ 0000775 0000000 0000000 00000000000 13262154162 0024030 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/codec/DecoderException.java 0000664 0000000 0000000 00000007121 13262154162 0030120 0 ustar 00root root 0000000 0000000 /*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.mangstadt.vinnie.codec;
/**
* Thrown when there is a failure condition during the decoding process. This exception is thrown when a Decoder
* encounters a decoding specific exception such as invalid data, or characters outside of the expected range.
*
* @version $Id: DecoderException.java 1619948 2014-08-22 22:53:55Z ggregory $
*/
public class DecoderException extends Exception {
/**
* Declares the Serial Version Uid.
*
* @see Always Declare Serial Version Uid
*/
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with null as its detail message. The cause is not initialized, and may
* subsequently be initialized by a call to {@link #initCause}.
*
* @since 1.4
*/
public DecoderException() {
super();
}
/**
* Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
* be initialized by a call to {@link #initCause}.
*
* @param message
* The detail message which is saved for later retrieval by the {@link #getMessage()} method.
*/
public DecoderException(final String message) {
super(message);
}
/**
* Constructs a new exception with the specified detail message and cause.
*
* Note that the detail message associated with cause is not automatically incorporated into this
* exception's detail message.
*
* @param message
* The detail message which is saved for later retrieval by the {@link #getMessage()} method.
* @param cause
* The cause which is saved for later retrieval by the {@link #getCause()} method. A null
* value is permitted, and indicates that the cause is nonexistent or unknown.
* @since 1.4
*/
public DecoderException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* Constructs a new exception with the specified cause and a detail message of (cause==null ?
* null : cause.toString()) (which typically contains the class and detail message of cause).
* This constructor is useful for exceptions that are little more than wrappers for other throwables.
*
* @param cause
* The cause which is saved for later retrieval by the {@link #getCause()} method. A null
* value is permitted, and indicates that the cause is nonexistent or unknown.
* @since 1.4
*/
public DecoderException(final Throwable cause) {
super(cause);
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/codec/EncoderException.java 0000664 0000000 0000000 00000007151 13262154162 0030135 0 ustar 00root root 0000000 0000000 /*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.mangstadt.vinnie.codec;
/**
* Thrown when there is a failure condition during the encoding process. This exception is thrown when an
* Encoder encounters a encoding specific exception such as invalid data, inability to calculate a checksum,
* characters outside of the expected range.
*
* @version $Id: EncoderException.java 1619948 2014-08-22 22:53:55Z ggregory $
*/
public class EncoderException extends Exception {
/**
* Declares the Serial Version Uid.
*
* @see Always Declare Serial Version Uid
*/
private static final long serialVersionUID = 1L;
/**
* Constructs a new exception with null as its detail message. The cause is not initialized, and may
* subsequently be initialized by a call to {@link #initCause}.
*
* @since 1.4
*/
public EncoderException() {
super();
}
/**
* Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently
* be initialized by a call to {@link #initCause}.
*
* @param message
* a useful message relating to the encoder specific error.
*/
public EncoderException(final String message) {
super(message);
}
/**
* Constructs a new exception with the specified detail message and cause.
*
*
* Note that the detail message associated with cause is not automatically incorporated into this
* exception's detail message.
*
*
* @param message
* The detail message which is saved for later retrieval by the {@link #getMessage()} method.
* @param cause
* The cause which is saved for later retrieval by the {@link #getCause()} method. A null
* value is permitted, and indicates that the cause is nonexistent or unknown.
* @since 1.4
*/
public EncoderException(final String message, final Throwable cause) {
super(message, cause);
}
/**
* Constructs a new exception with the specified cause and a detail message of (cause==null ?
* null : cause.toString()) (which typically contains the class and detail message of cause).
* This constructor is useful for exceptions that are little more than wrappers for other throwables.
*
* @param cause
* The cause which is saved for later retrieval by the {@link #getCause()} method. A null
* value is permitted, and indicates that the cause is nonexistent or unknown.
* @since 1.4
*/
public EncoderException(final Throwable cause) {
super(cause);
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/codec/QuotedPrintableCodec.java 0000664 0000000 0000000 00000015401 13262154162 0030734 0 ustar 00root root 0000000 0000000 /*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
Copyright (c) 2012-2016, Michael Angstadt
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
*/
package com.github.mangstadt.vinnie.codec;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.BitSet;
/**
*
* Encodes and decodes strings using quoted-printable encoding.
*
*
* The majority of this class's source code was taken from the Apache Commons
* Codec project (version 1.10). Defining this library as a project
* dependency causes an issue with Android devices, which is why parts of its
* source code have been directly incorporated into the vinnie code base.
*
* @author Apache Software Foundation
* @author Michael Angstadt
* @see Apache Commons
* Codec
*/
public class QuotedPrintableCodec {
private static final byte ESCAPE_CHAR = '=';
private static final byte TAB = 9;
private static final byte SPACE = 32;
private static final BitSet PRINTABLE_CHARS = new BitSet(256);
static {
// alpha characters
for (int i = 33; i <= 60; i++) {
PRINTABLE_CHARS.set(i);
}
for (int i = 62; i <= 126; i++) {
PRINTABLE_CHARS.set(i);
}
PRINTABLE_CHARS.set(TAB);
PRINTABLE_CHARS.set(SPACE);
}
private final String charset;
public QuotedPrintableCodec(String charset) {
this.charset = charset;
}
/**
* Encodes a string into its quoted-printable form.
* @param string the string to convert to quoted-printable form
* @return the quoted-printable string
* @throws EncoderException if the charset is not supported by the JVM
*/
public String encode(String string) throws EncoderException {
byte bytes[];
try {
bytes = string.getBytes(charset);
} catch (UnsupportedEncodingException e) {
throw new EncoderException(e);
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (byte c : bytes) {
int b = c;
if (b < 0) {
b = 256 + b;
}
if (PRINTABLE_CHARS.get(b)) {
buffer.write(b);
} else {
encodeQuotedPrintable(b, buffer);
}
}
try {
return new String(buffer.toByteArray(), "US-ASCII");
} catch (UnsupportedEncodingException e) {
//should never be thrown because all JVMs must support US-ASCII
throw new EncoderException(e);
}
}
/**
* Decodes a quoted-printable string into its original form.
* @param string the quoted-printable string
* @return the original string
* @throws DecoderException if the charset is not supported by the JVM or if
* there's a problem decoding the string
*/
public String decode(String string) throws DecoderException {
byte bytes[];
try {
bytes = string.getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
//should never be thrown because all JVMs must support US-ASCII
throw new DecoderException(e);
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (int i = 0; i < bytes.length; i++) {
int b = bytes[i];
if (b == ESCAPE_CHAR) {
try {
int u = digit16(bytes[++i]);
int l = digit16(bytes[++i]);
buffer.write((char) ((u << 4) + l));
} catch (ArrayIndexOutOfBoundsException e) {
throw new DecoderException("Invalid quoted-printable encoding", e);
}
} else {
buffer.write(b);
}
}
try {
return new String(buffer.toByteArray(), charset);
} catch (UnsupportedEncodingException e) {
throw new DecoderException(e);
}
}
/**
* Returns the numeric value of the given character in radix 16.
* @param b the character to be converted.
* @return the numeric value represented by the character in radix 16.
* @throws DecoderException when the byte is not valid per
* {@link Character#digit(char,int)}
*/
private static int digit16(byte b) throws DecoderException {
int i = Character.digit((char) b, 16);
if (i == -1) {
throw new DecoderException("Invalid URL encoding: not a valid digit (radix 16): " + b);
}
return i;
}
/**
* Encodes byte into its quoted-printable representation.
* @param b the byte to encode
* @param buffer the buffer to write to
*/
private static void encodeQuotedPrintable(int b, ByteArrayOutputStream buffer) {
buffer.write(ESCAPE_CHAR);
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
buffer.write(hex1);
buffer.write(hex2);
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/io/ 0000775 0000000 0000000 00000000000 13262154162 0023362 5 ustar 00root root 0000000 0000000 vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/io/Buffer.java 0000664 0000000 0000000 00000005003 13262154162 0025434 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie.io;
/**
* Wraps a {@link StringBuilder} object, providing utility methods for getting
* and clearing its value.
* @author Michael Angstadt
*/
class Buffer {
private final StringBuilder sb = new StringBuilder(1024);
/**
* Clears the buffer.
* @return this
*/
public Buffer clear() {
sb.setLength(0);
return this;
}
/**
* Gets the buffer's contents.
* @return the buffer's contents
*/
public String get() {
return sb.toString();
}
/**
* Gets the buffer's contents, then clears it.
* @return the buffer's contents
*/
public String getAndClear() {
String string = get();
clear();
return string;
}
/**
* Appends a character to the buffer.
* @param ch the character to append
* @return this
*/
public Buffer append(char ch) {
sb.append(ch);
return this;
}
/**
* Appends a character sequence to the buffer.
* @param string the character sequence to append
* @return this
*/
public Buffer append(CharSequence string) {
sb.append(string);
return this;
}
/**
* Removes the last character from the buffer (does nothing if the buffer is
* empty).
* @return this
*/
public Buffer chop() {
if (size() > 0) {
sb.setLength(sb.length() - 1);
}
return this;
}
/**
* Gets the number of characters in the buffer.
* @return the buffer's length
*/
public int size() {
return sb.length();
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/io/Context.java 0000664 0000000 0000000 00000005223 13262154162 0025653 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie.io;
import java.util.Collections;
import java.util.List;
/**
* Contains information related to the status of a parse operation, such as the
* parent component hierarchy and line number.
* @author Michael Angstadt
* @see VObjectDataListener
*/
public class Context {
final List parentComponents;
final Buffer unfoldedLine = new Buffer();
int lineNumber = 1;
boolean stop = false;
Context(List parentComponents) {
this.parentComponents = Collections.unmodifiableList(parentComponents);
}
/**
* Gets the hierarchy of parent components the parser is currently inside
* of, starting with the outer-most component.
* @return the component names (in uppercase; this list is immutable)
*/
public List getParentComponents() {
return parentComponents;
}
/**
* Gets the raw, unfolded line that was parsed.
* @return the raw, unfolded line
*/
public String getUnfoldedLine() {
return unfoldedLine.get();
}
/**
* Gets the line number of the parsed line. If the line was folded, this
* will be the line number of the first line.
* @return the line number
*/
public int getLineNumber() {
return lineNumber;
}
/**
* Instructs the parser to stop parsing and return from the call to
* {@link VObjectReader#parse}.
*/
public void stop() {
stop = true;
}
@Override
public String toString() {
return "Context [parentComponents=" + parentComponents + ", unfoldedLine=" + unfoldedLine.get() + ", lineNumber=" + lineNumber + ", stop=" + stop + "]";
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/io/FoldedLineWriter.java 0000664 0000000 0000000 00000023354 13262154162 0027436 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie.io;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
import com.github.mangstadt.vinnie.SyntaxStyle;
import com.github.mangstadt.vinnie.codec.EncoderException;
import com.github.mangstadt.vinnie.codec.QuotedPrintableCodec;
/**
* Automatically folds lines as they are written.
* @author Michael Angstadt
*/
public class FoldedLineWriter extends Writer {
private static final String CRLF = "\r\n";
private final Writer writer;
private Integer lineLength = 75;
private String indent = " ";
private int curLineLength = 0;
/**
* Creates a folded line writer.
* @param writer the writer object to wrap
*/
public FoldedLineWriter(Writer writer) {
this.writer = writer;
}
/**
* Writes a newline.
* @throws IOException if there's a problem writing to the output stream
*/
public void writeln() throws IOException {
write(CRLF);
}
/**
* Writes a string.
* @param str the string to write
* @param quotedPrintable true to encode the string in quoted-printable
* encoding, false not to
* @param charset the character set to use when encoding the string into
* quoted-printable
* @throws IOException if there's a problem writing to the output stream
*/
public void write(CharSequence str, boolean quotedPrintable, Charset charset) throws IOException {
write(str.toString().toCharArray(), 0, str.length(), quotedPrintable, charset);
}
/**
* Writes a portion of an array of characters.
* @param cbuf the array of characters
* @param off the offset from which to start writing characters
* @param len the number of characters to write
* @throws IOException if there's a problem writing to the output stream
*/
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
write(cbuf, off, len, false, null);
}
/**
* Writes a portion of an array of characters.
* @param cbuf the array of characters
* @param off the offset from which to start writing characters
* @param len the number of characters to write
* @param quotedPrintable true to encode the string in quoted-printable
* encoding, false not to
* @param charset the character set to use when encoding the string into
* quoted-printable
* @throws IOException if there's a problem writing to the output stream
*/
public void write(char[] cbuf, int off, int len, boolean quotedPrintable, Charset charset) throws IOException {
if (quotedPrintable) {
String str = new String(cbuf, off, len);
QuotedPrintableCodec codec = new QuotedPrintableCodec(charset.name());
String encoded;
try {
encoded = codec.encode(str);
} catch (EncoderException e) {
/*
* Thrown if an unsupported charset is passed into the codec.
* This should never happen because we already know the charset
* is valid (a Charset object is passed into the method).
*/
throw new IOException(e);
}
cbuf = encoded.toCharArray();
off = 0;
len = cbuf.length;
}
if (lineLength == null) {
/*
* If line folding is disabled, then write directly to the Writer.
*/
writer.write(cbuf, off, len);
return;
}
int effectiveLineLength = lineLength;
if (quotedPrintable) {
/*
* Account for the "=" character that must be appended onto each
* line.
*/
effectiveLineLength -= 1;
}
int encodedCharPos = -1;
int start = off;
int end = off + len;
for (int i = start; i < end; i++) {
char c = cbuf[i];
/*
* Keep track of the quoted-printable characters to prevent them
* from being cut in two at a folding boundary.
*/
if (encodedCharPos >= 0) {
encodedCharPos++;
if (encodedCharPos == 3) {
encodedCharPos = -1;
}
}
if (c == '\n') {
writer.write(cbuf, start, i - start + 1);
curLineLength = 0;
start = i + 1;
continue;
}
if (c == '\r') {
if (i == end - 1 || cbuf[i + 1] != '\n') {
writer.write(cbuf, start, i - start + 1);
curLineLength = 0;
start = i + 1;
} else {
curLineLength++;
}
continue;
}
if (c == '=' && quotedPrintable) {
encodedCharPos = 0;
}
if (curLineLength >= effectiveLineLength) {
/*
* If the last characters on the line are whitespace, then
* exceed the max line length in order to include the whitespace
* on the same line.
*
* This is to prevent the whitespace from merging with the
* folding whitespace of the following folded line and
* potentially being lost.
*
* Old syntax style allows multiple whitespace characters to be
* used for folding, so it could get lost here. New syntax style
* only allows one character to be used.
*/
if (Character.isWhitespace(c)) {
while (Character.isWhitespace(c) && i < end - 1) {
i++;
c = cbuf[i];
}
if (i >= end - 1) {
/*
* The rest of the char array is whitespace, so leave
* the loop.
*/
break;
}
}
/*
* If we are in the middle of a quoted-printable encoded
* character, then exceed the max line length so the sequence
* doesn't get split up across multiple lines.
*/
if (encodedCharPos > 0) {
i += 3 - encodedCharPos;
if (i >= end - 1) {
/*
* The rest of the char array was a quoted-printable
* encoded char, so leave the loop.
*/
break;
}
}
/*
* If the last char is the low (second) char in a surrogate
* pair, don't split the pair across two lines.
*/
if (Character.isLowSurrogate(c)) {
i++;
if (i >= end - 1) {
/*
* Surrogate pair finishes the char array, so leave the
* loop.
*/
break;
}
}
writer.write(cbuf, start, i - start);
if (quotedPrintable) {
writer.write('=');
}
writer.write(CRLF);
/*
* Do not include indentation whitespace if the value is
* quoted-printable.
*/
curLineLength = 1;
if (!quotedPrintable) {
writer.write(indent);
curLineLength += indent.length();
}
start = i;
continue;
}
curLineLength++;
}
writer.write(cbuf, start, end - start);
}
/**
* Gets the maximum length a line can be before it is folded (excluding the
* newline, defaults to 75).
* @return the line length or null if folding is disabled
*/
public Integer getLineLength() {
return lineLength;
}
/**
* Sets the maximum length a line can be before it is folded (excluding the
* newline, defaults to 75).
* @param lineLength the line length or null to disable folding
* @throws IllegalArgumentException if the line length is less than or equal
* to zero, or the line length is less than the length of the indent string
*/
public void setLineLength(Integer lineLength) {
if (lineLength != null) {
if (lineLength <= 0) {
throw new IllegalArgumentException("Line length must be greater than 0.");
}
if (lineLength <= indent.length()) {
throw new IllegalArgumentException("Line length must be greater than indent string length.");
}
}
this.lineLength = lineLength;
}
/**
* Gets the string that is prepended to each folded line (defaults to a
* single space character).
* @return the indent string
*/
public String getIndent() {
return indent;
}
/**
* Sets the string that is prepended to each folded line (defaults to a
* single space character).
* @param indent the indent string (cannot be empty, may only contain tabs
* and spaces). Note that data streams using {@link SyntaxStyle#NEW} syntax
* MUST use an indent string that contains EXACTLY ONE character.
* @throws IllegalArgumentException if the indent string is empty, or the
* length of the indent string is greater than the max line length, or the
* indent string contains illegal characters
*/
public void setIndent(String indent) {
if (indent.length() == 0) {
throw new IllegalArgumentException("Indent string cannot be empty.");
}
if (lineLength != null && indent.length() >= lineLength) {
throw new IllegalArgumentException("Indent string length must be less than the line length.");
}
for (int i = 0; i < indent.length(); i++) {
char c = indent.charAt(i);
switch (c) {
case ' ':
case '\t':
break;
default:
throw new IllegalArgumentException("Indent string can only contain tabs and spaces.");
}
}
this.indent = indent;
}
/**
* Gets the wrapped {@link Writer} object.
* @return the writer object
*/
public Writer getWriter() {
return writer;
}
/**
* Closes the writer.
*/
@Override
public void close() throws IOException {
writer.close();
}
/**
* Flushes the writer.
*/
@Override
public void flush() throws IOException {
writer.flush();
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/io/SyntaxRules.java 0000664 0000000 0000000 00000012462 13262154162 0026533 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie.io;
import java.util.HashMap;
import java.util.Map;
import com.github.mangstadt.vinnie.SyntaxStyle;
/**
* Defines a set of rules that determine what kind of syntax the vobject data
* stream uses.
* @author Michael Angstadt
*/
public class SyntaxRules {
private SyntaxStyle defaultSyntaxStyle;
private final Map> syntaxRules = new HashMap>();
/**
* Creates a new set of syntax rules.
* @param defaultSyntaxStyle the syntax style to use when it can't be
* determined what the data stream uses
*/
public SyntaxRules(SyntaxStyle defaultSyntaxStyle) {
this.defaultSyntaxStyle = defaultSyntaxStyle;
}
/**
* Gets the the syntax style to use when it can't be determined what the
* data stream uses.
* @return the default syntax style
*/
public SyntaxStyle getDefaultSyntaxStyle() {
return defaultSyntaxStyle;
}
/**
* Sets the syntax style to use when it can't be determined what the data
* stream uses.
* @param defaultSyntaxStyle the default syntax style (cannot be null)
*/
public void setDefaultSyntaxStyle(SyntaxStyle defaultSyntaxStyle) {
this.defaultSyntaxStyle = defaultSyntaxStyle;
}
/**
* Determines if this object contains rules for the given component.
* @param component the component name (e.g. "VCARD")
* @return true if this component has syntax rules, false if not
*/
public boolean hasSyntaxRules(String component) {
if (component != null) {
component = component.toUpperCase();
}
return syntaxRules.containsKey(component);
}
/**
* Gets the syntax style to use for a given component.
* @param component the component name (e.g. "VCARD")
* @param versionValue the value of the component's VERSION property (e.g.
* "2.1")
* @return the syntax style or null if none was found
*/
public SyntaxStyle getSyntaxStyle(String component, String versionValue) {
component = (component == null) ? null : component.toUpperCase();
Map rules = syntaxRules.get(component);
return (rules == null) ? null : rules.get(versionValue);
}
/**
* Adds a rule.
* @param component the name of the component that contains the VERSION
* property (e.g. "VCARD"), or null if the VERSION property will not be
* inside of any components
* @param version the value of the VERSION property
* @param syntaxStyle the syntax style to use when a VERSION property with
* the given value, and under the given component, is encountered
*/
public void addRule(String component, String version, SyntaxStyle syntaxStyle) {
component = (component == null) ? null : component.toUpperCase();
Map rules = syntaxRules.get(component);
if (rules == null) {
rules = new HashMap();
syntaxRules.put(component, rules);
}
rules.put(version, syntaxStyle);
}
/**
* Creates a set of rules for iCalendar data.
* @return the rules
*/
public static SyntaxRules iCalendar() {
/*
* Initialize to the old style syntax because the VERSION property can
* technically exist anywhere inside the data stream under this version.
*
* However, this setting is rarely important in practice because I've
* never seen an iCalendar object that doesn't put its VERSION property
* at the very beginning.
*/
SyntaxRules rules = new SyntaxRules(SyntaxStyle.OLD);
String component = "VCALENDAR";
rules.addRule(component, "1.0", SyntaxStyle.OLD);
rules.addRule(component, "2.0", SyntaxStyle.NEW);
return rules;
}
/**
* Creates a set of rules for vCard data.
* @return the rules
*/
public static SyntaxRules vcard() {
/*
* Initialize to the old style syntax because the VERSION property can
* technically exist anywhere inside the data stream under this version.
*
* However, this setting is rarely important in practice because I've
* never seen a vCard that doesn't put its VERSION property at the very
* beginning.
*/
SyntaxRules rules = new SyntaxRules(SyntaxStyle.OLD);
String component = "VCARD";
rules.addRule(component, "2.1", SyntaxStyle.OLD);
rules.addRule(component, "3.0", SyntaxStyle.NEW);
rules.addRule(component, "4.0", SyntaxStyle.NEW);
return rules;
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/io/VObjectDataAdapter.java 0000664 0000000 0000000 00000003430 13262154162 0027654 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie.io;
import com.github.mangstadt.vinnie.VObjectProperty;
/**
* Empty implementation of {@link VObjectDataListener}.
* @author Michael Angstadt
*/
public class VObjectDataAdapter implements VObjectDataListener {
public void onComponentBegin(String name, Context context) {
//empty
}
public void onComponentEnd(String name, Context context) {
//empty
}
public void onProperty(VObjectProperty property, Context context) {
//empty
}
public void onVersion(String value, Context context) {
//empty
}
public void onWarning(Warning warning, VObjectProperty property, Exception thrown, Context context) {
//empty
}
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/io/VObjectDataListener.java 0000664 0000000 0000000 00000007515 13262154162 0030071 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie.io;
import com.github.mangstadt.vinnie.VObjectProperty;
/**
* Callback interface used by {@link VObjectReader} for handling data as it is
* parsed off the data stream.
* @see VObjectReader#parse
* @author Michael Angstadt
*/
public interface VObjectDataListener {
/**
* Called when a component begins (in other words, when a BEGIN property is
* encountered).
* @param name the component name (will always be in uppercase)
* @param context the parse context
*/
void onComponentBegin(String name, Context context);
/**
*
* Called when a component ends (in other words, when an END property is
* encountered).
*
*
* If a component ends before any of its subcomponents end, then the parser
* will generate "onComponentEnd" events for each subcomponent.
*
*
*
* For example, the following data will cause the following sequence of
* events to occur. Notice that the outer component, A, ends before its
* subcomponents.
*
*
* @param name the component name (will always be in uppercase)
* @param context the parse context
*/
void onComponentEnd(String name, Context context);
/**
* Called when a property is read.
* @param property the property
* @param context the parse context
*/
void onProperty(VObjectProperty property, Context context);
/**
* Called when a VERSION property is read whose value and position (as
* defined in {@link SyntaxRules}) are recognized as valid.
* @param value the version string
* @param context the parse context
*/
void onVersion(String value, Context context);
/**
* Called when a non-fatal error occurs during parsing.
* @param warning the warning
* @param property the property that the warning is associated with, or null
* if the warning is not associated with a property
* @param thrown the exception that was thrown or null if no exception was
* thrown
* @param context the parse context
*/
void onWarning(Warning warning, VObjectProperty property, Exception thrown, Context context);
}
vinnie-2.0.2/src/main/java/com/github/mangstadt/vinnie/io/VObjectPropertyValues.java 0000664 0000000 0000000 00000067203 13262154162 0030516 0 ustar 00root root 0000000 0000000 /*
* MIT License
*
* Copyright (c) 2016 Michael Angstadt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.mangstadt.vinnie.io;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Contains utility methods for parsing and writing property values.
* @author Michael Angstadt
*/
public final class VObjectPropertyValues {
/**
* The local computer's newline character sequence.
*/
private static final String NEWLINE = System.getProperty("line.separator");
/**
*
* Unescapes all escaped characters in a property value. Escaped newlines
* are replaced with the local system's newline character sequence.
*
*
* @param value the value to unescape
* @return the unescaped value
*/
public static String unescape(String value) {
return unescape(value, 0, value.length());
}
/**
* Unescapes all escaped characters in a substring.
* @param string the entire string
* @param start the start index of the substring to unescape
* @param end the end index (exclusive) of the substring to unescape
* @return the unescaped substring
*/
private static String unescape(String string, int start, int end) {
StringBuilder sb = null;
boolean escaped = false;
for (int i = start; i < end; i++) {
char c = string.charAt(i);
if (escaped) {
escaped = false;
if (sb == null) {
sb = new StringBuilder(end - start);
sb.append(string.substring(start, i - 1));
}
switch (c) {
case 'n':
case 'N':
sb.append(NEWLINE);
continue;
}
sb.append(c);
continue;
}
switch (c) {
case '\\':
escaped = true;
continue;
}
if (sb != null) {
sb.append(c);
}
}
if (sb != null) {
return sb.toString();
}
/*
* The "String#substring" method makes no guarantee that the same String
* object will be returned if the entire string length is passed into
* the method.
*/
if (start == 0 && end == string.length()) {
return string;
}
return string.substring(start, end);
}
/**
*
* Escapes all special characters within a property value. These characters
* are:
*
*
*
backslashes ({@code \})
*
commas ({@code ,})
*
semi-colons ({@code ;})
*
*
* Newlines are not escaped by this method. They are automatically escaped
* by {@link VObjectWriter} when the data is serialized.
*
*
* @param value the value to escape
* @return the escaped value
*/
public static String escape(String value) {
StringBuilder sb = null;
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
switch (c) {
case '\\':
case ',':
case ';':
if (sb == null) {
sb = new StringBuilder(value.length() * 2);
sb.append(value.substring(0, i));
}
sb.append('\\').append(c);
break;
default:
if (sb != null) {
sb.append(c);
}
break;
}
}
return (sb == null) ? value : sb.toString();
}
/**
* Escapes all special characters within the given string.
* @param string the string to escape
* @param escapeCommas true to escape comma characters, false not to.
* Old-style syntax does not expect commas to be escaped in semi-structured
* values.
* @param sb the buffer on which to append the escaped string
*/
private static void escape(String string, boolean escapeCommas, StringBuilder sb) {
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
if (c == '\\' || c == ';' || (escapeCommas && c == ',')) {
sb.append('\\');
}
sb.append(c);
}
}
/**
*
* Parses a "list" property value.
*
*
* List values contain multiple values separated by commas. The order that
* the values are in usually doesn't matter.
*
*
* Example:
*
*
*
* String value = "one,two\\,three";
* List<String> list = VObjectPropertyValues.parseList(value);
* assertEquals(Arrays.asList("one", "two,three"), list);
*
*
* @param value the value to parse
* @return the parsed list
*/
public static List parseList(String value) {
return split(value, ',', -1);
}
/**
*
* Generates a "list" property value.
*
*
* List values contain multiple values separated by commas. The order that
* the values are in usually doesn't matter.
*
*
* Each list item's {@code toString()} method is called to generate its
* string representation. If a list item is null, then "null" will be
* outputted.
*
*
* Example:
*
*
*
* List<String> list = Arrays.asList("one", "two", null, "three,four");
* String value = VObjectPropertyValues.writeList(list);
* assertEquals("one,two,null,three\\,four", value);
*
*
* @param values the values to write
* @return the list value string
*/
public static String writeList(Collection> values) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Object value : values) {
if (!first) {
sb.append(',');
}
if (value == null) {
sb.append("null");
} else {
escape(value.toString(), true, sb);
}
first = false;
}
return sb.toString();
}
/**
*
* Parses a "semi-structured" property value.
*
*
* Semi-structured values contain multiple values separate by semicolons.
* Unlike structured values, each value cannot have their own
* comma-delimited list of sub-values. The order that the values are in
* usually matters.
*
*
* @param value the value to parse
* @return the parsed values
*/
public static List parseSemiStructured(String value) {
return parseSemiStructured(value, -1);
}
/**
*
* Parses a "semi-structured" property value.
*
*
* Semi-structured values contain multiple values separate by semicolons.
* Unlike structured values, each value cannot have their own
* comma-delimited list of sub-values. The order that the values are in
* usually matters.
*
*
* @param value the value to parse
* @param limit the max number of items to parse
* @return the parsed values
*/
public static List parseSemiStructured(String value, int limit) {
return split(value, ';', limit);
}
/**
*
* Writes a "semi-structured" property value.
*
*
* Semi-structured values contain multiple values separate by semicolons.
* Unlike structured values, each value cannot have their own
* comma-delimited list of sub-values. The order that the values are in
* usually matters.
*
* Example:
*
*
*
* List<String> list = Arrays.asList("one", null, "two;three", "");
*
* String value = VObjectPropertyValues.writeSemiStructured(list, false);
* assertEquals("one;null;two\\;three", value);
*
* value = VObjectPropertyValues.writeSemiStructured(list, true);
* assertEquals("one;null;two\\;three;", value);
*
*
* @param values the values to write
* @param escapeCommas true to escape comma characters, false not to.
* Old-style syntax does not expect commas to be escaped in semi-structured
* values.
* @param includeTrailingSemicolons true to include the semicolon delimiters
* for empty values at the end of the values list, false to trim them
* @return the semi-structured value string
*/
public static String writeSemiStructured(List> values, boolean escapeCommas, boolean includeTrailingSemicolons) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Object value : values) {
if (!first) {
sb.append(';');
}
if (value == null) {
sb.append("null");
} else {
escape(value.toString(), escapeCommas, sb);
}
first = false;
}
if (!includeTrailingSemicolons) {
trimTrailingSemicolons(sb);
}
return sb.toString();
}
/**
*
* Parses a "structured" property value.
*
*
* Structured values are essentially 2-D arrays. They contain multiple
* components separated by semicolons, and each component can have multiple
* values separated by commas. The order that the components are in matters,
* but the order that each component's list of values are in usually doesn't
* matter.
*
* @param value the value to parse
* @return the parsed values
*/
public static List> parseStructured(String value) {
if (value.length() == 0) {
return new ArrayList>(0); //return a mutable list
}
List> components = new ArrayList>();
List curComponent = new ArrayList();
components.add(curComponent);
boolean escaped = false;
int cursor = 0;
for (int i = 0; i < value.length(); i++) {
if (escaped) {
escaped = false;
continue;
}
char c = value.charAt(i);
switch (c) {
case ';':
String v = unescape(value, cursor, i);
if (curComponent.isEmpty() && v.length() == 0) {
/*
* If the component is empty, do not add an empty string to
* the list.
*/
} else {
curComponent.add(v);
}
curComponent = new ArrayList();
components.add(curComponent);
cursor = i + 1;
continue;
case ',':
v = unescape(value, cursor, i);
curComponent.add(v);
cursor = i + 1;
continue;
case '\\':
escaped = true;
continue;
}
}
String v = unescape(value, cursor, value.length());
if (curComponent.isEmpty() && v.length() == 0) {
/*
* If the component is empty, do not add an empty string to the
* list.
*/
} else {
curComponent.add(v);
}
return components;
}
/**
*
* Writes a "structured" property value.
*
*
* Structured values are essentially 2-D arrays. They contain multiple
* components separated by semicolons, and each component can have multiple
* values separated by commas. The order that the components are in matters,
* but the order that each component's list of values are in usually doesn't
* matter.
*
*
* The {@code toString()} method of each component value is called to
* generate its string representation. If a value is null, then "null" will
* be outputted.
*
* @param components the components to write
* @param includeTrailingSemicolons true to include the semicolon delimiters
* for empty components at the end of the written value, false to trim them
* @return the structured value string
*/
public static String writeStructured(List extends List>> components, boolean includeTrailingSemicolons) {
StringBuilder sb = new StringBuilder();
boolean firstComponent = true;
for (List> component : components) {
if (!firstComponent) {
sb.append(';');
}
boolean firstValue = true;
for (Object value : component) {
if (!firstValue) {
sb.append(',');
}
if (value == null) {
sb.append("null");
} else {
escape(value.toString(), true, sb);
}
firstValue = false;
}
firstComponent = false;
}
if (!includeTrailingSemicolons) {
trimTrailingSemicolons(sb);
}
return sb.toString();
}
/**
*
* Parses a "multimap" property value.
*
*
* Multimap values are collections of key/value pairs whose keys can be
* multi-valued. Key/value pairs are separated by semicolons. Values are
* separated by commas. Keys are converted to uppercase.
*
*
* @param value the value to parse
* @return the parsed values
*/
public static Map> parseMultimap(String value) {
if (value.length() == 0) {
return new HashMap>(0); //return a mutable map
}
Map> multimap = new LinkedHashMap>();
String curName = null;
List curValues = new ArrayList();
boolean escaped = false;
int cursor = 0;
for (int i = 0; i < value.length(); i++) {
if (escaped) {
escaped = false;
continue;
}
char c = value.charAt(i);
switch (c) {
case ';':
if (curName == null) {
curName = unescape(value, cursor, i).toUpperCase();
} else {
curValues.add(unescape(value, cursor, i));
}
if (curName.length() > 0) {
if (curValues.isEmpty()) {
curValues.add("");
}
List existing = multimap.get(curName);
if (existing == null) {
multimap.put(curName, curValues);
} else {
existing.addAll(curValues);
}
}
curName = null;
curValues = new ArrayList();
cursor = i + 1;
break;
case '=':
if (curName == null) {
curName = unescape(value, cursor, i).toUpperCase();
cursor = i + 1;
}
break;
case ',':
curValues.add(unescape(value, cursor, i));
cursor = i + 1;
break;
case '\\':
escaped = true;
break;
}
}
if (curName == null) {
curName = unescape(value, cursor, value.length()).toUpperCase();
} else {
curValues.add(unescape(value, cursor, value.length()));
}
if (curName.length() > 0) {
if (curValues.isEmpty()) {
curValues.add("");
}
List existing = multimap.get(curName);
if (existing == null) {
multimap.put(curName, curValues);
} else {
existing.addAll(curValues);
}
}
return multimap;
}
/**
*
* Writes a "multimap" property value.
*
*
* Multimap values are collections of key/value pairs whose keys can be
* multi-valued. Key/value pairs are separated by semicolons. Values are
* separated by commas. Keys are converted to uppercase.
*
*
* Each value's {@code toString()} method is called to generate its string
* representation. If a value is null, then "null" will be outputted.
*
*
* @param multimap the multimap to write
* @return the multimap value string
*/
public static String writeMultimap(Map> multimap) {
StringBuilder sb = new StringBuilder();
boolean firstKey = true;
for (Map.Entry> entry : multimap.entrySet()) {
if (!firstKey) {
sb.append(';');
}
String key = entry.getKey().toUpperCase();
escape(key, true, sb);
List> values = entry.getValue();
if (values.isEmpty()) {
continue;
}
sb.append('=');
boolean firstValue = true;
for (Object value : values) {
if (!firstValue) {
sb.append(',');
}
if (value == null) {
sb.append("null");
} else {
escape(value.toString(), true, sb);
}
firstValue = false;
}
firstKey = false;
}
return sb.toString();
}
/**
* Removes trailing semicolon characters from the end of the given buffer.
* @param sb the buffer
*/
private static void trimTrailingSemicolons(StringBuilder sb) {
int index = -1;
for (int i = sb.length() - 1; i >= 0; i--) {
char c = sb.charAt(i);
if (c != ';') {
index = i;
break;
}
}
sb.setLength(index + 1);
}
/**
* Splits a string.
* @param string the string to split
* @param delimiter the delimiter to split by
* @param limit the number of split values to parse or -1 to parse them all
* @return the split values
*/
private static List split(String string, char delimiter, int limit) {
if (string.length() == 0) {
return new ArrayList(0); //return a mutable list
}
List list = new ArrayList();
boolean escaped = false;
int cursor = 0;
for (int i = 0; i < string.length(); i++) {
char ch = string.charAt(i);
if (escaped) {
escaped = false;
continue;
}
if (ch == delimiter) {
String value = unescape(string, cursor, i);
list.add(value);
cursor = i + 1;
if (limit > 0 && list.size() == limit - 1) {
break;
}
continue;
}
switch (ch) {
case '\\':
escaped = true;
continue;
}
}
String value = unescape(string, cursor, string.length());
list.add(value);
return list;
}
/**
*
* Helper class for iterating over the values in a "semi-structured"
* property value.
*
*
* Semi-structured values contain multiple values separate by semicolons.
* Unlike structured values, each value cannot have their own
* comma-delimited list of sub-values. The order that the values are in
* usually matters.
*
*
* Example:
*
*
*
* String value = "one;two;;three";
*
* SemiStructuredValueIterator it = new SemiStructuredValueIterator(value);
* assertEquals("one", it.next());
* assertEquals("two", it.next());
* assertNull(it.next());
* assertEquals("three", it.next());
* assertFalse(it.hasNext());
*
* it = new SemiStructuredValueIterator(value, 2);
* assertEquals("one", it.next());
* assertEquals("two;;three", it.next());
* assertFalse(it.hasNext());
*
*/
public static class SemiStructuredValueIterator {
private final Iterator it;
/**
* Constructs a new semi-structured value iterator.
* @param value the value to parse
*/
public SemiStructuredValueIterator(String value) {
this(value, -1);
}
/**
* Constructs a new semi-structured value iterator.
* @param value the value to parse
* @param limit the number of values to parse, or -1 to parse all values
*/
public SemiStructuredValueIterator(String value, int limit) {
it = parseSemiStructured(value, limit).iterator();
}
/**
* Gets the next value.
* @return the next value or null if the value is empty or null if there
* are no more values
*/
public String next() {
if (!hasNext()) {
return null;
}
String next = it.next();
return (next.length() == 0) ? null : next;
}
/**
* Determines if there are any more values left.
* @return true if there are more values, false if not
*/
public boolean hasNext() {
return it.hasNext();
}
}
/**
*
* Helper class for building "semi-structured" property values.
*
*
* Semi-structured values contain multiple values separate by semicolons.
* Unlike structured values, each value cannot have their own
* comma-delimited list of sub-values. The order that the values are in
* usually matters.
*
*
* Example:
*
*
*
* SemiStructuredValueBuilder b = new SemiStructuredValueBuilder();
* b.append("one").append(null).append("two").append("");
* assertEquals("one;;two;", b.build());
* assertEquals("one;;two", b.build(false));
*
*/
public static class SemiStructuredValueBuilder {
private final List