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;
043:
044: import java.awt.event.ActionEvent;
045: import java.io.File;
046: import java.io.IOException;
047: import java.util.ArrayList;
048: import java.util.HashMap;
049: import java.util.HashSet;
050: import java.util.List;
051: import java.util.Map;
052: import java.util.Set;
053: import javax.swing.Action;
054: import org.apache.tools.ant.module.api.support.ActionUtils;
055: import org.netbeans.api.project.FileOwnerQuery;
056: import org.netbeans.api.project.Project;
057: import org.netbeans.api.project.ProjectManager;
058: import org.netbeans.api.project.ProjectUtils;
059: import org.netbeans.api.project.ui.OpenProjects;
060: import org.netbeans.api.queries.VisibilityQuery;
061: import org.netbeans.modules.apisupport.project.NbModuleProject;
062: import org.netbeans.modules.apisupport.project.suite.BrandingSupport;
063: import org.netbeans.modules.apisupport.project.suite.SuiteProject;
064: import org.netbeans.modules.apisupport.project.ui.customizer.BasicBrandingModel;
065: import org.netbeans.modules.apisupport.project.ui.customizer.SuiteProperties;
066: import org.netbeans.modules.apisupport.project.ui.customizer.SuiteUtils;
067: import org.netbeans.spi.project.ActionProvider;
068: import org.netbeans.spi.project.DeleteOperationImplementation;
069: import org.netbeans.spi.project.MoveOperationImplementation;
070: import org.netbeans.spi.project.SubprojectProvider;
071: import org.netbeans.spi.project.support.ProjectOperations;
072: import org.netbeans.spi.project.support.ant.AntProjectHelper;
073: import org.netbeans.spi.project.support.ant.EditableProperties;
074: import org.netbeans.spi.project.support.ant.GeneratedFilesHelper;
075: import org.netbeans.spi.project.ui.support.CommonProjectActions;
076: import org.openide.LifecycleManager;
077: import org.openide.filesystems.FileObject;
078: import org.openide.filesystems.FileUtil;
079: import org.openide.util.ContextAwareAction;
080: import org.openide.util.Mutex;
081: import org.openide.util.MutexException;
082: import org.openide.util.lookup.Lookups;
083:
084: /**
085: * @author Martin Krauskopf
086: */
087: public final class SuiteOperations implements
088: DeleteOperationImplementation, MoveOperationImplementation {
089:
090: private static final Map<String, Set<NbModuleProject>> TEMPORARY_CACHE = new HashMap<String, Set<NbModuleProject>>();
091:
092: private final SuiteProject suite;
093: private final FileObject projectDir;
094:
095: public SuiteOperations(final SuiteProject suite) {
096: this .suite = suite;
097: this .projectDir = suite.getProjectDirectory();
098: }
099:
100: public void notifyDeleting() throws IOException {
101: FileObject buildXML = projectDir
102: .getFileObject(GeneratedFilesHelper.BUILD_XML_PATH);
103: ActionUtils.runTarget(buildXML,
104: new String[] { ActionProvider.COMMAND_CLEAN }, null)
105: .waitFinished();
106:
107: // remove all suite components from the suite - i.e. make them standalone
108: SubprojectProvider spp = suite.getLookup().lookup(
109: SubprojectProvider.class);
110: for (Project suiteComponent : spp.getSubprojects()) {
111: SuiteUtils
112: .removeModuleFromSuite((NbModuleProject) suiteComponent);
113: }
114: }
115:
116: public void notifyDeleted() throws IOException {
117: suite.getHelper().notifyDeleted();
118: }
119:
120: public void notifyMoving() throws IOException {
121: Set<NbModuleProject> subprojects = SuiteUtils
122: .getSubProjects(suite);
123: if (!subprojects.isEmpty()) {
124: // XXX using suite's name is probably weak. Consider another solution. E.g.
125: // store some "private" property and than read it.
126: TEMPORARY_CACHE.put(ProjectUtils.getInformation(suite)
127: .getName(), subprojects);
128: }
129: // this will temporarily remove all suite components - this is needed
130: // to prevent infrastructure confusion about lost suite. They will be
131: // readded in the notifyMoved.
132: notifyDeleting();
133: }
134:
135: public void notifyMoved(Project original, File originalPath,
136: String nueName) throws IOException {
137: if (original == null) { // called on the original project
138: suite.getHelper().notifyDeleted();
139: } else { // called on the new project
140: String name = ProjectUtils.getInformation(suite).getName();
141: Set<NbModuleProject> subprojects = TEMPORARY_CACHE
142: .remove(name);
143: if (subprojects != null) {
144: Set<Project> toOpen = new HashSet<Project>();
145: for (Project _originalComp : subprojects) {
146: NbModuleProject originalComp = (NbModuleProject) _originalComp;
147:
148: boolean directoryChanged = !original
149: .getProjectDirectory().equals(
150: suite.getProjectDirectory());
151: if (directoryChanged
152: && FileUtil.isParentOf(
153: // wasRelative
154: original.getProjectDirectory(),
155: originalComp.getProjectDirectory())) {
156: boolean isOpened = SuiteOperations
157: .isOpened(originalComp);
158: Project nueComp = SuiteOperations.moveModule(
159: originalComp, suite
160: .getProjectDirectory());
161: SuiteUtils.addModule(suite,
162: (NbModuleProject) nueComp);
163: if (isOpened) {
164: toOpen.add(nueComp);
165: }
166: } else {
167: SuiteUtils.addModule(suite, originalComp);
168: }
169: }
170: OpenProjects.getDefault().open(
171: toOpen.toArray(new Project[toOpen.size()]),
172: false);
173: }
174: boolean isRename = original.getProjectDirectory()
175: .getParent().equals(
176: suite.getProjectDirectory().getParent());
177: if (isRename) {
178: setDisplayName(nueName);
179: }
180: FileObject origSuiteFO = FileUtil
181: .toFileObject(originalPath);
182: if (origSuiteFO != null
183: && origSuiteFO.getChildren().length == 0) {
184: origSuiteFO.delete();
185: }
186: }
187: }
188:
189: public List<FileObject> getMetadataFiles() {
190: List<FileObject> files = new ArrayList<FileObject>();
191: addFile(GeneratedFilesHelper.BUILD_XML_PATH, files);
192: addFile("nbproject", files); // NOI18N
193: return files;
194: }
195:
196: public List<FileObject> getDataFiles() {
197: List<FileObject> files = new ArrayList<FileObject>();
198: addFile(suite.getEvaluator().getProperty(
199: BrandingSupport.BRANDING_DIR_PROPERTY), files);
200: return files;
201: }
202:
203: private void addFile(String fileName, List<FileObject> result) {
204: FileObject file = projectDir.getFileObject(fileName);
205: if (file != null) {
206: result.add(file);
207: }
208: }
209:
210: private void setDisplayName(final String nueName)
211: throws IOException {
212: final SuiteProperties sp = new SuiteProperties(suite, suite
213: .getHelper(), suite.getEvaluator(), SuiteUtils
214: .getSubProjects(suite));
215: final BasicBrandingModel branding = sp.getBrandingModel();
216: try {
217: ProjectManager.mutex().writeAccess(
218: new Mutex.ExceptionAction<Object>() {
219: public Object run() throws IOException {
220: if (branding.isBrandingEnabled()) { // application
221: branding.setTitle(nueName);
222: sp.storeProperties();
223: } else { // ordinary suite of modules
224: EditableProperties props = suite
225: .getHelper()
226: .getProperties(
227: AntProjectHelper.PROJECT_PROPERTIES_PATH);
228: props
229: .setProperty(
230: BasicBrandingModel.TITLE_PROPERTY,
231: nueName);
232: suite
233: .getHelper()
234: .putProperties(
235: AntProjectHelper.PROJECT_PROPERTIES_PATH,
236: props);
237: }
238: ProjectManager.getDefault().saveProject(
239: suite);
240: return null;
241: }
242: });
243: } catch (MutexException e) {
244: throw (IOException) e.getException();
245: }
246: }
247:
248: /** Package private for unit tests <strong>only</strong>. */
249: static Project moveModule(final NbModuleProject original,
250: final FileObject targetParent) throws IOException,
251: IllegalArgumentException {
252: ProjectOperations.notifyMoving(original);
253: SuiteOperations.close(original);
254: FileObject origDir = original.getProjectDirectory();
255: FileObject copy = doCopy(original, origDir, targetParent);
256: ProjectManager.getDefault().clearNonProjectCache();
257: Project nueComp = ProjectManager.getDefault().findProject(copy);
258: assert nueComp != null;
259: File originalPath = FileUtil.toFile(origDir);
260: doDelete(original, origDir);
261: ProjectOperations.notifyMoved(original, nueComp, originalPath,
262: originalPath.getName());
263: return nueComp;
264: }
265:
266: private static boolean isOpened(final Project original) {
267: boolean opened = false;
268: Project[] openProjects = OpenProjects.getDefault()
269: .getOpenProjects();
270: for (int i = 0; i < openProjects.length; i++) {
271: if (openProjects[i] == original) {
272: opened = true;
273: break;
274: }
275: }
276: return opened;
277: }
278:
279: // XXX following is copy-pasted from the Project APIs
280: //<editor-fold defaultstate="collapsed" desc="copy-pasted from Project API">
281: private static FileObject doCopy(final Project original,
282: final FileObject from, final FileObject toParent)
283: throws IOException {
284: if (!VisibilityQuery.getDefault().isVisible(from)) {
285: //Do not copy invisible files/folders.
286: return null;
287: }
288:
289: if (!original.getProjectDirectory().equals(
290: FileOwnerQuery.getOwner(from).getProjectDirectory())) {
291: return null;
292: }
293:
294: FileObject copy;
295: if (from.isFolder()) {
296: copy = toParent.createFolder(from.getNameExt());
297: FileObject[] kids = from.getChildren();
298: for (int i = 0; i < kids.length; i++) {
299: doCopy(original, kids[i], copy);
300: }
301: } else {
302: assert from.isData();
303: copy = FileUtil.copyFile(from, toParent, from.getName(),
304: from.getExt());
305: }
306: return copy;
307: }
308:
309: private static boolean doDelete(final Project original,
310: final FileObject toDelete) throws IOException {
311: if (!original.getProjectDirectory()
312: .equals(
313: FileOwnerQuery.getOwner(toDelete)
314: .getProjectDirectory())) {
315: return false;
316: }
317:
318: if (toDelete.isFolder()) {
319: FileObject[] kids = toDelete.getChildren();
320: boolean delete = true;
321:
322: for (int i = 0; i < kids.length; i++) {
323: delete &= doDelete(original, kids[i]);
324: }
325:
326: if (delete) {
327: toDelete.delete();
328: }
329:
330: return delete;
331: } else {
332: assert toDelete.isData();
333: toDelete.delete();
334: return true;
335: }
336: }
337:
338: private static void close(final Project prj) {
339: Mutex.EVENT.readAccess(new Mutex.Action<Object>() {
340: public Object run() {
341: LifecycleManager.getDefault().saveAll();
342:
343: Action closeAction = CommonProjectActions
344: .closeProjectAction();
345: closeAction = closeAction instanceof ContextAwareAction ? ((ContextAwareAction) closeAction)
346: .createContextAwareInstance(Lookups
347: .fixed(new Object[] { prj }))
348: : null;
349:
350: if (closeAction != null && closeAction.isEnabled()) {
351: closeAction.actionPerformed(new ActionEvent(prj,
352: -1, "")); // NOI18N
353: } else {
354: //fallback:
355: OpenProjects.getDefault().close(
356: new Project[] { prj });
357: }
358:
359: return null;
360: }
361: });
362: }
363: //</editor-fold>
364:
365: }
|