blob: f7a7918fc36a4009053cbc3967ee51d3d771c992 [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.beans.beaninfo;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.StyledDocument;
import org.netbeans.api.editor.guards.GuardedSectionManager;
import org.netbeans.api.queries.FileEncodingQuery;
import org.netbeans.core.api.multiview.MultiViews;
import org.netbeans.core.spi.multiview.CloseOperationState;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.netbeans.core.spi.multiview.MultiViewElementCallback;
import org.netbeans.core.spi.multiview.MultiViewFactory;
import org.netbeans.modules.beans.beaninfo.GenerateBeanInfoAction.BeanInfoWorker;
import org.netbeans.spi.editor.guards.GuardedEditorSupport;
import org.netbeans.spi.editor.guards.GuardedSectionsFactory;
import org.netbeans.spi.editor.guards.GuardedSectionsProvider;
import org.netbeans.spi.java.project.support.ui.templates.JavaTemplates;
import org.openide.cookies.EditCookie;
import org.openide.cookies.EditorCookie;
import org.openide.cookies.OpenCookie;
import org.openide.cookies.PrintCookie;
import org.openide.cookies.SaveCookie;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileStatusEvent;
import org.openide.filesystems.FileStatusListener;
import org.openide.filesystems.FileSystem;
import org.openide.loaders.DataObject;
import org.openide.loaders.MultiDataObject;
import org.openide.nodes.CookieSet;
import org.openide.nodes.Node;
import org.openide.text.CloneableEditor;
import org.openide.text.CloneableEditorSupport.Pane;
import org.openide.text.DataEditorSupport;
import org.openide.text.NbDocument;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbCollections;
import org.openide.util.Task;
import org.openide.util.TaskListener;
import org.openide.windows.CloneableOpenSupport;
import org.openide.windows.CloneableTopComponent;
import org.openide.windows.TopComponent;
/**
*
* @author Jan Pokorsky
*/
public final class BIEditorSupport extends DataEditorSupport
implements OpenCookie, EditCookie, EditorCookie, PrintCookie, EditorCookie.Observable {
private static final String MV_JAVA_ID = "java"; // NOI18N
private static final String MV_BEANINFO_ID = "beaninfo"; // NOI18N
private static final String MIME_BEAN_INFO = "text/x-java-beaninfo"; //NOI18N
private BIGES guardedEditor;
private GuardedSectionsProvider guardedProvider;
private GenerateBeanInfoAction.BeanInfoWorker worker;
private static final Set<BIEditorSupport> opened = Collections.synchronizedSet(new HashSet<BIEditorSupport>());
private static Map<FileSystem,FileStatusListener> fsToStatusListener = new HashMap<FileSystem,FileStatusListener>();
/**
* The embracing multiview TopComponent (holds the form designer and
* java editor) - we remeber the last active TopComponent (not all clones)
*/
private CloneableTopComponent multiviewTC;
private TopComponentsListener topComponentsListener;
public BIEditorSupport(DataObject obj, CookieSet cookieSet) {
super(obj, new Environment(obj, cookieSet));
setMIMEType("text/x-java"); // NOI18N
}
public GuardedSectionManager getGuardedSectionManager() {
try {
StyledDocument doc = openDocument();
return GuardedSectionManager.getInstance(doc);
} catch (IOException ex) {
throw (IllegalStateException) new IllegalStateException("cannot open document").initCause(ex); // NOI18N
}
}
@Override
protected void loadFromStreamToKit(StyledDocument doc, InputStream stream, EditorKit kit)
throws IOException, BadLocationException {
if (guardedEditor == null) {
guardedEditor = new BIGES();
GuardedSectionsFactory gFactory = GuardedSectionsFactory.find(((DataEditorSupport.Env) env).getMimeType());
if (gFactory != null) {
guardedProvider = gFactory.create(guardedEditor);
}
}
if (guardedProvider != null) {
guardedEditor.doc = doc;
Charset c = FileEncodingQuery.getEncoding(this.getDataObject().getPrimaryFile());
Reader reader = guardedProvider.createGuardedReader(stream, c);
try {
kit.read(reader, doc, 0);
} finally {
reader.close();
}
} else {
super.loadFromStreamToKit(doc, stream, kit);
}
}
@Override
protected void saveFromKitToStream(StyledDocument doc, EditorKit kit, OutputStream stream)
throws IOException, BadLocationException {
if (guardedProvider != null) {
Charset c = FileEncodingQuery.getEncoding(this.getDataObject().getPrimaryFile());
Writer writer = guardedProvider.createGuardedWriter(stream, c);
try {
kit.write(writer, doc, 0, doc.getLength());
} finally {
writer.close();
}
} else {
super.saveFromKitToStream(doc, kit, stream);
}
}
@Override
public void saveDocument() throws IOException {
if (worker != null && worker.isModelModified()) {
worker.generateSources();
worker.waitFinished();
}
super.saveDocument();
}
@Override
protected boolean notifyModified() {
if (!super.notifyModified())
return false;
((Environment)this.env).addSaveCookie();
updateMVTCName();
return true;
}
@Override
protected void notifyUnmodified() {
super.notifyUnmodified();
((Environment)this.env).removeSaveCookie();
updateMVTCName();
}
@Override
protected void notifyClosed() {
opened.remove(this);
if (opened.isEmpty()) {
detachStatusListeners();
}
super.notifyClosed();
worker = null;
if (topComponentsListener != null) {
TopComponent.getRegistry().removePropertyChangeListener(topComponentsListener);
topComponentsListener = null;
}
}
@Override
protected Pane createPane() {
DataObject dobj = getDataObject();
if (dobj == null || !dobj.isValid()) {
return super.createPane();
}
return (Pane) MultiViews.createCloneableMultiView(MIME_BEAN_INFO, getDataObject());
}
/** This is called by the multiview elements whenever they are created
* (and given a observer knowing their multiview TopComponent). It is
* important during deserialization and clonig the multiview - i.e. during
* the operations we have no control over. But anytime a multiview is
* created, this method gets called.
*/
private void setTopComponent(TopComponent topComp) {
multiviewTC = (CloneableTopComponent)topComp;
updateMVTCName();
if (topComponentsListener == null) {
topComponentsListener = new TopComponentsListener();
TopComponent.getRegistry().addPropertyChangeListener(topComponentsListener);
}
opened.add(this);
try {
addStatusListener(getDataObject().getPrimaryFile().getFileSystem());
} catch (FileStateInvalidException fsiex) {
Exceptions.printStackTrace(fsiex);
}
}
private void addStatusListener(FileSystem fs) {
FileStatusListener fsl = fsToStatusListener.get(fs);
if (fsl == null) {
fsl = new FileStatusListener() {
@Override
public void annotationChanged(FileStatusEvent ev) {
synchronized (opened) {
Iterator<BIEditorSupport> iter = opened.iterator();
while (iter.hasNext()) {
BIEditorSupport fes = iter.next();
if (ev.hasChanged(fes.getDataObject().getPrimaryFile())) {
fes.updateMVTCName();
}
}
}
}
};
fs.addFileStatusListener(fsl);
fsToStatusListener.put(fs, fsl);
} // else do nothing - the listener is already added
}
private static void detachStatusListeners() {
Iterator<Map.Entry<FileSystem, FileStatusListener>> iter = fsToStatusListener.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<FileSystem, FileStatusListener> entry = iter.next();
FileSystem fs = entry.getKey();
FileStatusListener fsl = entry.getValue();
fs.removeFileStatusListener(fsl);
}
fsToStatusListener.clear();
}
private void updateMVTCName() {
Runnable task = new Runnable() {
public void run() {
updateMVTCNameInAwt();
}
};
if (EventQueue.isDispatchThread()) {
task.run();
} else {
EventQueue.invokeLater(task);
}
}
private void updateMVTCNameInAwt() {
CloneableTopComponent topComp = multiviewTC;
if (topComp != null) {
String htmlname = messageHtmlName();
String name = messageName();
String tip = messageToolTip();
for (CloneableTopComponent o : NbCollections.
iterable(topComp.getReference().getComponents())) {
topComp.setHtmlDisplayName(htmlname);
topComp.setDisplayName(name);
topComp.setName(name);
topComp.setToolTipText(tip);
}
}
}
static boolean isLastView(TopComponent tc) {
if (!(tc instanceof CloneableTopComponent))
return false;
boolean oneOrLess = true;
Enumeration en = ((CloneableTopComponent)tc).getReference().getComponents();
if (en.hasMoreElements()) {
en.nextElement();
if (en.hasMoreElements())
oneOrLess = false;
}
return oneOrLess;
}
static BIEditorSupport findEditor(DataObject dobj) {
return dobj.getLookup().lookup(BIEditorSupport.class);
}
final CloseOperationState canCloseElement(TopComponent tc) {
// if this is not the last cloned java editor component, closing is OK
if (!isLastView(tc)) {
return CloseOperationState.STATE_OK;
}
if (!isModified()) {
return CloseOperationState.STATE_OK;
}
AbstractAction save = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
try {
saveDocument();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
};
save.putValue(Action.LONG_DESCRIPTION, NbBundle.getMessage(BIEditorSupport.class, "MSG_MODIFIED", getDataObject().getPrimaryFile().getNameExt()));
// return a placeholder state - to be sure our CloseHandler is called
return MultiViewFactory.createUnsafeCloseState(
"ID_BEANINFO_CLOSING", // NOI18N
save,
MultiViewFactory.NOOP_CLOSE_ACTION);
}
@MultiViewElement.Registration(displayName = "#LAB_JavaSourceView",
iconBase = JavaTemplates.JAVA_ICON,
persistenceType = TopComponent.PERSISTENCE_ONLY_OPENED,
preferredID = MV_JAVA_ID,
mimeType = MIME_BEAN_INFO,
position = 10)
public static final class JavaElement extends CloneableEditor implements MultiViewElement, Externalizable {
private static final long serialVersionUID = 1L;
private MultiViewElementCallback callback;
public JavaElement(Lookup context) {
super(context.lookup(DataEditorSupport.class));
DataObject dataObject = context.lookup(DataObject.class);
setActivatedNodes(new Node[]{dataObject.getNodeDelegate()});
}
/**
* serialization stuff; do not use
*/
private JavaElement() {
super();
}
public JComponent getVisualRepresentation() {
return this;
}
public JComponent getToolbarRepresentation() {
JComponent toolbar = null;
JEditorPane jepane = getEditorPane();
if (jepane != null) {
Document doc = jepane.getDocument();
if (doc instanceof NbDocument.CustomToolbar) {
toolbar = ((NbDocument.CustomToolbar)doc).createToolbar(jepane);
}
}
return toolbar;
}
public void setMultiViewCallback(MultiViewElementCallback callback) {
this.callback = callback;
BIEditorSupport editor = (BIEditorSupport) cloneableEditorSupport();
editor.setTopComponent(callback.getTopComponent());
}
public CloseOperationState canCloseElement() {
DataObject dataObject = callback.getTopComponent().getLookup().lookup(DataObject.class);
BIEditorSupport editor = (BIEditorSupport) cloneableEditorSupport();
return editor.canCloseElement(callback.getTopComponent());
}
@Override
public void componentActivated() {
super.componentActivated();
BIEditorSupport editor = (BIEditorSupport) cloneableEditorSupport();
if (editor.worker != null && editor.worker.isModelModified()) {
editor.worker.generateSources();
}
}
@Override
public void componentDeactivated() {
super.componentDeactivated();
}
@Override
public void componentHidden() {
super.componentHidden();
}
@Override
public void componentShowing() {
super.componentShowing();
}
@Override
public void componentClosed() {
// XXX copied from form module see issue 55818
super.canClose(null, true);
super.componentClosed();
}
@Override
protected boolean closeLast() {
return true;
}
@Override
public void componentOpened() {
super.componentOpened();
}
@Override
public void updateName() {
super.updateName();
if (callback != null) {
callback.updateTitle(getDisplayName());
}
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal(in);
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
super.writeExternal(out);
}
}
@MultiViewElement.Registration (
displayName="#LAB_BeanInfoEditorView",
iconBase=JavaTemplates.JAVA_ICON,
persistenceType=TopComponent.PERSISTENCE_NEVER,
preferredID=MV_BEANINFO_ID,
mimeType=MIME_BEAN_INFO,
position=1000
)
public static final class BeanInfoElement extends CloneableTopComponent implements MultiViewElement, Externalizable {
private static final long serialVersionUID = 1L;
private MultiViewElementCallback callback;
private DataObject dataObject;
private boolean isInitialized = false;
private final JPanel emptyToolbar = new JPanel();
private BiPanel biPanel;
public BeanInfoElement(Lookup lookup) {
this.dataObject = lookup.lookup(DataObject.class);
setActivatedNodes(new Node[]{dataObject.getNodeDelegate()});
}
/**
* serialization stuff; do not use
*/
private BeanInfoElement() {
}
public JComponent getVisualRepresentation() {
return this;
}
public JComponent getToolbarRepresentation() {
return emptyToolbar;
}
public void setMultiViewCallback(MultiViewElementCallback callback) {
this.callback = callback;
BIEditorSupport editor = findEditor(dataObject);
editor.setTopComponent(callback.getTopComponent());
}
@Override
public CloseOperationState canCloseElement() {
BIEditorSupport editor = findEditor(dataObject);
return editor.canCloseElement(callback.getTopComponent());
}
@Override
public void componentActivated() {
super.componentActivated();
}
@Override
public void componentDeactivated() {
super.componentDeactivated();
}
@Override
public void componentHidden() {
super.componentHidden();
}
@Override
public void componentShowing() {
super.componentShowing();
initialize();
}
@Override
public void componentClosed() {
super.componentClosed();
}
@Override
public void componentOpened() {
super.componentOpened();
}
private void initialize() {
if (!isInitialized) {
setLayout(new BorderLayout());
biPanel = new BiPanel();
add(biPanel, BorderLayout.CENTER);
isInitialized = true;
} else {
biPanel.setContext(new BiNode.Wait());
}
FileObject biFile = dataObject.getPrimaryFile();
String name = biFile.getName();
name = name.substring(0, name.length() - "BeanInfo".length()); // NOI18N
FileObject javaFile = biFile.getParent().getFileObject(name, biFile.getExt());
BIEditorSupport editor = findEditor(dataObject);
if (javaFile != null) {
final BeanInfoWorker beanInfoWorker = new GenerateBeanInfoAction.BeanInfoWorker(javaFile, biPanel);
editor.worker = beanInfoWorker;
beanInfoWorker.analyzePatterns().addTaskListener(new TaskListener() {
public void taskFinished(Task task) {
beanInfoWorker.updateUI();
}
});
} else {
// notify missing source file
biPanel.setContext(BiNode.createNoSourceNode(biFile));
}
}
@Override
public void readExternal(ObjectInput oi) throws IOException, ClassNotFoundException {
super.readExternal(oi);
dataObject = (DataObject) oi.readObject();
// initialize();
}
@Override
public void writeExternal(ObjectOutput oo) throws IOException {
super.writeExternal(oo);
oo.writeObject(dataObject);
}
}
private static final class BIGES implements GuardedEditorSupport {
StyledDocument doc = null;
public StyledDocument getDocument() {
return BIGES.this.doc;
}
}
private static final class Environment extends DataEditorSupport.Env {
private static final long serialVersionUID = -1;
private final transient CookieSet cookieSet;
private transient SaveSupport saveCookie = null;
private final class SaveSupport implements SaveCookie {
public void save() throws java.io.IOException {
DataObject dobj = getDataObject();
((DataEditorSupport) findCloneableOpenSupport()).saveDocument();
dobj.setModified(false);
}
}
public Environment(DataObject obj, CookieSet cookieSet) {
super(obj);
this.cookieSet = cookieSet;
}
protected FileObject getFile() {
return this.getDataObject().getPrimaryFile();
}
protected FileLock takeLock() throws java.io.IOException {
return ((MultiDataObject)this.getDataObject()).getPrimaryEntry().takeLock();
}
public @Override CloneableOpenSupport findCloneableOpenSupport() {
return findEditor(this.getDataObject());
}
public void addSaveCookie() {
DataObject javaData = this.getDataObject();
if (javaData.getCookie(SaveCookie.class) == null) {
if (this.saveCookie == null)
this.saveCookie = new SaveSupport();
this.cookieSet.add(this.saveCookie);
javaData.setModified(true);
}
}
public void removeSaveCookie() {
DataObject javaData = this.getDataObject();
if (javaData.getCookie(SaveCookie.class) != null) {
this.cookieSet.remove(this.saveCookie);
javaData.setModified(false);
}
}
}
private class TopComponentsListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
if (TopComponent.Registry.PROP_OPENED.equals(evt.getPropertyName())) {
// Check closed top components
@SuppressWarnings("unchecked")
Set<TopComponent> closed = (Set<TopComponent>) evt.getOldValue();
closed.removeAll((Set) evt.getNewValue());
for (TopComponent o : closed) {
if (o instanceof CloneableTopComponent) {
final CloneableTopComponent topComponent = (CloneableTopComponent) o;
Enumeration en = topComponent.getReference().getComponents();
if (multiviewTC == topComponent) {
if (en.hasMoreElements()) {
// Remember next cloned top component
multiviewTC = (CloneableTopComponent) en.nextElement();
} else {
// All cloned top components are closed
notifyClosed();
}
}
}
}
}
}
}
}