/*
 * 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
 *
 *      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.apache.jasper.tagplugins.jstl.core;

import org.apache.jasper.compiler.tagplugin.TagPlugin;
import org.apache.jasper.compiler.tagplugin.TagPluginContext;
import org.apache.jasper.tagplugins.jstl.Util;

public class Set implements TagPlugin {

    @Override
    public void doTag(TagPluginContext ctxt) {

        //the flags to indicate whether the attributes have been specified
        boolean hasValue = false, hasVar = false, hasScope = false,
        hasTarget = false;

        //the scope name
        String strScope;
        //the id of the scope
        int iScope;

        //initialize the flags
        hasValue = ctxt.isAttributeSpecified("value");
        hasVar = ctxt.isAttributeSpecified("var");
        hasScope = ctxt.isAttributeSpecified("scope");
        hasTarget = ctxt.isAttributeSpecified("target");

        //the temp variables name
        String resultName = ctxt.getTemporaryVariableName();
        String targetName = ctxt.getTemporaryVariableName();
        String propertyName = ctxt.getTemporaryVariableName();

        //initialize the "result" which will be assigned to the var or target.property
        ctxt.generateJavaSource("Object " + resultName + " = null;");
        if(hasValue){
            ctxt.generateJavaSource(resultName + " = ");
            ctxt.generateAttribute("value");
            ctxt.generateJavaSource(";");
        }else{
            ctxt.dontUseTagPlugin();
            return;
        }

        //initialize the strScope
        if(hasScope){
            strScope = ctxt.getConstantAttribute("scope");
        }else{
            strScope = "page";
        }

        //get the iScope according to the strScope
        iScope = Util.getScope(strScope);

        String jspCtxt = null;
        if (ctxt.isTagFile()) {
            jspCtxt = "this.getJspContext()";
        } else {
            jspCtxt = "_jspx_page_context";
        }
        //if the attribute var has been specified then assign the result to the var;
        if(hasVar){
            String strVar = ctxt.getConstantAttribute("var");
            ctxt.generateJavaSource("if(null != " + resultName + "){");
            ctxt.generateJavaSource("    " + jspCtxt + ".setAttribute(\"" + strVar + "\"," + resultName + "," + iScope + ");");
            ctxt.generateJavaSource("} else {");
            if(hasScope){
                ctxt.generateJavaSource("    " + jspCtxt + ".removeAttribute(\"" + strVar + "\"," + iScope + ");");
            }else{
                ctxt.generateJavaSource("    " + jspCtxt + ".removeAttribute(\"" + strVar + "\");");
            }
            ctxt.generateJavaSource("}");

            //else assign the result to the target.property
        }else if(hasTarget){

            //generate the temp variable name
            String pdName = ctxt.getTemporaryVariableName();
            String successFlagName = ctxt.getTemporaryVariableName();
            String index = ctxt.getTemporaryVariableName();
            String methodName = ctxt.getTemporaryVariableName();

            //initialize the property
            ctxt.generateJavaSource("String " + propertyName + " = null;");
            ctxt.generateJavaSource("if(");
            ctxt.generateAttribute("property");
            ctxt.generateJavaSource(" != null){");
            ctxt.generateJavaSource("    " + propertyName + " = (");
            ctxt.generateAttribute("property");
            ctxt.generateJavaSource(").toString();");
            ctxt.generateJavaSource("}");

            //initialize the target
            ctxt.generateJavaSource("Object " + targetName + " = ");
            ctxt.generateAttribute("target");
            ctxt.generateJavaSource(";");

            //the target is ok
            ctxt.generateJavaSource("if(" + targetName + " != null){");

            //if the target is a map, then put the result into the map with the key property
            ctxt.generateJavaSource("    if(" + targetName + " instanceof java.util.Map){");
            ctxt.generateJavaSource("        if(null != " + resultName + "){");
            ctxt.generateJavaSource("            ((java.util.Map) " + targetName + ").put(" + propertyName + "," + resultName + ");");
            ctxt.generateJavaSource("        }else{");
            ctxt.generateJavaSource("            ((java.util.Map) " + targetName + ").remove(" + propertyName + ");");
            ctxt.generateJavaSource("        }");

            //else assign the result to the target.property
            ctxt.generateJavaSource("    }else{");
            ctxt.generateJavaSource("        try{");

            //get all the property of the target
            ctxt.generateJavaSource("            java.beans.PropertyDescriptor " + pdName + "[] = java.beans.Introspector.getBeanInfo(" + targetName + ".getClass()).getPropertyDescriptors();");

            //the success flag is to imply whether the assign is successful
            ctxt.generateJavaSource("            boolean " + successFlagName + " = false;");

            //find the right property
            ctxt.generateJavaSource("            for(int " + index + "=0;" + index + "<" + pdName + ".length;" + index + "++){");
            ctxt.generateJavaSource("                if(" + pdName + "[" + index + "].getName().equals(" + propertyName + ")){");

            //get the "set" method;
            ctxt.generateJavaSource("                    java.lang.reflect.Method " + methodName + " = " + pdName + "[" + index + "].getWriteMethod();");
            ctxt.generateJavaSource("                    if(null == " + methodName + "){");
            ctxt.generateJavaSource("                        throw new JspException(\"No setter method in &lt;set&gt; for property \"+" + propertyName + ");");
            ctxt.generateJavaSource("                    }");

            //invoke the method through the reflection
            ctxt.generateJavaSource("                    if(" + resultName + " != null){");
            ctxt.generateJavaSource("                        " + methodName + ".invoke(" + targetName + ", new Object[]{org.apache.el.lang.ELSupport.coerceToType(" + jspCtxt + ".getELContext(), " + resultName + ", " + methodName + ".getParameterTypes()[0])});");
            ctxt.generateJavaSource("                    }else{");
            ctxt.generateJavaSource("                        " + methodName + ".invoke(" + targetName + ", new Object[]{null});");
            ctxt.generateJavaSource("                    }");
            ctxt.generateJavaSource("                    " + successFlagName + " = true;");
            ctxt.generateJavaSource("                }");
            ctxt.generateJavaSource("            }");
            ctxt.generateJavaSource("            if(!" + successFlagName + "){");
            ctxt.generateJavaSource("                throw new JspException(\"Invalid property in &lt;set&gt;:\"+" + propertyName + ");");
            ctxt.generateJavaSource("            }");
            ctxt.generateJavaSource("        }");

            //catch the el exception and throw it as a JspException
            ctxt.generateJavaSource("        catch (IllegalAccessException ex) {");
            ctxt.generateJavaSource("            throw new JspException(ex);");
            ctxt.generateJavaSource("        } catch (java.beans.IntrospectionException ex) {");
            ctxt.generateJavaSource("            throw new JspException(ex);");
            ctxt.generateJavaSource("        } catch (java.lang.reflect.InvocationTargetException ex) {");
            ctxt.generateJavaSource("            if (ex.getCause() instanceof ThreadDeath) {");
            ctxt.generateJavaSource("                throw (ThreadDeath) ex.getCause();");
            ctxt.generateJavaSource("            }");
            ctxt.generateJavaSource("            if (ex.getCause() instanceof VirtualMachineError) {");
            ctxt.generateJavaSource("                throw (VirtualMachineError) ex.getCause();");
            ctxt.generateJavaSource("            }");
            ctxt.generateJavaSource("            throw new JspException(ex);");
            ctxt.generateJavaSource("        }");
            ctxt.generateJavaSource("    }");
            ctxt.generateJavaSource("}else{");
            ctxt.generateJavaSource("    throw new JspException();");
            ctxt.generateJavaSource("}");
        }
    }

}
