File: ClassQueryTool.java

package info (click to toggle)
libbase 1.1.6-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 944 kB
  • sloc: java: 8,709; xml: 1,522; makefile: 18
file content (273 lines) | stat: -rw-r--r-- 9,002 bytes parent folder | download | duplicates (4)
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
/*
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * Copyright (c) 2007 - 2009 Pentaho Corporation and Contributors.  All rights reserved.
 */

package org.pentaho.reporting.libraries.base.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * The class-query tool loads classes using a classloader and calls "processClass" for each class encountered. This is
 * highly expensive and sometimes dangerous excercise as the classloading may trigger static initializers and may
 * exhaust the "permgen" space of the Virtual machine.
 *
 * If possible anyhow, do not use this class.
 *
 * @author Thomas Morgner
 */
public abstract class ClassQueryTool
{
  /** A logger. */
  private static final Log logger = LogFactory.getLog(ClassQueryTool.class);

  /**
   * The default constructor.
   */
  protected ClassQueryTool()
  {
  }

  /**
   * Processes a single class-file entry. The method will try to load the given entry as java-class and if that
   * successeds will then call the "processClass" method to let the real implementation handle the class.
   *
   * @param classLoader the classloader that should be used for class- and resource loading.
   * @param entryName the file name in the classpath.
   */
  protected void processEntry(final ClassLoader classLoader, final String entryName)
  {
    if (entryName == null)
    {
      throw new NullPointerException();
    }
    if (classLoader == null)
    {
      throw new NullPointerException();
    }

    if (entryName.endsWith(".class") == false)
    {
      return;
    }
    final String className = entryName.substring(0, entryName.length() - 6).replace('/', '.');
    if (isValidClass(className) == false)
    {
      return;
    }
    try
    {
      final Class c = Class.forName(className, false, classLoader);
      processClass(classLoader, c);
    }
    catch (NoClassDefFoundError ndef)
    {
      // Ignore silently. This happens a lot if the classpath is incomplete.
    }
    catch (Throwable e)
    {
      // ignore ..
      logger.debug("At class '" + className + "': " + e);
    }
  }

  /**
   * Checks, whether the class is valid. If the class-name is not considered valid by this method, the class will
   * not be processed. Use this to pre-filter the class-stream as loading classes is expensive.
   *
   * @param className the name of the class.
   * @return true, if the class should be processed, false otherwise.
   */
  protected boolean isValidClass(final String className)
  {
    return true;
  }

  /**
   * The handler method that is called for every class encountered on the classpath.
   *
   * @param classLoader the classloader used to load the class.
   * @param c the class that should be handled.
   */
  protected abstract void processClass(final ClassLoader classLoader, final Class c);

  /**
   * Processes a single jar file. The Jar file is processed in the order of the entries contained within the
   * ZIP-directory.
   *
   * @param classLoader the classloader
   * @param jarFile the URL pointing to the jar file to be parsed.
   */
  private void processJarFile(final ClassLoader classLoader, final URL jarFile)
  {
    try
    {
      final ZipInputStream zf = new ZipInputStream(jarFile.openStream());
      ZipEntry ze;
      while ((ze = zf.getNextEntry()) != null)
      {
        if (!ze.isDirectory())
        {
          processEntry(classLoader, ze.getName());
        }
      }
      zf.close();
    }
    catch (final IOException e1)
    {
      logger.debug("Caught IO-Exception while processing file " + jarFile, e1);
    }
  }

  /**
   * Processes all entries from a given directory, ignoring any subdirectory contents.
   * If the directory contains sub-directories these directories are not searched for JAR or ZIP files.
   * <p/>
   * In addition to the directory given as parameter, the direcories and JAR/ZIP-files on the classpath are also
   * searched for entries.
   * <p/>
   * If directory is null, only the classpath is searched.
   *
   * @param directory the directory to be searched, or null to just use the classpath.
   * @throws IOException if an error occured while loading the resources from the directory.
   * @throws SecurityException if access to the system properties or access to the classloader is restricted.
   * @noinspection AccessOfSystemProperties
   */
  public void processDirectory(final File directory) throws IOException
  {
    final ArrayList allURLs = new ArrayList();
    final ArrayList jarURLs = new ArrayList();
    final ArrayList directoryURLs = new ArrayList();

    final String classpath = System.getProperty("java.class.path");
    final String pathSeparator = System.getProperty("path.separator");
    final StringTokenizer tokenizer = new StringTokenizer(classpath, pathSeparator);

    while (tokenizer.hasMoreTokens())
    {
      final String pathElement = tokenizer.nextToken();

      final File directoryOrJar = new File(pathElement);
      final File file = directoryOrJar.getAbsoluteFile();
      if (file.isDirectory() && file.exists() && file.canRead())
      {
        allURLs.add(file.toURL());
        directoryURLs.add(file);
        continue;
      }

      if (!file.isFile() || (file.exists() == false) || (file.canRead() == false))
      {
        continue;
      }

      final String fileName = file.getName();
      if (fileName.endsWith(".jar") || fileName.endsWith(".zip"))
      {
        allURLs.add(file.toURL());
        jarURLs.add(file.toURL());
      }
    }

    if (directory != null && directory.isDirectory())
    {
      final File[] driverFiles = directory.listFiles();
      for (int i = 0; i < driverFiles.length; i++)
      {
        final File file = driverFiles[i];
        if (file.isDirectory() && file.exists() && file.canRead())
        {
          allURLs.add(file.toURL());
          directoryURLs.add(file);
          continue;
        }

        if (!file.isFile() || (file.exists() == false) || (file.canRead() == false))
        {
          continue;
        }

        final String fileName = file.getName();
        if (fileName.endsWith(".jar") || fileName.endsWith(".zip"))
        {
          allURLs.add(file.toURL());
          jarURLs.add(file.toURL());
        }
      }
    }

    final URL[] urlsArray = (URL[]) jarURLs.toArray(new URL[jarURLs.size()]);
    final File[] dirsArray = (File[]) directoryURLs.toArray(new File[directoryURLs.size()]);
    final URL[] allArray = (URL[]) allURLs.toArray(new URL[allURLs.size()]);

    for (int i = 0; i < allArray.length; i++)
    {
      final URL url = allArray[i];
      logger.debug(url);
    }
    for (int i = 0; i < urlsArray.length; i++)
    {
      final URL url = urlsArray[i];
      final URLClassLoader classLoader = new URLClassLoader(allArray);
      processJarFile(classLoader, url);
    }
    for (int i = 0; i < dirsArray.length; i++)
    {
      final File file = dirsArray[i];
      final URLClassLoader classLoader = new URLClassLoader(allArray);
      processDirectory(classLoader, file, "");
    }
  }

  /**
   * Processes all entries from a given directory. If the directory contains sub-directories these directories
   * are processed in recursive depth-first mannor.
   *
   * @param classLoader the classloader to be used for loading classes.
   * @param file the directory to be searched.
   * @param pathPrefix the path prefix used to construct absolute filenames within the classpath.
   */
  private void processDirectory(final URLClassLoader classLoader, final File file, final String pathPrefix)
  {
    final File[] files = file.listFiles();
    for (int i = 0; i < files.length; i++)
    {
      final File subFile = files[i];
      if (subFile.exists() == false || subFile.canRead() == false)
      {
        continue;
      }

      if (subFile.isDirectory())
      {
        processDirectory(classLoader, subFile, pathPrefix + subFile.getName() + '/');
      }
      else if (subFile.isFile())
      {
        processEntry(classLoader, pathPrefix + subFile.getName());
      }
    }
  }
}