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.IOException;
045: import java.io.StringReader;
046: import javax.swing.text.Document;
047: import javax.xml.parsers.SAXParser;
048: import javax.xml.parsers.SAXParserFactory;
049: import org.netbeans.api.java.source.TreePathHandle;
050: import org.netbeans.api.project.FileOwnerQuery;
051: import org.netbeans.api.project.Project;
052: import org.netbeans.modules.apisupport.project.layers.LayerUtils;
053: import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
054: import org.netbeans.modules.refactoring.api.AbstractRefactoring;
055: import org.netbeans.modules.refactoring.api.Problem;
056: import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
057: import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
058: import org.netbeans.modules.refactoring.api.WhereUsedQuery;
059: import org.openide.ErrorManager;
060: import org.openide.cookies.EditorCookie;
061: import org.openide.filesystems.FileObject;
062: import org.openide.loaders.DataObject;
063: import org.openide.util.Exceptions;
064: import org.openide.util.Lookup;
065: import org.openide.util.NbBundle;
066: import org.openide.xml.EntityCatalog;
067: import org.xml.sax.Attributes;
068: import org.xml.sax.InputSource;
069: import org.xml.sax.Locator;
070: import org.xml.sax.SAXException;
071: import org.xml.sax.helpers.DefaultHandler;
072:
073: /**
074: *
075: * @author Milos Kleint - inspired by j2eerefactoring
076: */
077: public class NbWhereUsedRefactoringPlugin extends
078: AbstractRefactoringPlugin {
079:
080: /** This one is important creature - makes sure that cycles between plugins won't appear */
081: private static ThreadLocal semafor = new ThreadLocal();
082:
083: public void cancelRequest() {
084:
085: }
086:
087: public Problem fastCheckParameters() {
088: return null;
089: }
090:
091: /**
092: * Creates a new instance of NbWhereUsedRefactoringPlugin
093: */
094: public NbWhereUsedRefactoringPlugin(AbstractRefactoring refactoring) {
095: super (refactoring);
096: }
097:
098: /** Collects refactoring elements for a given refactoring.
099: * @param refactoringElements Collection of refactoring elements - the implementation of this method
100: * should add refactoring elements to this collections. It should make no assumptions about the collection
101: * content.
102: * @return Problems found or null (if no problems were identified)
103: */
104: public Problem prepare(RefactoringElementsBag refactoringElements) {
105: if (semafor.get() != null) {
106: return null;
107: }
108: semafor.set(new Object());
109: try {
110: WhereUsedQuery whereUsedRefactor = ((WhereUsedQuery) refactoring);
111:
112: if (!whereUsedRefactor
113: .getBooleanValue(WhereUsedQuery.FIND_REFERENCES)) {
114: return null;
115: }
116:
117: Problem problem = null;
118: Lookup lkp = whereUsedRefactor.getRefactoringSource();
119: InfoHolder infoholder = examineLookup(lkp);
120: final TreePathHandle handle = lkp
121: .lookup(TreePathHandle.class);
122:
123: Project project = FileOwnerQuery.getOwner(handle
124: .getFileObject());
125: if (project == null
126: || project.getLookup().lookup(
127: NbModuleProvider.class) == null) {
128: // take just netbeans module development into account..
129: return null;
130: }
131:
132: if (infoholder.isClass) {
133: checkManifest(project, infoholder.fullName,
134: refactoringElements);
135: checkMetaInfServices(project, infoholder.fullName,
136: refactoringElements);
137: checkLayer(project, infoholder.fullName,
138: refactoringElements);
139: }
140: if (infoholder.isMethod) {
141: checkMethodLayer(infoholder, handle.getFileObject(),
142: refactoringElements);
143: }
144: if (infoholder.isConstructor) {
145: checkConstructorLayer(infoholder, handle
146: .getFileObject(), refactoringElements);
147: }
148: err.log("Gonna return problem: " + problem);
149: return problem;
150: } catch (IOException ex) {
151: Exceptions.printStackTrace(ex);
152: return null;
153: } finally {
154: semafor.set(null);
155: }
156: }
157:
158: protected RefactoringElementImplementation createManifestRefactoring(
159: String fqname, FileObject manifestFile,
160: String attributeKey, String attributeValue, String section) {
161: return new ManifestWhereUsedRefactoringElement(attributeValue,
162: manifestFile, attributeKey, section);
163: }
164:
165: protected RefactoringElementImplementation createMetaInfServicesRefactoring(
166: String fqname, FileObject serviceFile, int line) {
167: return new ServicesWhereUsedRefactoringElement(fqname,
168: serviceFile, line);
169: }
170:
171: protected RefactoringElementImplementation createLayerRefactoring(
172: String fqname, LayerUtils.LayerHandle handle,
173: FileObject layerFileObject, String layerAttribute) {
174: return new LayerWhereUsedRefactoringElement(handle
175: .getLayerFile(), layerFileObject, layerAttribute);
176: }
177:
178: protected RefactoringElementImplementation createMethodLayerRefactoring(
179: String method, String fqname,
180: LayerUtils.LayerHandle handle, FileObject layerFileObject,
181: String layerAttribute) {
182: return new LayerWhereUsedRefactoringElement(handle
183: .getLayerFile(), layerFileObject, layerAttribute);
184: }
185:
186: protected RefactoringElementImplementation createConstructorLayerRefactoring(
187: String constructor, String fqname,
188: LayerUtils.LayerHandle handle, FileObject layerFileObject,
189: String layerAttribute) {
190: return new LayerWhereUsedRefactoringElement(handle
191: .getLayerFile(), layerFileObject, layerAttribute);
192: }
193:
194: public final class LayerWhereUsedRefactoringElement extends
195: AbstractRefactoringElement {
196: private String attr;
197: private String path;
198: private String attrValue;
199:
200: public LayerWhereUsedRefactoringElement(FileObject fo,
201: FileObject layerFo, String attribute) {
202: parentFile = fo;
203: attr = attribute;
204: this .path = layerFo.getPath();
205: if (attr != null) {
206: Object vl = layerFo.getAttribute("literal:" + attr); //NOI18N
207: if (vl instanceof String) {
208: attrValue = ((String) vl).replaceFirst(
209: "^(new|method):", ""); // NOI18N
210: }
211: }
212: }
213:
214: public String getDisplayText() {
215: if (attr != null && attrValue != null) {
216: return NbBundle.getMessage(
217: NbWhereUsedRefactoringPlugin.class,
218: "TXT_LayerAttrValueWhereUsed", path, attr,
219: attrValue);
220: }
221: if (attr != null) {
222: return NbBundle.getMessage(
223: NbWhereUsedRefactoringPlugin.class,
224: "TXT_LayerAttrWhereUsed", path, attr);
225: }
226: return NbBundle.getMessage(
227: NbWhereUsedRefactoringPlugin.class,
228: "TXT_LayerWhereUsed", path);
229: }
230:
231: protected int[] location() {
232: try {
233: DataObject d = DataObject.find(parentFile);
234: EditorCookie ec = (EditorCookie) d
235: .getCookie(EditorCookie.class);
236: Document doc = ec.openDocument();
237: String text = doc.getText(0, doc.getLength());
238: assert text.indexOf('\r') == -1; // should be in newline format only when a Document
239: InputSource in = new InputSource(new StringReader(text));
240: in.setSystemId(parentFile.getURL().toExternalForm());
241: SAXParserFactory factory = SAXParserFactory
242: .newInstance();
243: SAXParser parser = factory.newSAXParser();
244: final int[] lineAndColStartAndEnd = new int[4];
245: class Halt extends SAXException {
246: public Halt() {
247: super ((String) null);
248: }
249: }
250: class Handler extends DefaultHandler {
251: private int state = -1; // -1 - not encountered (or already found it but Halt did not work), 0 - in matching element, 1+ - in nested element
252: private Locator locator;
253: private String runningPath = "";
254:
255: public void setDocumentLocator(Locator l) {
256: locator = l;
257: }
258:
259: public void startElement(String uri,
260: String localname, String qname,
261: Attributes attr) throws SAXException {
262: if (qname.equals("file")
263: || qname.equals("folder")) { // NOI18N
264: String name = attr.getValue("name"); // NOI18N
265: if (name != null) {
266: if (runningPath.length() > 0) {
267: runningPath += '/';
268: }
269: runningPath += name;
270: }
271: }
272: if (state == -1 && path.equals(runningPath)) {
273: lineAndColStartAndEnd[0] = locator
274: .getLineNumber();
275: lineAndColStartAndEnd[1] = locator
276: .getColumnNumber();
277: state = 0;
278: } else if (state != -1) {
279: state++;
280: }
281: }
282:
283: public void endElement(String uri,
284: String localname, String qname)
285: throws SAXException {
286: if (qname.equals("file")
287: || qname.equals("folder")) { // NOI18N
288: runningPath = runningPath.substring(0, Math
289: .max(runningPath.lastIndexOf('/'),
290: 0));
291: }
292: if (state > 0) {
293: state--;
294: } else if (state == 0) {
295: lineAndColStartAndEnd[2] = locator
296: .getLineNumber();
297: lineAndColStartAndEnd[3] = locator
298: .getColumnNumber();
299: state = -1;
300: throw new Halt();
301: }
302: }
303:
304: public InputSource resolveEntity(String publicId,
305: String systemId) throws SAXException {
306: try {
307: return EntityCatalog.getDefault()
308: .resolveEntity(publicId, systemId);
309: } catch (IOException e) {
310: throw new SAXException(e);
311: }
312: }
313: }
314: try {
315: parser.parse(in, new Handler());
316: } catch (Halt h) {
317: // ignore
318: }
319: if (lineAndColStartAndEnd[0] == 0
320: || lineAndColStartAndEnd[1] == 0
321: || lineAndColStartAndEnd[2] == 0
322: || lineAndColStartAndEnd[3] == 0) {
323: return new int[] { 0, 0 };
324: }
325: int[] startAndEnd = new int[2];
326: int line = 0;
327: int col = 0;
328: for (int i = 0; i < text.length(); i++) {
329: if (line == lineAndColStartAndEnd[0] - 1
330: && col == lineAndColStartAndEnd[1] - 1) {
331: startAndEnd[0] = i;
332: } else if (line == lineAndColStartAndEnd[2] - 1
333: && col == lineAndColStartAndEnd[3] - 1) {
334: startAndEnd[1] = i;
335: }
336: char c = text.charAt(i);
337: if (c == '\n') {
338: line++;
339: col = 0;
340: } else {
341: col++;
342: }
343: }
344: // Start position given by SAX locator may actually be *end* of open tag, which is not good.
345: // Try to backtrack to opening '<'. Shouldn't be any other '<' in an XML element.
346: startAndEnd[0] = Math.max(text.lastIndexOf('<',
347: startAndEnd[0]), 0);
348: if (startAndEnd[1] == 0) {
349: // Minimized tag. Guess that unescaped '>' will not occur in the value.
350: startAndEnd[1] = Math.max(text.indexOf('>',
351: startAndEnd[0]), startAndEnd[0]);
352: }
353: // Right now we have the containing file object. Prefer to get the actual string.
354: String match;
355: if (attrValue != null) {
356: match = attrValue;
357: } else {
358: match = path.substring(path.lastIndexOf('/') + 1);
359: }
360: int loc = text.indexOf(match, startAndEnd[0]);
361: if (loc != -1 && loc < startAndEnd[1]) {
362: // Found it.
363: return new int[] { loc, loc + match.length() };
364: } else {
365: // OK, just show the whole <file>.
366: return startAndEnd;
367: }
368: } catch (Exception e) {
369: ErrorManager.getDefault().notify(
370: ErrorManager.INFORMATIONAL, e);
371: return new int[] { 0, 0 };
372: }
373: }
374: }
375:
376: public final class ManifestWhereUsedRefactoringElement extends
377: AbstractRefactoringElement {
378:
379: private String attrName;
380: private String sectionName = null;
381:
382: public ManifestWhereUsedRefactoringElement(String name,
383: FileObject parentFile, String attributeName) {
384: this .name = name;
385: this .parentFile = parentFile;
386: attrName = attributeName;
387: }
388:
389: public ManifestWhereUsedRefactoringElement(String name,
390: FileObject parentFile, String attributeName,
391: String secName) {
392: this (name, parentFile, attributeName);
393: sectionName = secName;
394: }
395:
396: public String getDisplayText() {
397: if (sectionName != null) {
398: return NbBundle.getMessage(
399: NbWhereUsedRefactoringPlugin.class,
400: "TXT_ManifestSectionWhereUsed", this .name,
401: sectionName);
402: }
403: return NbBundle.getMessage(
404: NbWhereUsedRefactoringPlugin.class,
405: "TXT_ManifestWhereUsed", this .name, attrName);
406: }
407:
408: protected int[] location() {
409: try {
410: DataObject d = DataObject.find(parentFile);
411: EditorCookie ec = (EditorCookie) d
412: .getCookie(EditorCookie.class);
413: Document doc = ec.openDocument();
414: String text = doc.getText(0, doc.getLength());
415: assert text.indexOf('\r') == -1; // should be in newline format only when a Document
416: int start = text.indexOf(name);
417: if (start == -1) {
418: return new int[] { 0, 0 };
419: } else {
420: return new int[] { start, start + name.length() };
421: }
422: } catch (Exception e) {
423: ErrorManager.getDefault().notify(
424: ErrorManager.INFORMATIONAL, e);
425: return new int[] { 0, 0 };
426: }
427: }
428:
429: }
430:
431: public final class ServicesWhereUsedRefactoringElement extends
432: AbstractRefactoringElement {
433:
434: private final int line;
435:
436: public ServicesWhereUsedRefactoringElement(String name,
437: FileObject file, int line) {
438: this .name = name;
439: parentFile = file;
440: this .line = line;
441: }
442:
443: public String getDisplayText() {
444: return NbBundle.getMessage(
445: NbWhereUsedRefactoringPlugin.class,
446: "TXT_ServicesWhereUsed", this .name);
447: }
448:
449: protected int[] location() {
450: try {
451: DataObject d = DataObject.find(parentFile);
452: EditorCookie ec = (EditorCookie) d
453: .getCookie(EditorCookie.class);
454: Document doc = ec.openDocument();
455: String text = doc.getText(0, doc.getLength());
456: assert text.indexOf('\r') == -1; // should be in newline format only when a Document
457: int[] startAndEnd = new int[2];
458: int line = 0;
459: int col = 0;
460: for (int i = 0; i < text.length(); i++) {
461: if (line == this .line && col == 0) {
462: startAndEnd[0] = i;
463: } else if (line == this .line + 1 && col == 0) {
464: startAndEnd[1] = i - 1;
465: }
466: char c = text.charAt(i);
467: if (c == '\n') {
468: line++;
469: col = 0;
470: } else {
471: col++;
472: }
473: }
474: return startAndEnd;
475: } catch (Exception e) {
476: ErrorManager.getDefault().notify(
477: ErrorManager.INFORMATIONAL, e);
478: return new int[] { 0, 0 };
479: }
480: }
481:
482: }
483: }
|