001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.form.project;
043:
044: import java.io.*;
045: import java.net.*;
046: import java.util.*;
047:
048: import org.openide.ErrorManager;
049: import org.openide.filesystems.*;
050: import org.openide.util.Lookup;
051: import org.openide.util.NbBundle;
052:
053: import org.netbeans.api.project.*;
054: import org.netbeans.api.project.ant.*;
055: import org.netbeans.api.java.classpath.ClassPath;
056: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
057: import org.netbeans.api.java.queries.SourceForBinaryQuery;
058:
059: /**
060: * Utility methods related to classpath in projects.
061: *
062: * @author Tomas Pavek
063: */
064:
065: public class ClassPathUtils {
066:
067: private static Map<Project, FormClassLoader> loaders = new WeakHashMap<Project, FormClassLoader>();
068:
069: /**
070: * Class loading type for a class to be always loaded by the IDE's system
071: * classloader (i.e. from a module). The system classloader is used even if
072: * the class was requested by a class that was loaded by the project
073: * classloader. This can also be used for user classes from project to link
074: * with classes provided by a module for design time. (E.g. used for binding
075: * validators.)
076: */
077: static final ClassLoadingType SYSTEM_CLASS = new ClassLoadingType(
078: "system"); // NOI18N
079:
080: /**
081: * Class loading type for a class to be loaded from a module by a classloader
082: * that also includes the project classpath. Can be used for special design
083: * module classes that need to access project classpath (classes or resources).
084: */
085: static final ClassLoadingType SYSTEM_CLASS_WITH_PROJECT = new ClassLoadingType(
086: "system_with_project"); // NOI18N
087:
088: /**
089: * Loads a class with a context of a project in mind (specified by arbitrary
090: * file contained in the project). Typically the class is loaded from the
091: * project's execution classpath unless it is a basic JDK class, or a class
092: * registred as a support (system) class.
093: */
094: public static Class<?> loadClass(String name,
095: FileObject fileInProject) throws ClassNotFoundException {
096: return Class.forName(name, true,
097: getFormClassLoader(fileInProject));
098: // LinkageError left uncaught
099: }
100:
101: public static boolean checkUserClass(String name,
102: FileObject fileInProject) {
103: ClassPath classPath = ClassPath.getClassPath(fileInProject,
104: ClassPath.EXECUTE);
105: if (classPath == null)
106: return false;
107:
108: String fileName = name.replace('.', '/').concat(".class"); // NOI18N
109: return classPath.findResource(fileName) != null;
110: }
111:
112: private static FormClassLoader getFormClassLoader(
113: FileObject fileInProject) {
114: Project p = FileOwnerQuery.getOwner(fileInProject);
115: FormClassLoader fcl = loaders.get(p);
116: ClassLoader existingProjectCL = fcl != null ? fcl
117: .getProjectClassLoader() : null;
118: ClassLoader newProjectCL = ProjectClassLoader
119: .getUpToDateClassLoader(fileInProject,
120: existingProjectCL);
121: if (fcl == null || newProjectCL != existingProjectCL) {
122: fcl = new FormClassLoader(newProjectCL);
123: loaders.put(p, fcl);
124: }
125: return fcl;
126: }
127:
128: // Don't use - public only because of FormLAF
129: public static ClassLoader getProjectClassLoader(
130: FileObject fileInProject) {
131: return getFormClassLoader(fileInProject)
132: .getProjectClassLoader();
133: }
134:
135: /**
136: * @return ClassLoadingType if the class should be loaded in a special way,
137: * or null to do default loading (from project classpath)
138: */
139: static ClassLoadingType getClassLoadingType(String className) {
140: int i = className.lastIndexOf("[L"); // NOI18N
141: if (i != -1) {
142: className = className.substring(i + 2,
143: className.length() - 1);
144: }
145: for (ClassLoadingType clType : CLASS_LOADING_TYPES) {
146: if (isClassLoadingType(className, clType)) {
147: return clType;
148: }
149: }
150: return null;
151: }
152:
153: /** Loads class from classpath described by ClassSource object.
154: * @return loaded class, null if class name in ClassSource is null
155: */
156: public static Class loadClass(ClassSource classSource)
157: throws ClassNotFoundException {
158: String className = classSource.getClassName();
159: if (className == null)
160: return null;
161:
162: ClassLoader loader = null;
163:
164: if (!classSource.hasEntries()) {
165: // for loading JDK classes
166: loader = Lookup.getDefault().lookup(ClassLoader.class);
167: } else
168: try {
169: List<URL> urlList = classSource.getClasspath();
170: if (urlList.size() > 0) {
171: URL[] roots = new URL[urlList.size()];
172: urlList.toArray(roots);
173: loader = ClassPathSupport.createClassPath(roots)
174: .getClassLoader(true);
175: } else
176: return null;
177: } catch (Exception ex) { // could not construct the classpath
178: IllegalArgumentException iae = new IllegalArgumentException();
179: ErrorManager.getDefault().annotate(iae, ex);
180: throw iae;
181: // ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
182: }
183:
184: return loader.loadClass(classSource.getClassName());
185: }
186:
187: /** Creates ClassSource object corresponding to project output classpath.
188: * @param fileInProject FileObject being source (.java) or output (.class)
189: * file in a project
190: * @param classname String name of class for which the ClassSource is
191: * created
192: */
193: public static ClassSource getProjectClassSource(
194: FileObject fileInProject, String classname) {
195: Project project = FileOwnerQuery.getOwner(fileInProject);
196: if (project == null)
197: return null; // the file is not in any project
198:
199: // find the project output (presumably a JAR file) where the given
200: // source file is compiled (packed) to
201: AntArtifact[] artifacts = AntArtifactQuery.findArtifactsByType(
202: project, "jar"); // NOI18N
203: if (artifacts.length == 0)
204: return null; // there is no project output
205:
206: for (AntArtifact aa : artifacts) {
207: ClassSource.Entry entry = new ClassSource.ProjectEntry(aa);
208: for (URL binaryRoot : entry.getClasspath()) {
209: for (FileObject sourceRoot : SourceForBinaryQuery
210: .findSourceRoots(binaryRoot).getRoots()) {
211: if (FileUtil.isParentOf(sourceRoot, fileInProject)) {
212: // Looks like the one.
213: return new ClassSource(classname, entry);
214: }
215: }
216: }
217: }
218:
219: // no output found for given source file - the file might not be
220: // a source file ... but a binary output file - in this case return
221: // simply all project outputs as there is no good way to recognize
222: // the right one (and j2se project has just one output anyway)
223:
224: if (!fileInProject.getExt().equals("class")) // NOI18N
225: return null; // not interested in other than .class binary files
226:
227: List<ClassSource.Entry> entries = new ArrayList<ClassSource.Entry>();
228: for (AntArtifact aa : artifacts) {
229: ClassSource.Entry entry = new ClassSource.ProjectEntry(aa);
230: entries.add(entry);
231: }
232: return new ClassSource(classname, entries);
233: }
234:
235: public static boolean isOnClassPath(FileObject fileInProject,
236: String className) {
237: String resourceName = className.replace('.', '/') + ".class"; // NOI18N
238: ClassPath classPath = ClassPath.getClassPath(fileInProject,
239: ClassPath.EXECUTE);
240: if (classPath == null)
241: return false;
242:
243: return classPath.findResource(resourceName) != null;
244: }
245:
246: public static boolean isJava6ProjectPlatform(
247: FileObject fileInProject) {
248: ClassPath classPath = ClassPath.getClassPath(fileInProject,
249: ClassPath.BOOT);
250: if (classPath == null)
251: return false;
252:
253: return classPath.findResource("javax/swing/GroupLayout.class") != null; // NOI18N
254: }
255:
256: /** Updates project'c classpath with entries from ClassSource object.
257: * @return null if operation was cancelled by user otherwise true or false
258: * if project classpath was changed or not
259: */
260: public static Boolean updateProject(FileObject fileInProject,
261: ClassSource classSource) throws IOException {
262: if (!classSource.hasEntries())
263: return Boolean.FALSE; // nothing to add to project
264:
265: Project project = FileOwnerQuery.getOwner(fileInProject);
266: if (project == null)
267: return Boolean.FALSE;
268:
269: return classSource.addToProjectClassPath(fileInProject,
270: ClassPath.COMPILE);
271: }
272:
273: /** Provides description for ClassSource object usable e.g. for error
274: * messages.
275: */
276: public static String getClassSourceDescription(
277: ClassSource classSource) {
278: if (classSource == null || !classSource.hasEntries()) {
279: String className = classSource.getClassName();
280: if (className != null) {
281: if (className.startsWith("javax.") // NOI18N
282: || className.startsWith("java.")) // NOI18N
283: return getBundleString("MSG_StandardJDKSource"); // NOI18N
284: if (className.startsWith("org.netbeans.")) // NOI18N
285: return getBundleString("MSG_NetBeansSource"); // NOI18N
286: }
287: return NbBundle.getMessage(ClassPathUtils.class,
288: "MSG_UnspecifiedSource");
289: } else {
290: return classSource.getEntries().iterator().next()
291: .getDisplayName();
292: }
293: }
294:
295: static String getBundleString(String key) {
296: return NbBundle.getBundle(ClassPathUtils.class).getString(key);
297: }
298:
299: // -----
300: // Registered class patterns for class loader type
301:
302: static class ClassLoadingType {
303: private String folderName;
304: private FileObject folder;
305: private List<ClassPattern> patterns;
306:
307: private ClassLoadingType(String folderName) {
308: this .folderName = folderName;
309: }
310: }
311:
312: private static final ClassLoadingType[] CLASS_LOADING_TYPES = {
313: SYSTEM_CLASS, SYSTEM_CLASS_WITH_PROJECT };
314:
315: private static final String CL_LAYER_BASE = "org-netbeans-modules-form/classloader/"; // NOI18N
316:
317: private static boolean isClassLoadingType(String className,
318: ClassLoadingType clType) {
319: List<ClassPattern> list = getClassPatterns(clType);
320: if (list == null) {
321: return false;
322: }
323:
324: Iterator it = list.iterator();
325: while (it.hasNext()) {
326: ClassPattern cp = (ClassPattern) it.next();
327: switch (cp.type) {
328: case (ClassPattern.CLASS):
329: if (className.equals(cp.name))
330: return true;
331: break;
332: case (ClassPattern.PACKAGE):
333: if (className.startsWith(cp.name)
334: && (className.lastIndexOf('.') <= cp.name
335: .length()))
336: return true;
337: break;
338: case (ClassPattern.PACKAGE_AND_SUBPACKAGES):
339: if (className.startsWith(cp.name))
340: return true;
341: break;
342: }
343: }
344: return false;
345: }
346:
347: private static List<ClassPattern> getClassPatterns(
348: ClassLoadingType clType) {
349: List<ClassPattern> list = clType.patterns;
350: if (list == null) {
351: list = loadClassPatterns(getClassPatternsFolder(clType));
352: clType.patterns = list;
353: }
354: return list;
355: }
356:
357: private static FileObject getClassPatternsFolder(
358: final ClassLoadingType clType) {
359: FileObject folder = clType.folder;
360: if (folder == null) {
361: folder = getClassPatternsFolder(clType.folderName);
362: if (folder == null) {
363: return null;
364: }
365: clType.folder = folder;
366: // in case of any change in files make all the patterns reload
367: folder.addFileChangeListener(new FileChangeAdapter() {
368: @Override
369: public void fileDataCreated(FileEvent ev) {
370: clType.patterns = null;
371: loaders.clear();
372: }
373:
374: @Override
375: public void fileDeleted(FileEvent ev) {
376: clType.patterns = null;
377: if (ev.getFile() == clType.folder) {
378: clType.folder.removeFileChangeListener(this );
379: clType.folder = null;
380: }
381: loaders.clear();
382: }
383: });
384: }
385: return folder;
386: }
387:
388: private static FileObject getClassPatternsFolder(String folderName) {
389: FileObject folder = null;
390: if (folderName != null) {
391: try {
392: folder = Repository.getDefault().getDefaultFileSystem()
393: .findResource(CL_LAYER_BASE + folderName);
394: } catch (Exception ex) {
395: ErrorManager.getDefault().notify(
396: ErrorManager.INFORMATIONAL, ex);
397: }
398: }
399: return folder;
400: }
401:
402: private static List<ClassPattern> loadClassPatterns(
403: FileObject folder) {
404: List<ClassPattern> list = new ArrayList<ClassPattern>();
405: if (folder == null)
406: return list;
407:
408: FileObject[] files = folder.getChildren();
409: for (int i = 0; i < files.length; i++) {
410: try {
411: BufferedReader r = new BufferedReader(
412: new InputStreamReader(files[i].getInputStream()));
413: String line = r.readLine();
414: while (line != null) {
415: line = line.trim();
416: if (!line.equals("")) { // NOI18N
417: ClassPattern cp;
418: if (line.endsWith("**")) { // NOI18N
419: cp = new ClassPattern(
420: line
421: .substring(0,
422: line.length() - 2),
423: ClassPattern.PACKAGE_AND_SUBPACKAGES);
424: } else if (line.endsWith("*")) { // NOI18N
425: cp = new ClassPattern(line.substring(0,
426: line.length() - 1),
427: ClassPattern.PACKAGE);
428: } else {
429: cp = new ClassPattern(line,
430: ClassPattern.CLASS);
431: }
432: list.add(cp);
433: }
434: line = r.readLine();
435: }
436: } catch (IOException ex) {
437: ErrorManager.getDefault().notify(
438: ErrorManager.INFORMATIONAL, ex);
439: }
440: }
441: return list;
442: }
443:
444: private static class ClassPattern {
445: static final int CLASS = 0;
446: static final int PACKAGE = 1;
447: static final int PACKAGE_AND_SUBPACKAGES = 2;
448: String name;
449: int type;
450:
451: ClassPattern(String name, int type) {
452: this.name = name;
453: this.type = type;
454: }
455: }
456: }
|