1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.ide.eclipse.traceview.editors;
17 
18 import com.android.ide.eclipse.ddms.JavaSourceRevealer;
19 import com.android.ide.eclipse.traceview.TraceviewPlugin;
20 import com.android.traceview.ColorController;
21 import com.android.traceview.DmTraceReader;
22 import com.android.traceview.MethodData;
23 import com.android.traceview.ProfileView;
24 import com.android.traceview.ProfileView.MethodHandler;
25 import com.android.traceview.SelectionController;
26 import com.android.traceview.TimeLineView;
27 import com.android.traceview.TraceReader;
28 import com.android.traceview.TraceUnits;
29 
30 import org.eclipse.core.filesystem.EFS;
31 import org.eclipse.core.filesystem.IFileStore;
32 import org.eclipse.core.filesystem.URIUtil;
33 import org.eclipse.core.resources.IFile;
34 import org.eclipse.core.resources.IWorkspace;
35 import org.eclipse.core.resources.IWorkspaceRoot;
36 import org.eclipse.core.resources.ResourcesPlugin;
37 import org.eclipse.core.runtime.CoreException;
38 import org.eclipse.core.runtime.IPath;
39 import org.eclipse.core.runtime.IProgressMonitor;
40 import org.eclipse.core.runtime.IStatus;
41 import org.eclipse.core.runtime.Status;
42 import org.eclipse.jface.dialogs.IDialogConstants;
43 import org.eclipse.jface.dialogs.IMessageProvider;
44 import org.eclipse.jface.dialogs.MessageDialog;
45 import org.eclipse.jface.window.Window;
46 import org.eclipse.swt.SWT;
47 import org.eclipse.swt.custom.SashForm;
48 import org.eclipse.swt.graphics.Color;
49 import org.eclipse.swt.layout.GridData;
50 import org.eclipse.swt.layout.GridLayout;
51 import org.eclipse.swt.widgets.Composite;
52 import org.eclipse.swt.widgets.Display;
53 import org.eclipse.swt.widgets.FileDialog;
54 import org.eclipse.swt.widgets.Label;
55 import org.eclipse.swt.widgets.Shell;
56 import org.eclipse.ui.IEditorInput;
57 import org.eclipse.ui.IEditorSite;
58 import org.eclipse.ui.PartInitException;
59 import org.eclipse.ui.dialogs.SaveAsDialog;
60 import org.eclipse.ui.ide.FileStoreEditorInput;
61 import org.eclipse.ui.part.EditorPart;
62 import org.eclipse.ui.part.FileEditorInput;
63 
64 import java.io.File;
65 import java.io.IOException;
66 import java.net.URI;
67 
68 public class TraceviewEditor extends EditorPart implements MethodHandler {
69 
70     private Composite mParent;
71     private String mFilename;
72     private Composite mContents;
73 
74     @Override
doSave(IProgressMonitor monitor)75     public void doSave(IProgressMonitor monitor) {
76         // We do not modify the file
77     }
78 
79     /*
80      * Copied from org.eclipse.ui.texteditor.AbstractDecoratedTextEditor.
81      */
82     /**
83      * Checks whether there given file store points to a file in the workspace.
84      * Only returns a workspace file if there's a single match.
85      *
86      * @param fileStore the file store
87      * @return the <code>IFile</code> that matches the given file store
88      */
getWorkspaceFile(IFileStore fileStore)89     private IFile getWorkspaceFile(IFileStore fileStore) {
90         IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
91         IFile[] files = workspaceRoot.findFilesForLocationURI(fileStore.toURI());
92         if (files != null && files.length == 1)
93             return files[0];
94         return null;
95     }
96 
97     /*
98      * Based on the performSaveAs() method defined in class
99      * org.eclipse.ui.texteditor.AbstractDecoratedTextEditor of the
100      * org.eclipse.ui.editors plugin.
101      */
102     @Override
doSaveAs()103     public void doSaveAs() {
104         Shell shell = getSite().getShell();
105         final IEditorInput input = getEditorInput();
106 
107         final IEditorInput newInput;
108 
109         if (input instanceof FileEditorInput) {
110             // the file is part of the current workspace
111             FileEditorInput fileEditorInput = (FileEditorInput) input;
112             SaveAsDialog dialog = new SaveAsDialog(shell);
113 
114             IFile original = fileEditorInput.getFile();
115             if (original != null) {
116                 dialog.setOriginalFile(original);
117             }
118 
119             dialog.create();
120 
121             if (original != null && !original.isAccessible()) {
122                 String message = String.format(
123                         "The original file ''%s'' has been deleted or is not accessible.",
124                         original.getName());
125                 dialog.setErrorMessage(null);
126                 dialog.setMessage(message, IMessageProvider.WARNING);
127             }
128 
129             if (dialog.open() == Window.CANCEL) {
130                 return;
131             }
132 
133             IPath filePath = dialog.getResult();
134             if (filePath == null) {
135                 return;
136             }
137 
138             IWorkspace workspace = ResourcesPlugin.getWorkspace();
139             IFile file = workspace.getRoot().getFile(filePath);
140 
141             if (copy(shell, fileEditorInput.getURI(), file.getLocationURI()) == null) {
142                 return;
143             }
144 
145             try {
146                 file.refreshLocal(IFile.DEPTH_ZERO, null);
147             } catch (CoreException e) {
148                 // TODO Auto-generated catch block
149                 e.printStackTrace();
150             }
151             newInput = new FileEditorInput(file);
152             setInput(newInput);
153             setPartName(newInput.getName());
154         } else if (input instanceof FileStoreEditorInput) {
155             // the file is not part of the current workspace
156             FileStoreEditorInput fileStoreEditorInput = (FileStoreEditorInput) input;
157             FileDialog dialog = new FileDialog(shell, SWT.SAVE);
158             IPath oldPath = URIUtil.toPath(fileStoreEditorInput.getURI());
159             if (oldPath != null) {
160                 dialog.setFileName(oldPath.lastSegment());
161                 dialog.setFilterPath(oldPath.toOSString());
162             }
163 
164             String path = dialog.open();
165             if (path == null) {
166                 return;
167             }
168 
169             // Check whether file exists and if so, confirm overwrite
170             final File localFile = new File(path);
171             if (localFile.exists()) {
172                 MessageDialog overwriteDialog = new MessageDialog(
173                         shell,
174                         "Save As",
175                         null,
176                         String.format(
177                                 "%s already exists.\nDo you want to replace it?"
178                                 , path),
179                         MessageDialog.WARNING,
180                         new String[] {
181                                 IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL
182                         }, 1); // 'No' is the default
183                 if (overwriteDialog.open() != Window.OK) {
184                     return;
185                 }
186             }
187 
188             IFileStore destFileStore = copy(shell, fileStoreEditorInput.getURI(), localFile.toURI());
189             if (destFileStore != null) {
190                 IFile file = getWorkspaceFile(destFileStore);
191                 if (file != null) {
192                     newInput = new FileEditorInput(file);
193                 } else {
194                     newInput = new FileStoreEditorInput(destFileStore);
195                 }
196                 setInput(newInput);
197                 setPartName(newInput.getName());
198             }
199         }
200     }
201 
copy(Shell shell, URI source, URI dest)202     private IFileStore copy(Shell shell, URI source, URI dest) {
203         IFileStore destFileStore = null;
204         IFileStore sourceFileStore = null;
205         try {
206             destFileStore = EFS.getStore(dest);
207             sourceFileStore = EFS.getStore(source);
208             sourceFileStore.copy(destFileStore, EFS.OVERWRITE, null);
209         } catch (CoreException ex) {
210             String title = "Problems During Save As...";
211             String msg = String.format("Save could not be completed. %s",
212                     ex.getMessage());
213             MessageDialog.openError(shell, title, msg);
214             return null;
215         }
216         return destFileStore;
217     }
218 
219     @Override
init(IEditorSite site, IEditorInput input)220     public void init(IEditorSite site, IEditorInput input) throws PartInitException {
221         // The contract of init() mentions we need to fail if we can't
222         // understand the input.
223         if (input instanceof FileEditorInput) {
224             // We try to open a file that is part of the current workspace
225             FileEditorInput fileEditorInput = (FileEditorInput) input;
226             mFilename = fileEditorInput.getPath().toOSString();
227             setSite(site);
228             setInput(input);
229             setPartName(input.getName());
230         } else if (input instanceof FileStoreEditorInput) {
231             // We try to open a file that is not part of the current workspace
232             FileStoreEditorInput fileStoreEditorInput = (FileStoreEditorInput) input;
233             mFilename = fileStoreEditorInput.getURI().getPath();
234             setSite(site);
235             setInput(input);
236             setPartName(input.getName());
237         } else {
238             throw new PartInitException("Input is not of type FileEditorInput " + //$NON-NLS-1$
239                     "nor FileStoreEditorInput: " + //$NON-NLS-1$
240                     input == null ? "null" : input.toString()); //$NON-NLS-1$
241         }
242     }
243 
244     @Override
isDirty()245     public boolean isDirty() {
246         return false;
247     }
248 
249     @Override
isSaveAsAllowed()250     public boolean isSaveAsAllowed() {
251         return true;
252     }
253 
254     @Override
createPartControl(Composite parent)255     public void createPartControl(Composite parent) {
256         mParent = parent;
257         try {
258             TraceReader reader = new DmTraceReader(mFilename, false);
259             reader.getTraceUnits().setTimeScale(TraceUnits.TimeScale.MilliSeconds);
260 
261             mContents = new Composite(mParent, SWT.NONE);
262 
263             Display display = mContents.getDisplay();
264             ColorController.assignMethodColors(display, reader.getMethods());
265             SelectionController selectionController = new SelectionController();
266 
267             GridLayout gridLayout = new GridLayout(1, false);
268             gridLayout.marginWidth = 0;
269             gridLayout.marginHeight = 0;
270             gridLayout.horizontalSpacing = 0;
271             gridLayout.verticalSpacing = 0;
272             mContents.setLayout(gridLayout);
273 
274             Color darkGray = display.getSystemColor(SWT.COLOR_DARK_GRAY);
275 
276             // Create a sash form to separate the timeline view (on top)
277             // and the profile view (on bottom)
278             SashForm sashForm1 = new SashForm(mContents, SWT.VERTICAL);
279             sashForm1.setBackground(darkGray);
280             sashForm1.SASH_WIDTH = 3;
281             GridData data = new GridData(GridData.FILL_BOTH);
282             sashForm1.setLayoutData(data);
283 
284             // Create the timeline view
285             new TimeLineView(sashForm1, reader, selectionController);
286 
287             // Create the profile view
288             new ProfileView(sashForm1, reader, selectionController).setMethodHandler(this);
289         } catch (IOException e) {
290             Label l = new Label(parent, 0);
291             l.setText("Failed to read the stack trace.");
292 
293             Status status = new Status(IStatus.ERROR, TraceviewPlugin.PLUGIN_ID,
294                     "Failed to read the stack trace.", e);
295             TraceviewPlugin.getDefault().getLog().log(status);
296         }
297 
298         mParent.layout();
299     }
300 
301     @Override
setFocus()302     public void setFocus() {
303         mParent.setFocus();
304     }
305 
306     // ---- MethodHandler methods
307 
308     @Override
handleMethod(MethodData method)309     public void handleMethod(MethodData method) {
310         String methodName = method.getMethodName();
311         String className = method.getClassName().replaceAll("/", ".");  //$NON-NLS-1$ //$NON-NLS-2$
312         String fqmn = className + "." + methodName; //$NON-NLS-1$
313 
314         JavaSourceRevealer.revealMethod(fqmn, null, -1, null);
315     }
316 }
317