content: /*
   * Copyright (C) 2020 ActiveJ LLC.
   *
   * 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.activej.codegen.expression.impl;
  
  import io.activej.codegen.Context;
  import io.activej.codegen.expression.Expression;
  import io.activej.common.annotation.ExposedInternals;
  import io.activej.common.builder.AbstractBuilder;
  import org.jetbrains.annotations.Nullable;
  import org.objectweb.asm.Label;
  import org.objectweb.asm.Type;
  import org.objectweb.asm.commons.GeneratorAdapter;
  import org.objectweb.asm.commons.Method;
  
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  
  import static io.activej.codegen.expression.Expressions.property;
  import static io.activej.codegen.expression.Expressions.self;
  import static io.activej.codegen.util.TypeChecks.checkType;
  import static io.activej.codegen.util.TypeChecks.isAssignable;
  import static io.activej.codegen.util.Utils.*;
  import static org.objectweb.asm.Type.getType;
  import static org.objectweb.asm.commons.Method.getMethod;
  
  /**
   * Defines methods which allow to create a string
   */
  @ExposedInternals
  public final class ToString implements Expression {
  	public String begin;
  	public String end;
  	public @Nullable String nameSeparator;
  	public String valueSeparator;
  	public final Map<Object, Expression> arguments;
  
  	public ToString(
  		String begin, String end, @Nullable String nameSeparator, String valueSeparator,
  		Map<Object, Expression> arguments
  	) {
  		this.begin = begin;
  		this.end = end;
  		this.nameSeparator = nameSeparator;
  		this.valueSeparator = valueSeparator;
  		this.arguments = arguments;
  	}
  
  	public static ToString create() {
  		return builder().build();
  	}
  
  	public static Builder builder() {
  		return new ToString("{", "}", ": ", ", ", new LinkedHashMap<>()).new Builder();
  	}
  
  	public final class Builder extends AbstractBuilder<Builder, ToString> {
  		private Builder() {}
  
  		public Builder withBeginTag(String begin) {
  			checkNotBuilt(this);
  			ToString.this.begin = begin;
  			return this;
  		}
  
  		public Builder withEndTag(String end) {
  			checkNotBuilt(this);
  			ToString.this.end = end;
  			return this;
  		}
  
  		public Builder withNameSeparator(@Nullable String nameSeparator) {
  			checkNotBuilt(this);
  			ToString.this.nameSeparator = nameSeparator;
  			return this;
  		}
  
  		public Builder withValueSeparator(String valueSeparator) {
  			checkNotBuilt(this);
  			ToString.this.valueSeparator = valueSeparator;
  			return this;
  		}
  
  		public Builder with(String label, Expression expression) {
  			checkNotBuilt(this);
  			ToString.this.arguments.put(label, expression);
  			return this;
  		}
  
  		public Builder with(Expression expression) {
  			checkNotBuilt(this);
  			ToString.this.arguments.put(arguments.size() + 1, expression);
  			return this;
  		}
  
  		public Builder withField(String field) {
  			checkNotBuilt(this);
  			ToString.this.arguments.put(field, property(self(), field));
  			return this;
  		}
  
  		public Builder withFields(List<String> fields) {
  			checkNotBuilt(this);
  			for (String field : fields) {
  				withField(field);
  			}
  			return this;
  		}
  
  		public Builder withFields(String... fields) {
  			checkNotBuilt(this);
  			return withFields(List.of(fields));
  		}
  
  		@Override
  		protected ToString doBuild() {
  			return ToString.this;
  		}
  	}
  
  	@Override
  	public Type load(Context ctx) {
  		GeneratorAdapter g = ctx.getGeneratorAdapter();
  
  		g.newInstance(getType(StringBuilder.class));
  		g.dup();
  		g.invokeConstructor(getType(StringBuilder.class), getMethod("void <init> ()"));
  
  		boolean first = true;
  
  		for (Map.Entry<Object, Expression> entry : arguments.entrySet()) {
  			String str = first ? begin : valueSeparator;
  			first = false;
  			if (entry.getKey() instanceof String) {
  				if (nameSeparator != null) {
  					str += entry.getKey() + nameSeparator;
  				}
  			}
  			if (!str.isEmpty()) {
  				g.dup();
  				g.push(str);
  				g.invokeVirtual(getType(StringBuilder.class), getMethod("StringBuilder append(String)"));
  				g.pop();
  			}
  
  			g.dup();
  			Type type = entry.getValue().load(ctx);
  			checkType(type, isAssignable());
  
  			if (isPrimitiveType(type)) {
  				g.invokeStatic(wrap(type), new Method("toString", getType(String.class), new Type[]{type}));
  			} else {
  				Label nullLabel = new Label();
  				Label afterToString = new Label();
  				g.dup();
  				g.ifNull(nullLabel);
  				invokeVirtualOrInterface(ctx, type, getMethod("String toString()"));
  				g.goTo(afterToString);
  				g.mark(nullLabel);
  				g.pop();
  				g.push("null");
  				g.mark(afterToString);
  			}
  			g.invokeVirtual(getType(StringBuilder.class), getMethod("StringBuilder append(String)"));
  			g.pop();
  		}
  
  		if (!end.isEmpty()) {
  			g.dup();
  			g.push(end);
  			g.invokeVirtual(getType(StringBuilder.class), getMethod("StringBuilder append(String)"));
  			g.pop();
  		}
  
  		g.invokeVirtual(getType(StringBuilder.class), getMethod("String toString()"));
  		return getType(String.class);
  	}
  }
  