# IterableQuerySource.java

    /*
     * Copyright (c) 2008, Rickard Öberg. All Rights Reserved.
     *
     * 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.qi4j.runtime.query;

    import java.util.Collections;
    import java.util.Comparator;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    import org.qi4j.api.composite.Composite;
    import org.qi4j.api.property.Property;
    import org.qi4j.api.query.grammar.OrderBy;
    import org.qi4j.api.util.Classes;
    import org.qi4j.functional.Iterables;
    import org.qi4j.functional.Specification;
    import org.qi4j.functional.Specifications;
    import org.qi4j.spi.query.QuerySource;

    /**
     * JAVADOC
     */
    public class IterableQuerySource
        implements QuerySource
    {
        private final Iterable iterable;

        /**
         * Constructor.
         *
         * @param iterable iterable
         */
        @SuppressWarnings( "raw" )
        IterableQuerySource( final Iterable iterable )
        {
            this.iterable = iterable;
        }

        @Override
        public <T> T find( Class<T> resultType,
                           Specification<Composite> whereClause,
                           Iterable<OrderBy> orderBySegments,
                           Integer firstResult,
                           Integer maxResults,
                           Map<String, Object> variables
        )
        {
            final Iterator<T> iterator = iterator( resultType, whereClause, orderBySegments, firstResult, maxResults, variables );
            if( iterator.hasNext() )
            {
                return iterator.next();
            }
            return null;
        }

        @Override
        public <T> long count( Class<T> resultType,
                               Specification<Composite> whereClause,
                               Iterable<OrderBy> orderBySegments,
                               Integer firstResult,
                               Integer maxResults,
                               Map<String, Object> variables
        )
        {
            return list( resultType, whereClause, orderBySegments, firstResult, maxResults, variables ).size();
        }

        @Override
        public <T> Iterator<T> iterator( Class<T> resultType,
                                         Specification<Composite> whereClause,
                                         Iterable<OrderBy> orderBySegments,
                                         Integer firstResult,
                                         Integer maxResults,
                                         Map<String, Object> variables
        )
        {
            return list( resultType, whereClause, orderBySegments, firstResult, maxResults, variables ).iterator();
        }

        @SuppressWarnings( {"raw", "unchecked"} )
        private <T> List<T> list( Class<T> resultType,
                                  Specification<Composite> whereClause,
                                  Iterable<OrderBy> orderBySegments,
                                  Integer firstResult,
                                  Integer maxResults,
                                  Map<String, Object> variables
        )
        {
            // Ensure it's a list first
            List<T> list = filter( resultType, whereClause );

            // Order list
            if( orderBySegments != null )
            {
                // Sort it
                Collections.sort( list, new OrderByComparator( orderBySegments ) );
            }

            // Cut results
            if( firstResult != null )
            {
                if( firstResult > list.size() )
                {
                    return Collections.emptyList();
                }

                int toIdx;
                if( maxResults != null )
                {
                    toIdx = Math.min( firstResult + maxResults, list.size() );
                }
                else
                {
                    toIdx = list.size();
                }

                list = list.subList( firstResult, toIdx );
            }
            else
            {
                int toIdx;
                if( maxResults != null )
                {
                    toIdx = Math.min( maxResults, list.size() );
                }
                else
                {
                    toIdx = list.size();
                }

                list = list.subList( 0, toIdx );
            }

            return list;
        }

        @SuppressWarnings( {"raw", "unchecked"} )
        private <T> List<T> filter( Class<T> resultType, Specification whereClause )
        {
            if( whereClause == null )
            {
                return Iterables.toList( Iterables.filter( Classes.instanceOf( resultType ), iterable ) );
            }
            else
            {
                return Iterables.toList( Iterables.filter( Specifications.and( Classes.instanceOf( resultType ), whereClause ), iterable ) );
            }
        }

        @Override
        public String toString()
        {
            return "IterableQuerySource{" + iterable + '}';
        }

        private static class OrderByComparator<T extends Composite>
            implements Comparator<T>
        {

            private final Iterable<OrderBy> orderBySegments;

            private OrderByComparator( Iterable<OrderBy> orderBySegments )
            {
                this.orderBySegments = orderBySegments;
            }

            @Override
            @SuppressWarnings( {"raw", "unchecked"} )
            public int compare( T o1, T o2 )
            {
                for( OrderBy orderBySegment : orderBySegments )
                {
                    try
                    {
                        final Property prop1 = orderBySegment.property().map( o1 );
                        final Property prop2 = orderBySegment.property().map( o2 );
                        if( prop1 == null || prop2 == null )
                        {
                            if( prop1 == null && prop2 == null )
                            {
                                return 0;
                            }
                            else if( prop1 != null )
                            {
                                return 1;
                            }
                            return -1;
                        }
                        final Object value1 = prop1.get();
                        final Object value2 = prop2.get();
                        if( value1 == null || value2 == null )
                        {
                            if( value1 == null && value2 == null )
                            {
                                return 0;
                            }
                            else if( value1 != null )
                            {
                                return 1;
                            }
                            return -1;
                        }
                        if( value1 instanceof Comparable )
                        {
                            int result = ( (Comparable) value1 ).compareTo( value2 );
                            if( result != 0 )
                            {
                                if( orderBySegment.order() == OrderBy.Order.ASCENDING )
                                {
                                    return result;
                                }
                                else
                                {
                                    return -result;
                                }
                            }
                        }
                    }
                    catch( Exception e )
                    {
                        return 0;
                    }
                }

                return 0;
            }
        }
    }