001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2008 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-2008 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.ruby.rubyproject;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.beans.PropertyChangeSupport;
047: import java.io.File;
048: import java.io.IOException;
049: import java.util.Collections;
050: import javax.swing.Icon;
051: import javax.swing.ImageIcon;
052: import org.netbeans.modules.gsfpath.api.classpath.ClassPath;
053: import org.netbeans.modules.gsfpath.api.classpath.GlobalPathRegistry;
054: import org.netbeans.api.project.Project;
055: import org.netbeans.api.project.ProjectInformation;
056: import org.netbeans.api.project.ProjectManager;
057: import org.netbeans.api.ruby.platform.RubyPlatformProvider;
058: import org.netbeans.modules.ruby.rubyproject.classpath.ClassPathProviderImpl;
059: import org.netbeans.modules.ruby.rubyproject.queries.RubyProjectEncodingQueryImpl;
060: import org.netbeans.modules.ruby.rubyproject.ui.RubyLogicalViewProvider;
061: import org.netbeans.modules.ruby.rubyproject.ui.customizer.CustomizerProviderImpl;
062: import org.netbeans.modules.ruby.rubyproject.ui.customizer.RubyProjectProperties;
063: import org.netbeans.spi.project.AuxiliaryConfiguration;
064: import org.netbeans.spi.project.SubprojectProvider;
065: import org.netbeans.spi.project.support.LookupProviderSupport;
066: import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectEvent;
067: import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectHelper;
068: import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectListener;
069: import org.netbeans.modules.ruby.spi.project.support.rake.FilterPropertyProvider;
070: import org.netbeans.modules.ruby.spi.project.support.rake.GeneratedFilesHelper;
071: import org.netbeans.modules.ruby.spi.project.support.rake.ProjectXmlSavedHook;
072: import org.netbeans.modules.ruby.spi.project.support.rake.PropertyEvaluator;
073: import org.netbeans.modules.ruby.spi.project.support.rake.PropertyProvider;
074: import org.netbeans.modules.ruby.spi.project.support.rake.PropertyUtils;
075: import org.netbeans.modules.ruby.spi.project.support.rake.ReferenceHelper;
076: import org.netbeans.spi.project.ui.PrivilegedTemplates;
077: import org.netbeans.spi.project.ui.ProjectOpenedHook;
078: import org.netbeans.spi.project.ui.RecommendedTemplates;
079: import org.netbeans.spi.project.ui.support.UILookupMergerSupport;
080: import org.openide.ErrorManager;
081: import org.openide.filesystems.FileObject;
082: import org.openide.filesystems.FileUtil;
083: import org.openide.util.Lookup;
084: import org.openide.util.Mutex;
085: import org.openide.util.Utilities;
086: import org.openide.util.lookup.Lookups;
087: import org.w3c.dom.Element;
088: import org.w3c.dom.Text;
089: import org.w3c.dom.Node;
090: import org.w3c.dom.NodeList;
091:
092: /**
093: * Represents one plain Ruby project.
094: * @author Jesse Glick, et al.
095: */
096: public final class RubyProject implements Project, RakeProjectListener {
097:
098: /**
099: * Ruby package root sources type.
100: * @see org.netbeans.api.project.Sources
101: */
102: public static final String SOURCES_TYPE_RUBY = "ruby"; // NOI18N
103:
104: private static final Icon Ruby_PROJECT_ICON = new ImageIcon(
105: Utilities
106: .loadImage("org/netbeans/modules/ruby/rubyproject/ui/resources/jruby.png")); // NOI18N
107:
108: private final AuxiliaryConfiguration aux;
109: private final RakeProjectHelper helper;
110: private final PropertyEvaluator eval;
111: private final ReferenceHelper refHelper;
112: private final GeneratedFilesHelper genFilesHelper;
113: private final Lookup lookup;
114: private final UpdateHelper updateHelper;
115: // private MainClassUpdater mainClassUpdater;
116: private SourceRoots sourceRoots;
117: private SourceRoots testRoots;
118:
119: RubyProject(RakeProjectHelper helper) throws IOException {
120: this .helper = helper;
121: eval = createEvaluator();
122: aux = helper.createAuxiliaryConfiguration();
123: refHelper = new ReferenceHelper(helper, aux, eval);
124: genFilesHelper = new GeneratedFilesHelper(helper);
125: this .updateHelper = new UpdateHelper(this , this .helper,
126: this .aux, this .genFilesHelper, UpdateHelper
127: .createDefaultNotifier());
128:
129: lookup = createLookup(aux);
130: helper.addRakeProjectListener(this );
131: }
132:
133: /**
134: * Returns the project directory
135: * @return the directory the project is located in
136: */
137: public FileObject getProjectDirectory() {
138: return helper.getProjectDirectory();
139: }
140:
141: @Override
142: public String toString() {
143: return "RubyProject["
144: + FileUtil.getFileDisplayName(getProjectDirectory())
145: + "]"; // NOI18N
146: }
147:
148: private PropertyEvaluator createEvaluator() {
149: // It is currently safe to not use the UpdateHelper for PropertyEvaluator; UH.getProperties() delegates to APH
150: // Adapted from APH.getStandardPropertyEvaluator (delegates to ProjectProperties):
151: PropertyEvaluator baseEval1 = PropertyUtils
152: .sequentialPropertyEvaluator(
153: helper.getStockPropertyPreprovider(),
154: helper
155: .getPropertyProvider(RubyConfigurationProvider.CONFIG_PROPS_PATH));
156: PropertyEvaluator baseEval2 = PropertyUtils
157: .sequentialPropertyEvaluator(
158: helper.getStockPropertyPreprovider(),
159: helper
160: .getPropertyProvider(RakeProjectHelper.PRIVATE_PROPERTIES_PATH));
161: return PropertyUtils
162: .sequentialPropertyEvaluator(
163: helper.getStockPropertyPreprovider(),
164: helper
165: .getPropertyProvider(RubyConfigurationProvider.CONFIG_PROPS_PATH),
166: new ConfigPropertyProvider(baseEval1,
167: "nbproject/private/configs", helper), // NOI18N
168: helper
169: .getPropertyProvider(RakeProjectHelper.PRIVATE_PROPERTIES_PATH),
170: PropertyUtils.userPropertiesProvider(baseEval2,
171: "user.properties.file", FileUtil
172: .toFile(getProjectDirectory())), // NOI18N
173: new ConfigPropertyProvider(baseEval1,
174: "nbproject/configs", helper), // NOI18N
175: helper
176: .getPropertyProvider(RakeProjectHelper.PROJECT_PROPERTIES_PATH));
177: }
178:
179: private static final class ConfigPropertyProvider extends
180: FilterPropertyProvider implements PropertyChangeListener {
181: private final PropertyEvaluator baseEval;
182: private final String prefix;
183: private final RakeProjectHelper helper;
184:
185: public ConfigPropertyProvider(PropertyEvaluator baseEval,
186: String prefix, RakeProjectHelper helper) {
187: super (computeDelegate(baseEval, prefix, helper));
188: this .baseEval = baseEval;
189: this .prefix = prefix;
190: this .helper = helper;
191: baseEval.addPropertyChangeListener(this );
192: }
193:
194: public void propertyChange(PropertyChangeEvent ev) {
195: if (RubyConfigurationProvider.PROP_CONFIG.equals(ev
196: .getPropertyName())) {
197: setDelegate(computeDelegate(baseEval, prefix, helper));
198: }
199: }
200:
201: private static PropertyProvider computeDelegate(
202: PropertyEvaluator baseEval, String prefix,
203: RakeProjectHelper helper) {
204: String config = baseEval
205: .getProperty(RubyConfigurationProvider.PROP_CONFIG);
206: if (config != null) {
207: return helper.getPropertyProvider(prefix + "/" + config
208: + ".properties"); // NOI18N
209: } else {
210: return PropertyUtils.fixedPropertyProvider(Collections
211: .<String, String> emptyMap());
212: }
213: }
214: }
215:
216: public PropertyEvaluator evaluator() {
217: return eval;
218: }
219:
220: ReferenceHelper getReferenceHelper() {
221: return this .refHelper;
222: }
223:
224: public UpdateHelper getUpdateHelper() {
225: return this .updateHelper;
226: }
227:
228: public Lookup getLookup() {
229: return lookup;
230: }
231:
232: public RakeProjectHelper getRakeProjectHelper() {
233: return helper;
234: }
235:
236: private Lookup createLookup(AuxiliaryConfiguration aux) {
237: SubprojectProvider spp = refHelper.createSubprojectProvider();
238: Lookup base = Lookups
239: .fixed(new Object[] {
240: new Info(),
241: aux,
242: helper.createCacheDirectoryProvider(),
243: spp,
244: new RubyActionProvider(this , this .updateHelper),
245: new RubyLogicalViewProvider(this ,
246: this .updateHelper, evaluator(), spp,
247: refHelper),
248: new ClassPathProviderImpl(this .helper,
249: evaluator(), getSourceRoots(),
250: getTestSourceRoots()), //Does not use APH to get/put properties/cfgdata
251: // new RubyCustomizerProvider(this, this.updateHelper, evaluator(), refHelper),
252: new CustomizerProviderImpl(this ,
253: this .updateHelper, evaluator(),
254: refHelper, this .genFilesHelper),
255: new ProjectXmlSavedHookImpl(),
256: new ProjectOpenedHookImpl(),
257: new RubySources(this .helper, evaluator(),
258: getSourceRoots(), getTestSourceRoots()),
259: new RubySharabilityQuery(this .helper,
260: evaluator(), getSourceRoots(),
261: getTestSourceRoots()), //Does not use APH to get/put properties/cfgdata
262: new RubyFileBuiltQuery(this .helper,
263: evaluator(), getSourceRoots(),
264: getTestSourceRoots()), //Does not use APH to get/put properties/cfgdata
265: new RecommendedTemplatesImpl(this .updateHelper),
266: this , // never cast an externally obtained Project to RubyProject - use lookup instead
267: new RubyProjectOperations(this ),
268: new RubyConfigurationProvider(this ),
269: UILookupMergerSupport
270: .createPrivilegedTemplatesMerger(),
271: UILookupMergerSupport
272: .createRecommendedTemplatesMerger(),
273: LookupProviderSupport.createSourcesMerger(),
274: new RubyProjectEncodingQueryImpl(evaluator()),
275: evaluator(), new RubyFileLocator(null, this ),
276: new RubyPlatformProvider(evaluator()) });
277: return LookupProviderSupport
278: .createCompositeLookup(base,
279: "Projects/org-netbeans-modules-ruby-rubyproject/Lookup"); //NOI18N
280: }
281:
282: public void configurationXmlChanged(RakeProjectEvent ev) {
283: if (ev.getPath().equals(RakeProjectHelper.PROJECT_XML_PATH)) {
284: // Could be various kinds of changes, but name & displayName might have changed.
285: Info info = (Info) getLookup().lookup(
286: ProjectInformation.class);
287: info.firePropertyChange(ProjectInformation.PROP_NAME);
288: info
289: .firePropertyChange(ProjectInformation.PROP_DISPLAY_NAME);
290: }
291: }
292:
293: public void propertiesChanged(RakeProjectEvent ev) {
294: // currently ignored (probably better to listen to evaluator() if you need to)
295: }
296:
297: // Package private methods -------------------------------------------------------------
298:
299: /**
300: * Returns the source roots of this project
301: * @return project's source roots
302: */
303: public synchronized SourceRoots getSourceRoots() {
304: if (this .sourceRoots == null) { //Local caching, no project metadata access
305: this .sourceRoots = new SourceRoots(this .updateHelper,
306: evaluator(), getReferenceHelper(), "source-roots",
307: false, "src.{0}{1}.dir"); //NOI18N
308: }
309: return this .sourceRoots;
310: }
311:
312: public synchronized SourceRoots getTestSourceRoots() {
313: if (this .testRoots == null) { //Local caching, no project metadata access
314: this .testRoots = new SourceRoots(this .updateHelper,
315: evaluator(), getReferenceHelper(), "test-roots",
316: true, "test.{0}{1}.dir"); //NOI18N
317: }
318: return this .testRoots;
319: }
320:
321: File getTestClassesDirectory() {
322: String testClassesDir = evaluator().getProperty(
323: RubyProjectProperties.BUILD_TEST_CLASSES_DIR);
324: if (testClassesDir == null) {
325: return null;
326: }
327: return helper.resolveFile(testClassesDir);
328: }
329:
330: // Currently unused (but see #47230):
331: /** Store configured project name. */
332: public void setName(final String name) {
333: ProjectManager.mutex().writeAccess(new Mutex.Action<Void>() {
334: public Void run() {
335: Element data = helper.getPrimaryConfigurationData(true);
336: // XXX replace by XMLUtil when that has findElement, findText, etc.
337: NodeList nl = data
338: .getElementsByTagNameNS(
339: RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,
340: "name"); // NOI18N
341: Element nameEl;
342: if (nl.getLength() == 1) {
343: nameEl = (Element) nl.item(0);
344: NodeList deadKids = nameEl.getChildNodes();
345: while (deadKids.getLength() > 0) {
346: nameEl.removeChild(deadKids.item(0));
347: }
348: } else {
349: nameEl = data
350: .getOwnerDocument()
351: .createElementNS(
352: RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,
353: "name"); // NOI18N
354: data.insertBefore(nameEl, /* OK if null */data
355: .getChildNodes().item(0));
356: }
357: nameEl.appendChild(data.getOwnerDocument()
358: .createTextNode(name));
359: helper.putPrimaryConfigurationData(data, true);
360: return null;
361: }
362: });
363: }
364:
365: // Private innerclasses ----------------------------------------------------------------
366:
367: private final class Info implements ProjectInformation {
368:
369: private final PropertyChangeSupport pcs = new PropertyChangeSupport(
370: this );
371:
372: Info() {
373: }
374:
375: void firePropertyChange(String prop) {
376: pcs.firePropertyChange(prop, null, null);
377: }
378:
379: public String getName() {
380: return PropertyUtils
381: .getUsablePropertyName(getDisplayName());
382: }
383:
384: public String getDisplayName() {
385: return ProjectManager.mutex().readAccess(
386: new Mutex.Action<String>() {
387: public String run() {
388: Element data = updateHelper
389: .getPrimaryConfigurationData(true);
390: // XXX replace by XMLUtil when that has findElement, findText, etc.
391: NodeList nl = data
392: .getElementsByTagNameNS(
393: RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,
394: "name"); // NOI18N
395: if (nl.getLength() == 1) {
396: nl = nl.item(0).getChildNodes();
397: if (nl.getLength() == 1
398: && nl.item(0).getNodeType() == Node.TEXT_NODE) {
399: return ((Text) nl.item(0))
400: .getNodeValue();
401: }
402: }
403: return "???"; // NOI18N
404: }
405: });
406: }
407:
408: public Icon getIcon() {
409: return Ruby_PROJECT_ICON;
410: }
411:
412: public Project getProject() {
413: return RubyProject.this ;
414: }
415:
416: public void addPropertyChangeListener(
417: PropertyChangeListener listener) {
418: pcs.addPropertyChangeListener(listener);
419: }
420:
421: public void removePropertyChangeListener(
422: PropertyChangeListener listener) {
423: pcs.removePropertyChangeListener(listener);
424: }
425:
426: }
427:
428: private final class ProjectXmlSavedHookImpl extends
429: ProjectXmlSavedHook {
430:
431: ProjectXmlSavedHookImpl() {
432: }
433:
434: protected void projectXmlSaved() throws IOException {
435: //May be called by {@link AuxiliaryConfiguration#putConfigurationFragment}
436: //which didn't affect the j2seproject
437: /*
438: if (updateHelper.isCurrent()) {
439: //Refresh build-impl.xml only for j2seproject/2
440: genFilesHelper.refreshBuildScript(
441: GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
442: RubyProject.class.getResource("resources/build-impl.xsl"),
443: false);
444: genFilesHelper.refreshBuildScript(
445: GeneratedFilesHelper.BUILD_XML_PATH,
446: RubyProject.class.getResource("resources/build.xsl"),
447: false);
448: }
449: */
450: }
451:
452: }
453:
454: static boolean bootRegistered = false;
455:
456: private final class ProjectOpenedHookImpl extends ProjectOpenedHook {
457:
458: ProjectOpenedHookImpl() {
459: }
460:
461: protected void projectOpened() {
462: // Check up on build scripts.
463: /*
464: try {
465: if (updateHelper.isCurrent()) {
466: //Refresh build-impl.xml only for j2seproject/2
467: genFilesHelper.refreshBuildScript(
468: GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
469: RubyProject.class.getResource("resources/build-impl.xsl"),
470: true);
471: genFilesHelper.refreshBuildScript(
472: GeneratedFilesHelper.BUILD_XML_PATH,
473: RubyProject.class.getResource("resources/build.xsl"),
474: true);
475: }
476: } catch (IOException e) {
477: ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
478: }
479: */
480: // register project's classpaths to GlobalPathRegistry
481: ClassPathProviderImpl cpProvider = lookup
482: .lookup(ClassPathProviderImpl.class);
483: if (!bootRegistered) {
484: GlobalPathRegistry
485: .getDefault()
486: .register(
487: ClassPath.BOOT,
488: cpProvider
489: .getProjectClassPaths(ClassPath.BOOT));
490: bootRegistered = true;
491: }
492: GlobalPathRegistry.getDefault().register(ClassPath.SOURCE,
493: cpProvider.getProjectClassPaths(ClassPath.SOURCE));
494: //GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, cpProvider.getProjectClassPaths(ClassPath.COMPILE));
495:
496: //register updater of main.class
497: //the updater is active only on the opened projects
498:
499: /*
500: // Make it easier to run headless builds on the same machine at least.
501: ProjectManager.mutex().writeAccess(new Mutex.Action<Void>() {
502: public Void run() {
503: EditableProperties ep = updateHelper.getProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH);
504: File buildProperties = new File(System.getProperty("netbeans.user"), "build.properties"); // NOI18N
505: ep.setProperty("user.properties.file", buildProperties.getAbsolutePath()); //NOI18N
506: updateHelper.putProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH, ep);
507: try {
508: ProjectManager.getDefault().saveProject(RubyProject.this);
509: } catch (IOException e) {
510: ErrorManager.getDefault().notify(e);
511: }
512: return null;
513: }
514: });
515: RubyLogicalViewProvider physicalViewProvider = (RubyLogicalViewProvider)
516: RubyProject.this.getLookup().lookup (RubyLogicalViewProvider.class);
517: if (physicalViewProvider != null && physicalViewProvider.hasBrokenLinks()) {
518: BrokenReferencesSupport.showAlert();
519: }
520: */
521: }
522:
523: protected void projectClosed() {
524: // Probably unnecessary, but just in case:
525: try {
526: ProjectManager.getDefault().saveProject(
527: RubyProject.this );
528: } catch (IOException e) {
529: ErrorManager.getDefault().notify(e);
530: }
531:
532: // unregister project's classpaths to GlobalPathRegistry
533: ClassPathProviderImpl cpProvider = lookup
534: .lookup(ClassPathProviderImpl.class);
535: //GlobalPathRegistry.getDefault().unregister(ClassPath.BOOT, cpProvider.getProjectClassPaths(ClassPath.BOOT));
536: GlobalPathRegistry.getDefault().unregister(
537: ClassPath.SOURCE,
538: cpProvider.getProjectClassPaths(ClassPath.SOURCE));
539: //GlobalPathRegistry.getDefault().unregister(ClassPath.COMPILE, cpProvider.getProjectClassPaths(ClassPath.COMPILE));
540:
541: //XXX: to compile workaround
542: // if (mainClassUpdater != null) {
543: // mainClassUpdater.unregister ();
544: // mainClassUpdater = null;
545: // }
546:
547: }
548:
549: }
550:
551: private static final class RecommendedTemplatesImpl implements
552: RecommendedTemplates, PrivilegedTemplates {
553:
554: RecommendedTemplatesImpl(UpdateHelper helper) {
555: this .helper = helper;
556: }
557:
558: private final UpdateHelper helper;
559:
560: // List of primarily supported templates
561:
562: private static final String[] APPLICATION_TYPES = new String[] {
563: "ruby", // NOI18N
564: "XML", // NOI18N
565: "simple-files" // NOI18N
566: };
567:
568: private static final String[] PRIVILEGED_NAMES = new String[] {
569: "Templates/Ruby/main.rb", // NOI18N
570: "Templates/Ruby/test.rb", // NOI18N
571: "Templates/Ruby/class.rb", // NOI18N
572: "Templates/Ruby/module.rb", // NOI18N
573: "Templates/Ruby/suite.rb", // NOI18N
574: "Templates/Ruby/rspec.rb", // NOI18N
575: };
576:
577: public String[] getRecommendedTypes() {
578: return APPLICATION_TYPES;
579: }
580:
581: public String[] getPrivilegedTemplates() {
582: return PRIVILEGED_NAMES;
583: }
584:
585: }
586: }
|