bt.bencoding.BEParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bt-bencoding Show documentation
Show all versions of bt-bencoding Show documentation
Library for parsing, encoding and validating bencoded documents in Java
/*
* Copyright (c) 2016—2017 Andrei Tomashpolskiy and individual contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package bt.bencoding;
import bt.bencoding.model.BEInteger;
import bt.bencoding.model.BEList;
import bt.bencoding.model.BEMap;
import bt.bencoding.model.BEObject;
import bt.bencoding.model.BEString;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Objects;
/**
* BEncoding parser. Should be closed when the source is processed.
*
* @since 1.0
*/
public class BEParser implements AutoCloseable {
static final char EOF = 'e';
static final char INTEGER_PREFIX = 'i';
static final char LIST_PREFIX = 'l';
static final char MAP_PREFIX = 'd';
private Scanner scanner;
private final BEType type;
private Object parsedObject;
/**
* Create a parser for the provided URL's content.
*
* @param url URL's content must be a well-formed bencoded document.
* @since 1.0
*/
public BEParser(URL url) {
Objects.requireNonNull(url, "Missing URL");
try {
this.scanner = new Scanner(url.openStream());
} catch (IOException e) {
throw new BtParseException("Failed to open stream for URL: " + url, new byte[0], e);
}
this.type = getTypeForPrefix((char) scanner.peek());
}
/**
* Create a parser for the provided binary input.
*
* @param in Input's content must be a well-formed bencoded document.
* @since 1.0
*/
public BEParser(InputStream in) {
Objects.requireNonNull(in, "Input stream is null");
this.scanner = new Scanner(in);
this.type = getTypeForPrefix((char) scanner.peek());
}
/**
* Create a parser for the provided bencoded document.
*
* @param bs Bencoded document.
* @since 1.0
*/
public BEParser(byte[] bs) {
if (bs == null || bs.length == 0) {
throw new IllegalArgumentException("Can't parse bytes array: null or empty");
}
this.scanner = new Scanner(bs);
this.type = getTypeForPrefix((char) scanner.peek());
}
/**
* Read type of the root object of the bencoded document that this parser was created for.
*
* @since 1.0
*/
public BEType readType() {
return type;
}
static char getPrefixForType(BEType type) {
if (type == null) {
throw new NullPointerException("Can't get prefix -- type is null");
}
switch (type) {
case INTEGER:
return INTEGER_PREFIX;
case LIST:
return LIST_PREFIX;
case MAP:
return MAP_PREFIX;
default:
throw new IllegalArgumentException("Unknown type: " + type.name().toLowerCase());
}
}
static BEType getTypeForPrefix(char c) {
if (Character.isDigit(c)) {
return BEType.STRING;
}
switch (c) {
case INTEGER_PREFIX: {
return BEType.INTEGER;
}
case LIST_PREFIX: {
return BEType.LIST;
}
case MAP_PREFIX: {
return BEType.MAP;
}
default: {
throw new IllegalStateException("Invalid type prefix: " + c);
}
}
}
static BEObjectBuilder extends BEObject>> builderForType(BEType type) {
switch (type) {
case STRING: {
return new BEStringBuilder();
}
case INTEGER: {
return new BEIntegerBuilder();
}
case LIST: {
return new BEListBuilder();
}
case MAP: {
return new BEMapBuilder();
}
default: {
throw new IllegalStateException("Unknown type: " + type.name().toLowerCase());
}
}
}
/**
* Try to read the document's root object as a bencoded string.
*
* @see #readType()
* @since 1.0
*/
public BEString readString() {
return readObject(BEType.STRING, BEStringBuilder.class);
}
/**
* Try to read the document's root object as a bencoded integer.
*
* @see #readType()
* @since 1.0
*/
public BEInteger readInteger() {
return readObject(BEType.INTEGER, BEIntegerBuilder.class);
}
/**
* Try to read the document's root object as a bencoded list.
*
* @see #readType()
* @since 1.0
*/
public BEList readList() {
return readObject(BEType.LIST, BEListBuilder.class);
}
/**
* Try to read the document's root object as a bencoded dictionary.
*
* @see #readType()
* @since 1.0
*/
public BEMap readMap() {
return readObject(BEType.MAP, BEMapBuilder.class);
}
private T readObject(BEType type, Class extends BEObjectBuilder> builderClass) {
assertType(type);
@SuppressWarnings("unchecked")
T result = (T) parsedObject;
if (result == null) {
try {
// relying on the default constructor being present
parsedObject = result = scanner.readObject(builderClass.newInstance());
} catch (Exception e) {
throw new BtParseException("Failed to read from encoded data", scanner.getScannedContents(), e);
}
}
return result;
}
private void assertType(BEType type) {
if (type == null) {
throw new NullPointerException("Invalid type -- null");
}
if (this.type != type) {
throw new IllegalStateException(
"Can't read " + type.name().toLowerCase() + " from: " + this.type.name().toLowerCase());
}
}
@Override
public void close() {
if (scanner != null) {
scanner.close();
}
}
}