Create protected AutoSuggest.getAutoSuggestScript() for customization

Other protected methods are made private again, this is the only one
required now. Which events are listened to can currently not be
controlled.
diff --git a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/AutoSuggest.java b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/AutoSuggest.java
index 60c2f20..732eb0f 100644
--- a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/AutoSuggest.java
+++ b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/AutoSuggest.java
@@ -148,17 +148,18 @@
     }
   }
 
-  protected void attachClientListeners() {
+  private void attachClientListeners() {
     int[] dropDownEventTypes = new int[] { SWT.Show, SWT.Hide, SWT.Selection, SWT.DefaultSelection };
     attachClientListenerToDropDown( EventDelegatorScript.getInstance(), dropDownEventTypes );
     attachClientListenerToText( EventDelegatorScript.getInstance(), SWT.Modify, SWT.Verify );
-    attachClientListenerToModel( AutoSuggestScript.getInstance(), "change", "accept" );
+    attachClientListenerToModel( getAutoSuggestScript(), "change", "accept" );
   }
 
-  protected void attachClientListenerToText( Script script, int... types ) {
-    if( textClientListener != null ) {
-      throw new IllegalStateException( "AutoSuggest: Can not add listener to Text twice." );
-    }
+  protected Script getAutoSuggestScript() {
+    return AutoSuggestScript.getInstance();
+  }
+
+  private void attachClientListenerToText( Script script, int... types ) {
     textClientListenerTypes = types;
     textClientListener = new ClientListener( script );
     for( int type : types ) {
@@ -166,14 +167,14 @@
     }
   }
 
-  protected void attachClientListenerToDropDown( Script script, int... types ) {
+  private void attachClientListenerToDropDown( Script script, int... types ) {
     ClientListener clientListener = new ClientListener( script );
     for( int type : types ) {
       dropDown.addListener( type, clientListener );
     }
   }
 
-  protected void attachClientListenerToModel( Script script, String... types ) {
+  private void attachClientListenerToModel( Script script, String... types ) {
     ClientModelListener clientModelListener = new ClientModelListener( script );
     for( String type : types ) {
       model.addListener( type, clientModelListener );
diff --git a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/AutoSuggestDemo.java b/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/AutoSuggestDemo.java
index 85b5fc8..e92674c 100644
--- a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/AutoSuggestDemo.java
+++ b/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/AutoSuggestDemo.java
@@ -18,7 +18,7 @@
 import org.eclipse.rap.addons.autosuggest.SuggestionSelectedListener;
 import org.eclipse.rap.addons.dropdown.demo.data.KFZ;
 import org.eclipse.rap.addons.dropdown.demo.scripts.CustomAutoSuggestScript;
-import org.eclipse.rap.addons.dropdown.demo.scripts.CustomDataBindingScript;
+import org.eclipse.rap.clientscripting.Script;
 import org.eclipse.rap.rwt.RWT;
 import org.eclipse.rap.rwt.application.AbstractEntryPoint;
 import org.eclipse.rap.rwt.internal.client.WidgetDataWhiteList;
@@ -48,12 +48,8 @@
     }
 
     @Override
-    protected void attachClientListeners() {
-      int[] dropDownEventTypes = new int[] { SWT.Show, SWT.Hide, SWT.Selection, SWT.DefaultSelection };
-      attachClientListenerToDropDown( CustomDataBindingScript.getInstance(), dropDownEventTypes );
-      attachClientListenerToText( CustomDataBindingScript.getInstance(), SWT.Modify, SWT.Verify );
-      attachClientListenerToModel( CustomDataBindingScript.getInstance(), "change" );
-      attachClientListenerToModel( CustomAutoSuggestScript.getInstance(), "change", "accept" );
+    protected Script getAutoSuggestScript() {
+      return CustomAutoSuggestScript.getInstance();
     }
 
   }
diff --git a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomAutoSuggest.js b/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomAutoSuggest.js
index 6bbcf2e..ccde3ed 100644
--- a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomAutoSuggest.js
+++ b/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomAutoSuggest.js
@@ -14,22 +14,51 @@
 ///////////////////
 // Event Delegation
 
+var MODEL_KEY = "org.eclipse.rap.addons.autosuggest#Model";
+
 function handleEvent( event ) {
+  if( event.widget ) {
+    var model = rap.getObject( event.widget.getData( MODEL_KEY ) );
+    if( event.widget.classname === "rwt.dropdown.DropDown" ) {
+      handleDropDownEvent( model, event );
+    } else {
+      handleTextEvent( model, event );
+    }
+  } else {
+    handleModelEvent( event.source, event );
+  }
+}
+
+function handleModelEvent( model, event ) {
+  var textWidget;
+  try {
+    textWidget = rap.getObject( model.get( "textWidgetId" ) );
+  } catch( ex ) {
+    // When Text is disposed, AutoSuggest may perform a set operation before it is also disposed
+    return;
+  }
+  var dropDown = rap.getObject( model.get( "dropDownWidgetId" ) );
   if( event.type === "accept" ) {
-    onAcceptSuggestion.apply( event.source, [ event ] );
+    onAcceptSuggestion.apply( model, [ event ] );
   } else {
     switch( event.property ) {
       case "dataSourceId":
-        onChangeDataSourceId.apply( event.source, [ event ] );
+        onChangeDataSourceId.apply( model, [ event ] );
       break;
       case "suggestions":
-        onChangeSuggestions.apply( event.source, [ event ] );
+        onChangeSuggestions.apply( model, [ event ] );
       break;
       case "userText":
-        onChangeUserText.apply( event.source, [ event ] );
+        onChangeUserText.apply( model, [ event ] );
+      break;
+      case "suggestionsVisible":
+        syncModelSuggestionsVisible.apply( model, [ dropDown, event ] );
       break;
       case "currentSuggestions":
-        onChangeCurrentSuggestions.apply( event.source, [ event ] );
+        onChangeCurrentSuggestions.apply( model, [ event ] );
+      break;
+      case "currentSuggestionTexts":
+        syncModelCurrentSuggestionTexts.apply( model, [ dropDown, event ] );
       break;
       case "selectedSuggestionIndex":
         onChangeSelectedSuggestionIndex.apply( event.source, [ event ] );
@@ -37,10 +66,84 @@
       case "replacementText":
         onChangeReplacementText.apply( event.source, [ event ] );
       break;
+      case "text":
+        syncModelText.apply( model, [ textWidget, event ] );
+      break;
+      case "textSelection":
+        syncModelTextSelection.apply( model, [ textWidget, event ] );
+      break;
     }
   }
 }
 
+function handleDropDownEvent( model, event ) {
+  switch( event.type ) {
+    case SWT.Show:
+    case SWT.Hide:
+      syncDropDownVisible.apply( model, [ event.widget, event ] );
+    break;
+    case SWT.Selection:
+      syncDropDownSelection.apply( model, [ event.widget, event ] );
+    break;
+    case SWT.DefaultSelection:
+      forwardDropDownDefaultSelection.apply( model, [ event.widget, event ] );
+    break;
+  }
+}
+
+function handleTextEvent( model, event ) {
+  var userAction = getUserAction( event );
+  switch( event.type ) {
+    case SWT.Modify:
+      syncTextText.apply( model, [ event.widget, event, userAction ] );
+    break;
+  }
+  setUserAction( event );
+}
+
+////////////////////////////////
+// Synchronize Model <-> Widgets
+
+function syncTextText( textWidget, event, userAction ) {
+  var text = textWidget.getText();
+  this.set( "text", text, { "action" : "sync" } );
+  if( userAction ) {
+    this.set( "userText", text, { "action" : userAction } );
+  }
+}
+
+function syncDropDownVisible( dropDown, event ) {
+  this.set( "suggestionsVisible", dropDown.getVisible(), { "action" : "sync" }  );
+}
+
+function syncModelSuggestionsVisible( dropDown, event ) {
+  if( event.options.action !== "sync" ) {
+    dropDown.setVisible( event.value );
+  }
+}
+
+function syncDropDownSelection( dropDown, event ) {
+  this.set( "selectedSuggestionIndex", event.index, { "action" : "sync" }  );
+}
+
+function forwardDropDownDefaultSelection( dropDown, event ) {
+  this.notify( "accept", { type : "accept", "source" : this }  );
+}
+
+function syncModelCurrentSuggestionTexts( dropDown, event ) {
+  dropDown.setItems( this.get( "currentSuggestionTexts" ) );
+}
+
+function syncModelText( textWidget, event ) {
+  if( event.options.action !== "sync" ) {
+    textWidget.setText( event.value );
+  }
+}
+
+function syncModelTextSelection( textWidget, event ) {
+  textWidget.setSelection( event.value );
+}
+
 //////////////////
 // Event Handling
 
@@ -96,6 +199,7 @@
         this.set( "textSelection", [ 0, text.length ] );
       }
     } else {
+      // TODO : not always working?
       this.set( "textSelection", [ userText.length, text.length ] );
     }
   }
@@ -291,3 +395,17 @@
     return result;
   }
 }
+
+function setUserAction( event ) {
+  if( event.type === SWT.Verify ) {
+    // See Bug 404896 - [ClientScripting] Verify event keyCode is always zero when replacing txt
+    var action = ( event.text !== "" /* && event.keyCode !== 0 */ ) ? "typing" : "deleting";
+    event.widget.setData( "userAction", action );
+  }
+}
+
+function getUserAction( event ) {
+  var action = event.widget.getData( "userAction" );
+  event.widget.setData( "userAction", null );
+  return action;
+}
diff --git a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomDataBinding.js b/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomDataBinding.js
deleted file mode 100644
index 42e84ee..0000000
--- a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomDataBinding.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2013 EclipseSource.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *    EclipseSource - initial API and implementation
- ******************************************************************************/
-
-//@ sourceURL=DataBinding.js
-
-///////////////////
-// Event Delegation
-
-var MODEL_KEY = "org.eclipse.rap.addons.autosuggest#Model";
-
-function handleEvent( event ) {
-  if( event.widget ) {
-    var model = rap.getObject( event.widget.getData( MODEL_KEY ) );
-    if( event.widget.classname === "rwt.dropdown.DropDown" ) {
-      handleDropDownEvent( model, event );
-    } else {
-      handleTextEvent( model, event );
-    }
-  } else {
-    handleModelEvent.apply( this, arguments );
-  }
-}
-
-function handleDropDownEvent( model, event ) {
-  switch( event.type ) {
-    case SWT.Show:
-    case SWT.Hide:
-      onDropDownChangeVisible( model, event );
-    break;
-    case SWT.Selection:
-      onDropDownSelection( model, event );
-    break;
-    case SWT.DefaultSelection:
-      onDropDownDefaultSelection( model, event );
-    break;
-  }
-}
-
-function handleTextEvent( model, event ) {
-  var userAction = getUserAction( event );
-  switch( event.type ) {
-    case SWT.Modify:
-      onTextModify( model, event, userAction );
-    break;
-  }
-  setUserAction( event );
-}
-
-function handleModelEvent( event ) {
-  var model = event.source;
-  var textWidget;
-  try {
-    textWidget = rap.getObject( model.get( "textWidgetId" ) );
-  } catch( ex ) {
-    // When Text is disposed, AutoSuggest may perform a set operation before it is disposed as well
-    return;
-  }
-  var dropDown = rap.getObject( model.get( "dropDownWidgetId" ) );
-  switch( event.property ) {
-    case "text":
-      onModelChangeText( textWidget, model, event );
-    break;
-    case "textSelection":
-      onModelChangeTextSelection( textWidget, model, event );
-    break;
-    case "currentSuggestionTexts":
-      onModelChangeCurrentSuggestionTexts( dropDown, model, event );
-    break;
-    case "suggestionsVisible":
-      onModelChangeSuggestionsVisible( dropDown, model, event );
-    break;
-  }
-}
-
-function setUserAction( event ) {
-  if( event.type === SWT.Verify ) {
-    // See Bug 404896 - [ClientScripting] Verify event keyCode is always zero when replacing txt
-    var action = ( event.text !== "" /* && event.keyCode !== 0 */ ) ? "typing" : "deleting";
-    event.widget.setData( "userAction", action );
-  }
-}
-
-function getUserAction( event ) {
-  var action = event.widget.getData( "userAction" );
-  event.widget.setData( "userAction", null );
-  return action;
-}
-
-/////////////////
-// Event Handling
-
-function onTextModify( model, event, userAction ) {
-  var text = event.widget.getText();
-  model.set( "text", text, { "action" : "sync" } );
-  if( userAction ) {
-    model.set( "userText", text, { "action" : userAction } );
-  }
-}
-
-function onDropDownChangeVisible( model, event ) {
-  model.set( "suggestionsVisible", event.widget.getVisible(), { "action" : "sync" }  );
-}
-
-function onDropDownSelection( model, event ) {
-  model.set( "selectedSuggestionIndex", event.index, { "action" : "sync" }  );
-}
-
-function onDropDownDefaultSelection( model, event ) {
-  model.notify( "accept", { type : "accept", "source" : model }  );
-}
-
-function onModelChangeCurrentSuggestionTexts( dropDown, model, event ) {
-  dropDown.setItems( model.get( "currentSuggestionTexts" ) );
-}
-
-function onModelChangeSuggestionsVisible( dropDown, model, event ) {
-  if( event.options.action !== "sync" ) {
-    dropDown.setVisible( event.value );
-  }
-}
-
-function onModelChangeText( textWidget, model, event ) {
-  if( event.options.action !== "sync" ) {
-    textWidget.setText( event.value );
-  }
-}
-
-function onModelChangeTextSelection( textWidget, model, event ) {
-  textWidget.setSelection( event.value );
-}
diff --git a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomDataBindingScript.java b/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomDataBindingScript.java
deleted file mode 100644
index a94334e..0000000
--- a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/scripts/CustomDataBindingScript.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2013 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *    EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.addons.dropdown.demo.scripts;
-
-import org.eclipse.rap.clientscripting.Script;
-import org.eclipse.rap.rwt.SingletonUtil;
-
-
-public class CustomDataBindingScript extends Script {
-
-  public static CustomDataBindingScript getInstance() {
-    return SingletonUtil.getSessionInstance( CustomDataBindingScript.class );
-  }
-
-  private CustomDataBindingScript() {
-    super( getText() );
-  }
-
-  private static String getText() {
-    String path = "org/eclipse/rap/addons/dropdown/demo/scripts/CustomDataBinding.js";
-    return ResourceLoaderUtil.readTextContent( path );
-  }
-
-}
diff --git a/tests/org.eclipse.rap.addons.autosuggest.test/src/org/eclipse/rap/addons/autosuggest/AutoSuggest_Test.java b/tests/org.eclipse.rap.addons.autosuggest.test/src/org/eclipse/rap/addons/autosuggest/AutoSuggest_Test.java
index 69ce0cd..166282e 100644
--- a/tests/org.eclipse.rap.addons.autosuggest.test/src/org/eclipse/rap/addons/autosuggest/AutoSuggest_Test.java
+++ b/tests/org.eclipse.rap.addons.autosuggest.test/src/org/eclipse/rap/addons/autosuggest/AutoSuggest_Test.java
@@ -34,7 +34,6 @@
 
 import org.eclipse.rap.addons.dropdown.DropDown;
 import org.eclipse.rap.clientscripting.ClientListener;
-import org.eclipse.rap.clientscripting.Script;
 import org.eclipse.rap.rwt.RWT;
 import org.eclipse.rap.rwt.internal.client.WidgetDataWhiteList;
 import org.eclipse.rap.rwt.internal.client.WidgetDataWhiteListImpl;
@@ -192,68 +191,6 @@
   }
 
   @Test
-  public void testConstructor_soesNotAttachListenersWithOverwrittenAttachClientListeners() {
-    AutoSuggest autoSuggest = new AutoSuggest( text ) {
-      @Override
-      protected void attachClientListeners() {
-      }
-    };
-
-    DropDown dropDown = autoSuggest.getDropDown();
-    assertFalse( text.isListening( SWT.Verify ) );
-    assertFalse( text.isListening( SWT.Modify ) );
-    assertFalse( dropDown.isListening( SWT.Show ) );
-    assertFalse( dropDown.isListening( SWT.Hide ) );
-    assertFalse( dropDown.isListening( SWT.Selection ) );
-    assertFalse( dropDown.isListening( SWT.DefaultSelection ) );
-  }
-
-  @Test
-  public void testAttachClientListenerToText() {
-    new AutoSuggest( text ) {
-      @Override
-      protected void attachClientListeners() {
-        attachClientListenerToText( new Script( "" ), SWT.Verify, SWT.Modify );
-      }
-    };
-
-    assertTrue( text.getListeners( SWT.Verify )[ 0 ] instanceof ClientListener );
-    assertTrue( text.getListeners( SWT.Modify )[ 0 ] instanceof ClientListener );
-  }
-
-  @Test
-  public void testAttachClientListenerToText_failsWhenCalledTwice() {
-    final AtomicReference<IllegalStateException> exception = new AtomicReference<IllegalStateException>();
-    new AutoSuggest( text ) {
-      @Override
-      protected void attachClientListeners() {
-        attachClientListenerToText( new Script( "" ), SWT.Verify, SWT.Modify );
-        try {
-          attachClientListenerToText( new Script( "" ), SWT.Verify, SWT.Modify );
-        } catch( IllegalStateException ex ) {
-          exception.set( ex );
-        }
-      }
-    };
-
-    assertTrue( exception.get().getMessage().indexOf( "twice" ) != -1 );
-  }
-
-  @Test
-  public void testAttachClientListenerToDropDown() {
-    AutoSuggest autoSuggest = new AutoSuggest( text ) {
-      @Override
-      protected void attachClientListeners() {
-        attachClientListenerToDropDown( new Script( "" ), SWT.Selection, SWT.Show );
-      }
-    };
-
-    DropDown dropDown = autoSuggest.getDropDown();
-    assertTrue( dropDown.getListeners( SWT.Selection )[ 0 ] instanceof ClientListener );
-    assertTrue( dropDown.getListeners( SWT.Show )[ 0 ] instanceof ClientListener );
-  }
-
-  @Test
   public void testIsDisposed_returnsFalse() {
     AutoSuggest autoSuggest = new AutoSuggest( text );
 
@@ -299,22 +236,6 @@
   }
 
   @Test
-  public void testDispose_removesCustomClientListenersFromText() {
-    AutoSuggest autoSuggest = new AutoSuggest( text ) {
-      @Override
-      protected void attachClientListeners() {
-        attachClientListenerToText( new Script( "" ), SWT.Verify, SWT.FocusIn );
-      }
-    };
-
-    autoSuggest.dispose();
-
-    assertFalse( text.isListening( SWT.Verify ) );
-    assertFalse( text.isListening( SWT.FocusIn ) );
-  }
-
-
-  @Test
   public void testDispose_disposeTwice() {
     AutoSuggest autoSuggest = new AutoSuggest( text );