blob: e147644fd3401470ed35261cde3572a6b9e45c91 [file] [log] [blame]
/*
* 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.java.debugjavac;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;
import java.util.WeakHashMap;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JScrollPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.StyledDocument;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.editor.EditorRegistry;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.java.source.CancellableTask;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.JavaSource.Priority;
import org.netbeans.api.java.source.JavaSourceTaskFactory;
import org.netbeans.api.java.source.support.EditorAwareJavaSourceTaskFactory;
import org.netbeans.core.spi.multiview.CloseOperationState;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.netbeans.core.spi.multiview.MultiViewElementCallback;
import org.netbeans.editor.GuardedDocument;
import org.netbeans.modules.editor.NbEditorDocument;
import org.netbeans.modules.editor.NbEditorKit;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.java.debugjavac.Decompiler.Input;
import org.netbeans.modules.java.debugjavac.Decompiler.Result;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.spi.TaskIndexingMode;
import org.netbeans.spi.editor.errorstripe.UpToDateStatus;
import org.netbeans.spi.editor.errorstripe.UpToDateStatusProvider;
import org.netbeans.spi.editor.errorstripe.UpToDateStatusProviderFactory;
import org.openide.awt.UndoRedo;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.SaveCookie;
import org.openide.filesystems.FileAttributeEvent;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
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.RequestProcessor;
import org.openide.util.lookup.ServiceProvider;
import org.openide.windows.TopComponent;
/**
*
* @author lahvac
*/
public class DecompiledTab {
public static final String PROP_EXTRA_PARAMS = DecompiledTab.class.getName() + ".extraParams";
private static final String ATTR_DECOMPILED = "decompiled-temporary";
private static final Map<FileObject, FileObject> source2Decompiled = new WeakHashMap<>();
private static final RequestProcessor DECOMPILE_RUNNER = new RequestProcessor(DecompiledTab.class.getName(), 1, false, false);
public static synchronized FileObject findDecompiled(FileObject source) {
return findDecompiled(source, true);
}
private static synchronized FileObject findDecompiled(FileObject source, boolean create) {
FileObject result = source2Decompiled.get(source);
if (result == null && create) {
try {
FileObject decompiledFO = FileUtil.createMemoryFileSystem().getRoot().createData(source.getName(), "djava");
decompiledFO.setAttribute(ATTR_DECOMPILED, Boolean.TRUE);
source2Decompiled.put(source, result = decompiledFO);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
return result;
}
private static @CheckForNull Document decompiledCodeDocument(FileObject file) {
return decompiledCodeDocument(file, true);
}
private static @CheckForNull Document decompiledCodeDocument(FileObject file, boolean create) {
try {
FileObject decompiled = findDecompiled(file, create);
if (decompiled == null) return null;
DataObject decompiledDO = DataObject.find(decompiled);
EditorCookie ec = decompiledDO.getLookup().lookup(EditorCookie.class);
return ec.openDocument();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
return null;
}
}
private static DecompilerDescription findDecompiler(String id) throws MalformedURLException {
for (DecompilerDescription decompiler : DecompilerDescription.getDecompilers()) {
if (id.equals(decompiler.id)) return decompiler;
}
return null;
}
private static void decompileIntoDocumentLater(final FileObject source) {
DECOMPILE_RUNNER.post(new Runnable() {
@Override public void run() {
doDecompileIntoDocument(source);
}
});
}
private static void doDecompileIntoDocument(FileObject source) {
FileObject decompiled = findDecompiled(source, true);
final Document doc = decompiledCodeDocument(source);
if (doc == null || doc.getProperty(DECOMPILE_TAB_ACTIVE) != Boolean.TRUE) return ;
try {
Object compilerDescription = decompiled.getAttribute(CompilerDescription.class.getName());
Object decompilerId = decompiled.getAttribute(DecompilerDescription.class.getName());
Object extraParams = decompiled.getAttribute(PROP_EXTRA_PARAMS);
if (!(compilerDescription instanceof CompilerDescription) || !(decompilerId instanceof String)) {
return ;
}
if (!(extraParams instanceof String) || extraParams == null) {
extraParams = "";
}
final String decompiledCode;
if (((CompilerDescription) compilerDescription).isValid()) {
final String code = Source.create(source).createSnapshot().getText().toString();
UpToDateStatusProviderImpl.get(doc).update(UpToDateStatus.UP_TO_DATE_PROCESSING);
DecompilerDescription decompiler = findDecompiler((String) decompilerId);
Result decompileResult = ((CompilerDescription) compilerDescription).decompile(decompiler, new Input(code, Utilities.commandLineParameters(source, (String) extraParams)));
if (decompileResult.exception != null) {
decompiledCode = "#Section(text/plain) Ooops, an exception occurred while decompiling:\n" + decompileResult.exception;
} else {
decompiledCode = (decompileResult.decompiledOutput != null ? "#Section(" + decompileResult.decompiledMimeType + ") Output:\n" + decompileResult.decompiledOutput + "\n" : "") +
(decompileResult.compileErrors != null ? "#Section(text/plain) Processing Errors:\n" + decompileResult.compileErrors + "\n" : "");
}
} else {
decompiledCode = "Unusable compiler";
}
NbDocument.runAtomic((StyledDocument) doc, new Runnable() {
@Override public void run() {
try {
doc.remove(0, doc.getLength());
if (doc instanceof GuardedDocument) {
((GuardedDocument) doc).getGuardedBlockChain().removeEmptyBlocks();
}
doc.insertString(0, decompiledCode, null);
if (doc instanceof GuardedDocument) {
((GuardedDocument) doc).getGuardedBlockChain().addBlock(0, doc.getLength(), true);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
});
SaveCookie sc = DataObject.find(decompiled).getLookup().lookup(SaveCookie.class);
if (sc != null) sc.save();
UpToDateStatusProviderImpl.get(doc).update(UpToDateStatus.UP_TO_DATE_OK);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
private static final String DECOMPILE_TAB_ACTIVE = "decompile-tab-active";
@MultiViewElement.Registration(
displayName="Decompile",
// iconBase="org/netbeans/modules/java/resources/class.gif",
persistenceType=TopComponent.PERSISTENCE_ONLY_OPENED,
preferredID="java.decompile",
mimeType="text/x-java",
position=5000
)
public static MultiViewElement createMultiViewEditorElement(Lookup context) {
final DataObject d = context.lookup(DataObject.class);
final FileObject decompiled = findDecompiled(d.getPrimaryFile(), true);
return new MultiViewElement() {
private JEditorPane pane;
private JComponent scrollPane;
private final FileChangeListener fileListener = new FileChangeAdapter() {
@Override public void fileAttributeChanged(FileAttributeEvent fe) {
decompileIntoDocumentLater(d.getPrimaryFile());
}
};
@Override
public JComponent getVisualRepresentation() {
if (pane == null) {
pane = new JEditorPane();
pane.setContentType("text/x-java-decompiled");
pane.setEditorKit(new NbEditorKit() {
@Override public String getContentType() {
return "text/x-java-decompiled";
}
});
Document doc = decompiledCodeDocument(d.getPrimaryFile());
if (doc != null)
pane.setDocument(doc);
scrollPane = doc instanceof NbEditorDocument ? (JComponent) ((NbEditorDocument) doc).createEditor(pane) : new JScrollPane(pane);
}
return scrollPane;
}
private DecompileToolbar toolbar;
@Override
public JComponent getToolbarRepresentation() {
if (toolbar == null) {
FileObject decompiled = findDecompiled(d.getPrimaryFile(), true);
toolbar = new DecompileToolbar(decompiled, d.getPrimaryFile());
}
return toolbar;
}
@Override
public Action[] getActions() {
return new Action[0];
}
@Override
public Lookup getLookup() {
return Lookup.EMPTY;
}
@Override
public void componentOpened() {
}
@Override
public void componentClosed() {
}
@Override
public void componentShowing() {
Document doc = decompiledCodeDocument(d.getPrimaryFile());
if (doc != null) doc.putProperty(DECOMPILE_TAB_ACTIVE, true);
decompiled.addFileChangeListener(fileListener);
decompileIntoDocumentLater(d.getPrimaryFile());
}
@Override
public void componentHidden() {
Document doc = decompiledCodeDocument(d.getPrimaryFile());
if (doc != null) doc.putProperty(DECOMPILE_TAB_ACTIVE, null);
decompiled.removeFileChangeListener(fileListener);
}
@Override
public void componentActivated() {
}
@Override
public void componentDeactivated() {
}
@Override
public UndoRedo getUndoRedo() {
return null;
}
@Override
public void setMultiViewCallback(MultiViewElementCallback callback) {
}
@Override
public CloseOperationState canCloseElement() {
return CloseOperationState.STATE_OK;
}
};
}
static {
EditorRegistry.addPropertyChangeListener(new DocL());
}
private static final class DocL implements PropertyChangeListener, DocumentListener {
private Document lastFocused;
@Override public void propertyChange(PropertyChangeEvent evt) {
JTextComponent fc = EditorRegistry.focusedComponent();
Document doc = fc != null ? fc.getDocument() : null;
if (doc == lastFocused) return ;
if (lastFocused != null) {
lastFocused.removeDocumentListener(this);
}
if (doc != null) {
doc.addDocumentListener(this);
}
lastFocused = doc;
}
@Override
public void insertUpdate(DocumentEvent e) {
update(e);
}
@Override
public void removeUpdate(DocumentEvent e) {
update(e);
}
private void update(DocumentEvent e) {
FileObject file = NbEditorUtilities.getFileObject(e.getDocument());
if (file == null) return ;
Document doc = decompiledCodeDocument(file, false);
if (doc == null) return ;
UpToDateStatusProviderImpl.get(doc).update(UpToDateStatus.UP_TO_DATE_DIRTY);
}
@Override
public void changedUpdate(DocumentEvent e) {
}
}
private static final class Updater implements CancellableTask<CompilationInfo> {
@Override public void run(CompilationInfo parameter) throws Exception {
doDecompileIntoDocument(parameter.getFileObject());
// FileObject sourceFile = parameter.getFileObject();
//// if (sourceFile.getAttribute(ATTR_DECOMPILED) == Boolean.TRUE) return;
// final FileObject decompiled = findDecompiled(sourceFile, false);
//
// if (decompiled == null) return ;
//
// final ElementHandle<?> handle = ElementHandle.create(parameter.getTopLevelElements().get(0));
//
// JavaSource.create(parameter.getClasspathInfo(), decompiled).runModificationTask(new Task<WorkingCopy>() {
// @Override public void run(WorkingCopy copy) throws Exception {
// copy.toPhase(Phase.RESOLVED);
//
// copy.rewrite(copy.getCompilationUnit(), CodeGenerator.generateCode(copy, (TypeElement) handle.resolve(copy)));
// }
// }).commit();
//
// SaveCookie sc = DataObject.find(decompiled).getLookup().lookup(SaveCookie.class);
//
// if (sc != null) sc.save();
}
@Override public void cancel() {
}
}
@ServiceProvider(service=JavaSourceTaskFactory.class)
public static final class UpdaterFactory extends EditorAwareJavaSourceTaskFactory {
public UpdaterFactory() {
super(Phase.RESOLVED, Priority.LOW, TaskIndexingMode.ALLOWED_DURING_SCAN);
}
@Override
protected CancellableTask<CompilationInfo> createTask(FileObject file) {
return new Updater();
}
}
@MimeRegistration(mimeType="text/x-java-decompiled", service=UpToDateStatusProviderFactory.class)
public static final class UpToDateStatusProviderFactoryImpl implements UpToDateStatusProviderFactory {
@Override public UpToDateStatusProvider createUpToDateStatusProvider(Document document) {
return UpToDateStatusProviderImpl.get(document);
}
}
private static final class UpToDateStatusProviderImpl extends UpToDateStatusProvider {
public static UpToDateStatusProviderImpl get(Document doc) {
UpToDateStatusProviderImpl result = (UpToDateStatusProviderImpl) doc.getProperty(UpToDateStatusProviderImpl.class);
if (result == null) {
result = new UpToDateStatusProviderImpl(doc);
}
return result;
}
private final Document doc;
private UpToDateStatusProviderImpl(Document doc) {
this.doc = doc;
}
@Override
public UpToDateStatus getUpToDate() {
UpToDateStatus status = (UpToDateStatus) doc.getProperty(UpToDateStatusProviderImpl.class.getName() + "-status-value");
if (status == null) status = UpToDateStatus.UP_TO_DATE_DIRTY;
return status;
}
private void update(UpToDateStatus newValue) {
UpToDateStatus oldValue = getUpToDate();
doc.putProperty(UpToDateStatusProviderImpl.class.getName() + "-status-value", newValue);
firePropertyChange(PROP_UP_TO_DATE, oldValue, newValue);
//TODO: the event above does not always repaint the error stripe, workarounding:
NbDocument.runAtomic((StyledDocument) doc, new Runnable() {
@Override public void run() {
try {
doc.insertString(0, " ", null);
doc.remove(0, 1);
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
});
try {
FileObject decompiledFO = NbEditorUtilities.getFileObject(doc);
SaveCookie sc = decompiledFO != null ? DataObject.find(decompiledFO).getLookup().lookup(SaveCookie.class) : null;
if (sc != null) sc.save();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
}