All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datarouter.binarydto.codec.BinaryDtoIndexedCodec Maven / Gradle / Ivy

There is a newer version: 0.0.125
Show newest version
/*
 * Copyright © 2009 HotPads ([email protected])
 *
 * 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 io.datarouter.binarydto.codec;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import io.datarouter.binarydto.dto.BaseBinaryDto;
import io.datarouter.binarydto.internal.BinaryDtoAllocator;
import io.datarouter.binarydto.internal.BinaryDtoFieldSchema;
import io.datarouter.bytes.ByteTool;
import io.datarouter.bytes.Codec;
import io.datarouter.bytes.varint.VarIntTool;

/**
 * For encoding non-key fields.
 *
 * The output is not comparable, but it allows adding and removing fields while staying backwards compatible with data
 * serialized by previous dto versions.
 *
 * Note that index re-use is dangerous unless you are certain that persisted data is free of the previous index. There
 * is not much benefit to reusing an index, only a tiny bit if you are exceeding index 127, which is where the varint
 * encoding starts taking a second byte.
 *
 * Fields are made of 3 pieces:
 * - index, as specified in the field's annotation or inferred by alphabetical field order
 * - length, the number of bytes in the value section.
 *     - We need this to know how many bytes to skip in case the field is removed from the dto.
 * - value, the actual value bytes for the field
 */
public class BinaryDtoIndexedCodec>
implements Codec{

	private static final Map>,BinaryDtoIndexedCodec> CACHE
			= new ConcurrentHashMap<>();

	private static final int TOKENS_PER_FIELD = 3;// index, length, value

	public final BinaryDtoFieldCache fieldCache;

	private BinaryDtoIndexedCodec(Class dtoClass){
		fieldCache = new BinaryDtoFieldCache<>(dtoClass);
	}

	@SuppressWarnings({"unchecked", "rawtypes"})
	public static > BinaryDtoIndexedCodec of(Class dtoClass){
		//Can't use computeIfAbsent here because it prohibits recursive calls to this method.  We may therefore
		// generate a few extra throwaway codecs.
		BinaryDtoIndexedCodec codec = CACHE.get(dtoClass);
		if(codec == null){
			codec = new BinaryDtoIndexedCodec(dtoClass);
			CACHE.put(dtoClass, codec);
		}
		return (BinaryDtoIndexedCodec) codec;
	}

	/*---------- encode ------------*/

	@Override
	public byte[] encode(T dto){
		List tokens = new ArrayList<>(TOKENS_PER_FIELD * fieldCache.fieldSchemaByIndex.size());
		for(int i = 0; i < fieldCache.fieldSchemaByIndex.size(); ++i){
			BinaryDtoFieldSchema fieldSchema = fieldCache.fieldSchemaByIndex.get(i);
			if(fieldSchema != null){
				if(fieldSchema.isNull(dto)){
					if(!fieldSchema.isNullable()){
						String message = String.format(
								"field=%s of class=%s can't contain nulls",
								fieldSchema.getName(),
								dto.getClass().getCanonicalName());
						throw new IllegalArgumentException(message);
					}
				}else{
					byte[] fieldBytes = fieldSchema.encodeIndexed(dto);
					tokens.add(VarIntTool.encode(i));
					tokens.add(VarIntTool.encode(fieldBytes.length));
					tokens.add(fieldBytes);
				}
			}
		}
		return ByteTool.concat(tokens);
	}

	@Override
	public T decode(byte[] bytes){
		return decode(bytes, 0, bytes.length);
	}

	public T decode(byte[] bytes, int offset, int length){
		int to = offset + length;
		T dto = BinaryDtoAllocator.allocate(fieldCache.dtoClass);
		int cursor = offset;
		while(cursor < to){
			int index = VarIntTool.decodeInt(bytes, cursor);
			cursor += VarIntTool.length(index);
			int fieldLength = VarIntTool.decodeInt(bytes, cursor);
			cursor += VarIntTool.length(fieldLength);
			int valueFrom = cursor;
			int valueTo = cursor + fieldLength;
			cursor = valueTo;
			BinaryDtoFieldSchema fieldSchema = index < fieldCache.fieldSchemaByIndex.size()
					? fieldCache.fieldSchemaByIndex.get(index)
					: null;
			if(fieldSchema != null){
				byte[] fieldBytes = Arrays.copyOfRange(bytes, valueFrom, valueTo);
				fieldSchema.decodeIndexed(dto, fieldBytes);
			}
		}
		return dto;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy