001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.cli.command.options;
018
019import java.io.File;
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023
024import joptsimple.OptionSet;
025
026import org.springframework.boot.cli.util.ResourceUtils;
027import org.springframework.util.Assert;
028import org.springframework.util.StringUtils;
029
030/**
031 * Extract source file options (anything following '--' in an {@link OptionSet}).
032 *
033 * @author Phillip Webb
034 * @author Dave Syer
035 * @author Greg Turnquist
036 * @author Andy Wilkinson
037 */
038public class SourceOptions {
039
040        private final List<String> sources;
041
042        private final List<?> args;
043
044        /**
045         * Create a new {@link SourceOptions} instance.
046         * @param options the source option set
047         */
048        public SourceOptions(OptionSet options) {
049                this(options, null);
050        }
051
052        /**
053         * Create a new {@link SourceOptions} instance.
054         * @param arguments the source arguments
055         */
056        public SourceOptions(List<?> arguments) {
057                this(arguments, null);
058        }
059
060        /**
061         * Create a new {@link SourceOptions} instance. If it is an error to pass options that
062         * specify non-existent sources, but the default paths are allowed not to exist (the
063         * paths are tested before use). If default paths are provided and the option set
064         * contains no source file arguments it is not an error even if none of the default
065         * paths exist).
066         * @param optionSet the source option set
067         * @param classLoader an optional classloader used to try and load files that are not
068         * found in the local filesystem
069         */
070        public SourceOptions(OptionSet optionSet, ClassLoader classLoader) {
071                this(optionSet.nonOptionArguments(), classLoader);
072        }
073
074        private SourceOptions(List<?> nonOptionArguments, ClassLoader classLoader) {
075                List<String> sources = new ArrayList<>();
076                int sourceArgCount = 0;
077                for (Object option : nonOptionArguments) {
078                        if (option instanceof String) {
079                                String filename = (String) option;
080                                if ("--".equals(filename)) {
081                                        break;
082                                }
083                                List<String> urls = new ArrayList<>();
084                                File fileCandidate = new File(filename);
085                                if (fileCandidate.isFile()) {
086                                        urls.add(fileCandidate.getAbsoluteFile().toURI().toString());
087                                }
088                                else if (!isAbsoluteWindowsFile(fileCandidate)) {
089                                        urls.addAll(ResourceUtils.getUrls(filename, classLoader));
090                                }
091                                for (String url : urls) {
092                                        if (isSource(url)) {
093                                                sources.add(url);
094                                        }
095                                }
096                                if (isSource(filename)) {
097                                        if (urls.isEmpty()) {
098                                                throw new IllegalArgumentException("Can't find " + filename);
099                                        }
100                                        else {
101                                                sourceArgCount++;
102                                        }
103                                }
104                        }
105                }
106                this.args = Collections.unmodifiableList(
107                                nonOptionArguments.subList(sourceArgCount, nonOptionArguments.size()));
108                Assert.isTrue(!sources.isEmpty(), "Please specify at least one file");
109                this.sources = Collections.unmodifiableList(sources);
110        }
111
112        private boolean isAbsoluteWindowsFile(File file) {
113                return isWindows() && file.isAbsolute();
114        }
115
116        private boolean isWindows() {
117                return File.separatorChar == '\\';
118        }
119
120        private boolean isSource(String name) {
121                return name.endsWith(".java") || name.endsWith(".groovy");
122        }
123
124        public List<?> getArgs() {
125                return this.args;
126        }
127
128        public String[] getArgsArray() {
129                return this.args.stream().map(this::asString).toArray(String[]::new);
130        }
131
132        private String asString(Object arg) {
133                return (arg != null) ? String.valueOf(arg) : null;
134        }
135
136        public List<String> getSources() {
137                return this.sources;
138        }
139
140        public String[] getSourcesArray() {
141                return StringUtils.toStringArray(this.sources);
142        }
143
144}