ComboTask.java revision 3019:176472b94f2e
1/* 2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24package combo; 25 26import com.sun.source.tree.CompilationUnitTree; 27import com.sun.source.util.JavacTask; 28import com.sun.source.util.TaskEvent.Kind; 29import com.sun.source.util.TaskListener; 30import com.sun.tools.javac.api.JavacTool; 31import com.sun.tools.javac.util.List; 32import combo.ComboParameter.Resolver; 33 34import javax.lang.model.element.Element; 35import javax.tools.Diagnostic; 36import javax.tools.DiagnosticListener; 37import javax.tools.JavaFileObject; 38import javax.tools.SimpleJavaFileObject; 39import java.io.IOException; 40import java.io.Writer; 41import java.net.URI; 42import java.util.HashMap; 43import java.util.Map; 44import java.util.stream.Collectors; 45import java.util.stream.StreamSupport; 46 47/** 48 * This class represents a compilation task associated with a combo test instance. This is a small 49 * wrapper around {@link JavacTask} which allows for fluent setup style and which makes use of 50 * the shared compilation context to speedup performances. 51 */ 52public class ComboTask { 53 54 /** Sources to be compiled in this task. */ 55 private List<JavaFileObject> sources = List.nil(); 56 57 /** Options associated with this task. */ 58 private List<String> options = List.nil(); 59 60 /** Diagnostic collector. */ 61 private DiagnosticCollector diagsCollector = new DiagnosticCollector(); 62 63 /** Output writer. */ 64 private Writer out; 65 66 /** Listeners associated with this task. */ 67 private List<TaskListener> listeners = List.nil(); 68 69 /** Underlying javac task object. */ 70 private JavacTask task; 71 72 /** Combo execution environment. */ 73 private ComboTestHelper<?>.Env env; 74 75 ComboTask(ComboTestHelper<?>.Env env) { 76 this.env = env; 77 } 78 79 /** 80 * Add a new source to this task. 81 */ 82 public ComboTask withSource(JavaFileObject comboSource) { 83 sources = sources.prepend(comboSource); 84 return this; 85 } 86 87 /** 88 * Add a new template source with given name to this task; the template is replaced with 89 * corresponding combo parameters (as defined in the combo test environment). 90 */ 91 public ComboTask withSourceFromTemplate(String name, String template) { 92 return withSource(new ComboTemplateSource(name, template)); 93 } 94 95 /** 96 * Add a new template source with default name ("Test") to this task; the template is replaced with 97 * corresponding combo parameters (as defined in the combo test environment). 98 */ 99 public ComboTask withSourceFromTemplate(String template) { 100 return withSource(new ComboTemplateSource("Test", template)); 101 } 102 103 /** 104 * Add a new template source with given name to this task; the template is replaced with 105 * corresponding combo parameters (as defined in the combo test environment). A custom resolver 106 * is used to add combo parameter mappings to the current combo test environment. 107 */ 108 public ComboTask withSourceFromTemplate(String name, String template, Resolver resolver) { 109 return withSource(new ComboTemplateSource(name, template, resolver)); 110 } 111 112 /** 113 * Add a new template source with default name ("Test") to this task; the template is replaced with 114 * corresponding combo parameters (as defined in the combo test environment). A custom resolver 115 * is used to add combo parameter mappings to the current combo test environment. 116 */ 117 public ComboTask withSourceFromTemplate(String template, Resolver resolver) { 118 return withSource(new ComboTemplateSource("Test", template, resolver)); 119 } 120 121 /** 122 * Add a new option to this task. 123 */ 124 public ComboTask withOption(String opt) { 125 options = options.append(opt); 126 return this; 127 } 128 129 /** 130 * Add a set of options to this task. 131 */ 132 public ComboTask withOptions(String[] opts) { 133 for (String opt : opts) { 134 options = options.append(opt); 135 } 136 return this; 137 } 138 139 /** 140 * Add a set of options to this task. 141 */ 142 public ComboTask withOptions(Iterable<? extends String> opts) { 143 for (String opt : opts) { 144 options = options.append(opt); 145 } 146 return this; 147 } 148 149 /** 150 * Set the output writer associated with this task. 151 */ 152 public ComboTask withWriter(Writer out) { 153 this.out = out; 154 return this; 155 } 156 157 /** 158 * Add a task listener to this task. 159 */ 160 public ComboTask withListener(TaskListener listener) { 161 listeners = listeners.prepend(listener); 162 return this; 163 } 164 165 /** 166 * Parse the sources associated with this task. 167 */ 168 public Result<Iterable<? extends CompilationUnitTree>> parse() throws IOException { 169 return new Result<>(getTask().parse()); 170 } 171 172 /** 173 * Parse and analyzes the sources associated with this task. 174 */ 175 public Result<Iterable<? extends Element>> analyze() throws IOException { 176 return new Result<>(getTask().analyze()); 177 } 178 179 /** 180 * Parse, analyze and perform code generation for the sources associated with this task. 181 */ 182 public Result<Iterable<? extends JavaFileObject>> generate() throws IOException { 183 return new Result<>(getTask().generate()); 184 } 185 186 /** 187 * Fork a new compilation task; if possible the compilation context from previous executions is 188 * retained (see comments in ReusableContext as to when it's safe to do so); otherwise a brand 189 * new context is created. 190 */ 191 public JavacTask getTask() { 192 if (task == null) { 193 ReusableContext context = env.context(); 194 String opts = options == null ? "" : 195 StreamSupport.stream(options.spliterator(), false).collect(Collectors.joining()); 196 context.clear(); 197 if (!context.polluted && (context.opts == null || context.opts.equals(opts))) { 198 //we can reuse former context 199 env.info().ctxReusedCount++; 200 } else { 201 env.info().ctxDroppedCount++; 202 //it's not safe to reuse context - create a new one 203 context = env.setContext(new ReusableContext()); 204 } 205 context.opts = opts; 206 JavacTask javacTask = ((JavacTool)env.javaCompiler()).getTask(out, env.fileManager(), 207 diagsCollector, options, null, sources, context); 208 javacTask.setTaskListener(context); 209 for (TaskListener l : listeners) { 210 javacTask.addTaskListener(l); 211 } 212 task = javacTask; 213 } 214 return task; 215 } 216 217 /** 218 * This class is used to help clients accessing the results of a given compilation task. 219 * Contains several helper methods to inspect diagnostics generated during the task execution. 220 */ 221 public class Result<D> { 222 223 /** The underlying compilation results. */ 224 private final D data; 225 226 public Result(D data) { 227 this.data = data; 228 } 229 230 public D get() { 231 return data; 232 } 233 234 /** 235 * Did this task generate any error diagnostics? 236 */ 237 public boolean hasErrors() { 238 return diagsCollector.diagsByKind.containsKey(Diagnostic.Kind.ERROR); 239 } 240 241 /** 242 * Did this task generate any warning diagnostics? 243 */ 244 public boolean hasWarnings() { 245 return diagsCollector.diagsByKind.containsKey(Diagnostic.Kind.WARNING); 246 } 247 248 /** 249 * Did this task generate any note diagnostics? 250 */ 251 public boolean hasNotes() { 252 return diagsCollector.diagsByKind.containsKey(Diagnostic.Kind.NOTE); 253 } 254 255 /** 256 * Did this task generate any diagnostic with given key? 257 */ 258 public boolean containsKey(String key) { 259 return diagsCollector.diagsByKeys.containsKey(key); 260 } 261 262 /** 263 * Retrieve the list of diagnostics of a given kind. 264 */ 265 public List<Diagnostic<? extends JavaFileObject>> diagnosticsForKind(Diagnostic.Kind kind) { 266 List<Diagnostic<? extends JavaFileObject>> diags = diagsCollector.diagsByKind.get(kind); 267 return diags != null ? diags : List.nil(); 268 } 269 270 /** 271 * Retrieve the list of diagnostics with given key. 272 */ 273 public List<Diagnostic<? extends JavaFileObject>> diagnosticsForKey(String key) { 274 List<Diagnostic<? extends JavaFileObject>> diags = diagsCollector.diagsByKeys.get(key); 275 return diags != null ? diags : List.nil(); 276 } 277 278 /** 279 * Dump useful info associated with this task. 280 */ 281 public String compilationInfo() { 282 return "instance#" + env.info().comboCount + ":[ options = " + options 283 + ", diagnostics = " + diagsCollector.diagsByKeys.keySet() 284 + ", dimensions = " + env.bindings 285 + ", sources = \n" + sources.stream().map(s -> { 286 try { 287 return s.getCharContent(true); 288 } catch (IOException ex) { 289 return ""; 290 } 291 }).collect(Collectors.joining(",")) + "]"; 292 } 293 } 294 295 /** 296 * This class represents a Java source file whose contents are defined in terms of a template 297 * string. The holes in such template are expanded using corresponding combo parameter 298 * instances which can be retrieved using a resolver object. 299 */ 300 class ComboTemplateSource extends SimpleJavaFileObject { 301 302 String source; 303 Map<String, ComboParameter> localParametersCache = new HashMap<>(); 304 305 protected ComboTemplateSource(String name, String template) { 306 this(name, template, null); 307 } 308 309 protected ComboTemplateSource(String name, String template, Resolver resolver) { 310 super(URI.create("myfo:/" + env.info().comboCount + "/" + name + ".java"), Kind.SOURCE); 311 source = ComboParameter.expandTemplate(template, pname -> resolveParameter(pname, resolver)); 312 } 313 314 @Override 315 public CharSequence getCharContent(boolean ignoreEncodingErrors) { 316 return source; 317 } 318 319 /** 320 * Combo parameter resolver function. First parameters are looked up in the global environment, 321 * then the local environment is looked up as a fallback. 322 */ 323 ComboParameter resolveParameter(String pname, Resolver resolver) { 324 //first search the env 325 ComboParameter parameter = env.parametersCache.get(pname); 326 if (parameter == null) { 327 //then lookup local cache 328 parameter = localParametersCache.get(pname); 329 if (parameter == null && resolver != null) { 330 //if still null and we have a custom resolution function, try that 331 parameter = resolver.lookup(pname); 332 if (parameter != null) { 333 //if a match was found, store it in the local cache to aviod redundant recomputation 334 localParametersCache.put(pname, parameter); 335 } 336 } 337 } 338 return parameter; 339 } 340 } 341 342 /** 343 * Helper class to collect all diagnostic generated during the execution of a given compilation task. 344 */ 345 class DiagnosticCollector implements DiagnosticListener<JavaFileObject> { 346 347 Map<Diagnostic.Kind, List<Diagnostic<? extends JavaFileObject>>> diagsByKind = new HashMap<>(); 348 Map<String, List<Diagnostic<? extends JavaFileObject>>> diagsByKeys = new HashMap<>(); 349 350 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 351 List<Diagnostic<? extends JavaFileObject>> diags = 352 diagsByKeys.getOrDefault(diagnostic.getCode(), List.nil()); 353 diagsByKeys.put(diagnostic.getCode(), diags.prepend(diagnostic)); 354 Diagnostic.Kind kind = diagnostic.getKind(); 355 diags = diagsByKind.getOrDefault(kind, List.nil()); 356 diagsByKind.put(kind, diags.prepend(diagnostic)); 357 } 358 } 359} 360