/*******************************************************************************
 * Copyright (c) 2013, 2014 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdt.core.dom;

import java.util.ArrayList;
import java.util.List;

/**
 * Lambda expression AST node type (added in JLS8 API).
 * <pre>
 * LambdaExpression:
 *    Identifier <b>-></b> Body
 *    <b>(</b> [ Identifier { <b>,</b> Identifier } ] <b>)</b> <b>-></b> Body
 *    <b>(</b> [ FormalParameter { <b>,</b> FormalParameter } ] <b>)</b> <b>-></b> Body
 * </pre>
 *
 *<p>
 * The first two forms use {@link VariableDeclarationFragment} for the parameter or parameters,
 * while the third form uses {@link SingleVariableDeclaration}.</p>
 *<p>The Body can be either a {@link Block} or an {@link Expression}.</p>
 *
 * @since 3.10
 * @noinstantiate This class is not intended to be instantiated by clients
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class LambdaExpression extends Expression {

	/**
	 * The "parentheses" structural property of this node type (type: {@link Boolean}).
	 * <p>
	 * Note that parentheses are required unless {@link #parameters()} contains
	 * just a single {@link VariableDeclarationFragment}.
	 * ASTRewrite may ignore this property if necessary.
	 * </p>
	 */
	public static final SimplePropertyDescriptor PARENTHESES_PROPERTY =
		new SimplePropertyDescriptor(LambdaExpression.class, "parentheses", boolean.class, MANDATORY); //$NON-NLS-1$

	/**
	 * The "parameters" structural property of this node type (element type: {@link VariableDeclaration}).
	 * <p>
	 * Note that all elements must be of the same type, either all {@link SingleVariableDeclaration} or all {@link VariableDeclarationFragment}.
	 * </p>
	 */
	public static final ChildListPropertyDescriptor PARAMETERS_PROPERTY =
		new ChildListPropertyDescriptor(LambdaExpression.class, "parameters", VariableDeclaration.class, CYCLE_RISK); //$NON-NLS-1$

	/**
	 * The "body" structural property of this node type (child type: {@link ASTNode},
	 * must be either a {@link Block} or an {@link Expression}).
	 */
	public static final ChildPropertyDescriptor BODY_PROPERTY =
		new ChildPropertyDescriptor(LambdaExpression.class, "body", ASTNode.class, MANDATORY, CYCLE_RISK); //$NON-NLS-1$

	/**
	 * A list of property descriptors (element type:
	 * {@link StructuralPropertyDescriptor}),
	 * or null if uninitialized.
	 */
	private static final List PROPERTY_DESCRIPTORS_8_0;

	static {
		List propertyList = new ArrayList(4);
		createPropertyList(LambdaExpression.class, propertyList);
		addProperty(PARENTHESES_PROPERTY, propertyList);
		addProperty(PARAMETERS_PROPERTY, propertyList);
		addProperty(BODY_PROPERTY, propertyList);
		PROPERTY_DESCRIPTORS_8_0 = reapPropertyList(propertyList);
	}

	/**
	 * Returns a list of structural property descriptors for this node type.
	 * Clients must not modify the result.
	 *
	 * @param apiLevel the API level; one of the AST.JLS* constants
	 * @return a list of property descriptors (element type:
	 * {@link StructuralPropertyDescriptor})
	 */
	public static List propertyDescriptors(int apiLevel) {
		return PROPERTY_DESCRIPTORS_8_0;
	}

	/**
	 * Indicates whether parentheses are present or not.
	 * Defaults to <code>true</code>.
	 */
	private boolean hasParentheses = true;

	/**
	 * The parameter declarations
	 * (element type: {@link VariableDeclaration}).
	 * Defaults to an empty list.
	 */
	private final ASTNode.NodeList parameters =
		new ASTNode.NodeList(PARAMETERS_PROPERTY);

	/**
	 * The method body.
	 * The method body; lazily initialized, defaults to an empty Block.
	 */
	private ASTNode body = null;

	/**
	 * Creates a new AST node for a LambdaExpression declaration owned
	 * by the given AST.
	 * <p>
	 * N.B. This constructor is package-private; all subclasses must be
	 * declared in the same package; clients are unable to declare
	 * additional subclasses.
	 * </p>
	 *
	 * @param ast the AST that is to own this node
	 */
	LambdaExpression(AST ast) {
		super(ast);
		unsupportedIn2_3_4();
	}

	@Override
	final List internalStructuralPropertiesForType(int apiLevel) {
		return propertyDescriptors(apiLevel);
	}

	@Override
	final boolean internalGetSetBooleanProperty(SimplePropertyDescriptor property, boolean get, boolean value) {
		if (property == PARENTHESES_PROPERTY) {
			if (get) {
				return hasParentheses();
			} else {
				setParentheses(value);
				return false;
			}
		}
		// allow default implementation to flag the error
		return super.internalGetSetBooleanProperty(property, get, value);
	}

	@Override
	final List internalGetChildListProperty(ChildListPropertyDescriptor property) {
		if (property == PARAMETERS_PROPERTY) {
			return parameters();
		}
		// allow default implementation to flag the error
		return super.internalGetChildListProperty(property);
	}

	@Override
	final ASTNode internalGetSetChildProperty(ChildPropertyDescriptor property, boolean get, ASTNode child) {
		if (property == BODY_PROPERTY) {
			if (get) {
				return getBody();
			} else {
				setBody( child);
				return null;
			}
		}
		// allow default implementation to flag the error
		return super.internalGetSetChildProperty(property, get, child);
	}

	@Override
	final int getNodeType0() {
		return LAMBDA_EXPRESSION;
	}

	@Override
	ASTNode clone0(AST target) {
		LambdaExpression result = new LambdaExpression(target);
		result.setSourceRange(getStartPosition(), getLength());
		result.setParentheses(hasParentheses());
		result.parameters().addAll(ASTNode.copySubtrees(target, parameters()));
		result.setBody(ASTNode.copySubtree(target, getBody()));
		return result;
	}

	@Override
	final boolean subtreeMatch0(ASTMatcher matcher, Object other) {
		// dispatch to correct overloaded match method
		return matcher.match(this, other);
	}

	@Override
	void accept0(ASTVisitor visitor) {
		boolean visitChildren = visitor.visit(this);
		if (visitChildren) {
			// visit children in normal left to right reading order
			acceptChildren(visitor, this.parameters);
			acceptChild(visitor, getBody());
		}
		visitor.endVisit(this);
	}

	/**
	 * Returns whether parentheses around the parameters are present or not.
	 * <p>
	 * Note that parentheses are required unless {@link #parameters()} contains
	 * just a single {@link VariableDeclarationFragment}.
	 * ASTRewrite may ignore this property if necessary.
	 * </p>
	 *
	 * @return <code>true</code> if this lambda expression has parentheses around
	 * its parameters and <code>false</code> otherwise
	 */
	public boolean hasParentheses() {
		return this.hasParentheses;
	}

	/**
	 * Sets whether this lambda expression has parentheses around its parameters or not.
	 * <p>
	 * Note that parentheses are required unless {@link #parameters()} contains
	 * just a single {@link VariableDeclarationFragment}.
	 * ASTRewrite may ignore this property if necessary.
	 * </p>
	 *
	 * @param hasParentheses <code>true</code> if this lambda expression has parentheses around its parameters
	 *  and <code>false</code> otherwise
	 */
	public void setParentheses(boolean hasParentheses) {
		preValueChange(PARENTHESES_PROPERTY);
		this.hasParentheses = hasParentheses;
		postValueChange(PARENTHESES_PROPERTY);
	}

	/**
	 * Returns the live ordered list of formal parameters of this lambda expression.
	 * Note that all elements must be of the same type, either
	 * <ul>
	 * <li>all {@link SingleVariableDeclaration} (explicit type), or</li>
	 * <li>all {@link VariableDeclarationFragment} (inferred type).</li>
	 * </ul>
	 *
	 * @return the live list of formal parameters of this lambda expression
	 *    (element type: {@link VariableDeclaration})
	 */
	public List parameters() {
		return this.parameters;
	}

	/**
	 * Returns the body of this lambda expression.
	 *
	 * @return the lambda expression body, which can be either a {@link Block} or an {@link Expression}
	 */
	public ASTNode getBody() {
		if (this.body == null) {
			// lazy init must be thread-safe for readers
			synchronized (this) {
				if (this.body == null) {
					preLazyInit();
					this.body = new Block(this.ast);
					postLazyInit(this.body, BODY_PROPERTY);
				}
			}
		}
		return this.body;
	}

	/**
	 * Sets the body of this lambda expression.
	 *
	 * @param body a block node or an expression node
	 * @exception IllegalArgumentException if:
	 * <ul>
	 * <li>the node belongs to a different AST</li>
	 * <li>the node already has a parent</li>
	 * <li>a cycle in would be created</li>
	 * <li>body is neither a {@link Block} nor an {@link Expression}</li>
	 * </ul>
	 */
	public void setBody(ASTNode body) {
		if (!(body instanceof Expression || body instanceof Block)) {
			throw new IllegalArgumentException();
		}
		ASTNode oldChild = this.body;
		preReplaceChild(oldChild, body, BODY_PROPERTY);
		this.body = body;
		postReplaceChild(oldChild, body, BODY_PROPERTY);
	}

	/**
	 * Resolves and returns the binding for the lambda expression
	 * <p>
	 * Note that bindings are generally unavailable unless requested when the
	 * AST is being built.
	 * </p>
	 *
	 * @return the binding, or <code>null</code> if the binding cannot be
	 *    resolved
	 */
	public IMethodBinding resolveMethodBinding() {
		return this.ast.getBindingResolver().resolveMethod(this);
	}

	@Override
	int memSize() {
		return BASE_NODE_SIZE + 3 * 4;
	}

	@Override
	int treeSize() {
		return
			memSize()
			+ this.parameters.listSize()
			+ (this.body == null ? 0 : getBody().treeSize());
	}
}