1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.api.plugin.testing;
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.Reader;
26 import java.io.StringReader;
27 import java.lang.reflect.AccessibleObject;
28 import java.lang.reflect.AnnotatedElement;
29 import java.lang.reflect.Field;
30 import java.net.URL;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.nio.file.Paths;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.HashMap;
37 import java.util.LinkedHashSet;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Objects;
41 import java.util.Optional;
42 import java.util.Set;
43 import java.util.stream.Collectors;
44 import java.util.stream.Stream;
45
46 import org.apache.maven.api.MojoExecution;
47 import org.apache.maven.api.Project;
48 import org.apache.maven.api.Session;
49 import org.apache.maven.api.di.Named;
50 import org.apache.maven.api.di.Priority;
51 import org.apache.maven.api.di.Provides;
52 import org.apache.maven.api.di.Singleton;
53 import org.apache.maven.api.di.testing.MavenDIExtension;
54 import org.apache.maven.api.model.Build;
55 import org.apache.maven.api.model.ConfigurationContainer;
56 import org.apache.maven.api.model.Model;
57 import org.apache.maven.api.plugin.Log;
58 import org.apache.maven.api.plugin.Mojo;
59 import org.apache.maven.api.plugin.descriptor.MojoDescriptor;
60 import org.apache.maven.api.plugin.descriptor.Parameter;
61 import org.apache.maven.api.plugin.descriptor.PluginDescriptor;
62 import org.apache.maven.api.plugin.testing.stubs.MojoExecutionStub;
63 import org.apache.maven.api.plugin.testing.stubs.PluginStub;
64 import org.apache.maven.api.plugin.testing.stubs.ProducedArtifactStub;
65 import org.apache.maven.api.plugin.testing.stubs.ProjectStub;
66 import org.apache.maven.api.plugin.testing.stubs.RepositorySystemSupplier;
67 import org.apache.maven.api.plugin.testing.stubs.SessionMock;
68 import org.apache.maven.api.services.ArtifactDeployer;
69 import org.apache.maven.api.services.ArtifactFactory;
70 import org.apache.maven.api.services.ArtifactInstaller;
71 import org.apache.maven.api.services.ArtifactManager;
72 import org.apache.maven.api.services.LocalRepositoryManager;
73 import org.apache.maven.api.services.ProjectBuilder;
74 import org.apache.maven.api.services.ProjectManager;
75 import org.apache.maven.api.services.RepositoryFactory;
76 import org.apache.maven.api.services.VersionParser;
77 import org.apache.maven.api.services.xml.ModelXmlFactory;
78 import org.apache.maven.api.xml.XmlNode;
79 import org.apache.maven.configuration.internal.EnhancedComponentConfigurator;
80 import org.apache.maven.di.Injector;
81 import org.apache.maven.di.Key;
82 import org.apache.maven.di.impl.DIException;
83 import org.apache.maven.internal.impl.DefaultLog;
84 import org.apache.maven.internal.impl.InternalSession;
85 import org.apache.maven.internal.impl.model.DefaultModelPathTranslator;
86 import org.apache.maven.internal.impl.model.DefaultPathTranslator;
87 import org.apache.maven.internal.xml.XmlNodeImpl;
88 import org.apache.maven.internal.xml.XmlPlexusConfiguration;
89 import org.apache.maven.lifecycle.internal.MojoDescriptorCreator;
90 import org.apache.maven.model.v4.MavenMerger;
91 import org.apache.maven.model.v4.MavenStaxReader;
92 import org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4;
93 import org.apache.maven.plugin.descriptor.io.PluginDescriptorStaxReader;
94 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
95 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
96 import org.codehaus.plexus.component.configurator.expression.TypeAwareExpressionEvaluator;
97 import org.codehaus.plexus.util.ReflectionUtils;
98 import org.codehaus.plexus.util.xml.XmlStreamReader;
99 import org.codehaus.plexus.util.xml.Xpp3Dom;
100 import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
101 import org.eclipse.aether.RepositorySystem;
102 import org.junit.jupiter.api.extension.BeforeEachCallback;
103 import org.junit.jupiter.api.extension.ExtensionContext;
104 import org.junit.jupiter.api.extension.ParameterContext;
105 import org.junit.jupiter.api.extension.ParameterResolutionException;
106 import org.junit.jupiter.api.extension.ParameterResolver;
107 import org.junit.platform.commons.support.AnnotationSupport;
108 import org.slf4j.LoggerFactory;
109
110 import static java.util.Objects.requireNonNull;
111
112
113
114
115
116
117
118
119
120
121 public class MojoExtension extends MavenDIExtension implements ParameterResolver, BeforeEachCallback {
122
123 protected static String pluginBasedir;
124 protected static String basedir;
125
126 public static String getTestId() {
127 return context.getRequiredTestClass().getSimpleName() + "-"
128 + context.getRequiredTestMethod().getName();
129 }
130
131 public static String getBasedir() {
132 return requireNonNull(basedir != null ? basedir : MavenDIExtension.basedir);
133 }
134
135 public static String getPluginBasedir() {
136 return requireNonNull(pluginBasedir);
137 }
138
139 @Override
140 public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
141 throws ParameterResolutionException {
142 return parameterContext.isAnnotated(InjectMojo.class)
143 || parameterContext.getDeclaringExecutable().isAnnotationPresent(InjectMojo.class);
144 }
145
146 @Override
147 public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
148 throws ParameterResolutionException {
149 try {
150 Class<?> holder = parameterContext.getTarget().get().getClass();
151 PluginDescriptor descriptor = extensionContext
152 .getStore(ExtensionContext.Namespace.GLOBAL)
153 .get(PluginDescriptor.class, PluginDescriptor.class);
154 Model model =
155 extensionContext.getStore(ExtensionContext.Namespace.GLOBAL).get(Model.class, Model.class);
156 InjectMojo parameterInjectMojo =
157 parameterContext.getAnnotatedElement().getAnnotation(InjectMojo.class);
158 String goal;
159 if (parameterInjectMojo != null) {
160 String pom = parameterInjectMojo.pom();
161 if (pom != null && !pom.isEmpty()) {
162 try (Reader r = openPomUrl(holder, pom, new Path[1])) {
163 Model localModel = new MavenStaxReader().read(r);
164 model = new MavenMerger().merge(localModel, model, false, null);
165 model = new DefaultModelPathTranslator(new DefaultPathTranslator())
166 .alignToBaseDirectory(model, Paths.get(getBasedir()), null);
167 }
168 }
169 goal = parameterInjectMojo.goal();
170 } else {
171 InjectMojo methodInjectMojo = AnnotationSupport.findAnnotation(
172 parameterContext.getDeclaringExecutable(), InjectMojo.class)
173 .orElse(null);
174 if (methodInjectMojo != null) {
175 goal = methodInjectMojo.goal();
176 } else {
177 goal = getGoalFromMojoImplementationClass(
178 parameterContext.getParameter().getType());
179 }
180 }
181
182 Set<MojoParameter> mojoParameters = new LinkedHashSet<>();
183 for (AnnotatedElement ae :
184 Arrays.asList(parameterContext.getDeclaringExecutable(), parameterContext.getAnnotatedElement())) {
185 mojoParameters.addAll(AnnotationSupport.findRepeatableAnnotations(ae, MojoParameter.class));
186 }
187 String[] coord = mojoCoordinates(goal);
188
189 XmlNode pluginConfiguration = model.getBuild().getPlugins().stream()
190 .filter(p ->
191 Objects.equals(p.getGroupId(), coord[0]) && Objects.equals(p.getArtifactId(), coord[1]))
192 .map(ConfigurationContainer::getConfiguration)
193 .findFirst()
194 .orElseGet(() -> new XmlNodeImpl("config"));
195 List<XmlNode> children = mojoParameters.stream()
196 .map(mp -> new XmlNodeImpl(mp.name(), mp.value()))
197 .collect(Collectors.toList());
198 XmlNode config = new XmlNodeImpl("configuration", null, null, children, null);
199 pluginConfiguration = XmlNode.merge(config, pluginConfiguration);
200
201
202
203 Mojo mojo = lookup(Mojo.class, coord[0] + ":" + coord[1] + ":" + coord[2] + ":" + coord[3]);
204 for (MojoDescriptor mojoDescriptor : descriptor.getMojos()) {
205 if (Objects.equals(mojoDescriptor.getGoal(), coord[3])) {
206 if (pluginConfiguration != null) {
207 pluginConfiguration = finalizeConfig(pluginConfiguration, mojoDescriptor);
208 }
209 }
210 }
211
212 Session session = getInjector().getInstance(Session.class);
213 Project project = getInjector().getInstance(Project.class);
214 MojoExecution mojoExecution = getInjector().getInstance(MojoExecution.class);
215 ExpressionEvaluator evaluator = new WrapEvaluator(
216 getInjector(), new PluginParameterExpressionEvaluatorV4(session, project, mojoExecution));
217
218 EnhancedComponentConfigurator configurator = new EnhancedComponentConfigurator();
219 configurator.configureComponent(
220 mojo, new XmlPlexusConfiguration(pluginConfiguration), evaluator, null, null);
221 return mojo;
222 } catch (Exception e) {
223 throw new ParameterResolutionException("Unable to resolve mojo", e);
224 }
225 }
226
227
228
229
230
231
232 private static String getGoalFromMojoImplementationClass(Class<?> cl) throws IOException {
233 return cl.getAnnotation(Named.class).value();
234 }
235
236 @Override
237 @SuppressWarnings("checkstyle:MethodLength")
238 public void beforeEach(ExtensionContext context) throws Exception {
239 if (pluginBasedir == null) {
240 pluginBasedir = MavenDIExtension.getBasedir();
241 }
242 basedir = AnnotationSupport.findAnnotation(context.getElement().get(), Basedir.class)
243 .map(Basedir::value)
244 .orElse(pluginBasedir);
245 if (basedir != null) {
246 if (basedir.isEmpty()) {
247 basedir = pluginBasedir + "/target/tests/"
248 + context.getRequiredTestClass().getSimpleName() + "/"
249 + context.getRequiredTestMethod().getName();
250 } else {
251 basedir = basedir.replace("${basedir}", pluginBasedir);
252 }
253 }
254
255 setContext(context);
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289 Path basedirPath = Paths.get(getBasedir());
290
291 InjectMojo mojo = AnnotationSupport.findAnnotation(context.getElement().get(), InjectMojo.class)
292 .orElse(null);
293 Model defaultModel = Model.newBuilder()
294 .groupId("myGroupId")
295 .artifactId("myArtifactId")
296 .version("1.0-SNAPSHOT")
297 .packaging("jar")
298 .build(Build.newBuilder()
299 .directory(basedirPath.resolve("target").toString())
300 .outputDirectory(basedirPath.resolve("target/classes").toString())
301 .sourceDirectory(basedirPath.resolve("src/main/java").toString())
302 .testSourceDirectory(
303 basedirPath.resolve("src/test/java").toString())
304 .testOutputDirectory(
305 basedirPath.resolve("target/test-classes").toString())
306 .build())
307 .build();
308 Path[] modelPath = new Path[] {null};
309 Model tmodel = null;
310 if (mojo != null) {
311 String pom = mojo.pom();
312 if (pom != null && !pom.isEmpty()) {
313 try (Reader r = openPomUrl(context.getRequiredTestClass(), pom, modelPath)) {
314 tmodel = new MavenStaxReader().read(r);
315 }
316 } else {
317 Path pomPath = basedirPath.resolve("pom.xml");
318 if (Files.exists(pomPath)) {
319 try (Reader r = Files.newBufferedReader(pomPath)) {
320 tmodel = new MavenStaxReader().read(r);
321 modelPath[0] = pomPath;
322 }
323 }
324 }
325 }
326 Model model;
327 if (tmodel == null) {
328 model = defaultModel;
329 } else {
330 model = new MavenMerger().merge(tmodel, defaultModel, false, null);
331 }
332 tmodel = new DefaultModelPathTranslator(new DefaultPathTranslator())
333 .alignToBaseDirectory(tmodel, Paths.get(getBasedir()), null);
334 context.getStore(ExtensionContext.Namespace.GLOBAL).put(Model.class, tmodel);
335
336
337
338 PluginDescriptor pluginDescriptor;
339 ClassLoader classLoader = context.getRequiredTestClass().getClassLoader();
340 try (InputStream is = requireNonNull(
341 classLoader.getResourceAsStream(getPluginDescriptorLocation()),
342 "Unable to find plugin descriptor: " + getPluginDescriptorLocation());
343 Reader reader = new BufferedReader(new XmlStreamReader(is))) {
344
345 pluginDescriptor = new PluginDescriptorStaxReader().read(reader);
346 }
347 context.getStore(ExtensionContext.Namespace.GLOBAL).put(PluginDescriptor.class, pluginDescriptor);
348
349
350
351
352 @SuppressWarnings({"unused", "MagicNumber"})
353 class Foo {
354
355 @Provides
356 @Singleton
357 @Priority(-10)
358 private InternalSession createSession() {
359 return SessionMock.getMockSession(getBasedir());
360 }
361
362 @Provides
363 @Singleton
364 @Priority(-10)
365 private Project createProject(InternalSession s) {
366 ProjectStub stub = new ProjectStub();
367 if (!"pom".equals(model.getPackaging())) {
368 ProducedArtifactStub artifact = new ProducedArtifactStub(
369 model.getGroupId(), model.getArtifactId(), "", model.getVersion(), model.getPackaging());
370 stub.setMainArtifact(artifact);
371 }
372 stub.setModel(model);
373 stub.setBasedir(Paths.get(MojoExtension.getBasedir()));
374 stub.setPomPath(modelPath[0]);
375 s.getService(ArtifactManager.class).setPath(stub.getPomArtifact(), modelPath[0]);
376 return stub;
377 }
378
379 @Provides
380 @Singleton
381 @Priority(-10)
382 private MojoExecution createMojoExecution() {
383 MojoExecutionStub mes = new MojoExecutionStub("executionId", null);
384 if (mojo != null) {
385 String goal = mojo.goal();
386 int idx = goal.lastIndexOf(':');
387 if (idx >= 0) {
388 goal = goal.substring(idx + 1);
389 }
390 mes.setGoal(goal);
391 for (MojoDescriptor md : pluginDescriptor.getMojos()) {
392 if (goal.equals(md.getGoal())) {
393 mes.setDescriptor(md);
394 }
395 }
396 requireNonNull(mes.getDescriptor());
397 }
398 PluginStub plugin = new PluginStub();
399 plugin.setDescriptor(pluginDescriptor);
400 mes.setPlugin(plugin);
401 return mes;
402 }
403
404 @Provides
405 @Singleton
406 @Priority(-10)
407 private Log createLog() {
408 return new DefaultLog(LoggerFactory.getLogger("anonymous"));
409 }
410
411 @Provides
412 static RepositorySystemSupplier newRepositorySystemSupplier() {
413 return new RepositorySystemSupplier();
414 }
415
416 @Provides
417 static RepositorySystem newRepositorySystem(RepositorySystemSupplier repositorySystemSupplier) {
418 return repositorySystemSupplier.getRepositorySystem();
419 }
420
421 @Provides
422 @Priority(10)
423 static RepositoryFactory newRepositoryFactory(Session session) {
424 return session.getService(RepositoryFactory.class);
425 }
426
427 @Provides
428 @Priority(10)
429 static VersionParser newVersionParser(Session session) {
430 return session.getService(VersionParser.class);
431 }
432
433 @Provides
434 @Priority(10)
435 static LocalRepositoryManager newLocalRepositoryManager(Session session) {
436 return session.getService(LocalRepositoryManager.class);
437 }
438
439 @Provides
440 @Priority(10)
441 static ArtifactInstaller newArtifactInstaller(Session session) {
442 return session.getService(ArtifactInstaller.class);
443 }
444
445 @Provides
446 @Priority(10)
447 static ArtifactDeployer newArtifactDeployer(Session session) {
448 return session.getService(ArtifactDeployer.class);
449 }
450
451 @Provides
452 @Priority(10)
453 static ArtifactManager newArtifactManager(Session session) {
454 return session.getService(ArtifactManager.class);
455 }
456
457 @Provides
458 @Priority(10)
459 static ProjectManager newProjectManager(Session session) {
460 return session.getService(ProjectManager.class);
461 }
462
463 @Provides
464 @Priority(10)
465 static ArtifactFactory newArtifactFactory(Session session) {
466 return session.getService(ArtifactFactory.class);
467 }
468
469 @Provides
470 @Priority(10)
471 static ProjectBuilder newProjectBuilder(Session session) {
472 return session.getService(ProjectBuilder.class);
473 }
474
475 @Provides
476 @Priority(10)
477 static ModelXmlFactory newModelXmlFactory(Session session) {
478 return session.getService(ModelXmlFactory.class);
479 }
480 }
481
482 getInjector().bindInstance(Foo.class, new Foo());
483
484 getInjector().injectInstance(context.getRequiredTestInstance());
485
486
487
488
489
490
491
492
493
494
495 }
496
497 private Reader openPomUrl(Class<?> holder, String pom, Path[] modelPath) throws IOException {
498 if (pom.startsWith("file:")) {
499 Path path = Paths.get(getBasedir()).resolve(pom.substring("file:".length()));
500 modelPath[0] = path;
501 return Files.newBufferedReader(path);
502 } else if (pom.startsWith("classpath:")) {
503 URL url = holder.getResource(pom.substring("classpath:".length()));
504 if (url == null) {
505 throw new IllegalStateException("Unable to find pom on classpath: " + pom);
506 }
507 return new XmlStreamReader(url.openStream());
508 } else if (pom.contains("<project>")) {
509 return new StringReader(pom);
510 } else {
511 Path path = Paths.get(getBasedir()).resolve(pom);
512 modelPath[0] = path;
513 return Files.newBufferedReader(path);
514 }
515 }
516
517 protected String getPluginDescriptorLocation() {
518 return "META-INF/maven/plugin.xml";
519 }
520
521 protected String[] mojoCoordinates(String goal) throws Exception {
522 if (goal.matches(".*:.*:.*:.*")) {
523 return goal.split(":");
524 } else {
525 Path pluginPom = Paths.get(getPluginBasedir(), "pom.xml");
526 Xpp3Dom pluginPomDom = Xpp3DomBuilder.build(Files.newBufferedReader(pluginPom));
527 String artifactId = pluginPomDom.getChild("artifactId").getValue();
528 String groupId = resolveFromRootThenParent(pluginPomDom, "groupId");
529 String version = resolveFromRootThenParent(pluginPomDom, "version");
530 return new String[] {groupId, artifactId, version, goal};
531 }
532 }
533
534 private XmlNode finalizeConfig(XmlNode config, MojoDescriptor mojoDescriptor) {
535 List<XmlNode> children = new ArrayList<>();
536 if (mojoDescriptor != null && mojoDescriptor.getParameters() != null) {
537 XmlNode defaultConfiguration;
538 defaultConfiguration = MojoDescriptorCreator.convert(mojoDescriptor);
539 for (Parameter parameter : mojoDescriptor.getParameters()) {
540 XmlNode parameterConfiguration = config.getChild(parameter.getName());
541 if (parameterConfiguration == null) {
542 parameterConfiguration = config.getChild(parameter.getAlias());
543 }
544 XmlNode parameterDefaults = defaultConfiguration.getChild(parameter.getName());
545 parameterConfiguration = XmlNode.merge(parameterConfiguration, parameterDefaults, Boolean.TRUE);
546 if (parameterConfiguration != null) {
547 Map<String, String> attributes = new HashMap<>(parameterConfiguration.getAttributes());
548
549
550
551
552 parameterConfiguration = new XmlNodeImpl(
553 parameter.getName(),
554 parameterConfiguration.getValue(),
555 attributes,
556 parameterConfiguration.getChildren(),
557 parameterConfiguration.getInputLocation());
558
559 children.add(parameterConfiguration);
560 }
561 }
562 }
563 return new XmlNodeImpl("configuration", null, null, children, null);
564 }
565
566 private boolean isEmpty(String str) {
567 return str == null || str.isEmpty();
568 }
569
570 private static Optional<Xpp3Dom> child(Xpp3Dom element, String name) {
571 return Optional.ofNullable(element.getChild(name));
572 }
573
574 private static Stream<Xpp3Dom> children(Xpp3Dom element) {
575 return Stream.of(element.getChildren());
576 }
577
578 public static XmlNode extractPluginConfiguration(String artifactId, Xpp3Dom pomDom) throws Exception {
579 Xpp3Dom pluginConfigurationElement = child(pomDom, "build")
580 .flatMap(buildElement -> child(buildElement, "plugins"))
581 .map(MojoExtension::children)
582 .orElseGet(Stream::empty)
583 .filter(e -> e.getChild("artifactId").getValue().equals(artifactId))
584 .findFirst()
585 .flatMap(buildElement -> child(buildElement, "configuration"))
586 .orElse(Xpp3DomBuilder.build(new StringReader("<configuration/>")));
587 return pluginConfigurationElement.getDom();
588 }
589
590
591
592
593
594
595 private static String resolveFromRootThenParent(Xpp3Dom pluginPomDom, String element) throws Exception {
596 return Optional.ofNullable(child(pluginPomDom, element).orElseGet(() -> child(pluginPomDom, "parent")
597 .flatMap(e -> child(e, element))
598 .orElse(null)))
599 .map(Xpp3Dom::getValue)
600 .orElseThrow(() -> new Exception("unable to determine " + element));
601 }
602
603
604
605
606
607
608 public static Object getVariableValueFromObject(Object object, String variable) throws IllegalAccessException {
609 Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses(variable, object.getClass());
610 field.setAccessible(true);
611 return field.get(object);
612 }
613
614
615
616
617
618
619 public static Map<String, Object> getVariablesAndValuesFromObject(Object object) throws IllegalAccessException {
620 return getVariablesAndValuesFromObject(object.getClass(), object);
621 }
622
623
624
625
626
627
628
629
630 public static Map<String, Object> getVariablesAndValuesFromObject(Class<?> clazz, Object object)
631 throws IllegalAccessException {
632 Map<String, Object> map = new HashMap<>();
633 Field[] fields = clazz.getDeclaredFields();
634 AccessibleObject.setAccessible(fields, true);
635 for (Field field : fields) {
636 map.put(field.getName(), field.get(object));
637 }
638 Class<?> superclass = clazz.getSuperclass();
639 if (!Object.class.equals(superclass)) {
640 map.putAll(getVariablesAndValuesFromObject(superclass, object));
641 }
642 return map;
643 }
644
645
646
647
648 public static void setVariableValueToObject(Object object, String variable, Object value)
649 throws IllegalAccessException {
650 Field field = ReflectionUtils.getFieldByNameIncludingSuperclasses(variable, object.getClass());
651 requireNonNull(field, "Field " + variable + " not found");
652 field.setAccessible(true);
653 field.set(object, value);
654 }
655
656 static class WrapEvaluator implements TypeAwareExpressionEvaluator {
657
658 private final Injector injector;
659 private final TypeAwareExpressionEvaluator evaluator;
660
661 WrapEvaluator(Injector injector, TypeAwareExpressionEvaluator evaluator) {
662 this.injector = injector;
663 this.evaluator = evaluator;
664 }
665
666 @Override
667 public Object evaluate(String expression) throws ExpressionEvaluationException {
668 return evaluate(expression, null);
669 }
670
671 @Override
672 public Object evaluate(String expression, Class<?> type) throws ExpressionEvaluationException {
673 Object value = evaluator.evaluate(expression, type);
674 if (value == null) {
675 String expr = stripTokens(expression);
676 if (expr != null) {
677 try {
678 value = injector.getInstance(Key.of(type, expr));
679 } catch (DIException e) {
680
681 }
682 }
683 }
684 return value;
685 }
686
687 private String stripTokens(String expr) {
688 if (expr.startsWith("${") && expr.endsWith("}")) {
689 return expr.substring(2, expr.length() - 1);
690 }
691 return null;
692 }
693
694 @Override
695 public File alignToBaseDirectory(File path) {
696 return evaluator.alignToBaseDirectory(path);
697 }
698 }
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730 }