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