/*
 * Based on JUEL 2.2.1 code, 2006-2009 Odysseus Software GmbH
 *
 * 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 org.activiti.engine.impl.juel;

import java.io.PrintWriter;
import java.util.EnumSet;

import org.activiti.engine.impl.javax.el.ELContext;
import org.activiti.engine.impl.javax.el.ELException;
import org.activiti.engine.impl.javax.el.ELResolver;
import org.activiti.engine.impl.javax.el.FunctionMapper;
import org.activiti.engine.impl.javax.el.VariableMapper;
import org.activiti.engine.impl.juel.Parser.ParseException;
import org.activiti.engine.impl.juel.Scanner.ScanException;

/**
 * Tree builder.
 * 
 * @author Christoph Beck
 */
public class Builder implements TreeBuilder {
    private static final long serialVersionUID = 1L;

    /**
     * Feature enumeration type.
     */
    public static enum Feature {
        /**
         * Method invocations as in <code>${foo.bar(1)}</code> as specified in JSR 245, maintenance release 2. The method to be invoked is resolved at evaluation time by calling
         * {@link ELResolver#invoke(javax.el.ELContext, Object, Object, Class[], Object[])}.
         */
        METHOD_INVOCATIONS,
        /**
         * For some reason we don't understand, the specification does not allow to resolve <code>null</code> property values. E.g. <code>${map[key]}</code> will always return <code>null</code> if
         * <code>key</code> evaluates to <code>null</code>. Enabling this feature will allow <em>JUEL</em> to pass <code>null</code> to the property resolvers just like any other property value.
         */
        NULL_PROPERTIES,
        /**
         * Allow for use of Java 5 varargs in function calls.
         */
        VARARGS
    }

    protected final EnumSet<Feature> features;

    public Builder() {
        this.features = EnumSet.noneOf(Feature.class);
    }

    public Builder(Feature... features) {
        if (features == null || features.length == 0) {
            this.features = EnumSet.noneOf(Feature.class);
        } else if (features.length == 1) {
            this.features = EnumSet.of(features[0]);
        } else {
            Feature[] rest = new Feature[features.length - 1];
            System.arraycopy(features, 1, rest, 0, features.length - 1);
            this.features = EnumSet.of(features[0], rest);
        }
    }

    /**
     * @return <code>true</code> iff the specified feature is supported.
     */
    public boolean isEnabled(Feature feature) {
        return features.contains(feature);
    }

    /**
     * Parse expression.
     */
    public Tree build(String expression) throws TreeBuilderException {
        try {
            return createParser(expression).tree();
        } catch (ScanException e) {
            throw new TreeBuilderException(expression, e.position, e.encountered, e.expected, e.getMessage());
        } catch (ParseException e) {
            throw new TreeBuilderException(expression, e.position, e.encountered, e.expected, e.getMessage());
        }
    }

    protected Parser createParser(String expression) {
        return new Parser(this, expression);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || obj.getClass() != getClass()) {
            return false;
        }
        return features.equals(((Builder) obj).features);
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }

    /**
     * Dump out abstract syntax tree for a given expression
     * 
     * @param args
     *            array with one element, containing the expression string
     */
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("usage: java " + Builder.class.getName() + " <expression string>");
            System.exit(1);
        }
        PrintWriter out = new PrintWriter(System.out);
        Tree tree = null;
        try {
            tree = new Builder(Feature.METHOD_INVOCATIONS).build(args[0]);
        } catch (TreeBuilderException e) {
            System.out.println(e.getMessage());
            System.exit(0);
        }
        NodePrinter.dump(out, tree.getRoot());
        if (!tree.getFunctionNodes().iterator().hasNext() && !tree.getIdentifierNodes().iterator().hasNext()) {
            ELContext context = new ELContext() {
                @Override
                public VariableMapper getVariableMapper() {
                    return null;
                }

                @Override
                public FunctionMapper getFunctionMapper() {
                    return null;
                }

                @Override
                public ELResolver getELResolver() {
                    return null;
                }
            };
            out.print(">> ");
            try {
                out.println(tree.getRoot().getValue(new Bindings(null, null), context, null));
            } catch (ELException e) {
                out.println(e.getMessage());
            }
        }
        out.flush();
    }
}

@@ -2770,68 +2770,110 @@
     
-System.arraycopy(features, 1, rest, 0, features.length - 1);
+for (int i = 1; i %3C features.length; i++) %7B%0D%0A                rest%5Bi - 1%5D = features%5Bi%5D;%0D%0A            %7D
 %0D%0A  

revert change in Flowable 5 JUEL code