/* * 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 com.addthis.hydra.data.tree.prop; import com.addthis.bundle.core.BundleField; import com.addthis.bundle.util.ValueUtil; import com.addthis.bundle.value.ValueFactory; import com.addthis.bundle.value.ValueObject; import com.addthis.codec.annotations.FieldConfig; import com.addthis.hydra.data.tree.DataTreeNode; import com.addthis.hydra.data.tree.DataTreeNodeUpdater; import com.addthis.hydra.data.tree.TreeDataParameters; import com.addthis.hydra.data.tree.TreeNodeData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DataSum extends TreeNodeData<DataSum.Config> { private static final Logger log = LoggerFactory.getLogger(DataSum.class); /** * This data attachment <span class="hydra-summary">computes the sum, mean value, and * number of instances for a specified integer field</span>. * <p/> * <p>By default null values are treated as zero values and included in the calculation * of the sum, mean value, and number of instances. The null values can be ignored * completely by setting 'countMissing' field to false. Alternatively the null values * can be treated as non-zero values by using the 'values' field. * <p/> * <p>Job Configuration Example:</p> * <pre> * {field:"UID", data.shares.sum.key:"IS_SHARE"} * </pre> * * <p><b>Query Path Directives</b> * * <p>${attachment}=sum returns the sum. * <p>${attachment}=num returns the number of instances. * <p>${attachment}=avg returns the sum divided by the number of instances. * <p>${attachment} returns the sum. * * <p>% operations are not supported</p> * * <p>Query Path Example:</p> * <pre> * /+$+shares=sum * </pre> * * @user-reference */ public static final class Config extends TreeDataParameters<DataSum> { /** * The target field for calculating statistics. */ @FieldConfig(codable = true) private String key; /** * The radix of the values stored in the key field. If this value is 0 then * use the String length of the target field. Default is 10. */ @FieldConfig(codable = true) private int base = 10; /** * If countMissing is true then use this stand-in value when there is a null * stored in the target field. Default is 0. */ @FieldConfig(codable = true) private int value = 0; /** * If true then use the 'value' field when there is a null in the target field. Default is true. */ @FieldConfig(codable = true) private boolean countMissing = true; @Override public DataSum newInstance() { DataSum ds = new DataSum(); return ds; } } @FieldConfig(codable = true) private long sum; @FieldConfig(codable = true) private long num; private BundleField keyField; private void sum(ValueObject vo, int value, int base, boolean countMissing) { if (countMissing && vo == null) { sum += value; num++; return; } else if (vo == null) { return; } if (vo.getObjectType() == ValueObject.TYPE.ARRAY) { for (ValueObject v : vo.asArray()) { sum(v, value, base, countMissing); } return; } if (base > 0) { try { sum += ValueUtil.asNumberOrParseLong(vo, base).asLong().getLong(); } catch (NumberFormatException nfe) { log.warn("Error adding data summing. " + vo + " is not a number"); } } else { sum += vo.toString().length(); } num++; } @Override public boolean updateChildData(DataTreeNodeUpdater state, DataTreeNode tn, DataSum.Config conf) { if (conf.key == null) { num++; sum += conf.value; return true; } if (keyField == null) { keyField = state.getBundle().getFormat().getField(conf.key); } sum(state.getBundle().getValue(keyField), conf.value, conf.base, conf.countMissing); return true; } @Override public ValueObject getValue(String key) { if (key == null) { key = "sum"; } switch (key) { case "sum": return ValueFactory.create(sum); case "avg": return ValueFactory.create(num > 0 ? sum / num : 0); case "num": case "count": return ValueFactory.create(num); default: return ValueFactory.create(sum); } } }