/* * JBoss, Home of Professional Open Source. * Copyright 2015, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.cli.handlers; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Objects; import org.jboss.as.cli.CommandContext; import org.jboss.as.cli.CommandFormatException; import org.jboss.as.cli.CommandLineCompleter; import org.jboss.as.cli.Util; import org.jboss.as.cli.impl.ArgumentWithValue; import org.jboss.as.cli.parsing.ExpressionBaseState; import org.jboss.as.cli.parsing.WordCharacterHandler; /** * * @author Alexey Loubyansky */ public abstract class FilenameTabCompleter implements CommandLineCompleter { private final CommandContext ctx; public FilenameTabCompleter(CommandContext ctx) { if(ctx == null) { throw new IllegalArgumentException("ctx is null"); } this.ctx = ctx; } protected int getCandidates(String buffer, List<String> candidates) { //First clear the path String clearedPath = clearPath(buffer); String translated = translatePath(clearedPath); final File f = new File(translated); final File dir; if (translated.endsWith(File.separator)) { dir = f; } else { dir = f.getParentFile(); } final File[] entries = (dir == null) ? new File[0] : dir.listFiles(); return matchFiles(buffer, translated, entries, candidates); } public static FilenameTabCompleter newCompleter(CommandContext ctx) { return Util.isWindows() ? new WindowsFilenameTabCompleter(ctx) : new DefaultFilenameTabCompleter(ctx); } /** * Translate a path that has previously been unescaped and unquoted. * That is called at command execution when the calue is retrieved prior to be * used as ModelNode value. * @param path The unquoted, unescaped path. * @return A path with ~ and default dir expanded. */ public String translatePath(String path) { String translated; // special character: ~ maps to the user's home directory if (path.startsWith("~" + File.separator)) { translated = System.getProperty("user.home") + path.substring(1); } else if (path.startsWith("~")) { String userName = path.substring(1); translated = new File(new File(System.getProperty("user.home")).getParent(), userName).getAbsolutePath(); // Keep the path separator in translated or add one if no user home specified translated = userName.isEmpty() || path.endsWith(File.separator) ? translated + File.separator : translated; } else if (!new File(path).isAbsolute()) { translated = ctx.getCurrentDir().getAbsolutePath() + File.separator + path; } else { translated = path; } return translated; } /** * Unescape and unquote the path. Ready for translation. */ private static String clearPath(String path) { try { ExpressionBaseState state = new ExpressionBaseState("EXPR", true, false); if (Util.isWindows()) { // to not require escaping FS name separator state.setDefaultHandler(WordCharacterHandler.IGNORE_LB_ESCAPE_OFF); } else { state.setDefaultHandler(WordCharacterHandler.IGNORE_LB_ESCAPE_ON); } // Remove escaping characters path = ArgumentWithValue.resolveValue(path, state); } catch (CommandFormatException ex) { // XXX OK, continue translation } // Remove quote to retrieve candidates. if (path.startsWith("\"")) { path = path.substring(1); } // Could be an escaped " character. We don't take into account this corner case. // concider it the end of the quoted part. if (path.endsWith("\"")) { path = path.substring(0, path.length() - 1); } return path; } /** * Match the specified <i>buffer</i> to the array of <i>entries</i> and * enter the matches into the list of <i>candidates</i>. This method can be * overridden in a subclass that wants to do more sophisticated file name * completion. * * @param buffer * the untranslated buffer * @param translated * the buffer with common characters replaced * @param entries * the list of files to match * @param candidates * the list of candidates to populate * * @return the offset of the match */ protected int matchFiles(String buffer, String translated, File[] entries, List<String> candidates) { if (entries == null) { return -1; } boolean isDirectory = false; for (int i = 0; i < entries.length; i++) { if (entries[i].getAbsolutePath().startsWith(translated)) { isDirectory = entries[i].isDirectory(); candidates.add(entries[i].getName()); } } // Append File.separator for inlined directory. if (candidates.size() == 1) { String candidate = candidates.get(0); if (isDirectory) { candidate = candidate + File.separator; } candidates.set(0, candidate); } // inline only the subpath from last File.separator or 0. int index = buffer.lastIndexOf(File.separatorChar) + 1; return index; } public static String expand(String path) throws IOException { Objects.requireNonNull(path); // Can be found on any platform (Windows powershell or shell). if (path.startsWith("~")) { String home = System.getProperty("user.home"); if (home == null) { throw new IOException("Path " + path + " can't be expanded. " + "No user.home property"); } if (path.startsWith("~" + File.separator)) { path = new File(home, path.substring(2)).getAbsolutePath(); } else { int i = path.indexOf(File.separator); if (i < 0 || i >= path.length() - 1) { throw new IOException("Invalid file " + path); } String user = path.substring(1, i); File homeDir = new File(new File(home).getParent(), user); path = new File(homeDir, path.substring(i + 1)).getAbsolutePath(); } } return path; } void postProcess(String buffer, List<String> candidates) { if (candidates.size() == 1) { String candidate = candidates.get(0); if (!buffer.contains(File.separator)) { if (buffer.startsWith("\"~")) { candidate = "\"~" + candidate; } else if (buffer.startsWith("\"")) { candidate = "\"" + candidate; } else if (buffer.startsWith("~")) { candidate = "~" + candidate; } } candidates.set(0, candidate); } } @Override public int complete(CommandContext ctx, String buffer, int cursor, List<String> candidates) { int result = getCandidates(buffer, candidates); Collections.sort(candidates); completeCandidates(ctx, buffer, cursor, candidates); postProcess(buffer, candidates); return result; } abstract void completeCandidates(CommandContext ctx, String buffer, int cursor, List<String> candidates); }