blob: 8bee97f91dd70bd02f11ebaeecdd3967c85cd38b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.tools.ant;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import org.apache.tools.ant.helper.ProjectHelper2;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.util.LoaderUtils;
import org.apache.tools.ant.util.StreamUtils;
/**
* Repository of {@link ProjectHelper} found in the classpath or via
* some System properties.
*
* <p>See the ProjectHelper documentation in the manual.</p>
*
* @since Ant 1.8.0
*/
public class ProjectHelperRepository {
private static final String DEBUG_PROJECT_HELPER_REPOSITORY =
"ant.project-helper-repo.debug";
// The message log level is not accessible here because everything
// is instantiated statically
private static final boolean DEBUG =
"true".equals(System.getProperty(DEBUG_PROJECT_HELPER_REPOSITORY));
private static ProjectHelperRepository instance =
new ProjectHelperRepository();
private List<Constructor<? extends ProjectHelper>> helpers = new ArrayList<>();
private static Constructor<ProjectHelper2> PROJECTHELPER2_CONSTRUCTOR;
static {
try {
PROJECTHELPER2_CONSTRUCTOR = ProjectHelper2.class.getConstructor();
} catch (Exception e) {
// ProjectHelper2 must be available
throw new BuildException(e);
}
}
public static ProjectHelperRepository getInstance() {
return instance;
}
private ProjectHelperRepository() {
collectProjectHelpers();
}
private void collectProjectHelpers() {
// First, try the system property
registerProjectHelper(getProjectHelperBySystemProperty());
// A JDK1.3 'service' (like in JAXP). That will plug a helper
// automatically if in CLASSPATH, with the right META-INF/services.
try {
ClassLoader classLoader = LoaderUtils.getContextClassLoader();
if (classLoader != null) {
for (URL resource : Collections.list(classLoader.getResources(MagicNames.PROJECT_HELPER_SERVICE))) {
URLConnection conn = resource.openConnection();
conn.setUseCaches(false);
registerProjectHelper(getProjectHelperByService(conn.getInputStream()));
}
}
InputStream systemResource =
ClassLoader.getSystemResourceAsStream(MagicNames.PROJECT_HELPER_SERVICE);
if (systemResource != null) {
registerProjectHelper(getProjectHelperByService(systemResource));
}
} catch (Exception e) {
System.err.println("Unable to load ProjectHelper from service "
+ MagicNames.PROJECT_HELPER_SERVICE + " ("
+ e.getClass().getName()
+ ": " + e.getMessage() + ")");
if (DEBUG) {
e.printStackTrace(System.err); //NOSONAR
}
}
}
/**
* Register the specified project helper into the repository.
* <p>
* The helper will be added after all the already registered helpers, but
* before the default one (ProjectHelper2)
*
* @param helperClassName
* the fully qualified name of the helper
* @throws BuildException
* if the class cannot be loaded or if there is no constructor
* with no argument
* @since Ant 1.8.2
*/
public void registerProjectHelper(String helperClassName)
throws BuildException {
registerProjectHelper(getHelperConstructor(helperClassName));
}
/**
* Register the specified project helper into the repository.
* <p>
* The helper will be added after all the already registered helpers, but
* before the default one (ProjectHelper2)
*
* @param helperClass
* the class of the helper
* @throws BuildException
* if there is no constructor with no argument
* @since Ant 1.8.2
*/
public void registerProjectHelper(Class<? extends ProjectHelper> helperClass) throws BuildException {
try {
registerProjectHelper(helperClass.getConstructor());
} catch (NoSuchMethodException e) {
throw new BuildException("Couldn't find no-arg constructor in "
+ helperClass.getName());
}
}
private void registerProjectHelper(Constructor<? extends ProjectHelper> helperConstructor) {
if (helperConstructor == null) {
return;
}
if (DEBUG) {
System.out.println("ProjectHelper "
+ helperConstructor.getClass().getName() + " registered.");
}
helpers.add(helperConstructor);
}
private Constructor<? extends ProjectHelper> getProjectHelperBySystemProperty() {
String helperClass = System.getProperty(MagicNames.PROJECT_HELPER_CLASS);
try {
if (helperClass != null) {
return getHelperConstructor(helperClass);
}
} catch (SecurityException e) {
System.err.println("Unable to load ProjectHelper class \""
+ helperClass + " specified in system property "
+ MagicNames.PROJECT_HELPER_CLASS + " ("
+ e.getMessage() + ")");
if (DEBUG) {
e.printStackTrace(System.err); //NOSONAR
}
}
return null;
}
private Constructor<? extends ProjectHelper> getProjectHelperByService(InputStream is) {
try {
// This code is needed by EBCDIC and other strange systems.
// It's a fix for bugs reported in xerces
BufferedReader rd = new BufferedReader(new InputStreamReader(is,
StandardCharsets.UTF_8));
String helperClassName = rd.readLine();
rd.close();
if (helperClassName != null && !helperClassName.isEmpty()) {
return getHelperConstructor(helperClassName);
}
} catch (Exception e) {
System.out.println("Unable to load ProjectHelper from service "
+ MagicNames.PROJECT_HELPER_SERVICE + " (" + e.getMessage() + ")");
if (DEBUG) {
e.printStackTrace(System.err); //NOSONAR
}
}
return null;
}
/**
* Get the constructor with not argument of an helper from its class name.
* It'll first try the thread class loader, then Class.forName() will load
* from the same loader that loaded this class.
*
* @param helperClass
* The name of the class to create an instance of. Must not be
* <code>null</code>.
*
* @return the constructor of the specified class.
*
* @exception BuildException
* if the class cannot be found or if a constructor with no
* argument cannot be found.
*/
private Constructor<? extends ProjectHelper> getHelperConstructor(String helperClass) throws BuildException {
ClassLoader classLoader = LoaderUtils.getContextClassLoader();
try {
Class<?> clazz = null;
if (classLoader != null) {
try {
clazz = classLoader.loadClass(helperClass);
} catch (ClassNotFoundException ex) {
// try next method
}
}
if (clazz == null) {
clazz = Class.forName(helperClass);
}
return clazz.asSubclass(ProjectHelper.class).getConstructor();
} catch (Exception e) {
throw new BuildException(e);
}
}
/**
* Get the helper that will be able to parse the specified build file. The helper
* will be chosen among the ones found in the classpath
*
* @param buildFile Resource
* @return the first ProjectHelper that fit the requirement (never <code>null</code>).
*/
public ProjectHelper getProjectHelperForBuildFile(Resource buildFile) throws BuildException {
ProjectHelper ph = StreamUtils.iteratorAsStream(getHelpers())
.filter(helper -> helper.canParseBuildFile(buildFile))
.findFirst().orElse(null);
if (ph == null) {
throw new BuildException("BUG: at least the ProjectHelper2 should "
+ "have supported the file " + buildFile);
}
if (DEBUG) {
System.out.println("ProjectHelper " + ph.getClass().getName()
+ " selected for the build file " + buildFile);
}
return ph;
}
/**
* Get the helper that will be able to parse the specified antlib. The helper
* will be chosen among the ones found in the classpath
*
* @param antlib Resource
* @return the first ProjectHelper that fit the requirement (never <code>null</code>).
*/
public ProjectHelper getProjectHelperForAntlib(Resource antlib) throws BuildException {
ProjectHelper ph = StreamUtils.iteratorAsStream(getHelpers())
.filter(helper -> helper.canParseAntlibDescriptor(antlib))
.findFirst().orElse(null);
if (ph == null) {
throw new BuildException("BUG: at least the ProjectHelper2 should "
+ "have supported the file " + antlib);
}
if (DEBUG) {
System.out.println("ProjectHelper " + ph.getClass().getName()
+ " selected for the antlib " + antlib);
}
return ph;
}
/**
* Get an iterator on the list of project helpers configured. The iterator
* will always return at least one element as there will always be the
* default project helper configured.
*
* @return an iterator of {@link ProjectHelper}
*/
public Iterator<ProjectHelper> getHelpers() {
Stream.Builder<Constructor<? extends ProjectHelper>> b = Stream.builder();
helpers.forEach(b::add);
return b.add(PROJECTHELPER2_CONSTRUCTOR).build().map(c -> {
try {
return c.newInstance();
} catch (Exception e) {
throw new BuildException("Failed to invoke no-arg constructor"
+ " on " + c.getName());
}
}).map(ProjectHelper.class::cast).iterator();
}
}