1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 
20 package org.apache.thrift.maven;
21 
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.ImmutableSet;
24 import org.codehaus.plexus.util.cli.CommandLineException;
25 import org.codehaus.plexus.util.cli.CommandLineUtils;
26 import org.codehaus.plexus.util.cli.Commandline;
27 import java.io.File;
28 import java.util.List;
29 import java.util.Set;
30 import static com.google.common.base.Preconditions.checkArgument;
31 import static com.google.common.base.Preconditions.checkNotNull;
32 import static com.google.common.base.Preconditions.checkState;
33 import static com.google.common.collect.Lists.newLinkedList;
34 import static com.google.common.collect.Sets.newHashSet;
35 
36 /**
37  * This class represents an invokable configuration of the {@code thrift}
38  * compiler. The actual executable is invoked using the plexus
39  * {@link Commandline}.
40  * <p/>
41  * This class currently only supports generating java source files.
42  */
43 final class Thrift {
44 
45     final static String GENERATED_JAVA = "gen-java";
46 
47     private final String executable;
48     private final String generator;
49     private final ImmutableSet<File> thriftPathElements;
50     private final ImmutableSet<File> thriftFiles;
51     private final File javaOutputDirectory;
52     private final CommandLineUtils.StringStreamConsumer output;
53     private final CommandLineUtils.StringStreamConsumer error;
54 
55     /**
56      * Constructs a new instance. This should only be used by the {@link Builder}.
57      *
58      * @param executable          The path to the {@code thrift} executable.
59      * @param generator           The value for the {@code --gen} option.
60      * @param thriftPath          The directories in which to search for imports.
61      * @param thriftFiles         The thrift source files to compile.
62      * @param javaOutputDirectory The directory into which the java source files
63      *                            will be generated.
64      */
Thrift(String executable, String generator, ImmutableSet<File> thriftPath, ImmutableSet<File> thriftFiles, File javaOutputDirectory)65     private Thrift(String executable, String generator, ImmutableSet<File> thriftPath,
66                    ImmutableSet<File> thriftFiles, File javaOutputDirectory) {
67         this.executable = checkNotNull(executable, "executable");
68         this.generator = checkNotNull(generator, "generator");
69         this.thriftPathElements = checkNotNull(thriftPath, "thriftPath");
70         this.thriftFiles = checkNotNull(thriftFiles, "thriftFiles");
71         this.javaOutputDirectory = checkNotNull(javaOutputDirectory, "javaOutputDirectory");
72         this.error = new CommandLineUtils.StringStreamConsumer();
73         this.output = new CommandLineUtils.StringStreamConsumer();
74     }
75 
76     /**
77      * Invokes the {@code thrift} compiler using the configuration specified at
78      * construction.
79      *
80      * @return The exit status of {@code thrift}.
81      * @throws CommandLineException
82      */
compile()83     public int compile() throws CommandLineException {
84 
85         for (File thriftFile : thriftFiles) {
86             Commandline cl = new Commandline();
87             cl.setExecutable(executable);
88             cl.addArguments(buildThriftCommand(thriftFile).toArray(new String[]{}));
89             final int result = CommandLineUtils.executeCommandLine(cl, null, output, error);
90 
91             if (result != 0) {
92                 return result;
93             }
94         }
95 
96         // result will always be 0 here.
97         return 0;
98     }
99 
100     /**
101      * Creates the command line arguments.
102      * <p/>
103      * This method has been made visible for testing only.
104      *
105      * @param thriftFile
106      * @return A list consisting of the executable followed by any arguments.
107      */
buildThriftCommand(final File thriftFile)108     ImmutableList<String> buildThriftCommand(final File thriftFile) {
109         final List<String> command = newLinkedList();
110         // add the executable
111         for (File thriftPathElement : thriftPathElements) {
112             command.add("-I");
113             command.add(thriftPathElement.toString());
114         }
115         command.add("-out");
116         command.add(javaOutputDirectory.toString());
117         command.add("--gen");
118         command.add(generator);
119         command.add(thriftFile.toString());
120         return ImmutableList.copyOf(command);
121     }
122 
123     /**
124      * @return the output
125      */
getOutput()126     public String getOutput() {
127         return output.getOutput();
128     }
129 
130     /**
131      * @return the error
132      */
getError()133     public String getError() {
134         return error.getOutput();
135     }
136 
137     /**
138      * This class builds {@link Thrift} instances.
139      */
140     static final class Builder {
141         private final String executable;
142         private final File javaOutputDirectory;
143         private Set<File> thriftPathElements;
144         private Set<File> thriftFiles;
145         private String generator;
146 
147         /**
148          * Constructs a new builder. The two parameters are present as they are
149          * required for all {@link Thrift} instances.
150          *
151          * @param executable          The path to the {@code thrift} executable.
152          * @param javaOutputDirectory The directory into which the java source files
153          *                            will be generated.
154          * @throws NullPointerException     If either of the arguments are {@code null}.
155          * @throws IllegalArgumentException If the {@code javaOutputDirectory} is
156          *                                  not a directory.
157          */
Builder(String executable, File javaOutputDirectory)158         public Builder(String executable, File javaOutputDirectory) {
159             this.executable = checkNotNull(executable, "executable");
160             this.javaOutputDirectory = checkNotNull(javaOutputDirectory);
161             checkArgument(javaOutputDirectory.isDirectory());
162             this.thriftFiles = newHashSet();
163             this.thriftPathElements = newHashSet();
164         }
165 
166         /**
167          * Adds a thrift file to be compiled. Thrift files must be on the thriftpath
168          * and this method will fail if a thrift file is added without first adding a
169          * parent directory to the thriftpath.
170          *
171          * @param thriftFile
172          * @return The builder.
173          * @throws IllegalStateException If a thrift file is added without first
174          *                               adding a parent directory to the thriftpath.
175          * @throws NullPointerException  If {@code thriftFile} is {@code null}.
176          */
addThriftFile(File thriftFile)177         public Builder addThriftFile(File thriftFile) {
178             checkNotNull(thriftFile);
179             checkArgument(thriftFile.isFile());
180             checkArgument(thriftFile.getName().endsWith(".thrift"));
181             checkThriftFileIsInThriftPath(thriftFile);
182             thriftFiles.add(thriftFile);
183             return this;
184         }
185 
186         /**
187          * Adds the option string for the Thrift executable's {@code --gen} parameter.
188          *
189          * @param generator
190          * @return The builder
191          * @throws NullPointerException If {@code generator} is {@code null}.
192          */
setGenerator(String generator)193         public Builder setGenerator(String generator) {
194             checkNotNull(generator);
195             this.generator = generator;
196             return this;
197         }
198 
checkThriftFileIsInThriftPath(File thriftFile)199         private void checkThriftFileIsInThriftPath(File thriftFile) {
200             assert thriftFile.isFile();
201             checkState(checkThriftFileIsInThriftPathHelper(thriftFile.getParentFile()));
202         }
203 
checkThriftFileIsInThriftPathHelper(File directory)204         private boolean checkThriftFileIsInThriftPathHelper(File directory) {
205             assert directory.isDirectory();
206             if (thriftPathElements.contains(directory)) {
207                 return true;
208             } else {
209                 final File parentDirectory = directory.getParentFile();
210                 return (parentDirectory == null) ? false
211                         : checkThriftFileIsInThriftPathHelper(parentDirectory);
212             }
213         }
214 
215         /**
216          * @see #addThriftFile(File)
217          */
addThriftFiles(Iterable<File> thriftFiles)218         public Builder addThriftFiles(Iterable<File> thriftFiles) {
219             for (File thriftFile : thriftFiles) {
220                 addThriftFile(thriftFile);
221             }
222             return this;
223         }
224 
225         /**
226          * Adds the {@code thriftPathElement} to the thriftPath.
227          *
228          * @param thriftPathElement A directory to be searched for imported thrift message
229          *                          buffer definitions.
230          * @return The builder.
231          * @throws NullPointerException     If {@code thriftPathElement} is {@code null}.
232          * @throws IllegalArgumentException If {@code thriftPathElement} is not a
233          *                                  directory.
234          */
addThriftPathElement(File thriftPathElement)235         public Builder addThriftPathElement(File thriftPathElement) {
236             checkNotNull(thriftPathElement);
237             checkArgument(thriftPathElement.isDirectory());
238             thriftPathElements.add(thriftPathElement);
239             return this;
240         }
241 
242         /**
243          * @see #addThriftPathElement(File)
244          */
addThriftPathElements(Iterable<File> thriftPathElements)245         public Builder addThriftPathElements(Iterable<File> thriftPathElements) {
246             for (File thriftPathElement : thriftPathElements) {
247                 addThriftPathElement(thriftPathElement);
248             }
249             return this;
250         }
251 
252         /**
253          * @return A configured {@link Thrift} instance.
254          * @throws IllegalStateException If no thrift files have been added.
255          */
build()256         public Thrift build() {
257             checkState(!thriftFiles.isEmpty());
258             return new Thrift(executable, generator, ImmutableSet.copyOf(thriftPathElements),
259                     ImmutableSet.copyOf(thriftFiles), javaOutputDirectory);
260         }
261     }
262 }
263