aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/ProjectHelperRepository.java
blob: 1dd44124f801fe54c4dc88155ffcf712d2603633 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/*
 *  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
 *
 *      http://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.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import org.apache.tools.ant.helper.ProjectHelper2;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.util.LoaderUtils;

/**
 * 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 instanciated 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<Constructor<? extends ProjectHelper>>();

    private static Constructor<ProjectHelper2> PROJECTHELPER2_CONSTRUCTOR;

    static {
        try {
            PROJECTHELPER2_CONSTRUCTOR = ProjectHelper2.class.getConstructor();
        } catch (Exception e) {
            // ProjectHelper2 must be available
            throw new RuntimeException(e);
        }
    }

    public static ProjectHelperRepository getInstance() {
        return instance;
    }

    private ProjectHelperRepository() {
        collectProjectHelpers();
    }

    private void collectProjectHelpers() {
        // First, try the system property
        Constructor<? extends ProjectHelper> projectHelper = getProjectHelperBySystemProperty();
        registerProjectHelper(projectHelper);

        // 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) {
                Enumeration<URL> resources =
                    classLoader.getResources(ProjectHelper.SERVICE_ID);
                while (resources.hasMoreElements()) {
                    URL resource = resources.nextElement();
                    URLConnection conn = resource.openConnection();
                    conn.setUseCaches(false);
                    projectHelper =
                        getProjectHelperByService(conn.getInputStream());
                    registerProjectHelper(projectHelper);
                }
            }

            InputStream systemResource =
                ClassLoader.getSystemResourceAsStream(ProjectHelper.SERVICE_ID);
            if (systemResource != null) {
                projectHelper = getProjectHelperByService(systemResource);
                registerProjectHelper(projectHelper);
            }
        } catch (Exception e) {
            System.err.println("Unable to load ProjectHelper from service "
                               + ProjectHelper.SERVICE_ID + " ("
                               + e.getClass().getName()
                               + ": " + e.getMessage() + ")");
            if (DEBUG) {
                e.printStackTrace(System.err);
            }
        }
    }

    /**
     * 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(ProjectHelper.HELPER_PROPERTY);
        try {
            if (helperClass != null) {
                return getHelperConstructor(helperClass);
            }
        } catch (SecurityException e) {
            System.err.println("Unable to load ProjectHelper class \""
                               + helperClass + " specified in system property "
                               + ProjectHelper.HELPER_PROPERTY + " ("
                               + e.getMessage() + ")");
            if (DEBUG) {
                e.printStackTrace(System.err);
            }
        }
        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
            InputStreamReader isr;
            try {
                isr = new InputStreamReader(is, "UTF-8");
            } catch (java.io.UnsupportedEncodingException e) {
                isr = new InputStreamReader(is);
            }
            BufferedReader rd = new BufferedReader(isr);

            String helperClassName = rd.readLine();
            rd.close();

            if (helperClassName != null && !"".equals(helperClassName)) {
                return getHelperConstructor(helperClassName);
            }
        } catch (Exception e) {
            System.out.println("Unable to load ProjectHelper from service "
                    + ProjectHelper.SERVICE_ID + " (" + e.getMessage() + ")");
            if (DEBUG) {
                e.printStackTrace(System.err);
            }
        }
        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
     *
     * @return the first ProjectHelper that fit the requirement (never <code>null</code>).
     */
    public ProjectHelper getProjectHelperForBuildFile(Resource buildFile) throws BuildException {
        for (Iterator<ProjectHelper> it = getHelpers(); it.hasNext();) {
            ProjectHelper helper = it.next();
            if (helper.canParseBuildFile(buildFile)) {
                if (DEBUG) {
                    System.out.println("ProjectHelper "
                                       + helper.getClass().getName()
                                       + " selected for the build file "
                                       + buildFile);
                }
                return helper;
            }
        }
        throw new RuntimeException("BUG: at least the ProjectHelper2 should "
                                   + "have supported the file " + buildFile);
    }

    /**
     * Get the helper that will be able to parse the specified antlib. The helper
     * will be chosen among the ones found in the classpath
     *
     * @return the first ProjectHelper that fit the requirement (never <code>null</code>).
     */
    public ProjectHelper getProjectHelperForAntlib(Resource antlib) throws BuildException {
        for (Iterator<ProjectHelper> it = getHelpers(); it.hasNext();) {
            ProjectHelper helper = it.next();
            if (helper.canParseAntlibDescriptor(antlib)) {
                if (DEBUG) {
                    System.out.println("ProjectHelper "
                                       + helper.getClass().getName()
                                       + " selected for the antlib "
                                       + antlib);
                }
                return helper;
            }
        }
        throw new RuntimeException("BUG: at least the ProjectHelper2 should "
                                   + "have supported the file " + antlib);
    }

    /**
     * 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() {
        return new ConstructingIterator(helpers.iterator());
    }

    private static class ConstructingIterator implements Iterator<ProjectHelper> {
        private final Iterator<Constructor<? extends ProjectHelper>> nested;
        private boolean empty = false;

        ConstructingIterator(Iterator<Constructor<? extends ProjectHelper>> nested) {
            this.nested = nested;
        }

        public boolean hasNext() {
            return nested.hasNext() || !empty;
        }

        public ProjectHelper next() {
            Constructor<? extends ProjectHelper> c;
            if (nested.hasNext()) {
                c = nested.next();
            } else {
                // last but not least, ant default project helper
                empty = true;
                c = PROJECTHELPER2_CONSTRUCTOR;
            }
            try {
                return c.newInstance();
            } catch (Exception e) {
                throw new BuildException("Failed to invoke no-arg constructor"
                                         + " on " + c.getName());
            }
        }

        public void remove() {
            throw new UnsupportedOperationException("remove is not supported");
        }
    }
}