/*
 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.corba.se.spi.orb ;

import java.util.StringTokenizer ;
import java.util.Arrays ;

import java.lang.reflect.Array ;

import java.net.URL ;
import java.net.MalformedURLException ;

import com.sun.corba.se.spi.logging.CORBALogDomains ;

import com.sun.corba.se.impl.logging.ORBUtilSystemException ;
import com.sun.corba.se.impl.orbutil.ObjectUtility ;

import sun.corba.SharedSecrets;

/** This is a static factory class for commonly used operations
* for property parsing.  The following operations are supported:
* <ul>
* <li>maskErrorAction( Operation op ) executes op and returns the result.  If op throws an
* exception, the result is null.
* <li>indexAction( int arg ) returns the [arg] element of value, which must be an Object[]</li>
* <li>identityAction() return the value</li>
* <li>booleanAction() return a Boolean representing true or false values of the String value</li>
* <li>integerAction() returns an Integer for the String value, which must be a decimal integer</li>
* <li>stringAction() returns the String value</li>
* <li>classAction() returns a class for the String value, as loaded by the ORB classloader</li>
* <li>setFlagAction() always return Boolean.TRUE</li>
* <li>URLAction() returns a java.net.URL for the String value, which must be a valid URL</li>
* <li>integerRangeAction( int min, int max ) returns an Integer for the String value, which must be a
* decimal integer in the range min to max inclusive</li>
* <li>listAction( String sep, Operation ) tokenizes the String value with sep as separator, then
* applies the Operation to each token, and returns an array of the result</li>
* <li>sequenceAction( String, Operation[] ) tokenizes the String value with sep as separator, then
* applies each Operation in the Operation array to successive tokens, and returns an array of the results</li>
* <li>compose( Operation op1, Operation op2 ) is the operation that applies op2 to the result of applying
* op1 to the value</li>
* <li>mapAction( Operation ) applies the Operation to each element of an array of objects, and returns
* an array of the results</li>
* <li>mapSequenceAction( Operation[] ) applies the corresponding element of the Operation array to an
* element of the Object[] value, and returns an array of the results</li>
* <li>convertIntegerToShort coerces an Integer into a Short.</li>
* </ul>
* Other operations can be directly defined, and combined using these basic operations.
*/
public abstract class OperationFactory {
    private OperationFactory() {}

    private static String getString( Object obj )
    {
        if (obj instanceof String)
            return (String)obj ;
        else
            throw new Error( "String expected" ) ;
    }

    private static Object[] getObjectArray( Object obj )
    {
        if (obj instanceof Object[])
            return (Object[])obj ;
        else
            throw new Error( "Object[] expected" ) ;
    }

    private static StringPair getStringPair( Object obj )
    {
        if (obj instanceof StringPair)
            return (StringPair)obj ;
        else
            throw new Error( "StringPair expected" ) ;
    }

    private static abstract class OperationBase implements Operation{
        public boolean equals( Object obj )
        {
            if (this==obj)
                return true ;

            if (!(obj instanceof OperationBase))
                return false ;

            OperationBase other = (OperationBase)obj ;

            return toString().equals( other.toString() ) ;
        }

        public int hashCode()
        {
            return toString().hashCode() ;
        }
    }

    private static class MaskErrorAction extends OperationBase
    {
        private Operation op ;

        public MaskErrorAction( Operation op )
        {
            this.op = op ;
        }

        public Object operate( Object arg )
        {
            try {
                return op.operate( arg ) ;
            } catch (java.lang.Exception exc) {
                return null ;
            }
        }

        public String toString()
        {
            return "maskErrorAction(" + op + ")" ;
        }
    }

    public static Operation maskErrorAction( Operation op )
    {
        return new MaskErrorAction( op ) ;
    }

    private static class IndexAction extends OperationBase
    {
        private int index ;

        public IndexAction( int index )
        {
            this.index = index ;
        }

        public Object operate( Object value )
        {
            return getObjectArray( value )[ index ] ;
        }

        public String toString()
        {
            return "indexAction(" + index + ")" ;
        }
    }

    public static Operation indexAction( int index )
    {
        return new IndexAction( index ) ;
    }

    private static class SuffixAction extends OperationBase
    {
        public Object operate( Object value )
        {
            return getStringPair( value ).getFirst() ;
        }

        public String toString() { return "suffixAction" ; }
    }

    private static Operation suffixActionImpl = new SuffixAction() ;

    private static class ValueAction extends OperationBase
    {
        public Object operate( Object value )
        {
            return getStringPair( value ).getSecond() ;
        }

        public String toString() { return "valueAction" ; }
    }

    private static Operation valueActionImpl = new ValueAction() ;

    private static class IdentityAction extends OperationBase
    {
        public Object operate( Object value )
        {
            return value ;
        }

        public String toString() { return "identityAction" ; }
    }

    private static Operation identityActionImpl = new IdentityAction() ;

    private static class BooleanAction extends OperationBase
    {
        public Object operate( Object value )
        {
            return new Boolean( getString( value ) ) ;
        }

        public String toString() { return "booleanAction" ; }
    }

    private static Operation booleanActionImpl = new BooleanAction() ;

    private static class IntegerAction extends OperationBase
    {
        public Object operate( Object value )
        {
            return new Integer( getString( value ) ) ;
        }

        public String toString() { return "integerAction" ; }
    }

    private static Operation integerActionImpl = new IntegerAction() ;

    private static class StringAction extends OperationBase
    {
        public Object operate( Object value )
        {
            return value ;
        }

        public String toString() { return "stringAction" ; }
    }

    private static Operation stringActionImpl = new StringAction() ;

    private static class ClassAction extends OperationBase
    {
        public Object operate( Object value )
        {
            String className = getString( value ) ;

            try {
                Class<?> result =
                    SharedSecrets.getJavaCorbaAccess().loadClass( className ) ;
                return result ;
            } catch (Exception exc) {
                ORBUtilSystemException wrapper = ORBUtilSystemException.get(
                    CORBALogDomains.ORB_LIFECYCLE ) ;
                throw wrapper.couldNotLoadClass( exc, className ) ;
            }
        }

        public String toString() { return "classAction" ; }
    }

    private static Operation classActionImpl = new ClassAction() ;

    private static class SetFlagAction extends OperationBase
    {
        public Object operate( Object value )
        {
            return Boolean.TRUE ;
        }

        public String toString() { return "setFlagAction" ; }
    }

    private static Operation setFlagActionImpl = new SetFlagAction() ;

    private static class URLAction extends OperationBase
    {
        public Object operate( Object value )
        {
            String val = (String)value ;
            try {
                return new URL( val ) ;
            } catch (MalformedURLException exc) {
                ORBUtilSystemException wrapper = ORBUtilSystemException.get(
                    CORBALogDomains.ORB_LIFECYCLE ) ;
                throw wrapper.badUrl( exc, val ) ;
            }
        }

        public String toString() { return "URLAction" ; }
    }

    private static Operation URLActionImpl = new URLAction() ;

    public static Operation identityAction()
    {
        return identityActionImpl ;
    }

    public static Operation suffixAction()
    {
        return suffixActionImpl ;
    }

    public static Operation valueAction()
    {
        return valueActionImpl ;
    }

    public static Operation booleanAction()
    {
        return booleanActionImpl ;
    }

    public static Operation integerAction()
    {
        return integerActionImpl ;
    }

    public static Operation stringAction()
    {
        return stringActionImpl ;
    }

    public static Operation classAction()
    {
        return classActionImpl ;
    }

    public static Operation setFlagAction()
    {
        return setFlagActionImpl ;
    }

    public static Operation URLAction()
    {
        return URLActionImpl ;
    }

    private static class IntegerRangeAction extends OperationBase
    {
        private int min ;
        private int max ;

        IntegerRangeAction( int min, int max )
        {
            this.min = min ;
            this.max = max ;
        }

        public Object operate( Object value )
        {
            int result = Integer.parseInt( getString( value ) ) ;
            if ((result >= min) && (result <= max))
                return new Integer( result ) ;
            else
                throw new IllegalArgumentException(
                    "Property value " + result + " is not in the range " +
                    min + " to " + max ) ;
        }

        public String toString() {
            return "integerRangeAction(" + min + "," + max + ")" ;
        }
    }

    public static Operation integerRangeAction( int min, int max )
    {
        return new IntegerRangeAction( min, max ) ;
    }

    private static class ListAction extends OperationBase {
        private String sep ;
        private Operation act ;

        ListAction( String sep, Operation act )
        {
            this.sep = sep ;
            this.act = act ;
        }

        // Note that this method carefully constructs an array of the type
        // of the first result, rather than just using Object[], which is
        // not convertible into the correct type.  Also note that no tokens
        // results in a null result.
        public Object operate( Object value )
        {
            StringTokenizer st = new StringTokenizer( getString( value ),
                sep ) ;
            int length = st.countTokens() ;
            Object result = null ;
            int ctr = 0 ;
            while (st.hasMoreTokens()) {
                String next = st.nextToken() ;
                Object val = act.operate( next ) ;
                if (result == null)
                    result = Array.newInstance( val.getClass(), length ) ;
                Array.set( result, ctr++, val ) ;
            }

            return result ;
        }

        public String toString() {
            return "listAction(separator=\"" + sep +
                "\",action=" + act + ")" ;
        }
    }

    public static Operation listAction( String sep, Operation act )
    {
        return new ListAction( sep, act ) ;
    }

    private static class SequenceAction extends OperationBase
    {
        private String sep ;
        private Operation[] actions ;

        SequenceAction( String sep, Operation[] actions )
        {
            this.sep = sep ;
            this.actions = actions ;
        }

        public Object operate( Object value )
        {
            StringTokenizer st = new StringTokenizer( getString( value ),
                sep ) ;

            int numTokens = st.countTokens() ;
            if (numTokens != actions.length)
                throw new Error(
                    "Number of tokens and number of actions do not match" ) ;

            int ctr = 0 ;
            Object[] result = new Object[ numTokens ] ;
            while (st.hasMoreTokens()) {
                Operation act = actions[ctr] ;
                String next = st.nextToken() ;
                result[ctr++] = act.operate( next ) ;
            }

            return result ;
        }

        public String toString() {
            return "sequenceAction(separator=\"" + sep +
                "\",actions=" +
                Arrays.toString(actions) + ")" ;
        }
    }

    public static Operation sequenceAction( String sep,
        Operation[] actions )
    {
        return new SequenceAction( sep, actions ) ;
    }

    private static class ComposeAction extends OperationBase
    {
        private Operation op1 ;
        private Operation op2 ;

        ComposeAction( Operation op1, Operation op2 )
        {
            this.op1 = op1 ;
            this.op2 = op2 ;
        }

        public Object operate( Object value )
        {
            return op2.operate( op1.operate( value ) ) ;
        }

        public String toString() {
            return "composition(" + op1 + "," + op2 + ")" ;
        }
    }

    public static Operation compose( Operation op1, Operation op2 )
    {
        return new ComposeAction( op1, op2 ) ;
    }

    private static class MapAction extends OperationBase
    {
        Operation op ;

        MapAction( Operation op )
        {
            this.op = op ;
        }

        public Object operate( Object value )
        {
            Object[] values = (Object[])value ;
            Object[] result = new Object[ values.length ] ;
            for (int ctr=0; ctr<values.length; ctr++ )
                result[ctr] = op.operate( values[ctr] ) ;
            return result ;
        }

        public String toString() {
            return "mapAction(" + op + ")" ;
        }
    }

    public static Operation mapAction( Operation op )
    {
        return new MapAction( op ) ;
    }

    private static class MapSequenceAction extends OperationBase
    {
        private Operation[] op ;

        public MapSequenceAction( Operation[] op )
        {
            this.op = op ;
        }

        // XXX Does this correctly handle array types?  It seems
        // that hetereogeneous arrays work this way, while
        // homogeneous arrays need to use Array.newInstance tricks.
        public Object operate( Object value )
        {
            Object[] values = (Object[])value ;
            Object[] result = new Object[ values.length ] ;
            for (int ctr=0; ctr<values.length; ctr++ )
                result[ctr] = op[ctr].operate( values[ctr] ) ;
            return result ;
        }

        public String toString() {
            return "mapSequenceAction(" +
                Arrays.toString(op) + ")" ;
        }
    }

    public static Operation mapSequenceAction( Operation[] op )
    {
        return new MapSequenceAction( op ) ;
    }

    private static class ConvertIntegerToShort extends OperationBase
    {
        public Object operate( Object value )
        {
            Integer val = (Integer)value ;
            return new Short( val.shortValue() ) ;
        }

        public String toString() {
            return "ConvertIntegerToShort" ;
        }
    }

    private static Operation convertIntegerToShortImpl = new ConvertIntegerToShort() ;

    public static Operation convertIntegerToShort()
    {
        return convertIntegerToShortImpl ;
    }
}
