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.refactoring;
043:
044: import java.io.File;
045: import java.io.FileNotFoundException;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.io.OutputStream;
049: import java.util.StringTokenizer;
050: import java.util.regex.Pattern;
051: import org.netbeans.api.java.source.TreePathHandle;
052: import org.netbeans.api.project.FileOwnerQuery;
053: import org.netbeans.api.project.Project;
054: import org.netbeans.modules.apisupport.project.EditableManifest;
055: import org.netbeans.modules.apisupport.project.layers.LayerUtils;
056: import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
057: import org.netbeans.modules.refactoring.api.AbstractRefactoring;
058: import org.netbeans.modules.refactoring.api.Problem;
059: import org.netbeans.modules.refactoring.api.SafeDeleteRefactoring;
060: import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
061: import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
062: import org.openide.ErrorManager;
063: import org.openide.filesystems.FileLock;
064: import org.openide.filesystems.FileObject;
065: import org.openide.filesystems.FileUtil;
066: import org.openide.util.Exceptions;
067: import org.openide.util.Lookup;
068: import org.openide.util.NbBundle;
069:
070: /**
071: *
072: * @author Milos Kleint - inspired by j2eerefactoring
073: */
074: public class NbSafeDeleteRefactoringPlugin extends
075: AbstractRefactoringPlugin {
076:
077: /** This one is important creature - makes sure that cycles between plugins won't appear */
078: private static ThreadLocal semafor = new ThreadLocal();
079:
080: /**
081: * Creates a new instance of NbRenameRefactoringPlugin
082: */
083: public NbSafeDeleteRefactoringPlugin(AbstractRefactoring refactoring) {
084: super (refactoring);
085: }
086:
087: public void cancelRequest() {
088:
089: }
090:
091: public Problem fastCheckParameters() {
092: return null;
093: }
094:
095: /** Collects refactoring elements for a given refactoring.
096: * @param refactoringElements Collection of refactoring elements - the implementation of this method
097: * should add refactoring elements to this collections. It should make no assumptions about the collection
098: * content.
099: * @return Problems found or null (if no problems were identified)
100: */
101: public Problem prepare(RefactoringElementsBag refactoringElements) {
102: if (semafor.get() != null) {
103: return null;
104: }
105: semafor.set(new Object());
106: try {
107: SafeDeleteRefactoring delete = (SafeDeleteRefactoring) refactoring;
108: Problem problem = null;
109: Lookup lkp = delete.getRefactoringSource();
110: InfoHolder infoholder = examineLookup(lkp);
111: final TreePathHandle handle = lkp
112: .lookup(TreePathHandle.class);
113:
114: Project project = FileOwnerQuery.getOwner(handle
115: .getFileObject());
116: if (project == null
117: || project.getLookup().lookup(
118: NbModuleProvider.class) == null) {
119: // take just netbeans module development into account..
120: return null;
121: }
122:
123: if (infoholder.isClass) {
124: checkManifest(project, infoholder.fullName,
125: refactoringElements);
126: checkMetaInfServices(project, infoholder.fullName,
127: refactoringElements);
128: checkLayer(project, infoholder.fullName,
129: refactoringElements);
130: }
131: if (infoholder.isMethod) {
132: checkMethodLayer(infoholder, handle.getFileObject(),
133: refactoringElements);
134: }
135: if (infoholder.isConstructor) {
136: checkConstructorLayer(infoholder, handle
137: .getFileObject(), refactoringElements);
138: }
139:
140: err.log("Gonna return problem: " + problem);
141: return problem;
142: } catch (IOException ex) {
143: Exceptions.printStackTrace(ex);
144: return null;
145: } finally {
146: semafor.set(null);
147: }
148: }
149:
150: protected RefactoringElementImplementation createManifestRefactoring(
151: String fqname, FileObject manifestFile,
152: String attributeKey, String attributeValue, String section) {
153: return new ManifestSafeDeleteRefactoringElement(manifestFile,
154: attributeValue, attributeKey, section);
155: }
156:
157: protected RefactoringElementImplementation createMetaInfServicesRefactoring(
158: String clazz, FileObject serviceFile, int line) {
159: return new ServicesSafeDeleteRefactoringElement(clazz,
160: serviceFile);
161: }
162:
163: protected RefactoringElementImplementation createConstructorLayerRefactoring(
164: String constructor, String fqname,
165: LayerUtils.LayerHandle handle, FileObject layerFileObject,
166: String layerAttribute) {
167: return new LayerSafeDeleteRefactoringElement(constructor,
168: handle, layerFileObject);
169:
170: }
171:
172: protected RefactoringElementImplementation createLayerRefactoring(
173: String fqname, LayerUtils.LayerHandle handle,
174: FileObject layerFileObject, String layerAttribute) {
175: return new LayerSafeDeleteRefactoringElement(fqname, handle,
176: layerFileObject, layerAttribute);
177:
178: }
179:
180: protected RefactoringElementImplementation createMethodLayerRefactoring(
181: String method, String fqname,
182: LayerUtils.LayerHandle handle, FileObject layerFileObject,
183: String layerAttribute) {
184: return new LayerSafeDeleteRefactoringElement(method, handle,
185: layerFileObject);
186: }
187:
188: public final class ManifestSafeDeleteRefactoringElement extends
189: AbstractRefactoringElement {
190:
191: private String attrName;
192: private String sectionName = null;
193: private String oldContent;
194:
195: public ManifestSafeDeleteRefactoringElement(
196: FileObject parentFile, String attributeValue,
197: String attributeName) {
198: this .name = attributeValue;
199: this .parentFile = parentFile;
200: attrName = attributeName;
201: // read old content here. in the unprobable case when 2 classes are to be removed
202: // and both are placed in same services file, we need the true original content
203: oldContent = Utility.readFileIntoString(parentFile);
204: }
205:
206: public ManifestSafeDeleteRefactoringElement(
207: FileObject parentFile, String attributeValue,
208: String attributeName, String secName) {
209: this (parentFile, attributeValue, attributeName);
210: sectionName = secName;
211: }
212:
213: /** Returns text describing the refactoring formatted for display (using HTML tags).
214: * @return Formatted text.
215: */
216: public String getDisplayText() {
217: if (sectionName != null) {
218: return NbBundle.getMessage(
219: NbSafeDeleteRefactoringPlugin.class,
220: "TXT_ManifestSectionDelete", this .name,
221: sectionName);
222: }
223: return NbBundle.getMessage(
224: NbSafeDeleteRefactoringPlugin.class,
225: "TXT_ManifestDelete", this .name, attrName);
226: }
227:
228: public void performChange() {
229: FileLock lock = null;
230: OutputStream stream = null;
231: InputStream instream = null;
232:
233: try {
234: instream = parentFile.getInputStream();
235: EditableManifest manifest = new EditableManifest(
236: instream);
237: instream.close();
238: instream = null;
239: if (sectionName != null) {
240: manifest.removeSection(name);
241: } else {
242: manifest.removeAttribute(attrName, null);
243: }
244: lock = parentFile.lock();
245: stream = parentFile.getOutputStream(lock);
246: manifest.write(stream);
247: } catch (FileNotFoundException ex) {
248: //TODO
249: err.notify(ex);
250: } catch (IOException exc) {
251: //TODO
252: err.notify(exc);
253: } finally {
254: if (instream != null) {
255: try {
256: instream.close();
257: } catch (IOException ex) {
258: err.notify(ex);
259: }
260: }
261: if (stream != null) {
262: try {
263: stream.close();
264: } catch (IOException ex) {
265: err.notify(ex);
266: }
267: }
268: if (lock != null) {
269: lock.releaseLock();
270: }
271: }
272: }
273:
274: public void undoChange() {
275: if (oldContent != null) {
276: Utility.writeFileFromString(parentFile, oldContent);
277: }
278: }
279:
280: }
281:
282: public final class ServicesSafeDeleteRefactoringElement extends
283: AbstractRefactoringElement {
284:
285: private String oldName;
286: private String oldContent;
287: private File parent;
288:
289: /**
290: * Creates a new instance of ServicesRenameRefactoringElement
291: */
292: public ServicesSafeDeleteRefactoringElement(String fqname,
293: FileObject file) {
294: this .name = fqname.substring(fqname.lastIndexOf('.'));
295: parentFile = file;
296: oldName = fqname;
297: // read old content here. in the unprobable case when 2 classes are to be removed
298: // and both are placed in same services file, we need the true original content
299: oldContent = Utility.readFileIntoString(parentFile);
300: parent = FileUtil.toFile(parentFile);
301: }
302:
303: /** Returns text describing the refactoring formatted for display (using HTML tags).
304: * @return Formatted text.
305: */
306: public String getDisplayText() {
307: return NbBundle.getMessage(
308: NbSafeDeleteRefactoringPlugin.class,
309: "TXT_ServicesDelete", this .name);
310: }
311:
312: public void performChange() {
313: String content = Utility.readFileIntoString(parentFile);
314: if (content != null) {
315: String longName = oldName;
316: longName = longName.replaceAll("[.]", "\\."); //NOI18N
317: content = content.replaceAll("^" + longName
318: + "[ \\\n]?", ""); //NOI18N
319: // now check if there's more entries in the file..
320: boolean hasMoreThanComments = false;
321: StringTokenizer tok = new StringTokenizer(content, "\n"); //NOI18N
322: while (tok.hasMoreTokens()) {
323: String token = tok.nextToken().trim();
324: if (token.length() > 0
325: && (!Pattern.matches("^[#].*", token))) { //NOI18N
326: hasMoreThanComments = true;
327: break;
328: }
329: }
330: if (hasMoreThanComments) {
331: Utility.writeFileFromString(parentFile, content);
332: } else {
333: try {
334: parentFile.delete();
335: } catch (IOException exc) {
336: err.notify(exc);
337: }
338: }
339: }
340: }
341:
342: public void undoChange() {
343: try {
344: if (oldContent != null) {
345: if (!parent.exists()) {
346: FileObject fo = FileUtil.toFileObject(parent
347: .getParentFile());
348: if (fo != null) {
349: parentFile = fo
350: .createData(parent.getName());
351: }
352: }
353: Utility.writeFileFromString(parentFile, oldContent);
354: }
355: } catch (IOException exc) {
356: err.notify(exc);
357: }
358: }
359: }
360:
361: public final class LayerSafeDeleteRefactoringElement extends
362: AbstractRefactoringElement {
363:
364: private FileObject layerFO;
365: private LayerUtils.LayerHandle handle;
366:
367: private String attribute;
368:
369: /**
370: * Creates a new instance of LayerRenameRefactoringElement
371: */
372: public LayerSafeDeleteRefactoringElement(String name,
373: LayerUtils.LayerHandle handle, FileObject layerFO,
374: String attr) {
375: this (name, handle, layerFO);
376: attribute = attr;
377: }
378:
379: public LayerSafeDeleteRefactoringElement(String name,
380: LayerUtils.LayerHandle handle, FileObject layerFO) {
381: this .name = name;
382: this .handle = handle;
383: parentFile = handle.getLayerFile();
384: this .layerFO = layerFO;
385: }
386:
387: /** Returns text describing the refactoring formatted for display (using HTML tags).
388: * @return Formatted text.
389: */
390: public String getDisplayText() {
391: return NbBundle.getMessage(
392: NbSafeDeleteRefactoringPlugin.class,
393: "TXT_LayerDelete", layerFO.getNameExt());
394: }
395:
396: public void performChange() {
397: boolean on = handle.isAutosave();
398: if (!on) {
399: //TODO is this a hack or not?
400: handle.setAutosave(true);
401: }
402: try {
403: if (attribute != null) {
404: layerFO.setAttribute(attribute, null);
405: if ("originalFile".equals(attribute)) {
406: layerFO.delete();
407: }
408: } else {
409: layerFO.delete();
410: }
411: deleteEmptyParent(layerFO.getParent());
412: } catch (IOException exc) {
413: ErrorManager.getDefault().notify(exc);
414: }
415: if (!on) {
416: handle.setAutosave(false);
417: }
418:
419: }
420:
421: private void deleteEmptyParent(FileObject parent)
422: throws IOException {
423: if (parent != null) {
424: if (!parent.getChildren(true).hasMoreElements()
425: && !parent.getAttributes().hasMoreElements()) {
426: FileObject parentToDel = parent.getParent();
427: parent.delete();
428: deleteEmptyParent(parentToDel);
429: }
430: }
431: }
432:
433: public void undoChange() {
434: //TODO
435: }
436:
437: }
438:
439: }
|