/*
 * Copyright 2005 Joe Walker
 *
 * 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.directwebremoting;

import org.directwebremoting.dwrp.ConversionConstants;
import org.directwebremoting.util.Logger;
import org.directwebremoting.util.Messages;

/**
 * A simple struct to hold data about a single converted javascript variable.
 * @author Joe Walker [joe at getahead dot ltd dot uk]
 */
public final class InboundVariable
{
    /**
     * Parsing ctor
     * @param context How we lookup references
     * @param key The name of the variable that this was transfered as
     * @param type The type information from javascript
     * @param value The javascript variable converted to a string
     */
    public InboundVariable(InboundContext context, String key, String type, String value)
    {
        this.context = context;
        this.type = type;
        this.value = value;
        this.key = key;
        this.dereferenced = attemptDereference();
    }

    /**
     * Attempt to de-reference an inbound variable.
     * We try de-referencing as soon as possible (why? there is a good reason
     * for it, it fixes some bug, but I can't remember what right now) However
     * the referenced variable may not exist yet, so the de-referencing may
     * fail, requiring us to have another go later.
     * @return Did the dereferencing succeed?
     */
    private boolean attemptDereference()
    {
        if (ConversionConstants.TYPE_REFERENCE.equals(type))
        {
            while (ConversionConstants.TYPE_REFERENCE.equals(type))
            {
                InboundVariable cd = context.getInboundVariable(value);
                if (cd == null)
                {
                    return false;
                }

                type = cd.type;
                value = cd.value;
            }

            // For references without an explicit variable name, we use the
            // name of the thing they point at
            if (key == null)
            {
                key = value;
            }
        }

        return true;
    }

    /**
     * Call <code>attemptDereference()</code>, and complain if it fails.
     * The assumption is that when we call this it really should succeed.
     */
    private void forceDereference()
    {
        if (!dereferenced)
        {
            dereferenced = attemptDereference();
            if (!dereferenced)
            {
                log.error(Messages.getString("InboundVariable.MissingVariable", value)); //$NON-NLS-1$
            }
        }
    }

    /**
     * @return Returns the lookup table.
     */
    public InboundContext getLookup()
    {
        forceDereference();
        return context;
    }

    /**
     * @return Returns the type.
     */
    public String getType()
    {
        forceDereference();
        return type;
    }

    /**
     * Was this type null on the way in
     * @return true if the javascript variable was null or undefined.
     */
    public boolean isNull()
    {
        forceDereference();
        return type.equals(ConversionConstants.INBOUND_NULL);
    }

    /**
     * @return Returns the value.
     */
    public String getValue()
    {
        forceDereference();
        return value;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString()
    {
        forceDereference();
        return type + ConversionConstants.INBOUND_TYPE_SEPARATOR + value;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals(Object obj)
    {
        if (this == obj)
        {
            return true;
        }

        if (!(obj instanceof InboundVariable))
        {
            return false;
        }

        InboundVariable that = (InboundVariable) obj;

        forceDereference();

        if (!this.type.equals(that.type))
        {
            return false;
        }

        if (!this.value.equals(that.value))
        {
            return false;
        }

        if (this.key == null || that.key == null)
        {
            return false;
        }

        return this.key.equals(that.key);
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    public int hashCode()
    {
        return value.hashCode() + type.hashCode();
    }

    /**
     * How do be lookup references?
     */
    private InboundContext context;

    /**
     * The variable name
     */
    private String key;

    /**
     * The javascript declared variable type
     */
    private String type;

    /**
     * The javascript declared variable value
     */
    private String value;

    /**
     * Has this variable been successfully de-referenced
     */
    private boolean dereferenced;

    /**
     * The log stream
     */
    private static final Logger log = Logger.getLogger(InboundVariable.class);
}

@@ -1916,32 +1916,59 @@
 ference()%0A    %7B%0A
+        int maxDepth = 0;%0A%0A
         if (Conv
@@ -2340,24 +2340,241 @@
  = cd.value;
+%0A%0A                maxDepth++;%0A                if (maxDepth %3E 20)%0A                %7B%0A                    throw new IllegalStateException(%22Max depth exceeded when dereferencing %22 + value); //$NON-NLS-1$%0A                %7D
 %0A           

fix de-ref loop