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: package org.netbeans.modules.ruby.railsprojects.classpath;
042:
043: import java.beans.PropertyChangeEvent;
044: import org.netbeans.api.ruby.platform.RubyInstallation;
045: import org.netbeans.modules.gsfpath.spi.classpath.ClassPathImplementation;
046: import org.netbeans.modules.gsfpath.spi.classpath.PathResourceImplementation;
047: import org.netbeans.modules.gsfpath.spi.classpath.support.ClassPathSupport;
048: import java.beans.PropertyChangeListener;
049: import java.beans.PropertyChangeSupport;
050: import java.io.File;
051: import java.net.MalformedURLException;
052: import java.net.URL;
053: import java.util.List;
054: import java.util.ArrayList;
055: import java.util.Collections;
056: import java.util.HashMap;
057: import java.util.Map;
058: import java.util.logging.Level;
059: import java.util.logging.Logger;
060: import java.util.regex.Matcher;
061: import java.util.regex.Pattern;
062: import java.util.regex.PatternSyntaxException;
063: import org.netbeans.api.ruby.platform.RubyPlatform;
064: import org.netbeans.api.ruby.platform.RubyPlatformProvider;
065: import org.netbeans.modules.gsf.LanguageRegistry;
066: import org.netbeans.modules.ruby.platform.gems.GemManager;
067: import org.netbeans.modules.ruby.railsprojects.RailsProjectUtil;
068: import org.netbeans.modules.ruby.spi.project.support.rake.PropertyEvaluator;
069: import org.openide.util.Exceptions;
070: import org.openide.util.WeakListeners;
071:
072: final class BootClassPathImplementation implements
073: ClassPathImplementation, PropertyChangeListener {
074:
075: private static final Logger LOGGER = Logger
076: .getLogger(BootClassPathImplementation.class.getName());
077:
078: // Flag for controlling last-minute workaround for issue #120231
079: private static final boolean INCLUDE_NONLIBPLUGINS = Boolean
080: .getBoolean("ruby.include_nonlib_plugins");
081:
082: private static final Pattern GEM_EXCLUDE_FILTER;
083: private static final Pattern GEM_INCLUDE_FILTER;
084: static {
085: String userExcludes = System
086: .getProperty("rails.prj.excludegems");
087: if (userExcludes == null || "none".equals(userExcludes)) {
088: GEM_EXCLUDE_FILTER = null;
089: } else {
090: Pattern p;
091: try {
092: p = Pattern.compile(userExcludes);
093: } catch (PatternSyntaxException pse) {
094: Logger.getAnonymousLogger().log(Level.WARNING,
095: "Invalid regular expression: " + userExcludes);
096: Logger.getAnonymousLogger().log(Level.WARNING,
097: pse.toString());
098: p = null;
099: }
100: GEM_EXCLUDE_FILTER = p;
101: }
102: String userIncludes = System
103: .getProperty("rails.prj.includegems");
104: if (userIncludes == null || "all".equals(userIncludes)) {
105: GEM_INCLUDE_FILTER = null;
106: } else {
107: Pattern p;
108: try {
109: p = Pattern.compile(userIncludes);
110: } catch (PatternSyntaxException pse) {
111: Logger.getAnonymousLogger().log(Level.WARNING,
112: "Invalid regular expression: " + userIncludes);
113: Logger.getAnonymousLogger().log(Level.WARNING,
114: pse.toString());
115: p = null;
116: }
117: GEM_INCLUDE_FILTER = p;
118: }
119: }
120:
121: // private static final String PLATFORM_ACTIVE = "platform.active"; //NOI18N
122: // private static final String ANT_NAME = "platform.ant.name"; //NOI18N
123: // private static final String J2SE = "j2se"; //NOI18N
124:
125: private File projectDirectory;
126: private final PropertyEvaluator evaluator;
127: // private JavaPlatformManager platformManager;
128: //name of project active platform
129: private String activePlatformName;
130: //active platform is valid (not broken reference)
131: private boolean isActivePlatformValid;
132: private List<PathResourceImplementation> resourcesCache;
133: private PropertyChangeSupport support = new PropertyChangeSupport(
134: this );
135:
136: public BootClassPathImplementation(File projectDirectory,
137: PropertyEvaluator evaluator) {
138: this .projectDirectory = projectDirectory;
139: assert evaluator != null;
140: this .evaluator = evaluator;
141: evaluator.addPropertyChangeListener(WeakListeners
142: .propertyChange(this , evaluator));
143: }
144:
145: public synchronized List<PathResourceImplementation> getResources() {
146: if (this .resourcesCache == null) {
147: // JavaPlatform jp = findActivePlatform ();
148: // if (jp != null) {
149: //TODO: May also listen on CP, but from Platform it should be fixed.
150: List<PathResourceImplementation> result = new ArrayList<PathResourceImplementation>();
151: RubyPlatform platform = new RubyPlatformProvider(evaluator)
152: .getPlatform();
153: if (platform == null) {
154: LOGGER.severe("Cannot resolve platform for project: "
155: + projectDirectory);
156: }
157:
158: if (!platform.hasRubyGemsInstalled()) {
159: LOGGER
160: .fine("Not RubyGems installed, returning empty result");
161: return Collections.emptyList();
162: }
163:
164: // the rest of code depend on RubyGems to be installed
165:
166: GemManager gemManager = platform.getGemManager();
167: assert gemManager != null : "not null when RubyGems are installed";
168: Map<String, URL> gemUrls = gemManager.getGemUrls();
169: Map<String, String> gemVersions = gemManager
170: .getGemVersions();
171:
172: for (URL url : gemManager.getNonGemLoadPath()) {
173: result.add(ClassPathSupport.createResource(url));
174: }
175:
176: // Perhaps I can filter vendor/rails iff the installation contains it
177:
178: Pattern includeFilter = GEM_INCLUDE_FILTER;
179: Pattern excludeFilter = GEM_EXCLUDE_FILTER;
180:
181: String include = evaluator.getProperty("ruby.includegems");
182: String exclude = evaluator.getProperty("ruby.excludegems");
183: try {
184: if (include != null) {
185: includeFilter = Pattern.compile(include);
186: }
187: if (exclude != null) {
188: excludeFilter = Pattern.compile(exclude);
189: }
190: } catch (PatternSyntaxException pse) {
191: Exceptions.printStackTrace(pse);
192: }
193:
194: // Add in all the vendor/ paths, if any
195: File vendor = new File(projectDirectory, "vendor");
196: if (vendor.exists()) {
197: List<URL> vendorPlugins = getVendorPlugins(vendor);
198: for (URL url : vendorPlugins) {
199: result.add(ClassPathSupport.createResource(url));
200: }
201:
202: // TODO - handle multiple gem versions in the same repository
203: List<URL> combinedGems = mergeVendorGems(vendor,
204: new HashMap<String, String>(gemVersions),
205: new HashMap<String, URL>(gemUrls));
206: for (URL url : combinedGems) {
207: if (includeFilter != null) {
208: String gem = getGemName(url);
209: if (includeFilter.matcher(gem).find()) {
210: result.add(ClassPathSupport
211: .createResource(url));
212: continue;
213: }
214: }
215:
216: if (excludeFilter != null) {
217: String gem = getGemName(url);
218: if (excludeFilter.matcher(gem).find()) {
219: continue;
220: }
221: }
222:
223: result.add(ClassPathSupport.createResource(url));
224: }
225:
226: } else {
227: for (URL url : gemUrls.values()) {
228: if (includeFilter != null) {
229: String gem = getGemName(url);
230: if (includeFilter.matcher(gem).find()) {
231: result.add(ClassPathSupport
232: .createResource(url));
233: continue;
234: }
235: }
236:
237: if (excludeFilter != null) {
238: String gem = getGemName(url);
239: if (excludeFilter.matcher(gem).find()) {
240: continue;
241: }
242: }
243:
244: result.add(ClassPathSupport.createResource(url));
245: }
246: }
247:
248: // Additional libraries - such as the JavaScript ones
249: for (URL url : LanguageRegistry.getInstance()
250: .getLibraryUrls()) {
251: result.add(ClassPathSupport.createResource(url));
252: }
253:
254: resourcesCache = Collections.unmodifiableList(result);
255: // XXX
256: // RubyInstallation.getInstance().removePropertyChangeListener(this);
257: // RubyInstallation.getInstance().addPropertyChangeListener(this);
258: }
259: return this .resourcesCache;
260: }
261:
262: private static String getGemName(URL gemUrl) {
263: String urlString = gemUrl.getFile();
264: if (urlString.endsWith("/lib/")) {
265: urlString = urlString
266: .substring(urlString.lastIndexOf('/', urlString
267: .length() - 6) + 1, urlString.length() - 5);
268: }
269:
270: return urlString;
271: }
272:
273: public void addPropertyChangeListener(
274: PropertyChangeListener listener) {
275: this .support.addPropertyChangeListener(listener);
276: }
277:
278: public void removePropertyChangeListener(
279: PropertyChangeListener listener) {
280: this .support.removePropertyChangeListener(listener);
281: }
282:
283: // private JavaPlatform findActivePlatform () {
284: // if (this.platformManager == null) {
285: // this.platformManager = JavaPlatformManager.getDefault();
286: // this.platformManager.addPropertyChangeListener(WeakListeners.propertyChange(this, this.platformManager));
287: // }
288: // this.activePlatformName = evaluator.getProperty(PLATFORM_ACTIVE);
289: // final JavaPlatform activePlatform = RubyProjectUtil.getActivePlatform (this.activePlatformName);
290: // this.isActivePlatformValid = activePlatform != null;
291: // return activePlatform;
292: // }
293: //
294: public void propertyChange(PropertyChangeEvent evt) {
295: if (evt.getSource() == RubyInstallation.getInstance()
296: && evt.getPropertyName().equals("roots")) {
297: resetCache();
298: }
299: // if (evt.getSource() == this.evaluator && evt.getPropertyName().equals(PLATFORM_ACTIVE)) {
300: // //Active platform was changed
301: // resetCache ();
302: // }
303: // else if (evt.getSource() == this.platformManager && JavaPlatformManager.PROP_INSTALLED_PLATFORMS.equals(evt.getPropertyName()) && activePlatformName != null) {
304: // //Platform definitions were changed, check if the platform was not resolved or deleted
305: // if (this.isActivePlatformValid) {
306: // if (RubyProjectUtil.getActivePlatform (this.activePlatformName) == null) {
307: // //the platform was not removed
308: // this.resetCache();
309: // }
310: // }
311: // else {
312: // if (RubyProjectUtil.getActivePlatform (this.activePlatformName) != null) {
313: // this.resetCache();
314: // }
315: // }
316: // }
317: }
318:
319: /**
320: * Resets the cache and firesPropertyChange
321: */
322: private void resetCache() {
323: synchronized (this ) {
324: resourcesCache = null;
325: }
326: support.firePropertyChange(PROP_RESOURCES, null, null);
327: }
328:
329: private List<URL> mergeVendorGems(File vendorFile,
330: Map<String, String> gemVersions, Map<String, URL> gemUrls) {
331: chooseGems(vendorFile.listFiles(), gemVersions, gemUrls);
332:
333: return new ArrayList<URL>(gemUrls.values());
334: }
335:
336: private static void chooseGems(File[] gems,
337: Map<String, String> gemVersions, Map<String, URL> gemUrls) {
338: // Try to match foo-1.2.3, foo-bar-1.2.3, foo-bar-1.2.3-ruby
339: Pattern GEM_FILE_PATTERN = Pattern
340: .compile("(\\S|-)+-((\\d+)\\.(\\d+)\\.(\\d+))(-\\S+)?"); // NOI18N
341:
342: for (File f : gems) {
343: if (!f.isDirectory()) {
344: continue;
345: }
346:
347: String n = f.getName();
348:
349: if ("plugins".equals(n)) {
350: // Special cased separately
351: continue;
352: }
353:
354: if ("rails".equals(n)) { // NOI18N
355: // Special case - what do we do here?
356: chooseRails(f.listFiles(), gemVersions, gemUrls);
357: continue;
358: }
359:
360: if ("gems".equals(n) || "gems-jruby".equals(n)) { // NOI18N
361: // Support both having gems in the vendor/ top directory as well as in a gems/ subdirectory }
362: chooseGems(f.listFiles(), gemVersions, gemUrls);
363: }
364:
365: if (n.indexOf('-') == -1) {
366: continue;
367: }
368:
369: Matcher m = GEM_FILE_PATTERN.matcher(n);
370: if (!m.matches()) {
371: continue;
372: }
373:
374: File lib = new File(f, "lib");
375: if (lib.exists()) {
376: try {
377: URL url = lib.toURI().toURL();
378: String name = m.group(1);
379: String version = m.group(2);
380: addGem(gemVersions, gemUrls, name, version, url);
381: } catch (MalformedURLException mufe) {
382: Exceptions.printStackTrace(mufe);
383: }
384: }
385: }
386: }
387:
388: private static void addGem(Map<String, String> gemVersions,
389: Map<String, URL> gemUrls, String name, String version,
390: URL url) {
391: if (!gemVersions.containsKey(name)
392: || GemManager.compareGemVersions(version, gemVersions
393: .get(name)) > 0) {
394: gemVersions.put(name, version);
395: gemUrls.put(name, url);
396: }
397: }
398:
399: private static void chooseRails(File[] gems,
400: Map<String, String> gemVersions, Map<String, URL> gemUrls) {
401: for (File f : gems) {
402: if (!f.isDirectory()) {
403: continue;
404: }
405:
406: String name = f.getName();
407: // actionpack/lib/action_pack/version.r
408: String middleName = name;
409: if (name.indexOf('_') == -1) {
410: if (name.startsWith("action")
411: || name.startsWith("active")) {
412: middleName = name.substring(0, 6) + "_"
413: + name.substring(6);
414: }
415: }
416: File lib = new File(f, "lib");
417: if (lib.exists()) {
418: File versionFile = new File(lib, middleName
419: + File.separator + "version.rb");
420: if (versionFile.exists()) {
421: String version = RailsProjectUtil
422: .getVersionString(versionFile);
423: if (version != null) {
424: try {
425: URL url = lib.toURI().toURL();
426: addGem(gemVersions, gemUrls, name, version,
427: url);
428: } catch (MalformedURLException mufe) {
429: Exceptions.printStackTrace(mufe);
430: }
431: }
432: }
433: }
434: }
435: }
436:
437: private List<URL> getVendorPlugins(File vendor) {
438: assert vendor != null;
439:
440: File plugins = new File(vendor, "plugins");
441: if (!plugins.exists()) {
442: return Collections.emptyList();
443: }
444:
445: List<URL> urls = new ArrayList<URL>();
446:
447: for (File f : plugins.listFiles()) {
448: File lib = new File(f, "lib");
449: if (INCLUDE_NONLIBPLUGINS) {
450: lib = f;
451: }
452: if (!lib.exists()) {
453: continue;
454: }
455: // TODO - preindex via version lookup somehow?
456: try {
457: URL url = lib.toURI().toURL();
458: urls.add(url);
459: // TODO - find versions for the plugins?
460: //Map<String, File> nameMap = gemFiles.get(name);
461: //if (nameMap != null) {
462: // String version = nameMap.keySet().iterator().next();
463: // RubyInstallation.getInstance().setGemRoot(url, name+ "-" + version);
464: //}
465: } catch (MalformedURLException ex) {
466: Exceptions.printStackTrace(ex);
467: }
468: }
469:
470: return urls;
471: }
472: }
|