blob: 13720ebca588451b39b9ef6f0a4ec75578024bf4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2013 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0, which accompanies this distribution
* and is available at https://www.eclipse.org/legal/epl-2.0/.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.jpa.ui.internal.plugin;
import java.util.Hashtable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jpt.common.core.internal.utility.JptPlugin;
import org.eclipse.jpt.common.ui.internal.JptUIPlugin;
import org.eclipse.jpt.common.ui.internal.swt.widgets.Adapter;
import org.eclipse.jpt.common.utility.internal.reference.AbstractBooleanReference;
import org.eclipse.jpt.common.utility.reference.BooleanReference;
import org.eclipse.jpt.jpa.core.JpaProjectManager;
import org.eclipse.jpt.jpa.core.JpaWorkspace;
import org.eclipse.jpt.jpa.ui.internal.InternalJpaWorkbench;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IWorkbench;
import org.osgi.framework.BundleContext;
/**
* Dali JPA UI plug-in.
*/
public class JptJpaUiPlugin
extends JptUIPlugin
{
private final Hashtable<IWorkbench, InternalJpaWorkbench> jpaWorkbenches = new Hashtable<IWorkbench, InternalJpaWorkbench>();
/**
* @see #focusIn(Control)
*/
private final FocusIsNotInDaliViewFlag focusIsNotInDaliViewFlag = new FocusIsNotInDaliViewFlag();
private final AsyncEventListenerFlag asyncEventListenerFlag = new AsyncEventListenerFlag(this.focusIsNotInDaliViewFlag);
private volatile Display display;
private final Listener focusListener = new FocusListener();
// ********** singleton **********
private static JptJpaUiPlugin INSTANCE;
/**
* Returns the singleton JPT UI plug-in.
*/
public static JptJpaUiPlugin instance() {
return INSTANCE;
}
// ********** Dali plug-in **********
public JptJpaUiPlugin() {
super();
}
@Override
protected void setInstance(JptPlugin plugin) {
INSTANCE = (JptJpaUiPlugin) plugin;
}
/**
* Register our SWT listener with the display so we receive notification
* of every "focus in" event.
*/
@Override
public void start(BundleContext context) throws Exception {
super.start(context);
// no leak here - the flag has no backpointer to the plug-in
this.getJpaProjectManager().addJavaEventListenerFlag(this.asyncEventListenerFlag);
// must be on UI thread...
this.display = Display.getCurrent();
// a little pre-construction leakage, but it should be OK as the listener
// interacts mostly with the 'asyncEventListenerFlag' and only once the
// plug-in is "active"
if ((this.display != null) && ( ! this.display.isDisposed())) {
this.display.addFilter(SWT.FocusIn, this.focusListener);
}
}
private JpaProjectManager getJpaProjectManager() {
return this.getJpaWorkspace().getJpaProjectManager();
}
private JpaWorkspace getJpaWorkspace() {
return (JpaWorkspace) ResourcesPlugin.getWorkspace().getAdapter(JpaWorkspace.class);
}
/**
* Unregister our SWT listener with the display.
*/
@Override
public void stop(BundleContext context) throws Exception {
try {
this.disposeJpaWorkbenches();
// must be on UI thread...
if ((this.display != null) && ( ! this.display.isDisposed())) {
this.display.removeFilter(SWT.FocusIn, this.focusListener);
}
this.getJpaProjectManager().removeJavaEventListenerFlag(this.asyncEventListenerFlag);
} finally {
this.display = null;
super.stop(context);
}
}
// ********** JPA workbenches **********
/**
* Return the JPA workbench corresponding to the specified Eclipse workbench.
* <p>
* The preferred way to retrieve a JPA workbench is via the Eclipse
* adapter framework:
* <pre>
* IWorkbench workbench = PlatformUI.getWorkbench();
* JpaWorkbench jpaWorkbench = PlatformTools.getAdapter(workbench, JpaWorkbench.class);
* </pre>
* @see org.eclipse.jpt.jpa.ui.internal.WorkbenchAdapterFactory#getJpaWorkbench(IWorkbench)
*/
public InternalJpaWorkbench getJpaWorkbench(IWorkbench workbench) {
synchronized (this.jpaWorkbenches) {
return this.getJpaWorkbench_(workbench);
}
}
/**
* Pre-condition: {@link #jpaWorkbenches} is <code>synchronized</code>
*/
private InternalJpaWorkbench getJpaWorkbench_(IWorkbench workbench) {
InternalJpaWorkbench jpaWorkbench = this.jpaWorkbenches.get(workbench);
if ((jpaWorkbench == null) && this.isActive()) { // no new workbenches can be built during "start" or "stop"...
jpaWorkbench = this.buildJpaWorkbench(workbench);
this.jpaWorkbenches.put(workbench, jpaWorkbench);
}
return jpaWorkbench;
}
private InternalJpaWorkbench buildJpaWorkbench(IWorkbench workbench) {
return new InternalJpaWorkbench(workbench);
}
private void disposeJpaWorkbenches() {
// the list will not change during "stop"
for (InternalJpaWorkbench jpaWorkbench : this.jpaWorkbenches.values()) {
try {
jpaWorkbench.dispose();
} catch (Throwable ex) {
this.logError(ex); // keep going
}
}
this.jpaWorkbenches.clear();
}
// ********** focus listener **********
/**
* This listener is registered to receive only {@link SWT#FocusIn} events.
*/
/* CU private */ class FocusListener
extends Adapter
{
@Override
public void handleEvent(Event event) {
JptJpaUiPlugin.this.focusIn((Control) event.widget);
}
}
// ********** focus event handling **********
/**
* Return whether the focus is <em>not</em> in a Dali view
* (currently only the JPA Details view is considered a Dali view).
* @see org.eclipse.jpt.jpa.ui.internal.selection.JpaTextEditorManager.TextEditorSelectionListener
*/
public boolean focusIsNotInDaliView() {
return this.focusIsNotInDaliViewFlag.getValue();
}
/**
* This method is called whenever a {@link SWT#FocusIn} event is generated.
* <p>
* If the control gaining focus is part of one the Dali composites
* (typically the JPA Details View), we deactivate the Dali Java change
* listener so we ignore any changes to the Java source code that probably
* originated from Dali. This means we will miss any changes to the Java
* source code that is caused by non-UI activity; but, we hope, these
* changes are unrelated to JPA annotations etc.
* <p>
* If the control gaining focus is <em>not</em> part of one of the Dali
* composites, we start listening to the Java change events again. This
* method is called whenever a non-Dali UI control gains the UI focus. When
* this happens we activate the Dali Java change listener so that we begin
* to keep the Dali model synchronized with the Java source code.
*/
/* CU private */ void focusIn(Control control) {
this.focusIsNotInDaliViewFlag.setValue(this.controlIsNonDali(control));
this.focusIsNotInDaliViewFlag.control = control; // debugging info
}
/**
* Return whether the specified control is <em><b>neither</b></em>
* a Dali UI component <em><b>nor</b></em> contained by a Dali UI component.
*/
private boolean controlIsNonDali(Control control) {
return ! this.controlIsDali(control);
}
/**
* Return whether the specified control, or any of its ancestors, is a Dali
* UI component.
*
* @see #controlAffectsJavaSource(Control)
*/
private boolean controlIsDali(Control control) {
String id = this.getPluginID();
if (id == null) {
return false;
}
while (control != null) {
if (control.getData(id) == DALI_UI_DATA) {
return true;
}
control = control.getParent();
}
return false;
}
/**
* Tag the specified control so that whenever it (or any of its descendants)
* has the focus, the Dali model ignores any Java change events. This method
* is to be called when the control is first constructed.
*
* @see #controlIsDali(Control)
*/
public void controlAffectsJavaSource(Control control) {
String id = this.getPluginID();
if (id != null) {
control.setData(id, DALI_UI_DATA);
}
}
private static final Object DALI_UI_DATA = new Object();
// ********** async event listener flag **********
/**
* This flag's value depends on the current thread. If the current thread is
* the background Java Reconciler thread, the flag's value is determined by
* the current UI focus (i.e. whether the focus is somewhere other than a
* Dali view); otherwise the flag's value is <code>true</code>.
* In other words, if a Dali view has the focus and a Java event is fired
* on the Java Reconciler thread, the event is ignored; if the event is
* fired on some other thread (typically synchronously on the Main thread),
* the event is forwarded to the JPA projects.
* @see FocusIsNotInDaliViewFlag
*/
/* CU private */ static class AsyncEventListenerFlag
extends AbstractBooleanReference
{
private final BooleanReference focusIsNotInDaliViewFlag;
@SuppressWarnings("restriction")
private static final String JAVA_RECONCILER_THREAD_NAME = org.eclipse.jdt.internal.ui.text.JavaReconciler.class.getName();
AsyncEventListenerFlag(BooleanReference focusIsNotInDaliViewFlag) {
super();
this.focusIsNotInDaliViewFlag = focusIsNotInDaliViewFlag;
}
public boolean getValue() {
if (Thread.currentThread().getName().equals(JAVA_RECONCILER_THREAD_NAME)) {
return this.focusIsNotInDaliViewFlag.getValue();
}
return true;
}
}
// ********** focus is not in Dali view flag **********
/**
* This flag's value is determined by the current UI focus (i.e. whether the
* focus is somewhere other than a Dali view, currently only the JPA Details view).
*/
/* CU private */ static class FocusIsNotInDaliViewFlag
extends AbstractBooleanReference
{
private volatile boolean value = true;
volatile Control control; // useful for debugging
FocusIsNotInDaliViewFlag() {
super();
}
public boolean getValue() {
return this.value;
}
public boolean setValue(boolean value) {
boolean old = this.value;
this.value = value;
return old;
}
}
}