| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.editor.global.format; |
| |
| import java.awt.Dialog; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import javax.swing.AbstractAction; |
| import javax.swing.JButton; |
| import javax.swing.SwingUtilities; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import javax.swing.text.StyledDocument; |
| import org.netbeans.api.fileinfo.NonRecursiveFolder; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.classpath.GlobalPathRegistry; |
| import org.netbeans.api.progress.ProgressHandle; |
| import org.netbeans.api.progress.ProgressHandleFactory; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.api.project.ProjectUtils; |
| import org.netbeans.api.project.SourceGroup; |
| import org.netbeans.api.project.Sources; |
| import org.netbeans.api.queries.VisibilityQuery; |
| import org.netbeans.editor.BaseKit; |
| import org.netbeans.editor.GuardedDocument; |
| import org.netbeans.lib.editor.util.swing.PositionRegion; |
| import org.netbeans.modules.editor.indent.api.Reformat; |
| import org.netbeans.modules.parsing.spi.indexing.PathRecognizer; |
| import org.netbeans.modules.refactoring.api.AbstractRefactoring; |
| import org.netbeans.modules.refactoring.api.Problem; |
| import org.netbeans.modules.refactoring.api.RefactoringSession; |
| import org.netbeans.modules.refactoring.spi.BackupFacility; |
| import org.netbeans.modules.refactoring.spi.BackupFacility.Handle; |
| import org.netbeans.modules.refactoring.spi.RefactoringElementsBag; |
| import org.netbeans.modules.refactoring.spi.RefactoringPlugin; |
| import org.netbeans.modules.refactoring.spi.RefactoringPluginFactory; |
| import org.netbeans.modules.refactoring.spi.Transaction; |
| import org.openide.DialogDescriptor; |
| import org.openide.DialogDisplayer; |
| import org.openide.cookies.EditorCookie; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.loaders.DataObject; |
| import org.openide.text.NbDocument; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle.Messages; |
| import org.openide.util.RequestProcessor; |
| import org.openide.util.UserQuestionException; |
| import org.openide.util.Utilities; |
| import org.openide.util.lookup.ServiceProvider; |
| |
| public final class GlobalFormatAction extends AbstractAction { |
| |
| private static final RequestProcessor WORKER = new RequestProcessor(GlobalFormatAction.class.getName(), 1, false, false); |
| |
| public GlobalFormatAction() { |
| putValue(NAME, BaseKit.formatAction); |
| } |
| |
| @Override |
| @Messages({"BTN_OK=OK", |
| "BTN_Cancel=Cancel", |
| "CAP_Reformat=Format Recursively" |
| }) |
| public void actionPerformed(ActionEvent e) { |
| final JButton ok = new JButton(Bundle.BTN_OK()); |
| JButton cancelButton = new JButton(Bundle.BTN_Cancel()); |
| final ProgressHandle handle = ProgressHandleFactory.createHandle("Format"); |
| final ConfirmationPanel panel = new ConfirmationPanel(handle); |
| DialogDescriptor nd = new DialogDescriptor(panel, Bundle.CAP_Reformat(), true, new Object[] {ok, cancelButton}, ok, DialogDescriptor.DEFAULT_ALIGN, null, new ActionListener() { |
| @Override public void actionPerformed(ActionEvent e) {} |
| }); |
| final Dialog[] d = new Dialog[1]; |
| final AtomicBoolean cancel = new AtomicBoolean(); |
| final AtomicBoolean started = new AtomicBoolean(); |
| ok.addActionListener(new ActionListener() { |
| @Override public void actionPerformed(ActionEvent e) { |
| ok.setEnabled(false); |
| panel.started(); |
| started.set(true); |
| WORKER.post(new Runnable() { |
| @Override public void run() { |
| try { |
| doFormat(handle, cancel); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } finally { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override public void run() { |
| d[0].setVisible(false); |
| d[0].dispose(); |
| } |
| }); |
| } |
| } |
| }); |
| } |
| }); |
| cancelButton.addActionListener(new ActionListener() { |
| @Override public void actionPerformed(ActionEvent e) { |
| cancel.set(true); |
| if (!started.get()) { |
| d[0].setVisible(false); |
| d[0].dispose(); |
| } |
| } |
| }); |
| handle.start(1);//need to "start" the progress, so that the progressbars have reasonable size |
| d[0] = DialogDisplayer.getDefault().createDialog(nd); |
| d[0].setVisible(true); |
| } |
| |
| @Messages({"LBL_Preparing=Preparing...", "#{0} - the name of the file being formatted", "LBL_Formatting=Formatting: {0}", "LBL_BulkFormatting=Formatting"}) |
| private static void doFormat(ProgressHandle handle, AtomicBoolean cancel) throws IOException { |
| try { |
| handle.switchToIndeterminate(); |
| handle.progress(Bundle.LBL_Preparing()); |
| |
| Set<String> sourceIds = new HashSet<String>(); |
| |
| for (PathRecognizer pr : Lookup.getDefault().lookupAll(PathRecognizer.class)) { |
| Set<String> ids = pr.getSourcePathIds(); |
| |
| if (ids == null) continue; |
| |
| sourceIds.addAll(ids); |
| } |
| |
| Lookup context = Utilities.actionsGlobalContext(); |
| List<FileObject> toFormat = new ArrayList<FileObject>(); |
| Set<FileObject> nonRecursiveRoots = new HashSet<FileObject>(); |
| |
| for (NonRecursiveFolder f : context.lookupAll(NonRecursiveFolder.class)) { |
| if (cancel.get()) return; |
| nonRecursiveRoots.add(f.getFolder()); |
| for (FileObject c : f.getFolder().getChildren()) { |
| if (!c.isData()) continue; |
| toFormat.add(c); |
| } |
| } |
| |
| for (FileObject r : context.lookupAll(FileObject.class)) { |
| if (cancel.get()) return; |
| if (nonRecursiveRoots.contains(r)) continue; |
| addRecursivelly(r, toFormat, sourceIds, null, null, cancel); |
| } |
| |
| for (SourceGroup sg : context.lookupAll(SourceGroup.class)) { |
| if (cancel.get()) return; |
| addRecursivelly(sg.getRootFolder(), toFormat, sourceIds, null, sg, cancel); |
| } |
| |
| for (DataObject d : context.lookupAll(DataObject.class)) { |
| if (cancel.get()) return; |
| if (nonRecursiveRoots.contains(d.getPrimaryFile())) continue; |
| addRecursivelly(d.getPrimaryFile(), toFormat, sourceIds, null, null, cancel); |
| } |
| |
| Collection<? extends Project> projects = context.lookupAll(Project.class); |
| |
| if (!projects.isEmpty()) { |
| Map<Project, Map<FileObject, ClassPath>> sourceRoots = new IdentityHashMap<Project, Map<FileObject, ClassPath>>(); |
| |
| for (String id : sourceIds) { |
| for (ClassPath sCP : GlobalPathRegistry.getDefault().getPaths(id)) { |
| for (FileObject root : sCP.getRoots()) { |
| if (cancel.get()) return; |
| Project owner = FileOwnerQuery.getOwner(root); |
| |
| if (owner != null) { |
| Map<FileObject, ClassPath> projectSources = sourceRoots.get(owner); |
| |
| if (projectSources == null) { |
| sourceRoots.put(owner, projectSources = new HashMap<FileObject, ClassPath>()); |
| } |
| |
| projectSources.put(root, sCP); |
| } |
| } |
| } |
| } |
| |
| for (Project prj : projects) { |
| Map<FileObject, ClassPath> roots = sourceRoots.get(prj); |
| |
| if (roots != null) { |
| for (Entry<FileObject, ClassPath> e : roots.entrySet()) { |
| if (cancel.get()) return; |
| addRecursivelly(e.getKey(), toFormat, Collections.<String>emptySet(), e.getValue(), null, cancel); |
| } |
| } else { |
| for (SourceGroup sg : ProjectUtils.getSources(prj).getSourceGroups(Sources.TYPE_GENERIC)) { |
| if (cancel.get()) return; |
| addRecursivelly(sg.getRootFolder(), toFormat, sourceIds, null, sg, cancel); |
| } |
| } |
| } |
| } |
| |
| if (cancel.get()) return; |
| |
| BackupFacility bf = BackupFacility.getDefault(); |
| final Handle backup = bf.backup(toFormat); |
| |
| handle.switchToDeterminate(toFormat.size()); |
| |
| int done = 0; |
| |
| for (FileObject current : toFormat) { |
| if (cancel.get()) break ; |
| try { |
| DataObject d = DataObject.find(current); |
| EditorCookie ec = d.getLookup().lookup(EditorCookie.class); |
| |
| if (ec == null) continue; |
| |
| handle.progress(Bundle.LBL_Formatting(FileUtil.getFileDisplayName(current))); |
| |
| StyledDocument sd; |
| try { |
| sd = ec.openDocument(); |
| } catch (UserQuestionException uqe) { |
| uqe.confirmed(); |
| sd = ec.openDocument(); |
| } |
| final StyledDocument doc = sd; |
| final Reformat reformat = Reformat.get(doc); |
| |
| reformat.lock(); |
| |
| try { |
| NbDocument.runAtomic(doc, new Runnable() { |
| @Override public void run() { |
| try { |
| reformat(reformat, doc, 0, doc.getLength(), new AtomicBoolean()); |
| } catch (BadLocationException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| }); |
| } finally { |
| reformat.unlock(); |
| } |
| |
| ec.saveDocument(); |
| } catch (UserQuestionException uqe) { |
| uqe.confirmed(); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } finally { |
| handle.progress(++done); |
| } |
| } |
| |
| RefactoringSession session = RefactoringSession.create(Bundle.LBL_BulkFormatting()); |
| FormatRefactoring refactoring = new FormatRefactoring(new Transaction() { |
| private boolean first = true; |
| @Override |
| public void commit() { |
| if (first) { |
| first = false; |
| } else { |
| try { |
| backup.restore(); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| } |
| |
| @Override |
| public void rollback() { |
| try { |
| backup.restore(); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| }); |
| |
| refactoring.prepare(session); |
| session.doRefactoring(false); |
| } finally { |
| handle.finish(); |
| } |
| } |
| |
| private static void addRecursivelly(FileObject top, List<FileObject> into, Set<String> sourceIds, ClassPath sourceCP, SourceGroup sg, AtomicBoolean cancel) { |
| List<FileObject> todo = new LinkedList<FileObject>(); |
| Iterator<String> sIDIter = sourceIds.iterator(); |
| |
| while (sourceCP == null && sIDIter.hasNext()) { |
| if (cancel.get()) return; |
| sourceCP = ClassPath.getClassPath(top, sIDIter.next()); |
| } |
| |
| todo.add(top); |
| |
| while (!todo.isEmpty()) { |
| if (cancel.get()) return; |
| |
| FileObject current = todo.remove(0); |
| |
| if (!VisibilityQuery.getDefault().isVisible(current)) continue; |
| if (sourceCP != null && !sourceCP.contains(current)) continue; |
| if (sg != null && !sg.contains(current)) continue; |
| |
| if (current.isData()) { |
| into.add(current); |
| } |
| |
| todo.addAll(Arrays.asList(current.getChildren())); |
| } |
| } |
| |
| //TODO: copied from org.netbeans.editor.ActionFactory: |
| static void reformat(Reformat formatter, Document doc, int startPos, int endPos, AtomicBoolean canceled) throws BadLocationException { |
| final GuardedDocument gdoc = (doc instanceof GuardedDocument) |
| ? (GuardedDocument) doc : null; |
| |
| int pos = startPos; |
| if (gdoc != null) { |
| pos = gdoc.getGuardedBlockChain().adjustToBlockEnd(pos); |
| } |
| |
| LinkedList<PositionRegion> regions = new LinkedList<PositionRegion>(); |
| while (pos < endPos) { |
| int stopPos = endPos; |
| if (gdoc != null) { // adjust to start of the next guarded block |
| stopPos = gdoc.getGuardedBlockChain().adjustToNextBlockStart(pos); |
| if (stopPos == -1 || stopPos > endPos) { |
| stopPos = endPos; |
| } |
| } |
| |
| if (pos < stopPos) { |
| regions.addFirst(new PositionRegion(doc, pos, stopPos)); |
| pos = stopPos; |
| } else { |
| pos++; //ensure to make progress |
| } |
| |
| if (gdoc != null) { // adjust to end of current block |
| pos = gdoc.getGuardedBlockChain().adjustToBlockEnd(pos); |
| } |
| } |
| |
| if (canceled.get()) return; |
| // Once we start formatting, the task can't be canceled |
| |
| for (PositionRegion region : regions) { |
| formatter.reformat(region.getStartOffset(), region.getEndOffset()); |
| } |
| } |
| |
| static final class FormatRefactoring extends AbstractRefactoring { |
| private final Transaction transaction; |
| public FormatRefactoring(Transaction transaction) { |
| super(Lookup.EMPTY); |
| this.transaction = transaction; |
| } |
| } |
| |
| static final class FormatRefactoringPlugin implements RefactoringPlugin { |
| private final FormatRefactoring refactoring; |
| public FormatRefactoringPlugin(FormatRefactoring refactoring) { |
| this.refactoring = refactoring; |
| } |
| @Override public Problem preCheck() { return null; } |
| @Override public Problem checkParameters() { return null; } |
| @Override public Problem fastCheckParameters() { return null; } |
| @Override public void cancelRequest() {} |
| @Override public Problem prepare(RefactoringElementsBag refactoringElements) { |
| refactoringElements.registerTransaction(refactoring.transaction); |
| return null; |
| } |
| } |
| |
| @ServiceProvider(service=RefactoringPluginFactory.class) |
| public static final class FormatRefactoringPluginFactory implements RefactoringPluginFactory { |
| @Override public RefactoringPlugin createInstance(AbstractRefactoring refactoring) { |
| if (refactoring instanceof FormatRefactoring) { |
| return new FormatRefactoringPlugin((FormatRefactoring) refactoring); |
| } |
| return null; |
| } |
| |
| } |
| } |