org.opentripplanner.model.projectinfo.GraphFileHeader Maven / Gradle / Ivy
Show all versions of otp Show documentation
package org.opentripplanner.model.projectinfo;
import org.opentripplanner.util.OtpAppException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The Graph.obj file start with file header. The header have two things:
*
* - Magic number: {@code "OpenTripPlannerGraph"}
* - The graph file serialization compatibility id
*
*
* This class represent the header and contain logic to parse and validate it.
*/
public class GraphFileHeader implements Serializable {
private static final long serialVersionUID = 1L;
private static final String MAGIC_NUMBER = "OpenTripPlannerGraph";
private static final char ID_PREFIX = '0';
private static final char DELIMITER = ';';
private static final int ID_LENGTH = 7;
private static final int HEADER_LENGTH = MAGIC_NUMBER.length() + ID_LENGTH + 2;
private static final Pattern HEADER_PATTERN = Pattern.compile(
MAGIC_NUMBER + DELIMITER + "([-\\w.:+/]{7})" + DELIMITER
);
public static final Charset CHARSET = StandardCharsets.ISO_8859_1;
public static final String UNKNOWN_ID = "UNKNOWN";
private final String otpSerializationVersionId;
private final byte[] bytes;
GraphFileHeader() {
this(UNKNOWN_ID);
}
GraphFileHeader(String otpSerializationVersionId) {
this.otpSerializationVersionId = stripId(otpSerializationVersionId);
this.bytes = asString().getBytes(CHARSET);
}
public static int headerLength() {
return HEADER_LENGTH;
}
public static GraphFileHeader parse(byte[] buf) {
if(buf.length < HEADER_LENGTH) {
throw new OtpAppException(
"Input file header is not large enough. At least " + HEADER_LENGTH + " bytes is needed. "
+ "Input: " + prettyBytesToString(buf)
);
}
String header = new String(buf, 0, HEADER_LENGTH, CHARSET);
Matcher m = HEADER_PATTERN.matcher(header);
if(!m.matches()) {
throw new OtpAppException(
"The file is no recognized as an OTP Graph file. The header do not match \""
+ HEADER_PATTERN.pattern() + "\".\n\tInput: " + prettyBytesToString(buf)
);
}
return new GraphFileHeader(m.group(1));
}
/** Return the entire header including magic-number and version id as a byte array */
public byte[] header() {
return bytes;
}
public String magicNumber() {
return MAGIC_NUMBER;
}
/**
* The OTP serialization version id. If OTP and a Graph.obj file have the same id,
* then OTP is compatible with the graph and should be able to deserialize it.
*
* The returned id do NOT include the prefix '0's. In the serialized form as bytes
* it have a fixed length {@link #ID_LENGTH} and is padded with {@link #ID_PREFIX}.
*/
public String otpSerializationVersionId() {
return otpSerializationVersionId;
}
public String otpSerializationVersionIdPadded() {
return padId(otpSerializationVersionId);
}
public String asString() {
return MAGIC_NUMBER + DELIMITER + otpSerializationVersionIdPadded() + DELIMITER;
}
public boolean isUnknown() {
return UNKNOWN_ID.equals(otpSerializationVersionId);
}
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
GraphFileHeader that = (GraphFileHeader) o;
return otpSerializationVersionId.equals(that.otpSerializationVersionId);
}
@Override
public int hashCode() {
return otpSerializationVersionId.hashCode();
}
@Override
public String toString() {
return asString();
}
/**
* Pad the given text until it have the expected length {@link #ID_LENGTH}
* using the {@link #ID_PREFIX}.
*/
static String padId(String text) {
StringBuilder buf = new StringBuilder();
while (buf.length() + text.length() < ID_LENGTH) { buf.append(ID_PREFIX); }
buf.append(text);
return buf.toString();
}
/**
* Strip of any {@link #ID_PREFIX} characters form the beginning of the given text.
*/
static String stripId(String text) {
int pos = 0;
while (pos < text.length() && text.charAt(pos) == ID_PREFIX) { ++pos; }
return text.substring(pos);
}
/** Example: 41 6C 66 61 2D 31 "Alfa-1" */
static String prettyBytesToString(byte[] text) {
if(text == null || text.length == 0) {
return "";
}
StringBuilder buf = new StringBuilder();
for (int v : text) {
buf.append(" ").append(String.format("%02X", v));
}
return buf.substring(1) + " \"" + new String(text, CHARSET) + "\"";
}
}