/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.hadoop.fs.shell;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedList;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.fs.shell.CommandFactory;
import org.apache.hadoop.fs.shell.CommandFormat;
import org.apache.hadoop.fs.shell.FsCommand;
import org.apache.hadoop.fs.shell.PathData;
import org.apache.hadoop.fs.shell.find.*;
/**
* Count the number of directories, files, bytes, quota, and remaining quota.
*/
@InterfaceAudience.Private
@InterfaceStability.Unstable
/**
* Implements a Hadoop find command.
*/
public class Find extends FsCommand {
public static final String NAME = "find";
public static final String USAGE = "<path> ... <expression> ...";
public static final String DESCRIPTION;
private static String[] HELP = {
"Finds all files that match the specified expression and applies selected actions to them."
};
private static final String OPTION_FOLLOW_LINK = "L";
private static final String OPTION_FOLLOW_ARG_LINK = "H";
/** List of expressions recognised by this command. */
@SuppressWarnings("rawtypes")
private static final Class[] EXPRESSIONS = {
Atime.class,
Blocksize.class,
ClassExpression.class,
Depth.class,
Empty.class,
Exec.class,
Group.class,
Mtime.class,
Name.class,
Newer.class,
Nogroup.class,
Nouser.class,
Perm.class,
Print.class,
Prune.class,
Replicas.class,
Size.class,
Type.class,
User.class,
And.class,
Or.class,
Not.class,
};
/** Options for use in this command */
private FindOptions options;
/** Root expression for this instance of the command. */
private Expression rootExpression;
/** Set of links followed to guard against infinite loops. */
private HashSet<PathData> linksFollowed = new HashSet<PathData>();
/** allows the command factory to be used if necessary */
private CommandFactory commandFactory = null;
/** sets the command factory for later use */
public void setCommandFactory(CommandFactory factory) { // FIXME FsShell should call this
this.commandFactory = factory;
}
/** retrieves the command factory */
protected CommandFactory getCommandFactory() {
return this.commandFactory;
}
/**
* Register the names for the count command
* @param factory the command factory that will instantiate this class
*/
public static void registerCommands(CommandFactory factory) {
factory.addClass(Find.class, "-find");
}
/** Register the expressions with the expression factory. */
static {
registerExpressions(ExpressionFactory.getExpressionFactory());
}
/** Build the description used by the help command. */
static {
DESCRIPTION = buildDescription(ExpressionFactory.getExpressionFactory());
}
/** Register the expressions with the expression factory. */
@SuppressWarnings("unchecked")
private static void registerExpressions(ExpressionFactory factory) {
for(Class<? extends Expression> exprClass : EXPRESSIONS) {
factory.registerExpression(exprClass);
}
}
/** Build the description used by the help command. */
@SuppressWarnings("unchecked")
private static String buildDescription(ExpressionFactory factory) {
ArrayList<Expression> operators = new ArrayList<Expression>();
ArrayList<Expression> primaries = new ArrayList<Expression>();
for(Class<? extends Expression> exprClass : EXPRESSIONS) {
Expression expr = factory.createExpression(exprClass, null);
if(expr.isOperator()) {
operators.add(expr);
}
else {
primaries.add(expr);
}
}
Collections.sort(operators, new Comparator<Expression>() {
@Override
public int compare(Expression arg0, Expression arg1) {
return arg0.getClass().getName().compareTo(arg1.getClass().getName());
}});
Collections.sort(primaries, new Comparator<Expression>() {
@Override
public int compare(Expression arg0, Expression arg1) {
return arg0.getClass().getName().compareTo(arg1.getClass().getName());
}});
StringBuilder sb = new StringBuilder();
for(String line : HELP) {
sb.append(line).append("\n");
}
sb.append("\n");
sb.append("The following primary expressions are recognised:\n");
for(Expression expr : primaries) {
for(String line : expr.getUsage()) {
sb.append(" ").append(line).append("\n");
}
for(String line : expr.getHelp()) {
sb.append(" ").append(line).append("\n");
}
sb.append("\n");
}
sb.append("The following operators are recognised:\n");
for(Expression expr : operators) {
for(String line : expr.getUsage()) {
sb.append(" ").append(line).append("\n");
}
for(String line : expr.getHelp()) {
sb.append(" ").append(line).append("\n");
}
sb.append("\n");
}
return sb.toString();
}
protected void processOptions(LinkedList<String> args) throws IOException {
CommandFormat cf = new CommandFormat(1, Integer.MAX_VALUE, OPTION_FOLLOW_LINK, OPTION_FOLLOW_ARG_LINK);
cf.parse(args);
if(cf.getOpt(OPTION_FOLLOW_LINK)) {
options.setFollowLink(true);
}
else if(cf.getOpt(OPTION_FOLLOW_ARG_LINK)) {
options.setFollowArgLink(true);
}
}
/**
* Set the root expression.
* @param expression
*/
@InterfaceAudience.Private
void setRootExpression(Expression expression) {
this.rootExpression = expression;
}
/**
* Return the root expression.
* @return the root expression
*/
@InterfaceAudience.Private
Expression getRootExpression() {
return this.rootExpression;
}
/** Returns the current find options, creating them if necessary. */
@InterfaceAudience.Private
FindOptions getOptions() {
if(options == null) {
options = new FindOptions();
options.setOut(out);
options.setErr(err);
options.setIn(System.in);
options.setCommandFactory(getCommandFactory());
}
return options;
}
/**
* Parse a list of arguments to extract the {@link PathData} elements.
* The input deque will be modified to remove the used elements.
* @param args arguments to be parsed
* @return list of {@link PathData} elements applicable to this command
* @throws IOException if list can not be parsed
*/
private LinkedList<PathData> parsePathData(Deque<String> args) throws IOException {
LinkedList<PathData> pathArgs = new LinkedList<PathData>();
while(!args.isEmpty()) {
String arg = args.pop();
if(isExpression(arg) || "(".equals(arg) || (arg == null) || arg.startsWith("-")) {
args.push(arg);
return pathArgs;
}
pathArgs.addAll(expandArgument(arg));
}
return pathArgs;
}
/**
* Parse a list of arguments to to extract the {@link Expression} elements.
* The input list will be modified to remove the used elements.
* @param args arguments to be parsed
* @return list of {@link Expression} elements applicable to this command
* @throws IOException if list can not be parsed
*/
private Expression parseExpression(Deque<String> args) throws IOException {
Deque<Expression> primaries = new LinkedList<Expression>();
Deque<Expression> operators = new LinkedList<Expression>();
Expression prevExpr = getExpression(And.class);
while(!args.isEmpty()) {
String arg = args.pop();
if("(".equals(arg)) {
Expression expr = parseExpression(args);
primaries.add(expr);
prevExpr = new BaseExpression(){}; // stub the previous expression to be a non-op
}
else if(")".equals(arg)) {
break;
}
else if(isExpression(arg)) {
Expression expr = getExpression(arg);
expr.addArguments(args);
if(expr.isOperator()) {
while(!operators.isEmpty()) {
if(operators.peek().getPrecedence() >= expr.getPrecedence()) {
Expression op = operators.pop();
op.addChildren(primaries);
primaries.push(op);
}
else {
break;
}
}
operators.push(expr);
}
else {
if(!prevExpr.isOperator()) {
Expression and = getExpression(And.class);
while(!operators.isEmpty()) {
if(operators.peek().getPrecedence() >= and.getPrecedence()) {
Expression op = operators.pop();
op.addChildren(primaries);
primaries.push(op);
}
else {
break;
}
}
operators.push(and);
}
primaries.push(expr);
}
prevExpr = expr;
}
else {
throw new IOException("Unexpected argument: " + arg);
}
}
while(!operators.isEmpty()) {
Expression operator = operators.pop();
operator.addChildren(primaries);
primaries.push(operator);
}
return primaries.isEmpty() ? getExpression(Print.class) : primaries.pop();
}
/** {@inheritDoc} */
@Override
protected LinkedList<PathData> expandArguments(LinkedList<String> argList) throws IOException {
Deque<String> args = new LinkedList<String>(argList);
LinkedList<PathData> pathArgs = parsePathData(args);
if(pathArgs.size() == 0) {
throw new IOException("No path specified");
}
Expression expression = parseExpression(args);
if(!expression.isAction()) {
Expression and = getExpression(And.class);
Deque<Expression> children = new LinkedList<Expression>();
children.add(getExpression(Print.class));
children.add(expression);
and.addChildren(children);
expression = and;
}
setRootExpression(expression);
return pathArgs;
}
@Override
protected void recursePath(PathData item) throws IOException {
if(item.stat.isSymlink() && getOptions().isFollowLink()) {
PathData linkedItem = new PathData(item.stat.getSymlink().toString(), getConf());
if(linksFollowed.contains(item)) {
getOptions().getErr().println("Infinite loop ignored: " + item.toString() + " -> " + linkedItem.toString());
return;
}
linksFollowed.add(item);
item = linkedItem;
}
if(item.stat.isDirectory()) {
super.recursePath(item);
}
}
/** {@inheritDoc} */
@Override
protected void processPaths(PathData parent, PathData ... items) throws IOException {
if(parent == null) {
// processing a command line argument so clear the links followed
linksFollowed.clear();
}
Expression expr = getRootExpression();
for (PathData item : items) {
try {
if(getOptions().isDepth()) {
recursePath(item);
expr.apply(item);
}
else if (expr.apply(item).isDescend()) {
recursePath(item);
}
} catch (IOException e) {
displayError(e);
}
}
}
/** {@inheritDoc} */
@Override
protected void processArguments(LinkedList<PathData> args) throws IOException {
Expression expr = getRootExpression();
expr.initialise(getOptions());
super.processArguments(args);
expr.finish();
}
/** {@inheritDoc} */
@Override
protected void processPathArgument(PathData item) throws IOException {
if(item.stat.isSymlink() && (getOptions().isFollowArgLink() || getOptions().isFollowLink())) {
item = new PathData(item.stat.getSymlink().toString(), getConf());
}
super.processPathArgument(item);
}
/** Gets a named expression from the factory. */
private Expression getExpression(String expressionName) {
return ExpressionFactory.getExpressionFactory().getExpression(expressionName, getConf());
}
/** Gets an instance of an expression from the factory. */
private Expression getExpression(Class<? extends Expression> expressionClass) {
return ExpressionFactory.getExpressionFactory().createExpression(expressionClass, getConf());
}
/** Asks the factory whether an expression is recognised. */
private boolean isExpression(String expressionName) {
return ExpressionFactory.getExpressionFactory().isExpression(expressionName);
}
}