View Javadoc
1   package auxtestlib;
2   
3   import java.io.File;
4   import java.util.ArrayList;
5   import java.util.List;
6   
7   import auxtestlib.CommandRunner.CommandOutput;
8   
9   /**
10   * Class that runs javac and compiles source code. Javac is run as an external
11   * process.
12   */
13  public final class JavacRunner {
14  	/**
15  	 * Utility class: no constructor.
16  	 */
17  	private JavacRunner() {
18  		/*
19  		 * Nothing to do.
20  		 */
21  	}
22  
23  	/**
24  	 * Runs javac.
25  	 * 
26  	 * @param dir the directory where javac should run
27  	 * @param source file with source code
28  	 * 
29  	 * @throws Exception failed to compile
30  	 */
31  	public static void javac(File dir, String source) throws Exception {
32  		javac(dir, source, null);
33  	}
34  
35  	/**
36  	 * Runs javac.
37  	 * 
38  	 * @param dir the directory where javac should run
39  	 * @param source file with source code
40  	 * @param cp additional classpath (<code>null</code> not to add any
41  	 * additional classpath). If this parameter is defined, any position in the
42  	 * array which is <code>null</code> is replaced by the application's current
43  	 * classpath. Inside this array both strings or files can be placed. Files
44  	 * will be added to the classpath as absolute paths.
45  	 * 
46  	 * @throws Exception compilation failed
47  	 */
48  	public static void javac(File dir, String source, Object cp[])
49  			throws Exception {
50  		javac(dir, new String[] { source }, cp);
51  	}
52  
53  	/**
54  	 * Creates the additional classpath to run javac.
55  	 * 
56  	 * @param classpath additional classpath (<code>null</code> not to add any
57  	 * additional classpath). If this parameter is defined, any position in the
58  	 * array which is <code>null</code> is replaced by the application's current
59  	 * classpath. Inside this array both strings or files can be placed. Files
60  	 * will be added to the classpath as absolute paths.
61  	 * 
62  	 * @return the classpath
63  	 * 
64  	 * @throws Exception failed to add the classpath
65  	 */
66  	private static String buildAdditionalClasspath(Object[] classpath)
67  			throws Exception {
68  		Object[] cp = classpath;
69  
70  		if (cp == null || cp.length == 0) {
71  			cp = new String[] { null };
72  		}
73  
74  		StringBuffer cpb = new StringBuffer();
75  		for (int i = 0; i < cp.length; i++) {
76  			if (i != 0) {
77  				cpb.append(File.pathSeparator);
78  			}
79  
80  			if (cp[i] == null) {
81  				cpb.append(System.getProperty("java.class.path"));
82  			} else {
83  				if (cp[i] instanceof String) {
84  					cpb.append(cp[i]);
85  				} else if (cp[i] instanceof File) {
86  					cpb.append(((File) cp[i]).getAbsolutePath());
87  				} else {
88  					throw new IllegalArgumentException("Path must "
89  							+ "contain only strings or files.");
90  				}
91  			}
92  		}
93  
94  		return cpb.toString();
95  	}
96  
97  	/**
98  	 * Runs javac.
99  	 * 
100 	 * @param dir directory where javac should be run
101 	 * @param sources files with source code. It can be <code>null</code> if
102 	 * there are no files to compile
103 	 * @param cp additional classpath (<code>null</code> not to add any
104 	 * additional classpath). If this parameter is defined, any position in the
105 	 * array which is <code>null</code> is replaced by the application's current
106 	 * classpath. Inside this array both strings or files can be placed. Files
107 	 * will be added to the classpath as absolute paths.
108 	 * 
109 	 * @throws Exception failed to compile
110 	 */
111 	public static void javac(File dir, String[] sources, Object[] cp)
112 			throws Exception {
113 
114 		// Leave if there is nothing to compile.
115 		if (sources == null || sources.length == 0) {
116 			return;
117 		}
118 
119 		String cpb = buildAdditionalClasspath(cp);
120 		CommandRunner cr = new CommandRunner();
121 
122 		List<String> cmdList = new ArrayList<>();
123 		cmdList.add("javac");
124 		cmdList.add("-d");
125 		cmdList.add(".");
126 		cmdList.add("-cp");
127 		cmdList.add(cpb);
128 		for (int i = 0; i < sources.length; i++) {
129 			cmdList.add(sources[i]);
130 		}
131 
132 		String[] cmds = cmdList.toArray(new String[cmdList.size()]);
133 		CommandOutput co = cr.run_command(cmds, dir, 60);
134 		if (co == null || co.exitCode != 0) {
135 			StringBuffer cmdsStr = new StringBuffer();
136 			cmdsStr.append('{');
137 			for (int i = 0; i < cmds.length; i++) {
138 				if (i != 0) {
139 					cmdsStr.append(';');
140 				}
141 
142 				cmdsStr.append('\'');
143 				cmdsStr.append(cmds[i]);
144 				cmdsStr.append('\'');
145 			}
146 
147 			cmdsStr.append('}');
148 
149 			StringBuffer extra = new StringBuffer();
150 			if (co != null) {
151 				extra.append("; stdout:\n");
152 				extra.append(co.output);
153 				extra.append("\nstderr:\n");
154 				extra.append(co.error);
155 			}
156 
157 			throw new CommandExecutionException("Failed running " + cmdsStr
158 					+ ", @ " + dir.getAbsolutePath() + extra);
159 		}
160 	}
161 
162 	/**
163 	 * Compiles a file which exists as a resource.
164 	 * 
165 	 * @param dir directory where javac should be run
166 	 * @param resource the resource to compile
167 	 * @param cls the name of the class
168 	 * @param cp additional classpath (<code>null</code> not to add any
169 	 * additional classpath). If this parameter is defined, any position in the
170 	 * array which is <code>null</code> is replaced by the application's current
171 	 * classpath. Inside this array both strings or files can be placed. Files
172 	 * will be added to the classpath as absolute paths.
173 	 * 
174 	 * @return the directory with the first "package".
175 	 * 
176 	 * @throws Exception failed to compile
177 	 */
178 	public static File javacResource(File dir, String resource, String cls,
179 			Object[] cp) throws Exception {
180 		return javacResources(dir, new String[] { resource },
181 				new String[] { cls }, cp);
182 	}
183 
184 	/**
185 	 * Compiles files which exists as resources.
186 	 * 
187 	 * @param dir directory where javac should be run
188 	 * @param resources the resources to compile
189 	 * @param cls the name of the classes
190 	 * @param cp additional classpath (<code>null</code> not to add any
191 	 * additional classpath). If this parameter is defined, any position in the
192 	 * array which is <code>null</code> is replaced by the application's current
193 	 * classpath. Inside this array both strings or files can be placed. Files
194 	 * will be added to the classpath as absolute paths.
195 	 * 
196 	 * @return the directory with the first "package".
197 	 * 
198 	 * @throws Exception failed to compile
199 	 */
200 	public static File javacResources(File dir, String[] resources,
201 			String[] cls, Object[] cp) throws Exception {
202 		String classes[] = new String[cls.length];
203 		File subdir = null;
204 
205 		// LEts break the name by "." because we want the directories.
206 		for (int j = 0; j < cls.length; j++) {
207 			String parts[] = cls[j].split("\\.");
208 
209 			if (parts.length < 2) {
210 				throw new IllegalArgumentException("The class to compile "
211 						+ "must have at least one package.");
212 			}
213 
214 			// We create all directories.
215 			File files[] = new File[parts.length];
216 			for (int i = 0; i < files.length - 1; i++) {
217 				files[i] = new File(i == 0 ? dir : files[i - 1], parts[i]);
218 				boolean res = files[i].mkdir();
219 				assert res || !res; // Otherwise findbugs complains.
220 			}
221 
222 			subdir = files[0];
223 
224 			// Create a file that represents .java.
225 			int pos = parts.length - 1;
226 			files[pos] = new File(files[pos - 1], parts[pos] + ".java");
227 
228 			// We define the file contents.
229 			FileContentWorker.set_contents_bin(files[pos], FileContentWorker
230 					.read_resource_bin(resources[j]));
231 			classes[j] = cls[j].replace('.', '/') + ".java";
232 		}
233 
234 		// Compile.
235 		JavacRunner.javac(dir, classes, cp);
236 
237 		return subdir;
238 	}
239 }