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.codegen.expression.LocalVariable;
  import io.activej.common.annotation.ExposedInternals;
  import io.activej.common.builder.AbstractBuilder;
  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.ArrayList;
  import java.util.List;
  
  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.INT_TYPE;
  import static org.objectweb.asm.commons.GeneratorAdapter.NE;
  
  /**
   * Defines methods to compare some fields
   */
  @ExposedInternals
  public final class Compare implements Expression {
  	public final List<Pair> pairs;
  
  	public record Pair(Expression left, Expression right, boolean nullable) {}
  
  	public Compare(List<Pair> pairs) {
  		this.pairs = pairs;
  	}
  
  	public static Builder builder() {
  		return new Compare(new ArrayList<>()).new Builder();
  	}
  
  	public final class Builder extends AbstractBuilder<Builder, Compare> {
  		private Builder() {}
  
  		public Builder with(Expression left, Expression right) {
  			checkNotBuilt(this);
  			return with(left, right, false);
  		}
  
  		public Builder with(Expression left, Expression right, boolean nullable) {
  			checkNotBuilt(this);
  			Compare.this.pairs.add(new Compare.Pair(left, right, nullable));
  			return this;
  		}
  
  		@Override
  		protected Compare doBuild() {
  			return Compare.this;
  		}
  	}
  
  	@Override
  	public Type load(Context ctx) {
  		GeneratorAdapter g = ctx.getGeneratorAdapter();
  
  		Label labelReturn = new Label();
  
  		for (Pair pair : pairs) {
  			Type leftPropertyType = pair.left.load(ctx);
  			checkType(leftPropertyType, isAssignable());
  
  			Type rightPropertyType = pair.right.load(ctx);
  			checkType(rightPropertyType, isAssignable());
  
  			if (!leftPropertyType.equals(rightPropertyType))
  				throw new IllegalArgumentException("Types of compared values should match");
  			if (isPrimitiveType(leftPropertyType)) {
  				g.invokeStatic(wrap(leftPropertyType), new Method("compare", INT_TYPE, new Type[]{leftPropertyType, leftPropertyType}));
  				g.dup();
  				g.ifZCmp(NE, labelReturn);
  				g.pop();
  			} else if (!pair.nullable) {
  				invokeVirtualOrInterface(ctx, leftPropertyType, new Method("compareTo", INT_TYPE, new Type[]{Type.getType(Object.class)}));
  				g.dup();
  				g.ifZCmp(NE, labelReturn);
  				g.pop();
  			} else {
  				LocalVariable varRight = ctx.newLocal(rightPropertyType);
  				varRight.store(ctx);
  
  				LocalVariable varLeft = ctx.newLocal(leftPropertyType);
  				varLeft.store(ctx);
  
  				Label continueLabel = new Label();
  				Label nonNulls = new Label();
  				Label leftNonNull = new Label();
  
  				varLeft.load(ctx);
  				g.ifNonNull(leftNonNull);
  
  				varRight.load(ctx);
  				g.ifNull(continueLabel);
  				g.push(-1);
  				g.returnValue();
  
  				g.mark(leftNonNull);
  
  				varRight.load(ctx);
  				g.ifNonNull(nonNulls);
  				g.push(1);
  				g.returnValue();
  
  				g.mark(nonNulls);
  
  				varLeft.load(ctx);
  				varRight.load(ctx);
  
  				invokeVirtualOrInterface(ctx, leftPropertyType, new Method("compareTo", INT_TYPE, new Type[]{Type.getType(Object.class)}));
  				g.dup();
  				g.ifZCmp(NE, labelReturn);
  				g.pop();
  
  				g.mark(continueLabel);
  			}
  		}
  
  		g.push(0);
  
  		g.mark(labelReturn);
  
  		return INT_TYPE;
  	}
  }
  