001package org.unix4j.unix.ls;
002
003import org.unix4j.command.AbstractCommand;
004import org.unix4j.context.ExecutionContext;
005import org.unix4j.line.Line;
006import org.unix4j.processor.LineProcessor;
007import org.unix4j.unix.Ls;
008import org.unix4j.util.FileUtil;
009import org.unix4j.util.sort.ReverseOrderComparator;
010
011import java.io.File;
012import java.util.ArrayList;
013import java.util.Arrays;
014import java.util.Collections;
015import java.util.Comparator;
016import java.util.List;
017
018/**
019 * Implementation of the {@link Ls ls} command.
020 */
021class LsCommand extends AbstractCommand<LsArguments> {
022        public LsCommand(LsArguments arguments) {
023                super(Ls.NAME, arguments);
024        }
025        
026        private List<File> getArgumentFiles(ExecutionContext context, LsArguments args) {
027                if (args.isFilesSet()) {
028                        return new ArrayList<File>(Arrays.asList(args.getFiles()));
029                } else if (args.isPathsSet()) {
030                        return FileUtil.expandFiles(context.getCurrentDirectory(), args.getPaths());
031                }
032                //no input files or paths ==> use just the current directory
033                final ArrayList<File> list = new ArrayList<File>(1);
034                list.add(context.getCurrentDirectory());
035                return list;
036        }
037
038        /*package*/ static String getSizeString(LsArguments args, long bytes) {
039                if (args.isHumanReadable()) {
040                        final String units = "BKMG";
041                        int unit = 0;
042                        int fraction = 0;
043                        while (bytes > 1000 && (unit + 1) < units.length()) {
044                                bytes /= 100;
045                                fraction = (int) (bytes % 10);
046                                bytes /= 10;
047                                unit++;
048                        }
049                        if (bytes < 10) {
050                                return bytes + "." + fraction + units.charAt(unit);
051                        } else {
052                                return (bytes < 100 ? " " : "") + bytes + units.charAt(unit);
053                        }
054                }
055                return String.valueOf(bytes);
056        }
057        
058        @Override
059        public LineProcessor execute(final ExecutionContext context, final LineProcessor output) {
060                final LsArguments args = getArguments(context);
061                final List<File> files = getArgumentFiles(context, args);
062                return new LineProcessor() {
063                        @Override
064                        public boolean processLine(Line line) {
065                                return false;//we ignore all input
066                        }
067                        @Override
068                        public void finish() {
069                                listFiles(context.getCurrentDirectory(), null, files, output, args);
070                                output.finish();
071                        }
072                };
073        }
074
075        private boolean listFiles(File relativeTo, File parent, List<File> files, LineProcessor output, LsArguments args) {
076                final Comparator<File> comparator = getComparator(parent == null ? relativeTo : parent, args);
077                final boolean allFiles = args.isAllFiles();
078                final boolean longFormat = args.isLongFormat();
079                final LsFormatter formatter = longFormat ? new LsFormatterLong(files, args) : LsFormatterShort.INSTANCE;
080
081                //add special directories . and ..
082                if (parent != null && allFiles) {
083                        //add special directories . and ..
084                        files.add(parent);
085                        final File grandParent = parent.getAbsoluteFile().getParentFile();
086                        if (grandParent != null) {
087                                files.add(grandParent);
088                        }
089                }
090                
091                //sort files
092                Collections.sort(files, comparator);
093                
094                //print directory header
095                if (parent != null) {
096                        final LsFormatter dirHeaderFmt = LsFormatterDirectoryHeader.FACTORY.create(relativeTo, parent, files, args);
097                        if (!dirHeaderFmt.writeFormatted(relativeTo, parent, args, output)) {
098                                return false;
099                        }
100                }
101                //print directory files and recurse if necessary
102                for (File file : files) {
103                        if (allFiles || !file.isHidden()) {
104                                if (file.isDirectory() && parent == null && !args.isRecurseSubdirs()) {
105                                        if (!listFiles(relativeTo, file, FileUtil.toList(file.listFiles()), output, args)) {
106                                                return false;
107                                        }
108                                } else {
109                                        if (!file.isDirectory() || parent != null) {
110                                                if (!formatter.writeFormatted(parent == null ? relativeTo : parent, file, args, output)) {
111                                                        return false;
112                                                }
113                                        }
114                                }
115                        }
116                }
117                
118                //recurse subdirs now
119                if (args.isRecurseSubdirs()) {
120                        for (File file : files) {
121                                if (allFiles || !file.isHidden()) {
122                                        if (file.isDirectory() && (parent == null || !file.equals(parent) && !file.equals(parent.getParentFile()))) {
123                                                if (!listFiles(relativeTo, file, FileUtil.toList(file.listFiles()), output, args)) {
124                                                        return false;
125                                                }
126                                        }
127                                }
128                        }
129                }
130                
131                return true;//we want more output
132        }
133
134        private Comparator<File> getComparator(File relativeTo, LsArguments args) {
135                final Comparator<File> comparator = args.isTimeSorted() ? FileComparators.timeAndRelativeFileName(relativeTo) : FileComparators.typeAndRelativeFileName(relativeTo);
136                return args.isReverseOrder() ? ReverseOrderComparator.reverse(comparator) : comparator;
137        }
138
139}