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: package org.netbeans.modules.mercurial.ui.annotate;
042:
043: import org.netbeans.modules.versioning.spi.VCSContext;
044:
045: import javax.swing.*;
046: import java.io.File;
047: import java.util.*;
048: import java.awt.event.ActionEvent;
049: import org.openide.nodes.Node;
050: import org.openide.cookies.EditorCookie;
051: import org.openide.util.NbBundle;
052: import org.openide.util.RequestProcessor;
053: import org.openide.windows.WindowManager;
054: import org.netbeans.modules.mercurial.HgException;
055: import org.netbeans.modules.mercurial.HgProgressSupport;
056: import org.netbeans.modules.mercurial.Mercurial;
057: import org.netbeans.modules.mercurial.OutputLogger;
058: import org.netbeans.modules.mercurial.FileStatusCache;
059: import org.netbeans.modules.mercurial.FileInformation;
060: import org.netbeans.modules.mercurial.util.HgUtils;
061: import org.netbeans.modules.mercurial.ui.actions.ContextAction;
062: import org.openide.filesystems.FileObject;
063: import org.openide.filesystems.FileUtil;
064: import org.openide.loaders.DataObject;
065: import org.netbeans.modules.mercurial.util.HgCommand;
066: import org.netbeans.modules.mercurial.util.HgLogMessage;
067: import org.openide.windows.TopComponent;
068: import java.util.logging.Level;
069: import java.util.regex.Matcher;
070: import java.util.regex.Pattern;
071: import org.openide.DialogDisplayer;
072: import org.openide.NotifyDescriptor;
073:
074: /**
075: * Annotate action for mercurial:
076: * hg annotate - show changeset information per file line
077: *
078: * @author John Rice
079: */
080: public class AnnotateAction extends ContextAction {
081:
082: private final VCSContext context;
083:
084: public AnnotateAction(String name, VCSContext context) {
085: this .context = context;
086: putValue(Action.NAME, name);
087: }
088:
089: public boolean isEnabled() {
090: File repository = HgUtils.getRootFile(context);
091: if (repository == null)
092: return false;
093:
094: Node[] nodes = context.getElements().lookupAll(Node.class)
095: .toArray(new Node[0]);
096: if (context.getRootFiles().size() > 0
097: && activatedEditorCookie(nodes) != null) {
098: FileStatusCache cache = Mercurial.getInstance()
099: .getFileStatusCache();
100: File file = activatedFile(nodes);
101: int status = cache.getStatus(file).getStatus();
102: if (status == FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY
103: || status == FileInformation.STATUS_NOTVERSIONED_EXCLUDED) {
104: return false;
105: } else {
106: return true;
107: }
108: } else {
109: return false;
110: }
111: }
112:
113: public void performAction(ActionEvent e) {
114: Node[] nodes = context.getElements().lookupAll(Node.class)
115: .toArray(new Node[0]);
116: if (visible(nodes)) {
117: JEditorPane pane = activatedEditorPane(nodes);
118: AnnotationBarManager.hideAnnotationBar(pane);
119: } else {
120: EditorCookie ec = activatedEditorCookie(nodes);
121: if (ec == null)
122: return;
123:
124: final File file = activatedFile(nodes);
125:
126: JEditorPane[] panes = ec.getOpenedPanes();
127: if (panes == null) {
128: ec.open();
129: }
130:
131: panes = ec.getOpenedPanes();
132: if (panes == null) {
133: return;
134: }
135: final JEditorPane currentPane = panes[0];
136: final TopComponent tc = (TopComponent) SwingUtilities
137: .getAncestorOfClass(TopComponent.class, currentPane);
138: tc.requestActive();
139:
140: final AnnotationBar ab = AnnotationBarManager
141: .showAnnotationBar(currentPane);
142: ab.setAnnotationMessage(NbBundle.getMessage(
143: AnnotateAction.class, "CTL_AnnotationSubstitute")); // NOI18N;
144:
145: final File repository = HgUtils.getRootFile(context);
146: if (repository == null)
147: return;
148:
149: RequestProcessor rp = Mercurial.getInstance()
150: .getRequestProcessor(repository);
151: HgProgressSupport support = new HgProgressSupport() {
152: public void perform() {
153: OutputLogger logger = getLogger();
154: logger
155: .outputInRed(NbBundle.getMessage(
156: AnnotateAction.class,
157: "MSG_ANNOTATE_TITLE")); // NOI18N
158: logger.outputInRed(NbBundle.getMessage(
159: AnnotateAction.class,
160: "MSG_ANNOTATE_TITLE_SEP")); // NOI18N
161: computeAnnotations(repository, file, this , ab);
162: logger.output("\t" + file.getAbsolutePath()); // NOI18N
163: logger.outputInRed(NbBundle.getMessage(
164: AnnotateAction.class, "MSG_ANNOTATE_DONE")); // NOI18N
165: }
166: };
167: support.start(rp, repository.getAbsolutePath(), NbBundle
168: .getMessage(AnnotateAction.class,
169: "MSG_Annotation_Progress")); // NOI18N
170: }
171: }
172:
173: private void computeAnnotations(File repository, File file,
174: HgProgressSupport progress, AnnotationBar ab) {
175: List<String> list = null;
176: try {
177: list = HgCommand.doAnnotate(repository, file, progress
178: .getLogger());
179: } catch (HgException ex) {
180: NotifyDescriptor.Exception e = new NotifyDescriptor.Exception(
181: ex);
182: DialogDisplayer.getDefault().notifyLater(e);
183: }
184: if (progress.isCanceled()) {
185: ab.setAnnotationMessage(NbBundle.getMessage(
186: AnnotateAction.class, "CTL_AnnotationFailed")); // NOI18N;
187: return;
188: }
189: if (list == null)
190: return;
191: AnnotateLine[] lines = toAnnotateLines(list);
192: try {
193: list = HgCommand.doLogShort(repository, file, progress
194: .getLogger());
195: } catch (HgException ex) {
196: NotifyDescriptor.Exception e = new NotifyDescriptor.Exception(
197: ex);
198: DialogDisplayer.getDefault().notifyLater(e);
199: }
200: if (progress.isCanceled()) {
201: return;
202: }
203: HgLogMessage[] logs = toHgLogMessages(list);
204: if (logs == null)
205: return;
206: fillCommitMessages(lines, logs);
207: ab.setLogs(logs);
208: ab.annotationLines(file, Arrays.asList(lines));
209: }
210:
211: private static void fillCommitMessages(AnnotateLine[] annotations,
212: HgLogMessage[] logs) {
213: long lowestRevisionNumber = Long.MAX_VALUE;
214: for (int i = 0; i < annotations.length; i++) {
215: AnnotateLine annotation = annotations[i];
216: for (int j = 0; j < logs.length; j++) {
217: HgLogMessage log = logs[j];
218: if (log.getRevision() < lowestRevisionNumber) {
219: lowestRevisionNumber = log.getRevision();
220: }
221: if (annotation.getRevision().equals(
222: log.getRevision().toString())) {
223: annotation.setDate(log.getDate());
224: annotation.setCommitMessage(log.getCommitMessage());
225: }
226: }
227: }
228: String lowestRev = Long.toString(lowestRevisionNumber);
229: for (int i = 0; i < annotations.length; i++) {
230: AnnotateLine annotation = annotations[i];
231: annotation.setCanBeRolledBack(!annotation.getRevision()
232: .equals(lowestRev));
233: }
234: }
235:
236: private static HgLogMessage[] toHgLogMessages(List<String> lines) {
237: List<HgLogMessage> logs = new ArrayList<HgLogMessage>();
238: HgLogMessage log = null;
239:
240: int i = 0;
241: for (Iterator j = lines.iterator(); j.hasNext();) {
242: String line = (String) j.next();
243: if (i % 4 == 0) {
244: log = new HgLogMessage();
245: try {
246: log.setRevision(Long.parseLong(line));
247: } catch (java.lang.Exception e) {
248: Mercurial.LOG.log(Level.SEVERE,
249: "Caught Exception while parsing revision",
250: e); // NOI18N
251: }
252: } else if (i % 4 == 1) {
253: log.setCommitMessage(line);
254: } else if (i % 4 == 2) {
255: String splits[] = line.split(" ", 2); // NOI18N
256: try {
257: log.setDate(new Date(Long.parseLong(splits[0]
258: .trim()) * 1000));
259: } catch (java.lang.Exception e) {
260: Mercurial.LOG.log(Level.SEVERE,
261: "Caught Exception while parsing date", e); // NOI18N
262: }
263: log.setTimeZoneOffset(splits[1]);
264: } else if (i % 4 == 3) {
265: log.setChangeSet(line);
266: logs.add(log);
267: }
268: i++;
269: }
270: return logs.toArray(new HgLogMessage[logs.size()]);
271: }
272:
273: private static AnnotateLine[] toAnnotateLines(
274: List<String> annotations) {
275: final int GROUP_AUTHOR = 1;
276: final int GROUP_REVISION = 2;
277: final int GROUP_FILENAME = 3;
278: final int GROUP_CONTENT = 4;
279:
280: AnnotateLine[] lines = new AnnotateLine[annotations.size()];
281: int i = 0;
282: Pattern p = Pattern
283: .compile("^\\s*(\\w+\\b)\\s+(\\d+)\\s+(\\b\\S*):\\s(.*)$"); //NOI18N
284: for (String line : annotations) {
285: Matcher m = p.matcher(line);
286: if (!m.matches()) {
287: Mercurial.LOG
288: .log(
289: Level.WARNING,
290: "AnnotateAction: toAnnotateLines(): Failed when matching: {0}",
291: new Object[] { line }); //NOI18N
292: continue;
293: }
294: lines[i] = new AnnotateLine();
295: lines[i].setAuthor(m.group(GROUP_AUTHOR));
296: lines[i].setRevision(m.group(GROUP_REVISION));
297: lines[i].setFileName(m.group(GROUP_FILENAME));
298: lines[i].setContent(m.group(GROUP_CONTENT));
299: lines[i].setLineNum(i + 1);
300: i++;
301: }
302: return lines;
303: }
304:
305: /**
306: * @param nodes or null (then taken from windowsystem, it may be wrong on editor tabs #66700).
307: */
308: public boolean visible(Node[] nodes) {
309: JEditorPane currentPane = activatedEditorPane(nodes);
310: return AnnotationBarManager.annotationBarVisible(currentPane);
311: }
312:
313: /**
314: * @return active editor pane or null if selected node
315: * does not have any or more nodes selected.
316: */
317: private JEditorPane activatedEditorPane(Node[] nodes) {
318: EditorCookie ec = activatedEditorCookie(nodes);
319: if (ec != null) {
320: JEditorPane[] panes = ec.getOpenedPanes();
321: if (panes != null && panes.length > 0) {
322: return panes[0];
323: }
324: }
325: return null;
326: }
327:
328: private EditorCookie activatedEditorCookie(Node[] nodes) {
329: if (nodes == null) {
330: nodes = WindowManager.getDefault().getRegistry()
331: .getActivatedNodes();
332: }
333: if (nodes.length == 1) {
334: Node node = nodes[0];
335: return (EditorCookie) node.getCookie(EditorCookie.class);
336: }
337: return null;
338: }
339:
340: private File activatedFile(Node[] nodes) {
341: if (nodes.length == 1) {
342: Node node = nodes[0];
343: DataObject dobj = (DataObject) node
344: .getCookie(DataObject.class);
345: if (dobj != null) {
346: FileObject fo = dobj.getPrimaryFile();
347: return FileUtil.toFile(fo);
348: }
349: }
350: return null;
351: }
352: }
|