content: /* Generated By:JJTree: Do not edit this line. SimpleNode.java */
  
  /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you 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
   *
   *    https://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 org.apache.cayenne.exp.parser;
  
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.util.Collections;
  import java.util.List;
  import java.util.Map;
  
  import org.apache.cayenne.CayenneRuntimeException;
  import org.apache.cayenne.ObjectId;
  import org.apache.cayenne.Persistent;
  import org.apache.cayenne.exp.Expression;
  import org.apache.cayenne.exp.ExpressionException;
  import org.apache.cayenne.util.Util;
  
  /**
   * Superclass of AST* expressions that implements Node interface defined by JavaCC framework.
   * <p>
   * Some parts of the parser are based on OGNL parser,
   * copyright (c) 2002, Drew Davidson and Luke Blanshard.
   * </p>
   * 
   * @since 1.1
   */
  public abstract class SimpleNode extends Expression implements Node {
  
  	private static final long serialVersionUID = 4471832357335707557L;
  	
  	protected Node parent;
  	protected Node[] children;
  	protected int id;
  
  	/**
  	 * <p>
  	 * This is a utility method that can represent the supplied scalar as either
  	 * an EJBQL literal into the supplied {@link java.io.PrintWriter} or is able
  	 * to add the scalar to the parameters and to instead write a positional
  	 * parameter to the EJBQL written to the {@link java.io.PrintWriter}. If the
  	 * parameters are null and the scalar object is not able to be represented
  	 * as an EJBQL literal then the method will throw a runtime exception to
  	 * indicate that it has failed to produce valid EJBQL.
  	 * </p>
  	 */
  
  	protected static void encodeScalarAsEJBQL(List<Object> parameterAccumulator, Appendable out, Object scalar)
  			throws IOException {
  
  		if (null == scalar) {
  			out.append("null");
  			return;
  		}
  
  		if (scalar instanceof Boolean) {
  			if ((Boolean) scalar) {
  				out.append("true");
  			} else {
  				out.append("false");
  			}
  			return;
  		}
  
  		if (null != parameterAccumulator) {
  			parameterAccumulator.add(scalar);
  			out.append('?');
  			out.append(Integer.toString(parameterAccumulator.size())); // parameters start at 1
  			return;
  		}
  
  		if (scalar instanceof Integer || scalar instanceof Long || scalar instanceof Float || scalar instanceof Double) {
  			out.append(scalar.toString());
  			return;
  		}
  
  		if (scalar instanceof Persistent) {
  			ObjectId id = ((Persistent) scalar).getObjectId();
  			Object encode = (id != null) ? id : scalar;
  			appendAsEscapedString(out, String.valueOf(encode));
  			return;
  		}
  
  		if (scalar instanceof Enum<?>) {
  			Enum<?> e = (Enum<?>) scalar;
  			out.append("enum:");
  			out.append(e.getClass().getName()).append(".").append(e.name());
  			return;
  		}
  
  		if (scalar instanceof String) {
  			out.append('\'');
  			appendAsEscapedString(out, scalar.toString());
  			out.append('\'');
  			return;
  		}
  
  		throw new IllegalStateException("the scalar type '" + scalar.getClass().getSimpleName()
  				+ "' is not supported as a scalar type in EJBQL");
  	}
  
  	/**
  	 * Utility method that encodes an object that is not an expression Node to
  	 * String.
  	 */
  	protected static void appendScalarAsString(Appendable out, Object scalar, char quoteChar) throws IOException {
  		boolean quote = scalar instanceof String;
  
  		if (quote) {
  			out.append(quoteChar);
  		}
  
  		// encode only ObjectId for Persistent, ensure that the order of keys is predictable....
  
  		// TODO: should we use UUID here?
  		if (scalar instanceof Persistent) {
  			ObjectId id = ((Persistent) scalar).getObjectId();
  			Object encode = (id != null) ? id : scalar;
  			appendAsEscapedString(out, String.valueOf(encode));
  		} else if (scalar instanceof Enum<?>) {
  			Enum<?> e = (Enum<?>) scalar;
  			out.append("enum:");
  			out.append(e.getClass().getName()).append(".").append(e.name());
  		} else {
  			appendAsEscapedString(out, String.valueOf(scalar));
  		}
  
  		if (quote) {
  			out.append(quoteChar);
  		}
  	}
  
  	/**
  	 * Utility method that prints a string to the provided Appendable, escaping special characters.
  	 */
  	protected static void appendAsEscapedString(Appendable out, String source) throws IOException {
  		int len = source.length();
  		for (int i = 0; i < len; i++) {
  			char c = source.charAt(i);
  
  			switch (c) {
  			case '\n':
  				out.append("\\n");
  				continue;
  			case '\r':
  				out.append("\\r");
  				continue;
  			case '\t':
  				out.append("\\t");
  				continue;
  			case '\b':
  				out.append("\\b");
  				continue;
  			case '\f':
  				out.append("\\f");
  				continue;
  			case '\\':
  				out.append("\\\\");
  				continue;
  			case '\'':
  				out.append("\\'");
  				continue;
  			case '\"':
  				out.append("\\\"");
  				continue;
  			default:
  				out.append(c);
  			}
  		}
  	}
  
  	protected SimpleNode(int i) {
  		id = i;
  	}
  
  	/**
  	 * Always returns empty map.
  	 * 
  	 * @since 3.0
  	 */
  	@Override
  	public Map<String, String> getPathAliases() {
  		return Collections.emptyMap();
  	}
  
  	protected abstract String getExpressionOperator(int index);
  
  	/**
  	 * Returns operator for EJBQL statements, which can differ for Cayenne expression operator
  	 */
  	protected String getEJBQLExpressionOperator(int index) {
  		return getExpressionOperator(index);
  	}
  
  	@Override
  	protected boolean pruneNodeForPrunedChild(Object prunedChild) {
  		return true;
  	}
  
  	/**
  	 * Implemented for backwards compatibility with exp package.
  	 */
  	@Override
  	public String expName() {
  		return ExpressionParserTreeConstants.jjtNodeName[id];
  	}
  
  	/**
  	 * Flattens the tree under this node by eliminating any children that are of
  	 * the same class as this node and copying their children to this node.
  	 */
  	@Override
  	protected void flattenTree() {
  		boolean shouldFlatten = false;
  		int newSize = 0;
  
  		for (Node child : children) {
  			if (child.getClass() == getClass()) {
  				shouldFlatten = true;
  				newSize += child.jjtGetNumChildren();
  			} else {
  				newSize++;
  			}
  		}
  
  		if (shouldFlatten) {
  			Node[] newChildren = new Node[newSize];
  			int j = 0;
  
  			for (Node c : children) {
  				if (c.getClass() == getClass()) {
  					for (int k = 0; k < c.jjtGetNumChildren(); ++k) {
  						newChildren[j++] = c.jjtGetChild(k);
  					}
  				} else {
  					newChildren[j++] = c;
  				}
  			}
  
  			if (j != newSize) {
  				throw new ExpressionException("Assertion error: " + j + " != " + newSize);
  			}
  
  			this.children = newChildren;
  		}
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	@Override
  	public void appendAsString(Appendable out) throws IOException {
  
  		if (parent != null) {
  			out.append("(");
  		}
  
  		if ((children != null) && (children.length > 0)) {
  			for (int i = 0; i < children.length; ++i) {
  				if (i > 0) {
  					out.append(' ');
  					out.append(getExpressionOperator(i));
  					out.append(' ');
  				}
  
  				if (children[i] == null) {
  					out.append("null");
  				} else {
  					((SimpleNode) children[i]).appendAsString(out);
  				}
  			}
  		}
  
  		if (parent != null) {
  			out.append(')');
  		}
  	}
  
  	@Override
  	public Object getOperand(int index) {
  		Node child = jjtGetChild(index);
  
  		// unwrap ASTScalar nodes - this is likely a temporary thing to keep it compatible
  		// with QualifierTranslator. In the future we might want to keep scalar nodes
  		// for the purpose of expression evaluation.
  		return unwrapChild(child);
  	}
  
  	protected Node wrapChild(Object child) {
  		// when child is null, there's no way of telling whether this is a scalar or not... fuzzy...
  		// maybe we should stop using this method - it is too generic
  		return (child instanceof Node || child == null) ? (Node) child : new ASTScalar(child);
  	}
  
  	protected Object unwrapChild(Node child) {
  		return (child instanceof ASTScalar) ? ((ASTScalar) child).getValue() : child;
  	}
  
  	@Override
  	public int getOperandCount() {
  		return jjtGetNumChildren();
  	}
  
  	@Override
  	public void setOperand(int index, Object value) {
  		Node node = (value == null || value instanceof Node) ? (Node) value : new ASTScalar(value);
  		jjtAddChild(node, index);
  
  		// set the parent, as jjtAddChild doesn't do it...
  		if (node != null) {
  			node.jjtSetParent(this);
  		}
  	}
  
  	public void jjtOpen() {
  
  	}
  
  	public void jjtClose() {
  
  	}
  
  	public void jjtSetParent(Node n) {
  		if (!isValidParent(n)) {
  			String label = (n instanceof SimpleNode) ? ((SimpleNode)n).expName() : String.valueOf(n);
  			throw new ExpressionException(expName() + ": invalid parent - " + label);
  		}
  		parent = n;
  	}
  
  	/**
  	 * Additional parent check that we can't handle properly in the grammar
  	 * By default it just returns true.
  	 *
  	 * @param n potential parent node to check
  	 * @return true if node could be set as a parent for the current node
  	 */
  	protected boolean isValidParent(Node n) {
  		return true;
  	}
  
  	public Node jjtGetParent() {
  		return parent;
  	}
  
  	public void jjtAddChild(Node n, int i) {
  		if (children == null) {
  			children = new Node[i + 1];
  		} else if (i >= children.length) {
  			Node c[] = new Node[i + 1];
  			System.arraycopy(children, 0, c, 0, children.length);
  			children = c;
  		}
  		children[i] = n;
  	}
  
  	public Node jjtGetChild(int i) {
  		return children[i];
  	}
  
  	public final int jjtGetNumChildren() {
  		return (children == null) ? 0 : children.length;
  	}
  
  	/**
  	 * Evaluates itself with object, pushing result on the stack.
  	 */
  	protected abstract Object evaluateNode(Object o) throws Exception;
  
  	/**
  	 * Sets the parent to this for all children.
  	 * 
  	 * @since 3.0
  	 */
  	protected void connectChildren() {
  		if (children != null) {
  			for (Node child : children) {
  				// although nulls are expected to be wrapped in scalar,
  				// still doing a check here to make it more robust
  				if (child != null) {
  					child.jjtSetParent(this);
  				}
  			}
  		}
  	}
  
  	protected Object evaluateChild(int index, Object o) throws Exception {
  		SimpleNode node = (SimpleNode) jjtGetChild(index);
  		return node != null ? node.evaluate(o) : null;
  	}
  
  	@Override
  	public Expression notExp() {
  		return new ASTNot(this);
  	}
  
  	/**
  	 * @inheritDoc
  	 * @since 5.0
  	 */
  	@Override
  	public Expression exists() {
  		throw new UnsupportedOperationException("Can't use exists() operator with this expression");
  	}
  
  	/**
  	 * @inheritDoc
  	 * @since 5.0
  	 */
  	@Override
  	public Expression notExists() {
  		throw new UnsupportedOperationException("Can't use not exists() operator with this expression");
  	}
  
  	@Override
  	public Object evaluate(Object o) {
  		// wrap in try/catch to provide unified exception processing
  		try {
  			return evaluateNode(o);
  		} catch (Throwable th) {
  			String string = this.toString();
  			throw new ExpressionException("Error evaluating expression '%s'",
  					string, Util.unwindException(th), string);
  		}
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	public void appendAsEJBQL(Appendable out, String rootId) throws IOException {
  		appendAsEJBQL(null, out, rootId);
  	}
  
  	/**
  	 * @since 4.0
  	 */
  	@Override
  	public void appendAsEJBQL(List<Object> parameterAccumulator, Appendable out, String rootId) throws IOException {
  		if (parent != null) {
  			out.append("(");
  		}
  
  		if ((children != null) && (children.length > 0)) {
  			appendChildrenAsEJBQL(parameterAccumulator, out, rootId);
  		}
  
  		if (parent != null) {
  			out.append(')');
  		}
  	}
  
  	/**
  	 * Encodes child of this node with specified index to EJBQL
  	 */
  	protected void appendChildrenAsEJBQL(List<Object> parameterAccumulator, Appendable out, String rootId)
  			throws IOException {
  		for (int i = 0; i < children.length; ++i) {
  			if (i > 0) {
  				out.append(' ');
  				out.append(getEJBQLExpressionOperator(i));
  				out.append(' ');
  			}
  
  			if (children[i] == null) {
  				out.append("null");
  			} else {
  				((SimpleNode) children[i]).appendAsEJBQL(parameterAccumulator, out, rootId);
  			}
  		}
  	}
  }
  