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 }