All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.inet.lib.less.Operation Maven / Gradle / Ivy

/**
 * MIT License (MIT)
 *
 * Copyright (c) 2014 - 2015 Volker Berlin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * UT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @author Volker Berlin
 * @license: The MIT license 
 */
package com.inet.lib.less;

import static com.inet.lib.less.ColorUtils.*;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * A arithmetic operation.
 */
class Operation extends Expression {

    private final ArrayList operands = new ArrayList<>();

    private final char                  operator;
    
    private int                         type;

    private static final HashMap> unitConversions = new HashMap<>();
    static {
        HashMap length = new HashMap<>();
        putConvertion( length, "m", 1 );
        putConvertion( length, "cm", 0.01 );
        putConvertion( length, "mm", 0.001 );
        putConvertion( length, "in", 0.0254 );
        putConvertion( length, "px", 0.0254 / 96 );
        putConvertion( length, "pt", 0.0254 / 72 );
        putConvertion( length, "pc", 0.0254 / 72 * 12 );

        HashMap duration = new HashMap<>();
        putConvertion( duration, "s", 1 );
        putConvertion( duration, "ms", 0.001 );

        HashMap angle = new HashMap<>();
        putConvertion( angle, "rad", 1 / (2 * Math.PI) );
        putConvertion( angle, "deg", 1 / 360.0 );
        putConvertion( angle, "grad", 1 / 400.0 );
        putConvertion( angle, "turn", 1 );
    }

    private static void putConvertion( HashMap group, String unit, double factor ) {
        unitConversions.put(unit, group );
        group.put( unit, factor );
    }

    Operation( LessObject reader, Expression left, char operator ) {
        this( reader, operator );
        if( left != null ) {
            this.operands.add( left );
        }
    }

    /**
     * Empty parameter list
     */
    Operation( LessObject reader, char operator ) {
        super( reader, String.valueOf( operator ) );
        this.operator = operator;
    }

    /**
     * Empty parameter list
     */
    Operation( LessObject reader ) {
        super( reader, "," );
        this.operator = ',';
    }

    /**
     * Get the operator of this operation
     * 
     * @return the operator
     */
    char getOperator() {
        return operator;
    }

    /**
     * Get the Operands of this operation
     * 
     * @return the operands
     */
    ArrayList getOperands() {
        return operands;
    }

    /**
     * Add the next operand. It must use the same operator like this operation.
     * @param right the next operand
     */
    void addOperand( Expression right ) {
        this.operands.add( right );
    }

    void addLeftOperand( Expression left ) {
        this.operands.add( 0, left );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getDataType( CssFormatter formatter ) {
        if( type == UNKNOWN ) {
            switch( operator ) {
                case ' ':
                case ',':
                    type = LIST;
                    break;
                case '&':
                case '|':
                case '>':
                case '<':
                case '=':
                case '≥':
                case '≤':
                    type = BOOLEAN;
                    break;
                default:
                    type = maxOperadType( formatter );
            }
        }
        return type;
    }

    private int maxOperadType( CssFormatter formatter ) {
        int dataType = operands.get( 0 ).getDataType( formatter );
        for( int i = 1; i < operands.size(); i++ ) {
            dataType = Math.max( dataType, operands.get( i ).getDataType( formatter ) );
        }
        return dataType;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void appendTo( CssFormatter formatter ) {
        switch( getDataType( formatter ) ) {
            case BOOLEAN:
                switch( operator ) {
                    case '=':
                        for( int i = 0; i < operands.size(); i++ ) {
                            if( i > 0 ) {
                                formatter.append( operator );
                            }
                            operands.get( i ).appendTo( formatter );
                        }
                        return;
                }
                break;
            case STRING:
            case LIST:
                switch( operator ) {
                    case ' ':
                        for( int i = 0; i < operands.size(); i++ ) {
                            if( i > 0 ) {
                                formatter.append( operator );
                            }
                            operands.get( i ).appendTo( formatter );
                        }
                        return;
                    case ',':
                        for( int i = 0; i < operands.size(); i++ ) {
                            if( i > 0 ) {
                                formatter.append( operator ).space();
                            }
                            operands.get( i ).appendTo( formatter );
                        }
                        return;
                    case '+':
                        for( int i = 0; i < operands.size(); i++ ) {
                            operands.get( i ).appendTo( formatter );
                        }
                        return;
                    case '/':
                        for( int i = 0; i < operands.size(); i++ ) {
                            if( i > 0 ) {
                                formatter.append( ' ' ).append( operator ).append( ' ' );
                            }
                            operands.get( i ).appendTo( formatter );
                        }
                        return;
                    case '~':
                        String str = operands.get( 0 ).stringValue( formatter );
                        StringBuilder builder = new StringBuilder();
                        char quote = 0;
                        for( int i = 0; i < str.length(); i++ ) {
                            char ch = str.charAt( i );
                            switch( ch ) {
                                case '"':
                                case '\'':
                                    if( quote == 0 ) {
                                        quote = ch;
                                    } else if( quote == ch ) {
                                        quote = 0;
                                    } else {
                                        builder.append( ch );
                                    }
                                    break;
                                case '\\':
                                    builder.append( str.charAt( ++i ) );
                                    break;
                                default:
                                    builder.append( ch );
                            }
                        }
                        new ValueExpression( this, builder.toString() ).appendTo( formatter );
                        return;
                    default:
                        builder = new StringBuilder( "Not supported Operation '" ).append( operator ).append( "' for '" );
                        for( int i = 0; i < operands.size(); i++ ) {
                            builder.append( operands.get( i ) );
                            if( i < operands.size() - 1 ) {
                                builder.append( "' and '" );
                            } else {
                                builder.append( "'" );
                            }
                        }
                        throw createException( builder.toString() );
                }
        }
        super.appendTo( formatter );
    }

    /**
     * Calculate the factor between 2 units.
     * @param leftUnit left unit
     * @param rightUnit right unit
     * @param fail true, should be fail if units incompatible; false, return 1 is incompatible
     * @return the factor between the 2 units.
     */
    static double unitFactor( String leftUnit, String rightUnit, boolean fail ) {
        if( leftUnit.length() == 0 || rightUnit.length() == 0 || leftUnit.equals( rightUnit ) ) {
            return 1;
        }
        HashMap leftGroup = unitConversions.get( leftUnit );
        if( leftGroup != null ) {
            HashMap rightGroup = unitConversions.get( rightUnit );
            if( leftGroup == rightGroup ) {
                return leftGroup.get( leftUnit ) / leftGroup.get( rightUnit );
            }
        }
        if( fail ) {
            throw new LessException( "Incompatible types" );
        }
        return 1;
    }

    @Override
    public double doubleValue( CssFormatter formatter ) {
        Expression leftOp = operands.get( 0 );
        int type = leftOp.getDataType( formatter );
        double value = leftOp.doubleValue( formatter );
        String unit = leftOp.unit( formatter );
        for( int i = 1; i < operands.size(); i++ ) {
            Expression rightOp = operands.get( i );
            int rightType = rightOp.getDataType( formatter );
            double right = rightOp.doubleValue( formatter );
            switch( operator ) {
                case '+':
                case '-':
                    right /= unitFactor( unit, rightOp.unit( formatter ), false );
            }
            if( type == COLOR ) {
                if( rightType == COLOR ) {
                    value = doubleValue2Colors( value, right );
                } else {
                    value = doubleValueLeftColor( value, right );
                }
            } else {
                if( rightType == COLOR ) {
                    value = doubleValueRightColor( value, right );
                } else {
                    value = doubleValue( value, right );
                }
            }
        }
        return value;
    }

    private double doubleValue( double left, double right ) {
        switch( operator ) {
            case '+':
                return left + right;
            case '-':
                return left - right;
            case '*':
                return left * right;
            case '/':
                return left / right;
            default:
                throw createException( "Not supported Oprator '" + operator + "' for Expression '" + toString() + '\'' );
        }
    }

    private double doubleValueLeftColor( double color, double right ) {
        return rgba( doubleValue( red( color ), right ), //
                        doubleValue( green( color ), right ), //
                        doubleValue( blue( color ), right ), 1 );
    }

    private double doubleValueRightColor( double left, double color ) {
        return rgba( doubleValue( left, red( color ) ), //
                        doubleValue( left, green( color ) ), //
                        doubleValue( left, blue( color ) ), 1 );
    }

    private double doubleValue2Colors( double left, double right ) {
        return rgba( doubleValue( red( left ), red( right ) ), //
                        doubleValue( green( left ), green( right ) ), //
                        doubleValue( blue( left ), blue( right ) ), 1 );
    }

    @Override
    public boolean booleanValue(CssFormatter formatter) {
        Expression leftOp = operands.get( 0 );
        switch( operator ) {
            case '&':
            case '|':
                boolean value = leftOp.booleanValue( formatter );
                for( int i = 1; i < operands.size(); i++ ) {
                    boolean right = operands.get( i ).booleanValue( formatter );
                    switch( operator ) {
                        case '&':
                            value &= right;
                            break;
                        case '|':
                            value |= right;
                            break;
                    }
                }
                return value;
            case '!':
                return !leftOp.booleanValue( formatter );
            case '>':
            case '<':
            case '=':
            case '≥':
            case '≤':
                int type = maxOperadType( formatter );
                switch( type ) {
                    case STRING: {
                        // need to differ between keyword without quotes and strings with quotes. The type of quote is ignored
                        String left = normlizeQuotes( leftOp.stringValue( formatter ) );
                        String right = normlizeQuotes( operands.get( 1 ).stringValue( formatter ) );
                        switch( operator ) {
                            case '>':
                                return left.compareTo( right ) > 0;
                            case '<':
                                return left.compareTo( right ) < 0;
                            case '=':
                                return left.compareTo( right ) == 0;
                            case '≥':
                                return left.compareTo( right ) >= 0;
                            case '≤':
                                return left.compareTo( right ) <= 0;
                        }
                        break;
                    }
                    case COLOR:
                    case RGBA:{
                        long left = Double.doubleToRawLongBits( leftOp.doubleValue( formatter ) );
                        long right = Double.doubleToRawLongBits( operands.get( 1 ).doubleValue( formatter ) );
                        switch( operator ) {
                            case '>':
                                return left > right;
                            case '<':
                                return left < right;
                            case '=':
                                return left == right;
                            case '≥':
                                return left >= right;
                            case '≤':
                                return left <= right;
                        }
                    }
                        //$FALL-THROUGH$
                    default: {
                        double left = leftOp.doubleValue( formatter );
                        Expression rightOp = operands.get( 1 );
                        double right = rightOp.doubleValue( formatter );
                        try {
                            right /= unitFactor( leftOp.unit( formatter ), rightOp.unit( formatter ), true );
                            switch( operator ) {
                                case '>':
                                    return left > right;
                                case '<':
                                    return left < right;
                                case '=':
                                    return left == right;
                                case '≥':
                                    return left >= right;
                                case '≤':
                                    return left <= right;
                            }
                        } catch (LessException ex ) {
                            return false;
                        }
                    }
                }
                //$FALL-THROUGH$
            default:
        }
        throw createException( "Not supported Oprator '" + operator + "' for Expression '" + toString() + '\'' );
    }

    /**
     * Convert single quotes to double quotes.
     * @param str input
     * @return normalize string
     */
    private String normlizeQuotes( String str ) {
        if( str.length() > 1 && str.charAt( 0 ) == '\'' && str.charAt( str.length() - 1 ) == '\'' ) {
            return '\"' + str.substring( 1, str.length() - 1 ) + '\"';
        }
        return str;
    }

    /**
     * Calculate the unit if there are different units. It use the numerator and denominator count.
     * @param formatter the CCS target
     * @param list previous result
     * @return null or a with with minimum one entry
     */
    private ArrayList unit( CssFormatter formatter, ArrayList list ){
        for( int i = 0; i < operands.size(); i++ ) {
            Expression exp = operands.get( i );
            if( exp.getClass() == Operation.class ) {
                Operation op = (Operation)exp;
                switch( op.operator ) {
                    case '*':
                    case '/':
                        list = op.unit( formatter, list );
                        break;
                    default:
                }
            } else {
                String unitStr = exp.unit( formatter );
                if( !unitStr.isEmpty() ) {
                    if( list == null ) {
                        list = new ArrayList<>();
                    }
                    Unit unit = null;
                    for( int j = 0; j < list.size(); j++ ) {
                        Unit unitLoop = list.get( j );
                        if( unitLoop.unit.equals( unitStr ) ) {
                            unit = unitLoop;
                            break;
                        }
                    }
                    if( unit == null ) {
                        unit = new Unit( unitStr );
                        list.add( unit );
                    }
                    if( i == 0 || operator == '*' ) {
                        unit.numerator++;
                    } else {
                        unit.denominator++;
                    }
                }
            }
        }
        return list;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String unit( CssFormatter formatter ) {
        switch( operator ) {
            case '*':
            case '/':
                ArrayList list = unit( formatter, null );
                if( list != null ) {
                    Unit unit = list.get( 0 );
                    int useCount = unit.useCount();
                    for( int i=1; i useCount ) {
                            unit = list.get( i );
                        }
                    }
                    return unit.unit;
                }
                return "";
        }
        for( int i = 0; i < operands.size(); i++ ) {
            String unit = operands.get( i ).unit( formatter );
            if( !unit.isEmpty() ) {
                return unit;
            }
        }
        return "";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Operation listValue( CssFormatter formatter ) {
        switch( operator ) {
            case ' ':
            case ',':
                return this;
        }
        return super.listValue( formatter );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        for( int i = 0; i < operands.size(); i++ ) {
            if( i > 0 ) {
                builder.append( ' ' ).append( operator ).append( ' ' );
            }
            builder.append( operands.get( i ) );
        }
        return builder.toString();
    }

    static int level( char operator ) {
        switch( operator ) {
            case ',':
                return 1;
            case ' ':
                return 2;
            case ':':
                return 3;
            case '=':
            case '>':
            case '<':
                return 4;
            case '+':
            case '-':
                return 5;
            case '*':
            case '/':
                return 6;
            case '~':
                return 7;
       }
        return 0;
    }

    private static class Unit {
        private int          numerator;

        private int          denominator;

        private final String unit;

        Unit( String unit ) {
            this.unit = unit;
        }

        int useCount() {
            return numerator - denominator;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy