/* * 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.task.output.tree; import java.util.List; 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.filter.value.ValueFilter; import com.addthis.hydra.data.tree.DataTreeNode; import com.addthis.hydra.data.util.Tokenizer; /** * This {@link PathElement PathElement} <span class="hydra-summary">convert path values to file hierarchies</span>. * <p/> * <p>The 'key' parameter specifies the name of a bundle field. Each value * of this bundle field is processed as a file path. If the 'expand' parameter * is false then create a file root node and a node for each bundle field value. * If the 'expand' parameter is true then tokenize the path and create a nested node * hierarchy terminated with the file. * <p/> * <p>If the 'expand' parameter is true then properties associated with this path * element will be attributed to the last node created (the terminating file or directory). * <p/> * <p>Example:</p> * <pre> * {file {name:"tpath", key:"PATH", expand:true}} * </pre> * * @user-reference */ public final class PathFile extends PathKeyValue { /** * If true then tokenize the path and create a nested node * hierarchy terminated with the file. * Default is false. */ @FieldConfig(codable = true) private boolean expand; /** * If 'expand' is true then do something here. Default is null. */ @FieldConfig(codable = true) private PathKeyValue each; /** * If 'expand' is true then optionally apply a * filter to each token produced by the path tokenizer. * Default is null. */ @FieldConfig(codable = true) private ValueFilter tokenFilter; /** * If 'expand' is true then use the following string * as a path separator. This parameter is ignored if the * 'tokens' parameter is non-null. Default is "/". */ @FieldConfig(codable = true) private String separator = "/"; /** * If 'expand' is true then optionally * specify a path tokenizer. Otherwise * use the default tokenizer. * Default is null. */ @FieldConfig(codable = true) private Tokenizer tokens; /** * If 'expand' is true then optionally specify * a maximum depth of the generated subtrees. * Default is zero. */ @FieldConfig(codable = true) private int depth; /** * Default is "TEMP". */ @FieldConfig(codable = true) private String tempKey = "TEMP"; // treat file token same as dir token /** * If 'expand' is true then treat file * tokens and directory tokens in the same manner. * Default is false. */ @FieldConfig(codable = true) private boolean same; /** * If 'expand' is true then cause filter returns * of null to terminate descent. * Default is false. */ @FieldConfig(codable = true) private boolean termFilter; /** * Default is false. */ @FieldConfig(codable = true) private boolean inheritData; private BundleField tempAccess; /** */ @Override public void resolve(TreeMapper mapper) { super.resolve(mapper); tempAccess = mapper.bindField(tempKey); if (each != null) { each.setKeyAccessor(tempAccess); } if (tokens != null) { separator = tokens.getSeparator(); } else { if (separator == null || separator.length() > 1) { throw new RuntimeException("invalid separator : " + separator); } tokens = new Tokenizer().setSeparator(separator).setPacking(true); } } @Override public List<DataTreeNode> processNodeUpdates(TreeMapState state, ValueObject ps) { ValueObject pv = getPathValue(state); String path = ValueUtil.asNativeString(pv); if (path.length() == 0 || path.equals(separator)) { return TreeMapState.empty(); } int len = path.length(); int start = 0; int end = len - 1; while (separator.indexOf(path.charAt(start)) >= 0 && start < end) { start++; } while (separator.indexOf(path.charAt(end)) >= 0 && end > start) { end--; } if (end - start < len) { path = path.substring(start, end + 1); } int lastsep = path.lastIndexOf(separator); String file = null; String root = null; if (lastsep < 0) { file = path; } else if (lastsep > 0) { file = path.substring(lastsep + 1); root = path.substring(0, lastsep); } if (expand) { List<DataTreeNode> ret = null; boolean term = same; int pop = 0; if (root != null) { List<String> seg = tokens.tokenize(root); if (same) { seg.add(file); } for (String dirString : seg) { ValueObject dir = ValueFactory.create(dirString); if (tokenFilter != null) { dir = tokenFilter.filter(dir); if (dir == null) { if (termFilter) { term = true; break; } continue; } } if (ValueUtil.isEmpty(dir)) { continue; } PathValue value; if (each != null) { value = each; state.getBundle().setValue(tempAccess, dir); } else { value = new PathValue(ValueUtil.asNativeString(dir)); } if (inheritData) { value.data = data; } ret = value.processNode(state); if (ret != null) { state.push(ret.get(0)); pop++; if (depth > 0 && pop == depth) { term = true; break; } } else { term = true; break; } } } if (!term && file != null) { ret = super.processNodeUpdates(state, ValueFactory.create(file)); } while (pop-- > 0) { state.pop().release(); } return ret; } else { if (root == null) { return new PathValue(file).processNode(state); } List<DataTreeNode> proc = new PathValue(root).processNode(state); state.push(proc.get(0)); List<DataTreeNode> ret = super.processNodeUpdates(state, ValueFactory.create(file)); state.pop().release(); return ret; } } }