/**
 * Copyright (c) 2011, Yubico AB.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 *  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 *  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 *  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 *  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 */
package com.yubico.jaas;

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

import javax.security.auth.callback.PasswordCallback;

/**
 * A class that extends PasswordCallback to keep a list of all values
 * set using setPassword(). If the application using this JAAS plugin
 * wants to pass us multiple authentication factors, it just calls
 * setPassword() more than once in the CallbackHandler.
 *
 * @author Fredrik Thulin (fredrik@yubico.com)
 *
 */
public class MultiValuePasswordCallback extends PasswordCallback {
	private static final long serialVersionUID = 5362005708680822656L;
	private ArrayList<char[]> secrets = new ArrayList<char[]>();

	public MultiValuePasswordCallback(String prompt, boolean echoOn) {
		super(prompt, echoOn);
	}

	/**
	 * @return Returns all the secrets.
	 */
	public List<char[]> getSecrets() {
		return secrets;
	}

	/**
	 * @param password  A secret to add to our list.
	 */
	public void setPassword(char[] password) {
		this.secrets.add(password);
	}

	/**
	 * Tries to clear all the passwords from memory.
	 */
	public void clearPassword() {
		for (char pw[] : this.secrets) {
			for (int i = 0; i < pw.length; i++) {
				pw[i] = 0;
			}
		}

		/* Now discard the list. */
		this.secrets = new ArrayList<char []>();
	}
}


package com.yubico.client.v2;

/* 	Copyright (c) 2011, Simon Buckle.  All rights reserved.
	Copyright (c) 2012, Yubico AB. All rights reserved.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions
	are met:

	* Redistributions of source code must retain the above copyright
	notice, this list of conditions and the following disclaimer.

	* Redistributions in binary form must reproduce the above copyright
	notice, this list of conditions and the following
	disclaimer in the documentation and/or other materials provided
	with the distribution.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
	CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
	INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
	MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
	DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
	BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
	TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
	ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
	TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
	THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
	SUCH DAMAGE.

	Written by Simon Buckle (simon@webteq.eu), September 2011.
*/

import com.yubico.client.v2.exceptions.YubicoVerificationException;
import com.yubico.client.v2.impl.VerificationResponseImpl;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

import static com.yubico.client.v2.ResponseStatus.BACKEND_ERROR;;
import static com.yubico.client.v2.ResponseStatus.REPLAYED_REQUEST;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;

/**
 * Fires off a number of validation requests to each specified URL
 * in parallel.
 *
 * @author Simon Buckle (simon@webteq.eu)
 */
public class VerificationRequester {
	private final ExecutorCompletionService<VerificationResponse> completionService;

	/**
	 * Sets up thread pool for validation requests.
	 */
	public VerificationRequester() {
		ThreadPoolExecutor pool = new ThreadPoolExecutor(0, 100, 250L,
				MILLISECONDS, new SynchronousQueue<Runnable>());
	    completionService = new ExecutorCompletionService<VerificationResponse>(pool);
	}

	/**
	 * Fires off a validation request to each url in the list, returning the first one
	 * that is not {@link ResponseStatus#REPLAYED_REQUEST}
	 *
	 * @param urls a list of validation urls to be contacted
	 * @param userAgent userAgent to send in request, if null one will be generated
	 * @return {@link VerificationResponse} object from the first server response that is not
	 * {@link ResponseStatus#REPLAYED_REQUEST}
	 * @throws com.yubico.client.v2.exceptions.YubicoVerificationException if validation fails on all urls
	 */
	public VerificationResponse fetch(List<String> urls, String userAgent) throws YubicoVerificationException {
	    List<Future<VerificationResponse>> tasks = new ArrayList<Future<VerificationResponse>>();
	    for(String url : urls) {
			tasks.add(completionService.submit(createTask(userAgent, url)));
	    }
	    VerificationResponse response = null;
		try {
			int tasksDone = 0;
			Throwable savedException = null;
			Future<VerificationResponse> futureResponse = completionService.poll(1L, MINUTES);
			while(futureResponse != null) {
				try {
					tasksDone++;
					tasks.remove(futureResponse);
					response = futureResponse.get();
					/**
					 * If the response returned is REPLAYED_REQUEST keep looking at responses
					 * and hope we get something else. REPLAYED_REQUEST will be returned if a
					 * validation server got sync before it parsed our query (otp and nonce is
					 * the same).
					 * @see https://forum.yubico.com/viewtopic21be.html
					 *
					 * Also if the response is BACKEND_ERROR, keep looking for a server that
					 * sends a valid response
					 * @see https://github.com/Yubico/yubico-java-client/issues/12
					 */
					if(!response.getStatus().equals(REPLAYED_REQUEST) && !response.getStatus().equals(BACKEND_ERROR)) {
						break;
					}
				} catch (CancellationException ignored) {
					// this would be thrown by old cancelled calls.
					tasksDone--;
				} catch (ExecutionException e) {
					// tuck the real exception away and use it if we don't get any valid answers.
					savedException = e.getCause();
				}
				if(tasksDone >= urls.size()) {
					break;
				}
				futureResponse = completionService.poll(1L, MINUTES);
			}
			if(futureResponse == null || response == null) {
				if(savedException != null) {
					throw new YubicoVerificationException(
							"Exception while executing validation.", savedException);
				} else {
					throw new YubicoVerificationException("Validation timeout.");
				}
			}
		} catch (InterruptedException e) {
			throw new YubicoVerificationException("Validation interrupted.", e);
		}

		for(Future<VerificationResponse> task : tasks) {
			task.cancel(true);
		}

	    return response;
	}

	protected VerifyTask createTask(String userAgent, String url) {
		return new VerifyTask(url, userAgent);
	}

	/**
	 * Inner class for doing requests to validation server.
	 */
	static class VerifyTask implements Callable<VerificationResponse> {

		private final Logger log = LoggerFactory.getLogger(VerifyTask.class);

		private final String url;
		private final String userAgent;

		/**
		 * Set up a VerifyTask for the Yubico Validation protocol v2
		 * @param url the url to be used
		 * @param userAgent the userAgent to be sent to the server, or NULL and one is calculated
		 */
		public VerifyTask(String url, String userAgent) {
			this.url = url;
			this.userAgent = userAgent;
		}

		/**
		 * Do the validation query for previous URL.
		 * @throws Exception should not be anything but {@link IOException}
		 */
		public VerificationResponse call() throws Exception {
			URL url = new URL(this.url);
			try {
				return new VerificationResponseImpl(getResponseStream(url));
			} catch (IOException e) {
				log.warn("Exception when requesting {}: {}", url.getHost(), e.getMessage());
				throw e;
			}
		}

		protected InputStream getResponseStream(URL url) throws IOException {
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			conn.setRequestProperty("User-Agent", userAgent);
			conn.setConnectTimeout(15000);
			conn.setReadTimeout(15000);
			return conn.getInputStream();
		}
	}
}


/* Copyright (c) 2011, Linus Widströmer.  All rights reserved.
   Copyright (c) 2011-2012, Yubico AB.  All rights reserved.
   Copyright (c) 2011, Simon Buckle.  All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   * Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.

   * Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following
     disclaimer in the documentation and/or other materials provided
     with the distribution.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
   CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   SUCH DAMAGE.

   Written by Linus Widströmer <linus.widstromer@it.su.se>, January 2011.
*/

package com.yubico.client.v2;

import com.yubico.client.v2.exceptions.YubicoValidationFailure;
import com.yubico.client.v2.exceptions.YubicoVerificationException;
import com.yubico.client.v2.impl.YubicoClientImpl;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base class for doing YubiKey validations using version 2 of the validation protocol.
 */

public abstract class YubicoClient {
    private static final Logger log = LoggerFactory.getLogger(YubicoClient.class);

    protected Integer clientId;
    protected byte[] key;
    protected Integer sync;
    protected String wsapi_urls[] = {
               "https://api.yubico.com/wsapi/2.0/verify",
               "https://api2.yubico.com/wsapi/2.0/verify",
               "https://api3.yubico.com/wsapi/2.0/verify",
               "https://api4.yubico.com/wsapi/2.0/verify",
               "https://api5.yubico.com/wsapi/2.0/verify"
    		};

    protected String userAgent = "yubico-java-client/" + Version.version +
            " (" + System.getProperty("java.vendor") + " " + System.getProperty("java.version") + ")";

    /**
     * Validate an OTP using a webservice call to one or more ykval validation servers.
     *
     * @param otp YubiKey OTP
     * @return result of the webservice validation operation
     * @throws com.yubico.client.v2.exceptions.YubicoVerificationException for validation errors, like unreachable servers
     * @throws YubicoValidationFailure for validation failures, like non matching OTPs in request and response
     * @throws IllegalArgumentException for arguments that are not correctly formatted OTP strings.
     */
    public abstract VerificationResponse verify(String otp) throws YubicoVerificationException, YubicoValidationFailure;

    /**
     * Get the ykval client identifier used to identify the application.
     * @return ykval client identifier
     * @see YubicoClient#setClientId(Integer)
     */
    public Integer getClientId() {
        return clientId;
    }

    /**
     * Set the ykval client identifier, used to identify the client application to
     * the validation servers. Such validation is only required for non-https-v2.0
     * validation queries, where the clientId tells the server what API key (shared
     * secret) to use to validate requests and sign responses.
     *
     * You can get a clientId and API key for the YubiCloud validation service at
     * https://upgrade.yubico.com/getapikey/
     *
     * @param clientId  ykval client identifier
     */
    public void setClientId(Integer clientId) {
        this.clientId = clientId;
    }

    /**
     * Set api key to be used for signing requests
     * @param key ykval client key
     * @see YubicoClient#setClientId(Integer)
     */
    public void setKey(String key) {
        this.key = Base64.decodeBase64(key.getBytes());
    }

    /**
     * Get the api key that is used for signing requests
     * @return ykval client key
     * @see YubicoClient#setClientId(Integer)
     */
    public String getKey() {
        return new String(Base64.encodeBase64(this.key));
    }

    /**
     * Set the sync percentage required for a successful auth.
     * Default is to let the server decide.
     * @param sync percentage or strings 'secure' or 'fast'
     */
    public void setSync(Integer sync) {
    	this.sync = sync;
    }

    /**
     * Get the list of URLs that will be used for validating OTPs.
     * @return list of base URLs
     */
    public String[] getWsapiUrls() {
		return wsapi_urls;
	}

    /**
     * Configure what URLs to use for validating OTPs. These URLs will have
     * all the necessary parameters appended to them. Example :
     * {"https://api.yubico.com/wsapi/2.0/verify"}
     * @param wsapi  list of base URLs
     */
	public void setWsapiUrls(String[] wsapi) {
		for (String url : wsapi) {
			warnIfDeprecatedUrl(url);
		}
		this.wsapi_urls = wsapi;
	}

	protected void warnIfDeprecatedUrl(String url) {
		if (url != null && url.startsWith("http:")) {
			log.warn("Deprecated YubiCloud URL: {} - naked HTTP requests are deprecated and will not be supported from 2019-02-04. See: https://status.yubico.com/2018/11/26/deprecating-yubicloud-v1-protocol-plain-text-requests-and-old-tls-versions/", url);
		}
	}

	/**
	 * Set user agent to be used in request to validation server
	 * @param userAgent the user agent used in requests
	 */
	public void setUserAgent(String userAgent) {
		this.userAgent = userAgent;
	}

	/**
	 * Instantiate a YubicoClient object.
     *
     * @param clientId Retrieved from https://upgrade.yubico.com/getapikey
	 * @return  client that can be used to validate YubiKey OTPs
	 */
	public static YubicoClient getClient(Integer clientId, String key) {
        return new YubicoClientImpl(clientId, key);
    }

    /**
	 * Extract the public ID of a YubiKey from an OTP it generated.
	 *
	 * @param otp	The OTP to extract ID from, in modhex format.
	 *
	 * @return string	Public ID of YubiKey that generated otp. Between 0 and 12 lower-case characters.
	 *
	 * @throws IllegalArgumentException for arguments that are null or too short to be valid OTP strings.
	 */
	public static String getPublicId(String otp) {
		if ((otp == null) || (otp.length() < OTP_MIN_LEN)){
			//not a valid OTP format, throw an exception
			throw new IllegalArgumentException("The OTP is too short to be valid");
		}

		Integer len = otp.length();

		/* The OTP part is always the last 32 bytes of otp. Whatever is before that
		 * (if anything) is the public ID of the YubiKey. The ID can be set to ''
		 * through personalization.
		 */
		return otp.substring(0, len - 32).toLowerCase();
	}

	private static final Integer OTP_MIN_LEN = 32;
	private static final Integer OTP_MAX_LEN = 48;
	/**
	 * Determines whether a given OTP is of the correct length
	 * and only contains printable characters, as per the recommendation.
	 *
	 * @param otp The OTP to validate
	 * @return boolean Returns true if it's valid; false otherwise
	 *
	 */
	public static boolean isValidOTPFormat(String otp) {
		if (otp == null){
			return false;
		}
		int len = otp.length();
		for (char c : otp.toCharArray()) {
			if (c < 0x20 || c > 0x7E) {
				return false;
			}
		}
		return OTP_MIN_LEN <= len && len <= OTP_MAX_LEN;
	}
}


//--------------------------------------------------------------------------
//	Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
//  All rights reserved.
//
//	Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are
//  met:
//
//	Redistributions of source code must retain the above copyright notice,
//  this list of conditions and the following disclaimer.
//	Redistributions in binary form must reproduce the above copyright
//  notice, this list of conditions and the following disclaimer in the
//  documentation and/or other materials provided with the distribution.
//	Neither the name of the Drew Davidson nor the names of its contributors
//  may be used to endorse or promote products derived from this software
//  without specific prior written permission.
//
//	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
//  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
//  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
//  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
//  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
//  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
//  DAMAGE.
//--------------------------------------------------------------------------
package ognl;

import java.util.*;

public final class EvaluationPool extends Object
{
    public EvaluationPool()
    {
        this(0);
    }

    public EvaluationPool(int initialSize)
    {
        super();
        // do not init object pooling
    }

    /**
        Returns an Evaluation that contains the node, source and whether it
        is a set operation.  If there are no Evaluation objects in the
        pool one is created and returned.
     */
    public Evaluation create(SimpleNode node, Object source)
    {
        return create(node, source, false);
    }

    /**
        Returns an Evaluation that contains the node, source and whether it
        is a set operation.
     */
    public Evaluation create(SimpleNode node, Object source, boolean setOperation)
    {
        // synchronization is removed as we do not rely anymore on the in-house object pooling
        return new Evaluation(node, source, setOperation);
    }

    /**
        Recycles an Evaluation
     * @deprecated object-pooling now relies on the jvm garbage collection
     */
    public void recycle(Evaluation value)
    {
        // no need of recycling, we rely on the garbage collection efficiency
    }

    /**
        Recycles an of Evaluation and all of it's siblings
        and children.
     * @deprecated object-pooling now relies on the jvm garbage collection
     */
    public void recycleAll(Evaluation value)
    {
        // no need of recycling, we rely on the garbage collection efficiency
    }

    /**
        Recycles a List of Evaluation objects
     * @deprecated object-pooling now relies on the jvm garbage collection
     */
    public void recycleAll(List value)
    {
        // no need of recycling, we rely on the garbage collection efficiency
    }

    /**
        Returns the number of items in the pool
     * @deprecated object-pooling now relies on the jvm garbage collection
     */
    public int getSize()
    {
        return 0;
    }

    /**
        Returns the number of items this pool has created since
        it's construction.
     * @deprecated object-pooling now relies on the jvm garbage collection
     */
    public int getCreatedCount()
    {
        return 0;
    }

    /**
        Returns the number of items this pool has recovered from
        the pool since its construction.
     * @deprecated object-pooling now relies on the jvm garbage collection
     */
    public int getRecoveredCount()
    {
        return 0;
    }

    /**
        Returns the number of items this pool has recycled since
        it's construction.
     * @deprecated object-pooling now relies on the jvm garbage collection
     */
    public int getRecycledCount()
    {
        return 0;
    }
}


// --------------------------------------------------------------------------
// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the Drew Davidson nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// --------------------------------------------------------------------------
package ognl;

import ognl.enhance.ExpressionCompiler;
import ognl.enhance.UnsupportedCompilationException;

import java.beans.IndexedPropertyDescriptor;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.Iterator;

/**
 * @author Luke Blanshard (blanshlu@netscape.net)
 * @author Drew Davidson (drew@ognl.org)
 */
public class ASTProperty extends SimpleNode implements NodeType
{
    private boolean _indexedAccess = false;

    private Class _getterClass;
    private Class _setterClass;

    public ASTProperty(int id)
    {
        super(id);
    }

    public void setIndexedAccess(boolean value)
    {
        _indexedAccess = value;
    }

    /**
     * Returns true if this property is itself an index reference.
     */
    public boolean isIndexedAccess()
    {
        return _indexedAccess;
    }

    /**
     * Returns true if this property is described by an IndexedPropertyDescriptor and that if
     * followed by an index specifier it will call the index get/set methods rather than go through
     * property accessors.
     */
    public int getIndexedPropertyType(OgnlContext context, Object source)
            throws OgnlException
    {
        Class type = context.getCurrentType();
        Class prevType = context.getPreviousType();
        try
        {
            if (!isIndexedAccess())
            {
                Object property = getProperty(context, source);

                if (property instanceof String)
                {
                    return OgnlRuntime.getIndexedPropertyType(context, (source == null)
                            ? null
                            : OgnlRuntime.getCompiler().getInterfaceClass(source.getClass()), (String) property);
                }
            }

            return OgnlRuntime.INDEXED_PROPERTY_NONE;
        } finally
        {
            context.setCurrentObject(source);
            context.setCurrentType(type);
            context.setPreviousType(prevType);
        }
    }

    public Object getProperty(OgnlContext context, Object source)
            throws OgnlException
    {
        return _children[0].getValue(context, context.getRoot());
    }

    protected Object getValueBody(OgnlContext context, Object source)
            throws OgnlException
    {
        Object property = getProperty(context, source);

        Object result = OgnlRuntime.getProperty(context, source, property);

        if (result == null)
        {
            result = OgnlRuntime.getNullHandler(OgnlRuntime.getTargetClass(source)).nullPropertyValue(context, source, property);
        }

        return result;
    }

    protected void setValueBody(OgnlContext context, Object target, Object value)
            throws OgnlException
    {
        OgnlRuntime.setProperty(context, target, getProperty(context, target), value);
    }

    public boolean isNodeSimpleProperty(OgnlContext context)
            throws OgnlException
    {
        return (_children != null) && (_children.length == 1) && ((SimpleNode) _children[0]).isConstant(context);
    }

    public Class getGetterClass()
    {
        return _getterClass;
    }

    public Class getSetterClass()
    {
        return _setterClass;
    }

    public String toString()
    {
        String result;

        if (isIndexedAccess())
        {
            result = "[" + _children[0] + "]";
        } else
        {
            result = ((ASTConst) _children[0]).getValue().toString();
        }
        return result;
    }

    public String toGetSourceString(OgnlContext context, Object target)
    {
        if (context.getCurrentObject() == null)
            throw new UnsupportedCompilationException("Current target is null.");

        String result = "";
        Method m = null;

        try {
            /* System.out.println("astproperty is indexed? : " + isIndexedAccess() + " child: " + _children[0].getClass().getName()
+ " target: " + target.getClass().getName() + " current object: " + context.getCurrentObject().getClass().getName());*/

            if (isIndexedAccess())
            {
                Object value = _children[0].getValue(context, context.getRoot());

                if (value == null || DynamicSubscript.class.isAssignableFrom(value.getClass()))
                    throw new UnsupportedCompilationException("Value passed as indexed property was null or not supported.");

                // Get root cast string if the child is a type that needs it (like a nested ASTProperty)

                String srcString = _children[0].toGetSourceString(context, context.getRoot());
                srcString = ExpressionCompiler.getRootExpression(_children[0], context.getRoot(), context) + srcString;

                if (ASTChain.class.isInstance(_children[0]))
                {
                    String cast = (String)context.remove(ExpressionCompiler.PRE_CAST);
                    if (cast != null)
                        srcString = cast + srcString;
                }

                if (ASTConst.class.isInstance(_children[0]) && String.class.isInstance(context.getCurrentObject()))
                    srcString = "\"" + srcString + "\"";

                // System.out.println("indexed getting with child srcString: " + srcString + " value class: " + value.getClass() + " and child: " + _children[0].getClass());

                if (context.get("_indexedMethod") != null)
                {
                    m = (Method)context.remove("_indexedMethod");
                    _getterClass = m.getReturnType();

                    Object indexedValue = OgnlRuntime.callMethod(context, target, m.getName(), new Object[]{value});

                    context.setCurrentType(_getterClass);
                    context.setCurrentObject(indexedValue);
                    context.setCurrentAccessor(OgnlRuntime.getCompiler().getSuperOrInterfaceClass(m, m.getDeclaringClass()));

                    return "." + m.getName() + "(" + srcString + ")";
                } else
                {
                    PropertyAccessor p = OgnlRuntime.getPropertyAccessor(target.getClass());

//                    System.out.println("child value : " + _children[0].getValue(context, context.getCurrentObject()) + " using propaccessor " + p.getClass().getName()
//                        + " and srcString " + srcString + " on target: " +  target);

                    Object currObj = context.getCurrentObject();
                    Class currType = context.getCurrentType();
                    Class prevType = context.getPreviousType();

                    Object indexVal = p.getProperty(context, target, value);

                    // reset current object for accessor

                    context.setCurrentObject(currObj);
                    context.setCurrentType(currType);
                    context.setPreviousType(prevType);

/*                    System.out.println("astprop srcString: " + srcString
   + " from child class " + _children[0].getClass().getName()
   + " and indexVal " + indexVal
   + " propertyAccessor : " + p.getClass().getName() + " context obj " + context.getCurrentObject()
   + " context obj is array? : " + context.getCurrentObject().getClass().isArray()
              + " current type: " + context.getCurrentType());*/

                    if (ASTConst.class.isInstance(_children[0]) && Number.class.isInstance(context.getCurrentObject()))
                        context.setCurrentType(OgnlRuntime.getPrimitiveWrapperClass(context.getCurrentObject().getClass()));

                    result = p.getSourceAccessor(context, target, srcString);
                    _getterClass = context.getCurrentType();
                    context.setCurrentObject(indexVal);

                    return result;
                }
            }

            String name =  ((ASTConst) _children[0]).getValue().toString();

            if (!Iterator.class.isAssignableFrom(context.getCurrentObject().getClass())
                || (Iterator.class.isAssignableFrom(context.getCurrentObject().getClass()) && name.indexOf("next") < 0))
            {
                Object currObj = target;

                try
                {
                    target = getValue(context, context.getCurrentObject());
                } catch (NoSuchPropertyException e)
                {
                    try {
                        target = getValue(context, context.getRoot());
                    } catch (NoSuchPropertyException ex) {
                        // ignore
                    }
                } finally
                {
                    context.setCurrentObject(currObj);
                }
            }

            PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor(context.getCurrentObject().getClass(), name);

            if (pd != null && pd.getReadMethod() != null
                && !context.getMemberAccess().isAccessible(context, context.getCurrentObject(), pd.getReadMethod(), name))
            {
                throw new UnsupportedCompilationException("Member access forbidden for property " + name + " on class " + context.getCurrentObject().getClass());
            }

            if (this.getIndexedPropertyType(context, context.getCurrentObject()) > 0 && pd != null)
            {
                // if an indexed method accessor need to use special property descriptors to find methods

                if (pd instanceof IndexedPropertyDescriptor)
                {
                    m = ((IndexedPropertyDescriptor) pd).getIndexedReadMethod();
                } else
                {
                    if (pd instanceof ObjectIndexedPropertyDescriptor)
                        m = ((ObjectIndexedPropertyDescriptor) pd).getIndexedReadMethod();
                    else
                        throw new OgnlException("property '" + name + "' is not an indexed property");
                }

                if (_parent == null)
                {
                    // the above pd will be the wrong result sometimes, such as methods like getValue(int) vs String[] getValue()
                    m = OgnlRuntime.getReadMethod(context.getCurrentObject().getClass(), name);

                    result = m.getName() + "()";
                    _getterClass = m.getReturnType();
                } else
                {
                    context.put("_indexedMethod", m);
                }
            } else
            {

                /*    System.out.println("astproperty trying to get " + name + " on object target: " + context.getCurrentObject().getClass().getName()
       + " current type " + context.getCurrentType() + " current accessor " + context.getCurrentAccessor()
   + " prev type " + context.getPreviousType() + " prev accessor " + context.getPreviousAccessor());*/

                PropertyAccessor pa = OgnlRuntime.getPropertyAccessor(context.getCurrentObject().getClass());

                if (context.getCurrentObject().getClass().isArray())
                {
                    if (pd == null)
                    {
                        pd = OgnlRuntime.getProperty(context.getCurrentObject().getClass(), name);

                        if (pd != null && pd.getReadMethod() != null)
                        {
                            m = pd.getReadMethod();
                            result = pd.getName();
                        } else
                        {
                            _getterClass = int.class;
                            context.setCurrentAccessor(context.getCurrentObject().getClass());
                            context.setCurrentType(int.class);
                            result = "." + name;
                        }
                    }
                } else
                {
                    if (pd != null && pd.getReadMethod() != null)
                    {
                        m = pd.getReadMethod();
                        result = "." + m.getName() + "()";
                    } else if (pa != null)
                    {
                        Object currObj = context.getCurrentObject();
                        Class currType = context.getCurrentType();
                        Class prevType = context.getPreviousType();

                        String srcString = _children[0].toGetSourceString(context, context.getRoot());

                        if (ASTConst.class.isInstance(_children[0]) &&
                            String.class.isInstance(context.getCurrentObject()))
                            srcString = "\"" + srcString + "\"";

                        context.setCurrentObject(currObj);
                        context.setCurrentType(currType);
                        context.setPreviousType(prevType);

                        result = pa.getSourceAccessor(context, context.getCurrentObject(), srcString);

                        _getterClass = context.getCurrentType();
                    }
                }
            }

        } catch (Throwable t)
        {
            throw OgnlOps.castToRuntime(t);
        }

        // set known property types for NodeType interface when possible

        if (m != null)
        {
            _getterClass = m.getReturnType();

            context.setCurrentType(m.getReturnType());
            context.setCurrentAccessor(OgnlRuntime.getCompiler().getSuperOrInterfaceClass(m, m.getDeclaringClass()));
        }

        context.setCurrentObject(target);

        return result;
    }

    Method getIndexedWriteMethod(PropertyDescriptor pd)
    {
        if (IndexedPropertyDescriptor.class.isInstance(pd))
        {
            return ((IndexedPropertyDescriptor)pd).getIndexedWriteMethod();
        } else if (ObjectIndexedPropertyDescriptor.class.isInstance(pd))
        {
            return ((ObjectIndexedPropertyDescriptor)pd).getIndexedWriteMethod();
        }

        return null;
    }

    public String toSetSourceString(OgnlContext context, Object target)
    {
        String result = "";
        Method m = null;

        if (context.getCurrentObject() == null)
            throw new UnsupportedCompilationException("Current target is null.");

        /*System.out.println("astproperty(setter) is indexed? : " + isIndexedAccess() + " child: " + _children[0].getClass().getName()
                           + " target: " + target.getClass().getName() + " children length: " + _children.length);*/

        try {

            if (isIndexedAccess())
            {
                Object value = _children[0].getValue(context, context.getRoot());

                if (value == null)
                    throw new UnsupportedCompilationException("Value passed as indexed property is null, can't enhance statement to bytecode.");

                String srcString = _children[0].toGetSourceString(context, context.getRoot());
                srcString = ExpressionCompiler.getRootExpression(_children[0], context.getRoot(), context) + srcString;

                if (ASTChain.class.isInstance(_children[0]))
                {
                    String cast = (String)context.remove(ExpressionCompiler.PRE_CAST);
                    if (cast != null)
                        srcString = cast + srcString;
                }

                if (ASTConst.class.isInstance(_children[0]) && String.class.isInstance(context.getCurrentObject()))
                {
                    srcString = "\"" + srcString + "\"";
                }

//                System.out.println("astproperty setter using indexed value " + value + " and srcString: " + srcString);

                if (context.get("_indexedMethod") != null)
                {
                    m = (Method)context.remove("_indexedMethod");
                    PropertyDescriptor pd = (PropertyDescriptor)context.remove("_indexedDescriptor");

                    boolean lastChild = lastChild(context);
                    if (lastChild)
                    {
                        m = getIndexedWriteMethod(pd);

                        if (m == null)
                            throw new UnsupportedCompilationException("Indexed property has no corresponding write method.");
                    }

                    _setterClass = m.getParameterTypes()[0];

                    Object indexedValue = null;
                    if (!lastChild)
                        indexedValue = OgnlRuntime.callMethod(context, target, m.getName(), new Object[]{value});

                    context.setCurrentType(_setterClass);
                    context.setCurrentAccessor(OgnlRuntime.getCompiler().getSuperOrInterfaceClass(m, m.getDeclaringClass()));

                    if (!lastChild)
                    {
                        context.setCurrentObject(indexedValue);
                        return "." + m.getName() + "(" + srcString + ")";
                    } else {
                        return "." + m.getName() + "(" + srcString + ", $3)";
                    }
                } else
                {
                    PropertyAccessor p = OgnlRuntime.getPropertyAccessor(target.getClass());

                    Object currObj = context.getCurrentObject();
                    Class currType = context.getCurrentType();
                    Class prevType = context.getPreviousType();

                    Object indexVal = p.getProperty(context, target, value);

                    // reset current object for accessor

                    context.setCurrentObject(currObj);
                    context.setCurrentType(currType);
                    context.setPreviousType(prevType);

                    if (ASTConst.class.isInstance(_children[0]) && Number.class.isInstance(context.getCurrentObject()))
                        context.setCurrentType(OgnlRuntime.getPrimitiveWrapperClass(context.getCurrentObject().getClass()));

                    result = lastChild(context) ? p.getSourceSetter(context, target, srcString) : p.getSourceAccessor(context, target, srcString);

                    /*System.out.println("ASTProperty using propertyaccessor and isLastChild? " + lastChild(context)
                                       + " generated source of: " + result + " using accessor class: " + p.getClass().getName());*/

                    //result = p.getSourceAccessor(context, target, srcString);
                    _getterClass = context.getCurrentType();
                    context.setCurrentObject(indexVal);

                    /* PropertyAccessor p = OgnlRuntime.getPropertyAccessor(target.getClass());

                    if (ASTConst.class.isInstance(_children[0]) && Number.class.isInstance(context.getCurrentObject()))
                    {
                        context.setCurrentType(OgnlRuntime.getPrimitiveWrapperClass(context.getCurrentObject().getClass()));
                    }

                    result = p.getSourceSetter(context, target, srcString);

                    context.setCurrentObject(value);
                    context.setCurrentType(_getterClass);*/
                    return result;
                }
            }

            String name = ((ASTConst) _children[0]).getValue().toString();

//            System.out.println(" astprop(setter) : trying to set " + name + " on object target " + context.getCurrentObject().getClass().getName());

            if (!Iterator.class.isAssignableFrom(context.getCurrentObject().getClass())
                || (Iterator.class.isAssignableFrom(context.getCurrentObject().getClass()) &&  name.indexOf("next") < 0)) {

                Object currObj = target;

                try {
                    target = getValue(context, context.getCurrentObject());
                } catch (NoSuchPropertyException e) {
                    try {

                        target = getValue(context, context.getRoot());

                    } catch (NoSuchPropertyException ex) { }
                } finally {

                    context.setCurrentObject(currObj);
                }
            }

            PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor(OgnlRuntime.getCompiler().getInterfaceClass(context.getCurrentObject().getClass()), name);

            if (pd != null)
            {
                Method pdMethod = lastChild(context) ? pd.getWriteMethod() : pd.getReadMethod();

                if (pdMethod != null && !context.getMemberAccess().isAccessible(context, context.getCurrentObject(), pdMethod, name))
                {
                    throw new UnsupportedCompilationException("Member access forbidden for property " + name + " on class " + context.getCurrentObject().getClass());
                }
            }

            if (pd != null && this.getIndexedPropertyType(context, context.getCurrentObject()) > 0)
            {
                // if an indexed method accessor need to use special property descriptors to find methods

                if (pd instanceof IndexedPropertyDescriptor)
                {
                    IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor)pd;
                    m = lastChild(context) ? ipd.getIndexedWriteMethod() : ipd.getIndexedReadMethod();
                } else
                {
                    if (pd instanceof ObjectIndexedPropertyDescriptor)
                    {
                        ObjectIndexedPropertyDescriptor opd = (ObjectIndexedPropertyDescriptor)pd;

                        m = lastChild(context) ? opd.getIndexedWriteMethod() : opd.getIndexedReadMethod();
                    } else
                    {
                        throw new OgnlException("property '" + name + "' is not an indexed property");
                    }
                }

                if (_parent == null)
                {
                    // the above pd will be the wrong result sometimes, such as methods like getValue(int) vs String[] getValue()

                    m = OgnlRuntime.getWriteMethod(context.getCurrentObject().getClass(), name);
                    Class parm = m.getParameterTypes()[0];
                    String cast = parm.isArray() ? ExpressionCompiler.getCastString(parm) : parm.getName();

                    result = m.getName() + "((" + cast + ")$3)";
                    _setterClass = parm;
                } else
                {
                    context.put("_indexedMethod", m);
                    context.put("_indexedDescriptor", pd);
                }

            } else {
                PropertyAccessor pa = OgnlRuntime.getPropertyAccessor(context.getCurrentObject().getClass());

                /*System.out.println("astproperty trying to set " + name + " on object target: " + context.getCurrentObject().getClass().getName()
                                   + " using propertyaccessor type: " + pa);*/

                if (target != null)
                    _setterClass = target.getClass();

                if (_parent != null && pd != null && pa == null)
                {
                    m = pd.getReadMethod();
                    result = m.getName() + "()";
                } else
                {
                    if (context.getCurrentObject().getClass().isArray())
                    {
                        result = "";
                    } else if (pa != null)
                    {
                        Object currObj = context.getCurrentObject();
                        //Class currType = context.getCurrentType();
                        //Class prevType = context.getPreviousType();

                        String srcString = _children[0].toGetSourceString(context, context.getRoot());

                        if (ASTConst.class.isInstance(_children[0]) && String.class.isInstance(context.getCurrentObject()))
                        {
                            srcString = "\"" + srcString + "\"";
                        }

                        context.setCurrentObject(currObj);
                        //context.setCurrentType(currType);
                        //context.setPreviousType(prevType);

                        if (!lastChild(context))
                        {
                            result = pa.getSourceAccessor(context, context.getCurrentObject(), srcString);
                        } else
                        {
                            result = pa.getSourceSetter(context, context.getCurrentObject(), srcString);
                        }

                        _getterClass = context.getCurrentType();
                    }
                }
            }

        } catch (Throwable t)
        {
            throw OgnlOps.castToRuntime(t);
        }

        context.setCurrentObject(target);

        if (m != null)
        {
            context.setCurrentType(m.getReturnType());
            context.setCurrentAccessor(OgnlRuntime.getCompiler().getSuperOrInterfaceClass(m, m.getDeclaringClass()));
        }

        return result;
    }
}


// --------------------------------------------------------------------------
// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// Neither the name of the Drew Davidson nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// --------------------------------------------------------------------------
package ognl;

import java.util.List;
import java.util.Map;

/**
 * Implementation of PropertyAccessor that uses reflection on the target object's class to find a
 * field or a pair of set/get methods with the given property name.
 *
 * @author Luke Blanshard (blanshlu@netscape.net)
 * @author Drew Davidson (drew@ognl.org)
 */
public class ObjectMethodAccessor implements MethodAccessor
{

    /* MethodAccessor interface */
    public Object callStaticMethod(Map context, Class targetClass, String methodName, Object[] args)
            throws MethodFailedException
    {
        List methods = OgnlRuntime.getMethods(targetClass, methodName, true);

        return OgnlRuntime.callAppropriateMethod((OgnlContext) context, targetClass,
                                                 null, methodName, null, methods, args);
    }

    public Object callMethod(Map context, Object target, String methodName, Object[] args)
            throws MethodFailedException
    {
        Class targetClass = (target == null) ? null : target.getClass();
        List methods = OgnlRuntime.getMethods(targetClass, methodName, false);

        if ((methods == null) || (methods.size() == 0))
        {
            methods = OgnlRuntime.getMethods(targetClass, methodName, true);

        }

        return OgnlRuntime.callAppropriateMethod((OgnlContext) context, target,
                                                 target, methodName, null, methods, args);
    }
}