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