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

ch.rasc.bsoncodec.codegen.MapCodeGen Maven / Gradle / Ivy

/**
 * Copyright 2015-2016 Ralph Schaer 
 *
 * 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 ch.rasc.bsoncodec.codegen;

import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.Set;
import java.util.TreeMap;

import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;

import org.bson.BsonType;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec.Builder;
import com.squareup.javapoet.TypeName;

import ch.rasc.bsoncodec.Util;
import ch.rasc.bsoncodec.model.FieldModel;

public class MapCodeGen extends CompoundCodeGen {

	private TypeMirror implementationType;

	private final TypeMirror keyType;

	public MapCodeGen(CompoundCodeGen parent, TypeMirror type, TypeMirror keyType) {
		super(parent, type);
		this.keyType = keyType;
	}

	public void setImplementationType(TypeMirror implementationType) {
		this.implementationType = implementationType;
	}

	protected TypeName getImplementationType() {
		if (this.implementationType == null) {
			if (((DeclaredType) getType()).asElement()
					.getKind() == ElementKind.INTERFACE) {
				return ClassName.get(LinkedHashMap.class);
			}
			return ClassName.get((TypeElement) Util.typeUtils.asElement(this.getType()));
		}
		return ClassName
				.get((TypeElement) Util.typeUtils.asElement(this.implementationType));
	}

	@Override
	public void addEncodeStatements(CodeGeneratorContext ctx) {
		FieldModel field = ctx.field();
		Builder builder = ctx.builder();

		if (!field.disableEncodeNullCheck() && !hasParent()) {
			builder.beginControlFlow("if ($L != null)", ctx.getter());
		}

		if (!field.storeEmptyCollection()) {
			builder.beginControlFlow("if (!$L.isEmpty())", ctx.getter());
		}

		if (!hasParent()) {
			builder.addStatement("writer.writeName($S)", field.name());
		}

		TypeMirror childType = this.getChildCodeGen().getType();
		builder.addStatement("writer.writeStartDocument()");

		if (!Util.isSameType(this.keyType, Object.class)) {
			builder.beginControlFlow("for (Map.Entry<$T, $T> $L : $L.entrySet())",
					this.keyType, childType, ctx.getLoopVar(), ctx.getter());
		}
		else {
			builder.beginControlFlow("for (Map.Entry $L : (Set)$L.entrySet())",
					ctx.getLoopVar(), ctx.getter());
		}
		if (Util.isSameType(this.keyType, String.class)) {
			builder.addStatement("writer.writeName($L.getKey())", ctx.getLoopVar());
		}
		else {
			if (Util.isSameType(getType(), EnumMap.class)) {
				builder.addStatement("writer.writeName($L.getKey().name())",
						ctx.getLoopVar());
			}
			else {
				builder.addStatement("writer.writeName($T.valueOf($L.getKey()))",
						String.class, ctx.getLoopVar());
			}
		}

		boolean permittNullElements = permitNullElements();
		if (!field.disableEncodeNullCheck() && permittNullElements) {
			builder.beginControlFlow("if ($L != null)", ctx.getLoopVar() + ".getValue()");
		}

		this.getChildCodeGen().addEncodeStatements(
				ctx.createEncodeChildContext(ctx.getLoopVar() + ".getValue()"));

		if (!field.disableEncodeNullCheck() && permittNullElements) {
			builder.nextControlFlow("else").addStatement("writer.writeNull()");
			builder.endControlFlow();
		}

		builder.endControlFlow();
		builder.addStatement("writer.writeEndDocument()");

		if (!field.storeEmptyCollection()) {
			builder.endControlFlow();
		}

		if (!field.disableEncodeNullCheck() && !hasParent()) {
			if (field.storeNullValue()) {
				builder.nextControlFlow("else").addStatement("writer.writeNull($S)",
						field.name());
			}
			builder.endControlFlow();
		}
	}

	@Override
	public void addDecodeStatements(CodeGeneratorContext ctx) {
		FieldModel field = ctx.field();
		Builder builder = ctx.builder();
		char lv = ctx.getLoopVar();

		if (!field.disableDecodeNullCheck() && !hasParent()) {
			builder.beginControlFlow("if (bsonType != $T.NULL)", BsonType.class);
		}

		builder.addStatement("reader.readStartDocument()");
		if (Util.isSameType(getType(), EnumMap.class)) {
			builder.addStatement("$T $L = new $T<>($T.class)", getType(), lv,
					EnumMap.class, this.keyType);
		}
		else {
			builder.addStatement("$T $L = new $T<>()", getType(), lv,
					this.getImplementationType());
		}

		builder.beginControlFlow(
				"while ((bsonType = reader.readBsonType()) != $T.END_OF_DOCUMENT)",
				BsonType.class);

		if (Util.isSameType(this.keyType, String.class)
				|| Util.isSameType(this.keyType, Object.class)) {
			builder.addStatement("String $LKey = reader.readName()", lv);
		}
		else {
			builder.addStatement("$T $LKey = $T.valueOf(reader.readName())", this.keyType,
					lv, this.keyType);
		}

		boolean permittNullElements = permitNullElements();
		if (permittNullElements) {
			builder.beginControlFlow("if (bsonType != $T.NULL)", BsonType.class);
		}

		CodeGeneratorContext childCtx = ctx
				.createDecodeChildContext(lv + ".put(" + lv + "Key, %s)");
		this.getChildCodeGen().addDecodeStatements(childCtx);

		if (permittNullElements) {
			builder.nextControlFlow("else").addStatement("reader.readNull()");
			builder.addStatement("$L.put($LKey, null)", lv, lv);
			builder.endControlFlow();
		}

		builder.endControlFlow();
		builder.addStatement("reader.readEndDocument()");

		builder.addStatement(ctx.setter("$L"), lv);

		if (!field.disableDecodeNullCheck() && !hasParent()) {
			builder.nextControlFlow("else").addStatement("reader.readNull()");
			if (!ctx.field().disableSetNullStatement()) {
				this.getChildCodeGen().addSetNullStatements(ctx);
			}
			builder.endControlFlow();
		}

	}

	private static Set permitNullCollections = new HashSet<>();

	static {
		permitNullCollections.addAll(Arrays.asList(HashMap.class.getCanonicalName(),
				LinkedHashMap.class.getCanonicalName(), TreeMap.class.getCanonicalName(),
				IdentityHashMap.class.getCanonicalName()));
		// permit null: EnumMap (values)
		// not permit null: EnumMap (keys)
		// not permit null: ConcurrentHashMap, ConcurrentSkipListMap
	}

	private boolean permitNullElements() {
		return permitNullCollections.contains(this.getImplementationType().toString());
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy