/*
* myLib - https://github.com/taktod/myLib
* Copyright (c) 2014 ttProject. All rights reserved.
*
* Licensed under The MIT license.
*/
package com.ttProject.media.flv.amf;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.ttProject.nio.channels.IReadChannel;
import com.ttProject.util.BufferUtil;
/**
* amf0のデータを扱うクラス
* @author taktod
* @see http://download.macromedia.com/pub/labs/amf/amf0_spec_121207.pdf
* Number 0x00 8バイト doublebits
* Boolean 0x01 1バイト 0x01:true 0x00:false
* String 0x02 2バイト(サイズ) データ
* Object 0x03 [2バイト(サイズ) データ 型タイプ 型データ] x 要素数分 00 00 09(eof)
* MovieClip 0x04 ;予約済みで未サポート
* Null 0x05
* Undefined 0x06
* Reference 0x07 2バイト(参照値)
* Map 0x08 4バイト(intデータ(要素数?)) [2バイト(サイズ) データ 型タイプ 型データ] x 要素数分 [00 00 2バイト(サイズ0)] 09(eof)
* ObjectEnd 0x09
* Array 0x0A [型タイプ 型データ] x 要素数分 [00 00 2バイト(サイズ0)] 09(eof)
* Date 0x0B 8バイト(doubleBits(unixtime)) 2バイト(timezone?)
* LongString 0x0C 4バイト(サイズ) データ
* Unsupported 0x0D
* RecordSet 0x0E ;予約済みで未サポート
* XmlDocument 0x0F
* TypedObject 0x10
*/
public class Amf0Value {
/**
* データタイプ
*/
public enum Type {
Number(0x00),
Boolean(0x01),
String(0x02),
Object(0x03),
MovieClip(0x04),
Null(0x05),
Undefined(0x06),
Reference(0x07),
Map(0x08),
ObjectEnd(0x09), // これ先頭にこないっぽい。
Array(0x0A),
Date(0x0B),
LongString(0x0C),
Unsupported(0x0D),
RecordSet(0x0E),
XmlDocument(0x0F),
TypedObject(0x10);
private final int value;
private Type(int value) {
this.value = value;
}
public int intValue() {
return value;
}
public static Type getType(int value) {
for(Type t : values()) {
if(t.intValue() == value) {
return t;
}
}
throw new RuntimeException("解析不能なデータでした。" + value);
}
}
/**
* ファイルからデータを呼び出してオブジェクト化していく。
* @param source
* @return
*/
public static Object getValueObject(IReadChannel source) throws Exception {
ByteBuffer data = null;
Type type = Type.getType(BufferUtil.safeRead(source, 1).get());
// 先頭の1バイトを読み込む
switch(type) {
case Number:
{
data = BufferUtil.safeRead(source, 8);
return Double.longBitsToDouble(data.getLong());
}
case Boolean:
{
return BufferUtil.safeRead(source, 1).get() != 0x00;
}
case String:
{
int length = BufferUtil.safeRead(source, 2).getShort();
data = BufferUtil.safeRead(source, length);
return new String(data.array()).intern();
}
case LongString:
{
int length = BufferUtil.safeRead(source, 4).getInt();
data = BufferUtil.safeRead(source, length);
return new String(data.array()).intern();
}
case Object:
{
Amf0Object<String, Object> object = new Amf0Object<String, Object>();
int nameSize;
while((nameSize = BufferUtil.safeRead(source, 2).getShort()) != 0) {
data = BufferUtil.safeRead(source, nameSize);
String key = new String(data.array()).intern();
Object value = getValueObject(source);
object.put(key, value);
}
if(Type.getType(BufferUtil.safeRead(source, 1).get()) != Type.ObjectEnd) {
throw new Exception("objectの終端がおかしかったです。");
}
return object;
}
case Null:
case Unsupported:
case Undefined:
{
return null;
}
case Map:
{
Map<String, Object> map = new LinkedHashMap<String, Object>();
/*int length = */BufferUtil.safeRead(source, 4).getInt();
int nameSize;
while((nameSize = BufferUtil.safeRead(source, 2).getShort()) != 0) {
data = BufferUtil.safeRead(source, nameSize);
String key = new String(data.array()).intern();
Object value = getValueObject(source);
map.put(key, value);
}
if(Type.getType(BufferUtil.safeRead(source, 1).get()) != Type.ObjectEnd) {
throw new Exception("mapの終端がおかしかったです。");
}
return map;
}
case Array:
{
List<Object> array = new ArrayList<Object>();
int length = BufferUtil.safeRead(source, 4).getInt();
for(int i = 0;i < length;i ++) {
array.add(getValueObject(source));
}
return array;
}
case Date:
{
data = BufferUtil.safeRead(source, 8);
Date date = new Date((long)Double.longBitsToDouble(data.getLong()));
BufferUtil.safeRead(source, 2); // timezone?
return date;
}
default:
throw new Exception("知らないデータがきました。:" + type);
}
}
/**
* 任意のオブジェクトをAMF0用のbyteBufferにする
* @param data
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static ByteBuffer getValueBuffer(Object data) throws Exception {
// 入力データに対応したバイトデータをAMF0オブジェクトとして応答する。
if(data instanceof String) {
return getStringBuffer((String)data);
}
if(data instanceof Boolean) {
return getBooleanBuffer((Boolean)data);
}
if(data instanceof Number) {
return getNumberBuffer((Number)data);
}
if(data instanceof Amf0Object<?, ?>) {
return getObjectBuffer((Amf0Object<String, Object>)data);
}
if(data instanceof Map<?, ?>) {
return getMapBuffer((Map<String, Object>)data);
}
if(data instanceof List<?>) {
return getArrayBuffer((List<Object>)data);
}
if(data instanceof Date) {
return getDateBuffer((Date) data);
}
throw new Exception("unknown amf0Data");
}
/**
* 文字列用の処理
* @param data
* @return
*/
private static ByteBuffer getStringBuffer(String data) {
byte[] dat = data.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(dat.length + 3);
// フラグ
buffer.put((byte)0x02);
// 長さ
buffer.putShort((short)dat.length);
// データ
buffer.put(dat);
buffer.flip();
return buffer;
}
/**
* boolean用の処理
* @param data
* @return
*/
private static ByteBuffer getBooleanBuffer(Boolean data) {
ByteBuffer buffer = ByteBuffer.allocate(2);
// フラグ
buffer.put((byte)0x01);
// データ
buffer.put((byte)(data ? 1 : 0));
buffer.flip();
return buffer;
}
/**
* 数値用の処理
* @param num
* @return
*/
private static ByteBuffer getNumberBuffer(Number num) {
ByteBuffer buffer = ByteBuffer.allocate(9);
// フラグ
buffer.put((byte)0x00);
// データ
buffer.putLong(Double.doubleToLongBits(num.doubleValue()));
buffer.flip();
return buffer;
}
private static ByteBuffer getDateBuffer(Date data) {
ByteBuffer buffer = ByteBuffer.allocate(11);
// フラグ
buffer.put((byte)0x0B);
// unixtime
buffer.putLong(Double.doubleToLongBits(data.getTime()));
// timezone(とりあえず0でうめとく。)
buffer.putShort((short)0);
buffer.flip();
return buffer;
}
/**
* 配列データ用の処理
* @param data
* @return
* @throws Exception
*/
private static ByteBuffer getArrayBuffer(List<Object> data) throws Exception {
List<ByteBuffer> amfDataList = new ArrayList<ByteBuffer>();
int length = 0;
for(Object dat : data) {
ByteBuffer amfData = getValueBuffer(dat);
length += amfData.remaining();
amfDataList.add(amfData);
}
ByteBuffer buffer = ByteBuffer.allocate(length + 1 + 4);
buffer.put((byte)0x0A);
buffer.putInt(amfDataList.size());
for(ByteBuffer amfData : amfDataList) {
buffer.put(amfData);
}
buffer.flip();
return buffer;
}
/**
* ActionScriptのObject(map)用のBufferを取得
* @param data
* @return
* @throws Exception
*/
private static ByteBuffer getObjectBuffer(Amf0Object<String, Object> data) throws Exception {
// 中身の準備
List<ByteBuffer> amfDataList = new ArrayList<ByteBuffer>();
int length = 0;
for(Entry<String, Object> entry : data.entrySet()) {
ByteBuffer amfData = makeMapElementBuffer(entry.getKey(), entry.getValue());
length += amfData.remaining();
amfDataList.add(amfData);
}
// 子要素を足していって、必要なサイズをみつける必要あり。
ByteBuffer buffer = ByteBuffer.allocate(length + 1 + 3);
// フラグ
buffer.put((byte)0x03);
// 中身書き込み
for(ByteBuffer amfData : amfDataList) {
buffer.put(amfData);
}
// eof
buffer.put((byte)0x00);
buffer.put((byte)0x00);
buffer.put((byte)0x09);
buffer.flip();
return buffer;
}
/**
* Map用の処理
* @param data
* @return
* @throws Exception
*/
private static ByteBuffer getMapBuffer(Map<String, Object> data) throws Exception {
// 中身の準備
List<ByteBuffer> amfDataList = new ArrayList<ByteBuffer>();
int length = 0;
for(Entry<String, Object> entry : data.entrySet()) {
ByteBuffer amfData = makeMapElementBuffer(entry.getKey(), entry.getValue());
length += amfData.remaining();
amfDataList.add(amfData);
}
// 子要素を足していって、必要なサイズをみつける必要あり。
ByteBuffer buffer = ByteBuffer.allocate(length + 5 + 3);
// フラグ
buffer.put((byte)0x08);
// サイズ
buffer.putInt(amfDataList.size());
// 中身書き込み
for(ByteBuffer amfData : amfDataList) {
buffer.put(amfData);
}
// eof
buffer.put((byte)0x00);
buffer.put((byte)0x00);
buffer.put((byte)0x09);
buffer.flip();
return buffer;
}
/**
* マップの内部データ用の処理
* @param name
* @param data
* @return
* @throws Exception
*/
private static ByteBuffer makeMapElementBuffer(String name, Object data) throws Exception {
ByteBuffer amfData = Amf0Value.getValueBuffer(data);
byte[] nameBytes = name.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(amfData.remaining() + nameBytes.length + 2);
buffer.putShort((short)nameBytes.length);
buffer.put(nameBytes);
buffer.put(amfData);
buffer.flip();
return buffer;
}
}