001/*
002 * Copyright (c) 2007-2022 The Cascading Authors. All Rights Reserved.
003 *
004 * Project and contact information: https://cascading.wensel.net/
005 *
006 * This file is part of the Cascading project.
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *     http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020
021package cascading.nested.core.aggregate;
022
023import java.util.Objects;
024import java.util.function.Supplier;
025
026import cascading.nested.core.NestedAggregate;
027import cascading.operation.SerPredicate;
028import cascading.tuple.Fields;
029import cascading.tuple.Tuple;
030import cascading.tuple.coerce.Coercions;
031import cascading.tuple.type.CoercibleType;
032import cascading.tuple.type.CoercionFrom;
033
034/**
035 * Class BaseNumberNestedAggregate is the base class used to create number oriented aggregation operations.
036 * <p>
037 * Subclasses can optimize based on an expected primitive type while still honoring the {@code Long.class} and
038 * {@code Long.TYPE} semantics around {@code null} or {@code 0} empty values.
039 * <p>
040 * Note that subclasses can also be independent of the {@code Node} type (JSON etc). All Node specific operations
041 * are passed back to the {@link cascading.nested.core.NestedCoercibleType} instance.
042 *
043 * @see SumLongNestedAggregate
044 * @see SumDoubleNestedAggregate
045 * @see AverageDoubleNestedAggregate
046 */
047public abstract class BaseNumberNestedAggregate<Node, Type, Context extends BaseNumberNestedAggregate.BaseContext<Type, Node>> implements NestedAggregate<Node, Context>
048  {
049  public abstract static class BaseContext<Type, Node>
050    {
051    protected final CoercibleType<Node> coercibleType;
052    protected final CoercionFrom<Node, Type> to;
053    protected final Tuple results;
054    protected final SerPredicate<Type> discardValue;
055    protected final Supplier<Tuple> complete;
056    protected boolean allValuesDiscarded = true;
057
058    public BaseContext( BaseNumberNestedAggregate<Node, Type, BaseContext<Type, Node>> aggregateFunction, CoercibleType<Node> coercibleType )
059      {
060      this.coercibleType = coercibleType;
061      this.to = coercibleType.to( aggregateFunction.aggregateType );
062
063      this.results = createResultTuple( aggregateFunction );
064
065      if( aggregateFunction.discardNullValues() )
066        this.discardValue = Objects::isNull;
067      else
068        this.discardValue = v -> false;
069
070      if( aggregateFunction.returnNullForEmpty() )
071        this.complete = this::nullIfDiscard;
072      else
073        this.complete = this::valueIfDiscard;
074      }
075
076    protected Tuple createResultTuple( BaseNumberNestedAggregate<Node, Type, BaseContext<Type, Node>> aggregateFunction )
077      {
078      return Tuple.size( aggregateFunction.getFieldDeclaration().size() );
079      }
080
081    protected Tuple valueIfDiscard()
082      {
083      completeAggregateValue( results );
084
085      return results;
086      }
087
088    protected Tuple nullIfDiscard()
089      {
090      if( allValuesDiscarded )
091        results.setAllTo( null );
092      else
093        completeAggregateValue( results );
094
095      return results;
096      }
097
098    public void aggregate( Node node )
099      {
100      // if node is missing, always return null
101      if( node == null )
102        return;
103
104      Type value = coerceFrom( node );
105
106      addAggregateValue( value );
107      }
108
109    protected Type coerceFrom( Node node )
110      {
111      return to.coerce( node );
112      }
113
114    protected void addAggregateValue( Type value )
115      {
116      if( discardValue.test( value ) )
117        return;
118
119      allValuesDiscarded = false;
120
121      aggregateFilteredValue( value );
122      }
123
124    protected abstract void aggregateFilteredValue( Type value );
125
126    public Tuple complete()
127      {
128      return complete.get();
129      }
130
131    protected abstract void completeAggregateValue( Tuple results );
132
133    public void reset()
134      {
135      this.allValuesDiscarded = true;
136      }
137    }
138
139  protected Fields fieldDeclaration;
140  protected Class<Type> aggregateType;
141
142  protected BaseNumberNestedAggregate()
143    {
144    }
145
146  protected BaseNumberNestedAggregate( Fields fieldDeclaration, Class<Type> defaultType )
147    {
148    if( !fieldDeclaration.hasTypes() )
149      fieldDeclaration = fieldDeclaration.applyTypeToAll( defaultType );
150
151    this.fieldDeclaration = fieldDeclaration;
152    this.aggregateType = fieldDeclaration.getTypeClass( 0 ); // if coercible type, returns canonical type
153
154    if( Coercions.asNonPrimitive( this.aggregateType ) != Coercions.asNonPrimitive( defaultType ) )
155      throw new IllegalArgumentException( "fieldDeclaration must declare either " + defaultType.getSimpleName() + " object or primitive type" );
156    }
157
158  @Override
159  public Fields getFieldDeclaration()
160    {
161    return fieldDeclaration;
162    }
163
164  protected boolean returnNullForEmpty()
165    {
166    return !aggregateType.isPrimitive();
167    }
168
169  protected boolean discardNullValues()
170    {
171    return !aggregateType.isPrimitive();
172    }
173
174  @Override
175  public void aggregate( Context context, Node node )
176    {
177    context.aggregate( node );
178    }
179
180  @Override
181  public Tuple complete( Context context )
182    {
183    return context.complete();
184    }
185
186  @Override
187  public Context resetContext( Context context )
188    {
189    context.reset();
190
191    return context;
192    }
193  }