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-2007 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: package org.netbeans.modules.java.editor.imports;
042:
043: import com.sun.source.tree.CompilationUnitTree;
044: import com.sun.source.tree.ImportTree;
045: import com.sun.source.util.TreePath;
046: import java.awt.Dialog;
047: import java.awt.Toolkit;
048: import java.io.IOException;
049: import java.util.ArrayList;
050: import java.util.Collections;
051: import java.util.HashMap;
052: import java.util.List;
053: import java.util.Map;
054: import java.util.prefs.Preferences;
055: import javax.lang.model.element.TypeElement;
056: import javax.swing.Icon;
057: import javax.swing.ImageIcon;
058: import org.netbeans.api.java.source.Task;
059: import org.netbeans.api.java.source.JavaSource;
060: import org.netbeans.api.java.source.JavaSource.Phase;
061: import org.netbeans.api.java.source.SourceUtils;
062: import org.netbeans.api.java.source.TreeMaker;
063: import org.netbeans.api.java.source.TreePathHandle;
064: import org.netbeans.api.java.source.WorkingCopy;
065: import org.netbeans.api.java.source.ui.ElementIcons;
066: import org.netbeans.modules.java.editor.semantic.SemanticHighlighter;
067: import org.netbeans.modules.editor.java.Utilities;
068: import org.openide.DialogDescriptor;
069: import org.openide.DialogDisplayer;
070: import org.openide.ErrorManager;
071: import org.openide.awt.StatusDisplayer;
072: import org.openide.filesystems.FileObject;
073: import org.openide.util.NbBundle;
074: import org.openide.util.NbPreferences;
075:
076: /**
077: *
078: * @author Jan Lahoda
079: */
080: public class JavaFixAllImports {
081:
082: private static final String PREFS_KEY = JavaFixAllImports.class
083: .getName();
084: private static final String KEY_REMOVE_UNUSED_IMPORTS = "removeUnusedImports";
085: private static final JavaFixAllImports INSTANCE = new JavaFixAllImports();
086:
087: public static JavaFixAllImports getDefault() {
088: return INSTANCE;
089: }
090:
091: /** Creates a new instance of JavaFixAllImports */
092: private JavaFixAllImports() {
093: }
094:
095: public void fixAllImports(FileObject fo) {
096: Task<WorkingCopy> task = new Task<WorkingCopy>() {
097:
098: public void run(final WorkingCopy wc) {
099: try {
100: wc.toPhase(Phase.RESOLVED);
101:
102: ComputeImports.Pair<Map<String, List<TypeElement>>, Map<String, List<TypeElement>>> candidates = new ComputeImports()
103: .computeCandidates(wc);
104:
105: Map<String, List<TypeElement>> filteredCandidates = candidates.a;
106: Map<String, List<TypeElement>> notFilteredCandidates = candidates.b;
107:
108: int size = notFilteredCandidates.size();
109: String[] names = new String[size];
110: String[][] variants = new String[size][];
111: Icon[][] icons = new Icon[size][];
112: String[] defaults = new String[size];
113: Map<String, TypeElement> fqn2TE = new HashMap<String, TypeElement>();
114: Map<String, String> displayName2FQN = new HashMap<String, String>();
115: Preferences prefs = NbPreferences.forModule(
116: JavaFixAllImports.class).node(PREFS_KEY);
117:
118: int index = 0;
119:
120: boolean shouldShowImportsPanel = false;
121:
122: for (String key : notFilteredCandidates.keySet()) {
123: names[index] = key;
124:
125: List<TypeElement> unfilteredVars = notFilteredCandidates
126: .get(key);
127: List<TypeElement> filteredVars = filteredCandidates
128: .get(key);
129:
130: shouldShowImportsPanel |= unfilteredVars.size() > 1;
131:
132: if (!unfilteredVars.isEmpty()) {
133: variants[index] = new String[unfilteredVars
134: .size()];
135: icons[index] = new Icon[variants[index].length];
136:
137: int i = -1;
138: int minImportanceLevel = Integer.MAX_VALUE;
139:
140: for (TypeElement e : filteredVars) {
141: variants[index][++i] = e
142: .getQualifiedName().toString();
143: icons[index][i] = ElementIcons
144: .getElementIcon(e.getKind(), e
145: .getModifiers());
146: int level = Utilities
147: .getImportanceLevel(variants[index][i]);
148: if (level < minImportanceLevel) {
149: defaults[index] = variants[index][i];
150: minImportanceLevel = level;
151: }
152: fqn2TE.put(e.getQualifiedName()
153: .toString(), e);
154: }
155:
156: for (TypeElement e : unfilteredVars) {
157: if (filteredVars.contains(e))
158: continue;
159:
160: String fqn = e.getQualifiedName()
161: .toString();
162: String dn = "<html><font color='#808080'><s>"
163: + fqn;
164:
165: variants[index][++i] = dn;
166: int level = Utilities
167: .getImportanceLevel(fqn);
168: if (level < minImportanceLevel) {
169: defaults[index] = variants[index][i];
170: minImportanceLevel = level;
171: }
172: fqn2TE.put(fqn, e);
173: displayName2FQN.put(dn, fqn);
174: }
175: } else {
176: variants[index] = new String[1];
177: variants[index][0] = NbBundle.getMessage(
178: JavaFixAllImports.class,
179: "FixDupImportStmts_CannotResolve"); //NOI18N
180: defaults[index] = variants[index][0];
181: icons[index] = new Icon[1];
182: icons[index][0] = new ImageIcon(
183: org.openide.util.Utilities
184: .loadImage("org/netbeans/modules/java/editor/resources/error-glyph.gif"));//NOI18N
185: }
186:
187: index++;
188: }
189:
190: boolean fixImports = false;
191: String[] selections = null;
192: boolean removeUnusedImports;
193:
194: if (shouldShowImportsPanel) {
195: FixDuplicateImportStmts panel = new FixDuplicateImportStmts();
196:
197: panel
198: .initPanel(
199: names,
200: variants,
201: icons,
202: defaults,
203: prefs
204: .getBoolean(
205: KEY_REMOVE_UNUSED_IMPORTS,
206: true));
207:
208: DialogDescriptor dd = new DialogDescriptor(
209: panel, NbBundle.getMessage(
210: JavaFixAllImports.class,
211: "FixDupImportStmts_Title")); //NOI18N
212: Dialog d = DialogDisplayer.getDefault()
213: .createDialog(dd);
214:
215: d.setVisible(true);
216:
217: d.setVisible(false);
218: d.dispose();
219: fixImports = dd.getValue() == DialogDescriptor.OK_OPTION;
220: selections = panel.getSelections();
221: removeUnusedImports = panel
222: .getRemoveUnusedImports();
223: } else {
224: fixImports = true;
225: selections = defaults;
226: removeUnusedImports = prefs.getBoolean(
227: KEY_REMOVE_UNUSED_IMPORTS, true);
228: }
229:
230: if (fixImports) {
231:
232: if (shouldShowImportsPanel)
233: prefs.putBoolean(KEY_REMOVE_UNUSED_IMPORTS,
234: removeUnusedImports);
235:
236: //do imports:
237: List<String> toImport = new ArrayList<String>();
238:
239: for (String dn : selections) {
240: String fqn = displayName2FQN.get(dn);
241: TypeElement el = fqn2TE
242: .get(fqn != null ? fqn : dn);
243:
244: if (el != null) {
245: toImport.add(el.getQualifiedName()
246: .toString());
247: }
248: }
249:
250: CompilationUnitTree cut = wc
251: .getCompilationUnit();
252:
253: boolean someImportsWereRemoved = false;
254: if (removeUnusedImports) {
255: //compute imports to remove:
256: List<TreePathHandle> unusedImports = SemanticHighlighter
257: .computeUnusedImports(wc);
258: someImportsWereRemoved = !unusedImports
259: .isEmpty();
260:
261: // make the changes to the source
262: for (TreePathHandle handle : unusedImports) {
263: TreePath path = handle.resolve(wc);
264:
265: assert path != null;
266:
267: cut = wc.getTreeMaker()
268: .removeCompUnitImport(
269: cut,
270: (ImportTree) path
271: .getLeaf());
272: }
273: }
274:
275: cut = addImports(cut, toImport, wc
276: .getTreeMaker());
277:
278: wc.rewrite(wc.getCompilationUnit(), cut);
279:
280: if (!shouldShowImportsPanel) {
281: String statusText;
282: if (toImport.isEmpty()
283: && !someImportsWereRemoved) {
284: Toolkit.getDefaultToolkit().beep();
285: statusText = NbBundle.getMessage(
286: JavaFixAllImports.class,
287: "MSG_NothingToFix"); //NOI18N
288: } else if (toImport.isEmpty()
289: && someImportsWereRemoved) {
290: statusText = NbBundle.getMessage(
291: JavaFixAllImports.class,
292: "MSG_UnusedImportsRemoved"); //NOI18N
293: } else {
294: statusText = NbBundle.getMessage(
295: JavaFixAllImports.class,
296: "MSG_ImportsFixed"); //NOI18N
297: }
298: StatusDisplayer.getDefault().setStatusText(
299: statusText);
300: }
301: }
302: } catch (IOException ex) {
303: //TODO: ErrorManager
304: ex.printStackTrace();
305: }
306: }
307: };
308: try {
309: JavaSource javaSource = JavaSource.forFileObject(fo);
310: javaSource.runModificationTask(task).commit();
311: } catch (IOException ioe) {
312: ErrorManager.getDefault().notify(ioe);
313: }
314: }
315:
316: //XXX: copied from SourceUtils.addImports. Ideally, should be on one place only:
317: public static CompilationUnitTree addImports(
318: CompilationUnitTree cut, List<String> toImport,
319: TreeMaker make) throws IOException {
320: // do not modify the list given by the caller (may be reused or immutable).
321: toImport = new ArrayList<String>(toImport);
322: Collections.sort(toImport);
323:
324: List<ImportTree> imports = new ArrayList<ImportTree>(cut
325: .getImports());
326: int currentToImport = toImport.size() - 1;
327: int currentExisting = imports.size() - 1;
328:
329: while (currentToImport >= 0 && currentExisting >= 0) {
330: String currentToImportText = toImport.get(currentToImport);
331:
332: while (currentExisting >= 0
333: && (imports.get(currentExisting).isStatic() || imports
334: .get(currentExisting)
335: .getQualifiedIdentifier().toString()
336: .compareTo(currentToImportText) > 0))
337: currentExisting--;
338:
339: if (currentExisting >= 0) {
340: imports.add(currentExisting + 1, make.Import(make
341: .Identifier(currentToImportText), false));
342: currentToImport--;
343: }
344: }
345: // we are at the head of import section and we still have some imports
346: // to add, put them to the very beginning
347: while (currentToImport >= 0) {
348: String importText = toImport.get(currentToImport);
349: imports.add(0, make.Import(make.Identifier(importText),
350: false));
351: currentToImport--;
352: }
353: // return a copy of the unit with changed imports section
354: return make.CompilationUnit(cut.getPackageName(), imports, cut
355: .getTypeDecls(), cut.getSourceFile());
356: }
357:
358: }
|