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 */
16package com.android.ide.eclipse.traceview.editors;
17
18import com.android.ide.eclipse.ddms.JavaSourceRevealer;
19import com.android.ide.eclipse.traceview.TraceviewPlugin;
20import com.android.traceview.ColorController;
21import com.android.traceview.DmTraceReader;
22import com.android.traceview.MethodData;
23import com.android.traceview.ProfileView;
24import com.android.traceview.ProfileView.MethodHandler;
25import com.android.traceview.SelectionController;
26import com.android.traceview.TimeLineView;
27import com.android.traceview.TraceReader;
28import com.android.traceview.TraceUnits;
29
30import org.eclipse.core.filesystem.EFS;
31import org.eclipse.core.filesystem.IFileStore;
32import org.eclipse.core.filesystem.URIUtil;
33import org.eclipse.core.resources.IFile;
34import org.eclipse.core.resources.IWorkspace;
35import org.eclipse.core.resources.IWorkspaceRoot;
36import org.eclipse.core.resources.ResourcesPlugin;
37import org.eclipse.core.runtime.CoreException;
38import org.eclipse.core.runtime.IPath;
39import org.eclipse.core.runtime.IProgressMonitor;
40import org.eclipse.core.runtime.IStatus;
41import org.eclipse.core.runtime.Status;
42import org.eclipse.jface.dialogs.IDialogConstants;
43import org.eclipse.jface.dialogs.IMessageProvider;
44import org.eclipse.jface.dialogs.MessageDialog;
45import org.eclipse.jface.window.Window;
46import org.eclipse.swt.SWT;
47import org.eclipse.swt.custom.SashForm;
48import org.eclipse.swt.graphics.Color;
49import org.eclipse.swt.layout.GridData;
50import org.eclipse.swt.layout.GridLayout;
51import org.eclipse.swt.widgets.Composite;
52import org.eclipse.swt.widgets.Display;
53import org.eclipse.swt.widgets.FileDialog;
54import org.eclipse.swt.widgets.Label;
55import org.eclipse.swt.widgets.Shell;
56import org.eclipse.ui.IEditorInput;
57import org.eclipse.ui.IEditorSite;
58import org.eclipse.ui.PartInitException;
59import org.eclipse.ui.dialogs.SaveAsDialog;
60import org.eclipse.ui.ide.FileStoreEditorInput;
61import org.eclipse.ui.part.EditorPart;
62import org.eclipse.ui.part.FileEditorInput;
63
64import java.io.File;
65import java.io.IOException;
66import java.net.URI;
67
68public class TraceviewEditor extends EditorPart implements MethodHandler {
69
70    private Composite mParent;
71    private String mFilename;
72    private Composite mContents;
73
74    @Override
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     */
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
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
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
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
245    public boolean isDirty() {
246        return false;
247    }
248
249    @Override
250    public boolean isSaveAsAllowed() {
251        return true;
252    }
253
254    @Override
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
302    public void setFocus() {
303        mParent.setFocus();
304    }
305
306    // ---- MethodHandler methods
307
308    @Override
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