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.apisupport.project.ui.customizer;
043:
044: import java.io.File;
045: import java.io.IOException;
046: import java.util.ArrayList;
047: import java.util.Arrays;
048: import java.util.Collection;
049: import java.util.HashSet;
050: import java.util.Iterator;
051: import java.util.List;
052: import java.util.Set;
053: import org.netbeans.api.project.Project;
054: import org.netbeans.api.project.ProjectManager;
055: import org.netbeans.api.project.ProjectUtils;
056: import org.netbeans.api.queries.CollocationQuery;
057: import org.netbeans.modules.apisupport.project.NbModuleProject;
058: import org.netbeans.modules.apisupport.project.NbModuleProjectGenerator;
059: import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
060: import org.netbeans.modules.apisupport.project.ProjectXMLManager;
061: import org.netbeans.modules.apisupport.project.SuiteProvider;
062: import org.netbeans.modules.apisupport.project.Util;
063: import org.netbeans.modules.apisupport.project.suite.SuiteProject;
064: import org.netbeans.spi.project.SubprojectProvider;
065: import org.netbeans.spi.project.support.ant.EditableProperties;
066: import org.netbeans.spi.project.support.ant.PropertyUtils;
067: import org.openide.ErrorManager;
068: import org.openide.filesystems.FileObject;
069: import org.openide.filesystems.FileSystem;
070: import org.openide.filesystems.FileUtil;
071: import org.openide.util.Mutex;
072: import org.openide.util.MutexException;
073: import org.openide.util.NbCollections;
074:
075: /**
076: * Utility methods for miscellaneous suite module operations like moving its
077: * subModules between individual suites, removing subModules, adding and other
078: * handy methods.<br>
079: * Note that some of the methods may acquire {@link ProjectManager#mutex} read
080: * or write access. See javadoc to individual methods.
081: *
082: *
083: * @author Martin Krauskopf
084: */
085: public final class SuiteUtils {
086:
087: // XXX also match "${dir}/somedir/${anotherdir}"
088: private static final String ANT_PURE_PROPERTY_REFERENCE_REGEXP = "\\$\\{\\p{Graph}+\\}"; // NOI18N
089:
090: private static final String PRIVATE_PLATFORM_PROPERTIES = "nbproject/private/platform-private.properties"; // NOI18N
091:
092: static final String MODULES_PROPERTY = "modules"; // NOI18N
093:
094: private final SuiteProperties suiteProps;
095:
096: private SuiteUtils(final SuiteProperties suiteProps) {
097: this .suiteProps = suiteProps;
098: }
099:
100: /**
101: * Gets suite components from the same suite which have set a given suite
102: * component as a dependency.
103: */
104: public static NbModuleProject[] getDependentModules(
105: final NbModuleProject suiteComponent) throws IOException {
106: final String cnb = suiteComponent.getCodeNameBase();
107: try {
108: return ProjectManager.mutex().readAccess(
109: new Mutex.ExceptionAction<NbModuleProject[]>() {
110: public NbModuleProject[] run() throws Exception {
111: Set<NbModuleProject> result = new HashSet<NbModuleProject>();
112: SuiteProject suite = SuiteUtils
113: .findSuite(suiteComponent);
114: if (suite == null) { // #88303
115: Util.err.log(ErrorManager.WARNING,
116: "Cannot find suite for the given suitecomponent ("
117: + suiteComponent + ')'); // NOI18N
118: } else {
119: for (NbModuleProject p : SuiteUtils
120: .getSubProjects(suite)) {
121: for (ModuleDependency dep : new ProjectXMLManager(
122: p).getDirectDependencies()) {
123: if (dep.getModuleEntry()
124: .getCodeNameBase()
125: .equals(cnb)) {
126: result.add(p);
127: break;
128: }
129: }
130: }
131: }
132: return result
133: .toArray(new NbModuleProject[result
134: .size()]);
135: }
136: });
137: } catch (MutexException e) {
138: throw (IOException) e.getException();
139: }
140: }
141:
142: /**
143: * Reads needed information from the given {@link SuiteProperties} and
144: * appropriately replace its all modules with new ones.
145: * <p>Acquires write access.</p>
146: */
147: public static void replaceSubModules(
148: final SuiteProperties suiteProps) throws IOException {
149: try {
150: ProjectManager.mutex().writeAccess(
151: new Mutex.ExceptionAction<Void>() {
152: public Void run() throws Exception {
153: SuiteUtils utils = new SuiteUtils(
154: suiteProps);
155: Set<NbModuleProject> currentModules = suiteProps
156: .getSubModules();
157: Set<NbModuleProject> origSubModules = suiteProps
158: .getOrigSubModules();
159:
160: // remove removed modules
161: for (NbModuleProject origModule : origSubModules) {
162: if (!currentModules
163: .contains(origModule)) {
164: Util.err.log("Removing module: "
165: + origModule); // NOI18N
166: removeModule(origModule, suiteProps);
167: }
168: }
169:
170: // add new modules
171: for (NbModuleProject currentModule : currentModules) {
172: if (SuiteUtils.contains(suiteProps
173: .getProject(), currentModule)) {
174: Util.err
175: .log("Module \""
176: + currentModule
177: + "\" or a module with the same CNB is already contained in the suite."); // NOI18N
178: continue;
179: }
180: utils.addModule(currentModule);
181: }
182: return null;
183: }
184: });
185: } catch (MutexException e) {
186: throw (IOException) e.getException();
187: }
188: }
189:
190: /**
191: * Adds the given module to the given suite if it is not already contained
192: * there. If the module is already suite component of another suite it will
193: * be appropriatelly removed from it (i.e moved from module's current suite
194: * to the given suite).
195: * <p>Acquires write access.</p>
196: */
197: public static void addModule(final SuiteProject suite,
198: final NbModuleProject project) throws IOException {
199: try {
200: ProjectManager.mutex().writeAccess(
201: new Mutex.ExceptionAction<Void>() {
202: public Void run() throws Exception {
203: final SuiteProperties suiteProps = new SuiteProperties(
204: suite, suite.getHelper(), suite
205: .getEvaluator(),
206: getSubProjects(suite));
207: if (!SuiteUtils.contains(suite, project)) {
208: SuiteUtils utils = new SuiteUtils(
209: suiteProps);
210: utils.addModule(project);
211: suiteProps.storeProperties();
212: } else {
213: Util.err
214: .log("Module \""
215: + project
216: + "\" or a module with the same CNB is already contained in the suite."); // NOI18N
217: }
218: ProjectManager.getDefault().saveProject(
219: suite);
220: return null;
221: }
222: });
223: } catch (MutexException e) {
224: throw (IOException) e.getException();
225: }
226: }
227:
228: /**
229: * Removes module from its current suite if the given module is a suite
230: * component and also remove all dependencies on this module from the suite
231: * components in the same suite.
232: * <p>Acquires write access.</p>
233: */
234: public static void removeModuleFromSuiteWithDependencies(
235: final NbModuleProject suiteComponent) throws IOException {
236: try {
237: ProjectManager.mutex().writeAccess(
238: new Mutex.ExceptionAction<Void>() {
239: public Void run() throws Exception {
240: NbModuleProject[] modules = SuiteUtils
241: .getDependentModules(suiteComponent);
242: // remove all dependencies on the being removed suite component
243: String cnb = suiteComponent
244: .getCodeNameBase();
245: for (int j = 0; j < modules.length; j++) {
246: ProjectXMLManager pxm = new ProjectXMLManager(
247: modules[j]);
248: pxm.removeDependency(cnb);
249: ProjectManager.getDefault()
250: .saveProject(modules[j]);
251: }
252: // finally remove suite component itself
253: SuiteUtils
254: .removeModuleFromSuite(suiteComponent);
255: return null;
256: }
257: });
258: } catch (MutexException e) {
259: throw (IOException) e.getException();
260: }
261: }
262:
263: /**
264: * Removes module from its current suite if the given module is a suite
265: * component. Does nothing otherwise.
266: * <p>Acquires write access.</p>
267: */
268: public static void removeModuleFromSuite(
269: final NbModuleProject suiteComponent) throws IOException {
270: try {
271: ProjectManager.mutex().writeAccess(
272: new Mutex.ExceptionAction<Void>() {
273: public Void run() throws Exception {
274: SuiteProject suite = SuiteUtils
275: .findSuite(suiteComponent);
276: if (suite != null) {
277: // detach module from its current suite
278: SuiteProperties suiteProps = new SuiteProperties(
279: suite, suite.getHelper(), suite
280: .getEvaluator(),
281: getSubProjects(suite));
282: SuiteUtils utils = new SuiteUtils(
283: suiteProps);
284: removeModule(suiteComponent, suiteProps);
285: suiteProps.storeProperties();
286: ProjectManager.getDefault()
287: .saveProject(suite);
288: } else if (Util
289: .getModuleType(suiteComponent) == NbModuleProvider.SUITE_COMPONENT) {
290: removeModule(suiteComponent, null);
291: }
292: return null;
293: }
294: });
295: } catch (MutexException e) {
296: throw (IOException) e.getException();
297: }
298: }
299:
300: private void addModule(final NbModuleProject project)
301: throws IOException, IllegalArgumentException {
302: SuiteUtils.removeModuleFromSuite(project);
303: // attach it to the new suite
304: attachSubModuleToSuite(project);
305: }
306:
307: /**
308: * Detach the given <code>subModule</code> from the suite. This actually
309: * means deleting its <em>nbproject/suite.properties</em> and eventually
310: * <em>nbproject/private/suite-private.properties</em> if it exists from
311: * <code>subModule</code>'s base directory. Also set the
312: * <code>subModule</code>'s type to standalone. Then it accordingly set the
313: * <code>suite</code>'s properties (see {@link #removeFromProperties})
314: * for details).
315: * <p>
316: * Also saves <code>subModule</code> using {@link ProjectManager#saveProject}.
317: * </p>
318: */
319: private static void removeModule(final NbModuleProject subModule,
320: final SuiteProperties/*or null*/suiteProps) {
321: NbModuleProvider.NbModuleType type = Util
322: .getModuleType(subModule);
323: assert type == NbModuleProvider.SUITE_COMPONENT : "Not a suite component: "
324: + subModule;
325: try {
326: subModule.getProjectDirectory().getFileSystem()
327: .runAtomicAction(new FileSystem.AtomicAction() {
328: public void run() throws IOException {
329: subModule.setRunInAtomicAction(true);
330: try {
331: // remove both suite properties files
332: FileObject subModuleDir = subModule
333: .getProjectDirectory();
334: FileObject fo = subModuleDir
335: .getFileObject("nbproject/suite.properties"); // NOI18N
336: if (fo != null) {
337: // XXX this is a bit dangerous. Surely would be better to delete just the relevant
338: // property from it, then delete it iff it is empty. (Would require that
339: // NbModuleProjectGenerator.createSuiteProperties accept an existing file.)
340: fo.delete();
341: }
342: fo = subModuleDir
343: .getFileObject("nbproject/private/suite-private.properties"); // NOI18N
344: if (fo != null) {
345: fo.delete();
346: }
347:
348: if (suiteProps != null) {
349: // copy suite's platform.properties to the module (needed by standalone module)
350: FileObject plafPropsFO = suiteProps
351: .getProject()
352: .getProjectDirectory()
353: .getFileObject(
354: "nbproject/platform.properties"); // NOI18N
355: FileObject subModuleNbProject = subModuleDir
356: .getFileObject("nbproject"); // NOI18N
357: if (subModuleNbProject
358: .getFileObject("platform.properties") == null) { // NOI18N
359: FileUtil.copyFile(plafPropsFO,
360: subModuleNbProject,
361: "platform"); // NOI18N
362: }
363: }
364: EditableProperties props = subModule
365: .getHelper()
366: .getProperties(
367: PRIVATE_PLATFORM_PROPERTIES);
368: if (props
369: .getProperty("user.properties.file") == null) { // NOI18N
370: String nbuser = System
371: .getProperty("netbeans.user"); // NOI18N
372: if (nbuser != null) {
373: props
374: .setProperty(
375: "user.properties.file",
376: new File(
377: nbuser,
378: "build.properties")
379: .getAbsolutePath()); // NOI18N
380: subModule
381: .getHelper()
382: .putProperties(
383: PRIVATE_PLATFORM_PROPERTIES,
384: props);
385: } else {
386: Util.err
387: .log("netbeans.user system property is not defined. Skipping "
388: + PRIVATE_PLATFORM_PROPERTIES
389: + " creation."); // NOI18N
390: }
391: }
392:
393: SuiteUtils.setNbModuleType(subModule,
394: NbModuleProvider.STANDALONE);
395: // save subModule
396: ProjectManager.getDefault()
397: .saveProject(subModule);
398: } finally {
399: subModule.setRunInAtomicAction(false);
400: }
401: }
402: });
403:
404: // now clean up the suite
405: if (suiteProps != null) {
406: removeFromProperties(subModule, suiteProps);
407: }
408: } catch (IOException ex) {
409: ErrorManager.getDefault().notify(ex);
410: }
411: }
412:
413: /**
414: * Adjust <em>modules</em> property together with removing appropriate
415: * other properties from <code>projectProps</code> and
416: * <code>privateProps</code>.
417: *
418: * @return wheter something has changed or not
419: */
420: private static boolean removeFromProperties(
421: NbModuleProject moduleToRemove, SuiteProperties suiteProps) {
422: String modulesProp = suiteProps.getProperty(MODULES_PROPERTY);
423: boolean removed = false;
424: if (modulesProp != null) {
425: List<String> pieces = new ArrayList<String>(Arrays
426: .asList(PropertyUtils.tokenizePath(modulesProp)));
427: for (Iterator<String> piecesIt = pieces.iterator(); piecesIt
428: .hasNext();) {
429: String unevaluated = piecesIt.next();
430: String evaluated = suiteProps.getEvaluator().evaluate(
431: unevaluated);
432: if (evaluated == null) {
433: Util.err.log("Cannot evaluate " + unevaluated
434: + " property."); // NOI18N
435: continue;
436: }
437: if (moduleToRemove.getProjectDirectory() != suiteProps
438: .getHelper().resolveFileObject(evaluated)) {
439: continue;
440: }
441: piecesIt.remove();
442: String[] newModulesProp = getAntProperty(pieces);
443: suiteProps
444: .setProperty(MODULES_PROPERTY, newModulesProp);
445: removed = true;
446: // if the value is pure reference also tries to remove that
447: // reference which is nice to have. Otherwise just do nothing.
448: if (unevaluated
449: .matches(ANT_PURE_PROPERTY_REFERENCE_REGEXP)) {
450: String key = unevaluated.substring(2, unevaluated
451: .length() - 1);
452: suiteProps.removeProperty(key);
453: suiteProps.removePrivateProperty(key);
454: }
455: break;
456: }
457: }
458: if (!removed) {
459: Util.err.log("Removing of " + moduleToRemove
460: + " was unsuccessful."); // NOI18N
461: }
462: return removed;
463: }
464:
465: private void attachSubModuleToSuite(Project subModule)
466: throws IOException {
467: // adjust suite project's properties
468: File projectDirF = FileUtil.toFile(subModule
469: .getProjectDirectory());
470: File suiteDirF = suiteProps.getProjectDirectoryFile();
471: String projectPropKey = generatePropertyKey(subModule);
472: String rel = PropertyUtils.relativizeFile(suiteDirF,
473: projectDirF);
474: //mkleint: removed CollocationQuery.areCollocated() reference
475: // when AlwaysRelativeCQI gets removed the condition resolves to false more frequently.
476: // that might not be desirable.
477: if (rel != null) {
478: suiteProps.setProperty(projectPropKey, rel);
479: } else {
480: suiteProps.setPrivateProperty(projectPropKey, projectDirF
481: .getAbsolutePath());
482: }
483: String origModules = suiteProps.getProperty(MODULES_PROPERTY);
484: StringBuffer modules = new StringBuffer(
485: origModules == null ? "" : origModules);
486: if (modules.length() > 0) {
487: modules.append(':');
488: }
489: modules.append("${").append(projectPropKey).append('}'); // NOI18N
490: suiteProps.setProperty(MODULES_PROPERTY, modules.toString()
491: .split("(?<=:)", -1)); // NOI18N
492:
493: // adjust subModule's properties
494: NbModuleProjectGenerator.createSuiteProperties(subModule
495: .getProjectDirectory(), suiteDirF);
496: setNbModuleType(subModule, NbModuleProvider.SUITE_COMPONENT);
497: ProjectManager.getDefault().saveProject(subModule);
498: }
499:
500: /** Generates unique property key suitable for a given modules. */
501: private String generatePropertyKey(final Project subModule) {
502: String key = "project."
503: + ProjectUtils.getInformation(subModule).getName(); // NOI18N
504: String[] keys = suiteProps.getProperty(MODULES_PROPERTY).split(
505: "(?<=:)", -1); // NOI18N
506: int index = 0;
507: while (Arrays.binarySearch(keys, "${" + key + "}") >= 0) { // NOI18N
508: key += "_" + ++index; // NOI18N
509: }
510: return key;
511: }
512:
513: private static void setNbModuleType(Project module,
514: NbModuleProvider.NbModuleType type) throws IOException {
515: ProjectXMLManager pxm = new ProjectXMLManager(
516: ((NbModuleProject) module));
517: pxm.setModuleType(type);
518: }
519:
520: private static String[] getAntProperty(
521: final Collection<String> pieces) {
522: List<String> l = new ArrayList<String>();
523: for (Iterator<String> it = pieces.iterator(); it.hasNext();) {
524: String piece = it.next() + (it.hasNext() ? ":" : ""); // NOI18N
525: l.add(piece);
526: }
527: return l.toArray(new String[l.size()]);
528: }
529:
530: /**
531: * Returns whether a given directory contains regular <em>suite</em>. Note
532: * it returns <code>false</code> for suite components.
533: *
534: * @return <code>true</code> if a given directory contains regular
535: * <em>suite</em>; <code>false</code> otherwise.
536: */
537: public static boolean isSuite(final File maybeSuiteDir) {
538: boolean isSuite = false;
539: try {
540: FileObject dirFO = FileUtil.toFileObject(maybeSuiteDir);
541: if (dirFO != null) {
542: Project maybeSuite = ProjectManager.getDefault()
543: .findProject(dirFO);
544: if (maybeSuite != null) {
545: isSuite = maybeSuiteDir
546: .equals(getSuiteDirectory(maybeSuite));
547: }
548: }
549: } catch (IOException e) {
550: // leave it false
551: }
552: return isSuite;
553: }
554:
555: /**
556: * Returns suite for the given suite component. May return
557: * <code>null</code>.
558: * <p>Acquires read access.</p>
559: */
560: public static SuiteProject findSuite(final Project suiteComponent)
561: throws IOException {
562: try {
563: return ProjectManager.mutex().readAccess(
564: new Mutex.ExceptionAction<SuiteProject>() {
565: public SuiteProject run() throws Exception {
566: Project suite = null;
567: File suiteDir = SuiteUtils
568: .getSuiteDirectory(suiteComponent);
569: if (suiteDir != null) {
570: FileObject fo = FileUtil
571: .toFileObject(suiteDir);
572: if (fo == null) {
573: Util.err
574: .log(
575: ErrorManager.WARNING,
576: "Module in the \""
577: + // NOI18N
578: FileUtil
579: .toFile(
580: suiteComponent
581: .getProjectDirectory())
582: .getAbsolutePath()
583: + "\" directory claims to be a subcomponent of a suite in the \""
584: + // NOI18N
585: suiteDir
586: .getAbsolutePath()
587: + "\" which does not exist however."); // NOI18N
588: } else {
589: suite = ProjectManager.getDefault()
590: .findProject(fo);
591: }
592: }
593: return suite instanceof SuiteProject ? (SuiteProject) suite
594: : /* #80786 */null;
595: }
596: });
597: } catch (MutexException e) {
598: throw (IOException) e.getException();
599: }
600: }
601:
602: /**
603: * Returns whether a given suite already contains a given project or a
604: * project with the same code name base.
605: */
606: public static boolean contains(final SuiteProject suite,
607: final NbModuleProject project) {
608: Set<NbModuleProject> subModules = getSubProjects(suite);
609: if (subModules.contains(project)) {
610: return true;
611: }
612: for (Iterator it = subModules.iterator(); it.hasNext();) {
613: NbModuleProject p = (NbModuleProject) it.next();
614: if (p.getCodeNameBase().equals(project.getCodeNameBase())) {
615: return true;
616: }
617: }
618: return false;
619: }
620:
621: /**
622: * Utility method to acquire modules contains within a given suite. Just
623: * delegates to {@link SubprojectProvider#getSubprojects()}.
624: */
625: public static Set<NbModuleProject> getSubProjects(
626: final Project suite) {
627: assert suite != null;
628: SubprojectProvider spp = suite.getLookup().lookup(
629: SubprojectProvider.class);
630: return NbCollections.checkedSetByFilter(spp.getSubprojects(),
631: NbModuleProject.class, true);
632: }
633:
634: /**
635: * Convenient method for getting a suite directory from a given project
636: * which should contain an instance of {@link SuiteProvider} in its lookup.
637: * @return either suite directory or <code>null</code>
638: */
639: public static File getSuiteDirectory(final Project project) {
640: File suiteDir = null;
641: SuiteProvider sp = project.getLookup().lookup(
642: SuiteProvider.class);
643: if (sp != null) {
644: suiteDir = sp.getSuiteDirectory();
645: }
646: return suiteDir;
647: }
648:
649: /**
650: * Returns {@link #getSuiteDirectory}'s absolute path.
651: * @return path or <code>null</code>
652: */
653: public static String getSuiteDirectoryPath(final Project project) {
654: File suiteDir = getSuiteDirectory(project);
655: return suiteDir != null ? suiteDir.getAbsolutePath() : null;
656: }
657:
658: }
|