| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.jack.jayce; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.nio.charset.Charset; |
| import java.util.Arrays; |
| |
| import javax.annotation.CheckForNull; |
| import javax.annotation.Nonnegative; |
| import javax.annotation.Nonnull; |
| |
| /** |
| * Jayce Header. |
| */ |
| public class JayceHeader { |
| |
| private static final char VERSION_SEPARATOR = '.'; |
| private static final char STRING_DELIMITER = '"'; |
| private static final char VALUE_SEPARATOR = ' '; |
| private static final char LEFT_BRACKET = '('; |
| private static final char RIGHT_BRACKET = ')'; |
| |
| @Nonnull |
| private static final String JAYCE_KEYWORD = "jayce"; |
| @Nonnull |
| private static final Charset DEFAULT_CHARSET = Charset.forName("US-ASCII"); |
| @Nonnull |
| private static final byte[] JAYCE_KEYWORD_BYTE_ARRAY = JAYCE_KEYWORD.getBytes(DEFAULT_CHARSET); |
| @Nonnull |
| private static final String STANDARD_ERROR_MESSAGE = "Invalid Jayce header"; |
| @Nonnegative |
| private static final int INT_MAX_DIGITS = String.valueOf(Integer.MAX_VALUE).length(); |
| @Nonnegative |
| private static final int EMITTER_ID_MAX_LENGTH = 1024; |
| @Nonnull |
| private static final String VERSION_FORMAT = "%04d"; |
| |
| @Nonnegative |
| private int majorVersion; |
| @Nonnegative |
| private int minorVersion; |
| @CheckForNull |
| private String emitterId = null; |
| |
| private char previousChar; |
| |
| public JayceHeader(@Nonnull InputStream is) throws IOException, JayceFormatException { |
| readHeader(is); |
| } |
| |
| private void readHeader(@Nonnull InputStream in) throws IOException, JayceFormatException { |
| |
| checkJayceKeyword(in); |
| |
| checkLeftBracket(readChar(in)); |
| |
| majorVersion = readInt(in); |
| |
| checkVersionSeparator(getPreviousChar()); |
| |
| minorVersion = readInt(in); |
| |
| if (checkIfRightBracket(getPreviousChar())) { |
| return; |
| } |
| |
| emitterId = readString(in, EMITTER_ID_MAX_LENGTH); |
| |
| if (!checkIfRightBracket(readChar(in))) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } |
| } |
| |
| private void checkLeftBracket(char readChar) throws JayceFormatException { |
| if (readChar != LEFT_BRACKET) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } |
| } |
| |
| private boolean checkIfRightBracket(char readChar) throws JayceFormatException { |
| if (readChar == RIGHT_BRACKET) { |
| return true; |
| } else if (readChar != VALUE_SEPARATOR) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } else { |
| return false; |
| } |
| } |
| |
| private void checkVersionSeparator(char potentialSeparator) throws JayceFormatException { |
| if (potentialSeparator != VERSION_SEPARATOR) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } |
| } |
| |
| private void checkJayceKeyword(@Nonnull InputStream in) |
| throws IOException, JayceFormatException { |
| byte[] byteArray = new byte[JAYCE_KEYWORD_BYTE_ARRAY.length]; |
| if (in.read(byteArray) != -1) { |
| if (!Arrays.equals(byteArray, JAYCE_KEYWORD_BYTE_ARRAY)) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } |
| } else { |
| throw new JayceFormatException("No Jayce header found"); |
| } |
| } |
| |
| private int readInt(@Nonnull InputStream in) throws IOException, JayceFormatException { |
| StringBuffer buffer = new StringBuffer(2); |
| char readChar = readChar(in); |
| int numRead = 1; |
| while (Character.isDigit(readChar)) { |
| if (numRead > INT_MAX_DIGITS) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } |
| buffer.append(readChar); |
| readChar = readChar(in); |
| numRead++; |
| } |
| try { |
| return Integer.parseInt(buffer.toString()); |
| } catch (NumberFormatException e) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } |
| } |
| |
| @Nonnull |
| private String readString(@Nonnull InputStream in, int upperLimit) |
| throws IOException, JayceFormatException { |
| char readChar = readChar(in); |
| if (readChar != STRING_DELIMITER) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } |
| StringBuffer buffer = new StringBuffer(upperLimit); |
| |
| readChar = readChar(in); |
| int numRead = 1; |
| while (readChar != STRING_DELIMITER) { |
| if (numRead > upperLimit) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } |
| buffer.append(readChar); |
| readChar = readChar(in); |
| numRead++; |
| } |
| return buffer.toString(); |
| } |
| |
| public void writeHeader(@Nonnull OutputStream out) throws IOException { |
| OutputStreamWriter writer = new OutputStreamWriter(out, DEFAULT_CHARSET); |
| writer.append(JAYCE_KEYWORD); |
| writer.append(LEFT_BRACKET); |
| writer.append(String.valueOf(majorVersion)); |
| writer.append(VERSION_SEPARATOR); |
| writer.append(String.valueOf(minorVersion)); |
| if (emitterId != null) { |
| writer.append(VALUE_SEPARATOR); |
| writer.append(STRING_DELIMITER); |
| writer.append(emitterId); |
| writer.append(STRING_DELIMITER); |
| } |
| writer.append(RIGHT_BRACKET); |
| writer.flush(); |
| } |
| |
| @Nonnegative |
| public int getMajorVersion() { |
| return majorVersion; |
| } |
| |
| @Nonnegative |
| public int getMinorVersion() { |
| return minorVersion; |
| } |
| |
| @Nonnull |
| public String getMajorVersionString() { |
| return getVersionString(majorVersion); |
| } |
| |
| @Nonnull |
| public static String getVersionString(@Nonnegative int version) { |
| return String.format(VERSION_FORMAT, Integer.valueOf(version)); |
| } |
| |
| @CheckForNull |
| public String getEmitterId() { |
| return emitterId; |
| } |
| |
| private char readChar(@Nonnull InputStream in) throws IOException, JayceFormatException { |
| int readChar = in.read(); |
| if (readChar == '\t') { |
| readChar = VALUE_SEPARATOR; |
| } |
| |
| // skip when several contiguous value separators (typically white spaces) |
| if (previousChar == VALUE_SEPARATOR) { |
| while (readChar == VALUE_SEPARATOR) { |
| readChar = in.read(); |
| } |
| } |
| |
| if (readChar == -1) { |
| throw new JayceFormatException(STANDARD_ERROR_MESSAGE); |
| } else { |
| previousChar = (char) readChar; |
| return previousChar; |
| } |
| } |
| |
| private char getPreviousChar() { |
| return previousChar; |
| } |
| |
| } |