/* * 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.Bundle; 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; public class DataBits extends TreeNodeData<DataBits.Config> { /** * <p>This data attachment <span class="hydra-summary">counts data frequency by individual bits</span>. * Examines every bundle in the field set by key. The value is parsed as a long in the radix specified. * The mask is applied to the resulting long, and then for every bit left, we increment our total bit bucket * for that spot by one. * <p/> * <p>Job Configuration Example:</p> * <pre> * // hexadecimal bit counter * {const:"bitcounter", data.bits.bits {key:"UID", bits:128, radix:16}} * </pre> * * <p><b>Query Path Directives</b> * * <p>${attachment}={index} returns the bit bucket value for the bit index provided. * * <p>% operations are not supported</p> * * <p>Query Path Example:</p> * <pre> * /bitcounter$+bits=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 * </pre> * * @user-reference */ public static final class Config extends TreeDataParameters<DataBits> { /** * Number of bits to track starting from the least significant. * This field is required. */ @FieldConfig(codable = true) private int bits; /** * Radix to parse the value in. Use 10 for numbers, * 36 to include all letters. This field is required.. */ @FieldConfig(codable = true) private int radix; /** * Mask to apply when incrementing bit counters. For example, * a mask of "1" would only count the least significant bit. * The default value of 0 is treated as special case in which no mask is applied. */ @FieldConfig(codable = true) private long mask; /** * Name of bundle field to select for bit counting. * This field is required.. */ @FieldConfig(codable = true) private String key; @Override public DataBits newInstance() { DataBits db = new DataBits(); db.bits = new long[bits]; return db; } } @FieldConfig(codable = true) private long[] bits; private BundleField keyAccess; @Override public boolean updateChildData(DataTreeNodeUpdater state, DataTreeNode tn, Config conf) { long val = 0; Bundle p = state.getBundle(); if (keyAccess == null) { keyAccess = p.getFormat().getField(conf.key); } ValueObject vo = p.getValue(keyAccess); if (!ValueUtil.isEmpty(vo)) { try { val = Long.parseLong(vo.toString(), conf.radix); } catch (NumberFormatException ne) { /* track this at some point -- who is generating? */ } } if (val > 0) { long bit = 1; final int bl = bits.length; for (int i = 0; i < bl; i++) { if ((conf.mask == 0 || (conf.mask & bit) == bit) && (val & bit) == bit) { bits[i]++; } bit <<= 1; } } return true; } @Override public ValueObject getValue(String key) { return ValueFactory.create(bits[Integer.parseInt(key)]); } }