Program editor UI framework - code overview

The program editor UI framework provides the ability to build code-level customization UIs on the fly by reflecting directly on the properties, methods, constructors supported by the domain objects. It also provides a crude management UI out of the box, creating instances, viewing life property values, updating instance properties or making changes to the code that initializes the property. It supports editing of data binding rules both by modifying code in place as well as by editing code in a new layer. It's a prototype demonstrating a more flexible workflows between developers, and those business users that would like more control in customizing logic in the system.

There are two tree views to organize types and instances: one navigates the name space of types and instances directly. The other navigates types and instances by their organization in layers.

Both views are kept in sync, so you can find an application type, then choose a specific layer of that type to edit.

Annotations exist to customize the UI - to hide fields, mark them read-only, and identify create methods.

There are three different views: the default data view which lets you navigate the types and instances of the system. When you select an instance, a form is created to let you view and edit the properties.

There is a 'type view' which lets you see and edit the code behind the way those types were configured in the source file and change that configuration. It provides structured editing with code-completion for editing data binding expression so this becomes a way to edit the rules behind the way this instance operates. Some fields and initialization values can be customized on the fly and it's possible to build rich declarative frameworks that are editable without restarts. Or detect when restarts are required and manage them.

There's also a code view which matches the type view, but shows you the code. It's updated on-the-fly as you make changes, or you can change the code in the code view and hit refresh as well. Or just change the files on file system and hit refresh.

The code view shows you the various formats available for each type. For StrataCode files, you can see both the source and the generated source. For schtml files, it shows the StrataCode source which is the intermediate form schtml to Java that's not generally stored on the file system.

The editor runs on the desktop using swing, or in the browser using schtml. In the browser it runs in two modes: client/server and server only mode. In client/server the editor code is compiled into both the JS and Java runtimes and it uses the sync framework to fetch data back and forth. In server only mode, a small JS file is downloaded to parse the sync framework and manage the DOM to sync integration. The same JSON sync protocol is used in both.


The swing version

The schtml version
  • editor.model (1600 Lines): Defines the key data structures behind the program editor. This layer runs both in the browser and on the server and so represents the data structures that StrataCode synchronizes. There is EditorModel which stores the current type and selection and TypeTreeModel, which implements the tree view itself.
  • editor.modelImpl (2200 lines): Runs just on the server in the client/server version. This layer contains the logic which populates EditorModel and TypeTreeModel for any user interface. It extends sys.layeredSystem in addition to the model layer. Because sys.layeredSystem only runs on the server, this layer will only run on the server too.
  • editor.coreui (2400 lines): The view model which runs on both the client and the server. Contains some core user-interface code shared by both swing and the Javascript layers.
  • editor.swing.core (3200 lines): Implements the Swing user interface.
  • editor.html.core (1500 lines): Implements the Javascript user interface. Runs on both the client and the server.
The swing user interface code uses data binding for all layout which is more verbose than html.

This example shows significant code-reuse across platform the code is easy to navigate and maintain. Building the client/server version from the existing desktop version involved refactoring the existing code as a first step into platform dependent and independent layers. Code which depends on swing went into the swing/core layer, and code that depends on server features, went into the editor.modelImpl layer. Independent code went into the editor.model layer that runs the same way in all three processes: desktop, client and server. The resulting swing version had only cosmetic changes and so ran the same, but after refactoring the two applications shared the most difficult aspects of the domain model.

For the client/server version, the model essentially defines the overlapping parts - i.e. the info that's used on both the client and server. The model layer defines the domain model and the coreui layer defines the view model. These layers have no real framework dependencies and so can run anywhere. Most frameworks require some replication of the domain model and view model code for each UI framework or for specifying the service layer or data transfer layers. With this approach, the service and transfer classes are generated mainly just from the organization of the code.

Here is the model layer of the EditorModel class, which manages the overall domain model for the program editor UI framework. It has the currently selected types and instances, the current layer and other layers that make up the selected types, access to the metadata of the types and propeties. This code is included in both client and server versions of the generated class.

file: editor/model/EditorModel.sc
import sc.lang.java.DeclarationType;

/**
   The main view model object for viewing and editing of the program model or instances.  It exposes
   the current selection and provides access to the currently selected property, types and layers. 
   */
@sc.obj.Component
class EditorModel implements sc.bind.IChangeable, sc.dyn.IDynListener {
   /** Access to the system which stores the list of layers, provides access to type lookup */
   LayeredSystem system;

   /**
    * Access to the EditorContext that Manages the dynamic runtime,
    * current type, instance and features for updating code on the fly.
    * This can be a reference to the command line interpreter to keep the UI
    * and commands/scripts in sync or another EditorContext not connected to
    * the command line.
    */
   @Bindable(sameValueCheck=true)
   EditorContext ctx;

   /** The array of absolute type names of the current types */
   String[] typeNames = new String[0];

   /** The current type */
   Object currentType;

   /** When a property has focus, set to the property */
   Object currentProperty;

   /** Used the current layer defined in ctx - needs crossScope because it's set from the command-line/test scripts */
   @Bindable(crossScope=true, sameValueCheck=true)
   Layer currentLayer :=: ctx.currentLayer;

   /** List of wrappers around the currently selected instances */
   List<InstanceWrapper> selectedInstances = null;

   /** The current Java model for the type */
   JavaModel currentJavaModel;

   UIIcon currentPropertyIcon;

   /** The enclosing type of the current property */
   Object currentPropertyType;

   String currentTypeName;

   /** The name of currentProperty */
   String currentPropertyName;

   /** The currentPropertyType filtered based on the imported type name */
   String importedPropertyType;

   /** Set this to search for a current type - if found, it is set and currentType is changed */
   String currentTypeSearch;

   /** If there's a selected instance, the instance */
   Object currentInstance;

   /** Stores info about the currentInstance - used in particular in the 'pendingCreate' mode - before the instance has been created */
   InstanceWrapper currentWrapper;

   /** Generated values, kept in sync when you change typeNames and currentLayer */
   ArrayList<Object> visibleTypes = new ArrayList<Object>();     // The list of types used to create the form view - removes types filtered by the merge and inherited flags
   ArrayList<Object> types;                 // The current list of just the selected types
   ArrayList<Object> inheritedTypes;        // Like types only includes any inherited types as well when inherit is true

   @Sync(syncMode=SyncMode.Disabled)
   ArrayList<Object> filteredTypes;         // The merged list of the most specific type in the current selected set of types/layers
   ArrayList<Layer> typeLayers;             // The list of layers which define the types
   @Sync(syncMode=SyncMode.Disabled)
   ArrayList<Layer> filteredTypeLayers;     // The list of layers which define the types based on currentLayer/mergeLayers flags - used for 3d view
   @Sync(syncMode=SyncMode.Disabled)
   ArrayList<List<Object>> typesPerLayer;   // For each layer in the current set, the set of types in this layer - used for 3d view
   @Sync(syncMode=SyncMode.Disabled)
   Map<String, List<Object>> filteredTypesByLayer;   // For each selected type, the list of types for each selected layer - used for 3d view

   /** When the add/minus button is pressed, this gets toggled */
   boolean createMode = false;

    /** 0 = iconified, 1 = open, 2 = maximized */
   int windowState = 0;

   CreateMode currentCreateMode = CreateMode.Instance;

   String currentPropertyOperator;

   String savedPropertyOperator;

   /** Property value bound to the current text field - updated live */
   String currentPropertyValue;

   /** Set to the currently selected package, or if a type is selected, the package of that type */
   String currentPackage;

   /** Last known value from the model for the property */
   String savedPropertyValue;

   /** Set to true when the current type is a layer */
   boolean currentTypeIsLayer;

   /** Set to true after the user has started a createInstance but not yet completed the create */
   boolean pendingCreate = false;

   /** Set to the list of ConstructorProperties or regular properties when pendingCreate = true if there are required params to build the new instance */
   @Sync(syncMode=SyncMode.Disabled)
   List<ConstructorProperty> constructorProps = null;

   /** Set to an error string to display when pendingCreate = true and missing required values are present */
   String pendingCreateError = null;

   /** Incremented each time the selection changes so we can update any one who depends on the selection from a central event. */
   int selectionChanged = 0;

   /** Incremented each time a new instance is selected with the same type */
   int newInstSelected = 0;

   /** Incremented each time the type remains the same but the instance mode changes (meaning we need to rebuild the editor lists) */
   int instanceModeChanged = 0;

   /** Set to true when changes have been made to source files in the current runtime which require a process restart. */
   boolean staleCompiledModel;

   /** True when we the current property is an editable one. */
   boolean editSelectionEnabled = false;

   /** List of type names that can be created from the 'Add instance' panel. */
   String[] currentInstTypeNames;

   /** Set when in createMode and a type is selected */
   String createModeTypeName;

   /** Set when the model is rebuilt, used to detect changes */
   String[] oldTypeNames;

   /** When a type is selected in data view that has more than one instance, show the find editor */
   boolean showFindEditor = false;

   /** For controlling the search results view */
   List<String> searchOrderByProps = new ArrayList<String>();
   int searchStartIx = 0;
   int searchMaxResults = 2;

   int numSearchResults;
   List<Object> searchResults = null;
   String searchText;
   // type name of the last search
   String searchTypeName;

   @sc.obj.Constant
   static List<String> operatorList = {"=", ":=", "=:", ":=:"};

   @Sync(onDemand=true)
   static class SelectedFile {
      SrcEntry file;
      List<Object> types;
      JavaModel model;
      Layer layer; // Layer where this file was selected.  if the layer is transparent, it may not be the same as the model's layer

      Layer getModelLayer() {
         return model.layer;
      }
   }

   LinkedHashMap<String, SelectedFile> selectedFileIndex; // Groups selected, filtered types by the files they live in for the code view

   // The selected files can contain merged base types - we have the option of editing them all or just the main one
   boolean editAllFiles = false;

   ArrayList<SelectedFile> selectedFileList;

   ArrayList<CodeType> codeTypes = new ArrayList(CodeType.allSet);
   boolean triggeredByUndo; // When a type change occurs because of an undo operation we do not want to record that op in the redo list again.

   @Sync(syncMode=SyncMode.Disabled)
   int refreshInstancesCt = 0;
   @Sync(syncMode=SyncMode.Disabled)
   boolean refreshInstancesValid = true;

   boolean confirmDeleteAllLayers = false;

   void init() {
      SyncManager.initStandardTypes();
      DynUtil.addDynListener(this);
   }

   boolean isTypeNameSelected(String typeName) {
      if (typeName == null)
         return false;

      // When an instance is selected, it's type is not
      if (selectedInstances != null && selectedInstances.size() > 0)
         return false;

      for (String tn:typeNames)
         if (tn.equals(typeName))
            return true;

      return false;
   }

   boolean isCreateModeTypeNameSelected(String typeName) {
      if (createModeTypeName == null)
         return false;

      return createModeTypeName.equals(typeName);
   }

   void changeCurrentInstance(Object newInst) {
      if (newInst == currentInstance)
         return;
      boolean modeChange = currentInstance == null;
      changeCurrentType(currentType, newInst, null);
      if (modeChange)
         instanceModeChanged++;
      else
         newInstSelected++;
   }

   void changeCurrentType(Object type, Object inst, InstanceWrapper wrapper) {
      if (type == currentType && inst == currentInstance && wrapper == currentWrapper)
         return;

      String newTypeName = null;
      if (type != null) {
         newTypeName = ModelUtil.getTypeName(type);
         String[] newTypeNames = new String[1];
         newTypeNames[0] = newTypeName;
         typeNames = newTypeNames;
         currentTypeName = newTypeName;
      }
      else {
         typeNames = new String[0];
         currentTypeName = null;
      }

      currentType = type;
      currentInstance = inst;
      // Previously used to pop/push current type here
      if (type != null) {
         List<InstanceWrapper> selInstances = new ArrayList<InstanceWrapper>(1);
         if (wrapper == null)
            wrapper = new InstanceWrapper(ctx, inst, newTypeName, null, false);
         selInstances.add(wrapper);
         selectedInstances = selInstances;
         currentWrapper = wrapper;
      }
      else {
         selectedInstances = null;
         currentWrapper = null;
         selectedInstances = null;
      }
      selectionChanged++;
      if (currentWrapper == null)
         pendingCreate = false;
      else
         pendingCreate = currentWrapper.pendingCreate;

      refreshTypeChanged();
   }

   void refreshTypeChanged() {
      showFindEditor = currentType != null && !ModelUtil.isObjectType(currentType) && !createMode;
   }

   void clearCurrentType() {
      typeNames = new String[0];
      currentTypeName = null;
      currentType = null;
      currentInstance = null;
      currentWrapper = null;
      pendingCreate = false;
      pendingCreateError = null;
      showFindEditor = false;
      searchResults = null; // TODO: dispose of these?
      searchText = null;
      searchTypeName = null;
   }

   void changeCurrentTypeName(String typeName) {
      String[] newTypeNames = new String[1];
      newTypeNames[0] = typeName;
      typeNames = newTypeNames;
      currentTypeName = typeName;
   }

   String getPropertySelectionName() {
      return " Property";
   }

   String getTypeSelectionName() {
      return DynUtil.isObject(currentType) ? " Object" : " Class";
   }

   String getCurrentSelectionName() {
      if (currentProperty != null) {
         return getPropertySelectionName();
      }
      else if (currentTypeIsLayer) {
         if (currentLayer.dynamic)
            return " Dynamic Layer";
         else
            return " Compiled Layer";

      }
      else if (currentType != null)
         return getTypeSelectionName();
      else
         return null;
   }

   boolean getDebugBindingEnabled() {
      return Bind.trace;
   }

   void setDebugBindingEnabled(boolean de) {
      if (system != null && system.options != null)
         system.options.verbose = de;
      Bind.trace = de;
   }

   void toggleDebugBindingEnabled() {
      setDebugBindingEnabled(!getDebugBindingEnabled());
   }

   boolean getDebugHTMLEnabled() {
      return Element.trace;
   }

   void setDebugHTMLEnabled(boolean de) {
      Element.trace = de;
   }

   void toggleDebugHTMLEnabled() {
      setDebugHTMLEnabled(!getDebugHTMLEnabled());
   }

   boolean getDebugSyncEnabled() {
      return SyncManager.trace;
   }

   void setDebugSyncEnabled(boolean de) {
      SyncManager.trace = de;
   }

   void toggleDebugSyncEnabled() {
      setDebugSyncEnabled(!getDebugSyncEnabled());
   }

   abstract Object[] getPropertiesForType(Object type, sc.type.IResponseListener listener);

   boolean enableUpdateProperty := !DynUtil.equalObjects(currentPropertyValue, savedPropertyValue) ||
                                   !DynUtil.equalObjects(currentPropertyOperator, savedPropertyOperator); 

   //abstract String setElementValue(Object type, Object inst, Object prop, String expr, boolean updateInstances, boolean valueIsExpr);

   public boolean filteredProperty(Object type, Object prop, boolean perLayer, boolean instanceMode) {
      if (instanceMode) {
         if (prop instanceof IVariableInitializer) {
            IVariableInitializer varInit = (IVariableInitializer) prop;
            String opStr = varInit.getOperatorStr();
            if (opStr != null && opStr.equals("=:"))
               return true;
         }
      }
      return false;
   }

   public static boolean isConstantProperty(Object prop) {
      if (prop == null)
         return true;
      if (prop instanceof CustomProperty)
         return ((CustomProperty) prop).isConstant();
      // For a getX only method - no way to set it
      if (prop instanceof VariableDefinition && !(((VariableDefinition) prop).getWritable()))
         return true;
      return ModelUtil.hasAnnotation(prop, "sc.obj.Constant") || ModelUtil.hasModifier(prop, "final");
   }

   public static boolean isSettableFromString(Object propC, Object propType) {
      if (!isConstantProperty(propC)) {
         if (propC instanceof CustomProperty)
            return ((CustomProperty) propC).isSettableFromString(propType);
         return sc.type.RTypeUtil.canConvertTypeFromString(propType);
      }
      return false;
   }

   /** When merging layers we use extendsLayer so that we do not pick up independent layers which which just happen to sit lower in the stack, below the selected layer */
   public boolean currentLayerMatches(Layer layer) {
      if (currentLayer == null)
         return true;
      if (ctx.currentLayers.contains(layer))
         return true;
      return false;
      //return ((!mergeLayers && currentLayer == layer) || (mergeLayers && (layer == currentLayer || currentLayer.extendsLayer(layer))));
   }

   BodyTypeDeclaration processVisibleType(Object typeObj) {
      if (typeObj instanceof BodyTypeDeclaration) {
         return (BodyTypeDeclaration) typeObj;
      }
      return null;
   }

   static String getDisplayNameAnnotation(Object typeOrProp) {
      String name = (String) ModelUtil.getAnnotationValue(typeOrProp, "sc.obj.EditorSettings", "displayName");
      if (name != null && name.length() > 0)
         return name;
      return null;
   }

   static String getPropertyName(Object prop) {
      if (prop == null)
         return ("*** null property");
      if (prop instanceof CustomProperty)
         return ((CustomProperty) prop).name;
      String name = getDisplayNameAnnotation(prop);
      if (name != null)
         return name;
      String res = ModelUtil.getPropertyName(prop);
      if (res == null) {
         System.err.println("*** Null property name returned for prop: " + prop);
         res = ModelUtil.getPropertyName(prop);
      }
      return res;
   }

   static String getClassDisplayName(Object type) {
      String name = getDisplayNameAnnotation(type);
      if (name != null)
         return name;
      return ModelUtil.getClassName(type);
   }

   boolean isVisible(Object prop) {
      if (prop instanceof CustomProperty)
         return true;
      Boolean vis = (Boolean) ModelUtil.getPropertyAnnotationValue(prop, "sc.obj.EditorSettings", "visible");
      if (vis != null && !vis)
         return false;
      return true;
   }

   boolean isReferenceType(Object type) {
      if (ModelUtil.isObjectType(type))
         return true;
      if (ModelUtil.hasAnnotation(type, "sc.obj.ValueObject"))
         return false;
      return true;
   }

   void changeFocus(Object newProp, Object newInst) {
      this.currentProperty = newProp;
      this.currentInstance = newInst;
   }

   void cancelPropertyEdit() {
      /* to reset back to the original values instead of just clearing the current property like we do now
      currentPropertyValue = savedPropertyValue;
      currentPropertyOperator = savedPropertyOperator;
      */
      currentProperty = null;
   }

   void instanceAdded(Object inst) {
      refreshInstances();
   }
   void instanceRemoved(Object inst) {
      if (inst == currentInstance)
         changeCurrentType(currentType, null, null);
      refreshInstances();
   }
   void refreshInstances() {
      if (refreshInstancesValid) {
         refreshInstancesValid = false;
         DynUtil.invokeLater(new Runnable() {
            void run() {
               refreshInstancesValid = true;
               refreshInstancesCt++;
            }
         }, 300);
      }
   }

   void refreshInstancesCheck(Object obj) {
      if (refreshInstancesValid) // This makes sure we do not refresh the bindings unless the instances were valid when this editor was created
         Bind.refreshBinding(obj, "instancesOfType");
   }

   static Layer getLayerForMember(Object prop) {
      if (prop == null || prop instanceof CustomProperty)
         return null;
      return ModelUtil.getLayerForMember(null, prop);
   }

   boolean getPropertyInherited(Object prop, Layer layer) {
      if (prop == null) // list element
         return false;
      if (prop instanceof CustomProperty)
         return false;
      Layer memberLayer = ModelUtil.getLayerForMember(null, prop);
      return memberLayer != layer;
   }

   static Object getPropertyType(Object prop) {
      if (prop instanceof CustomProperty)
         return ((CustomProperty) prop).propertyType;
      return ModelUtil.getPropertyType(prop);
   }

   Object fetchInstanceType(Object inst) {
      Object instType = DynUtil.getType(inst);
      instType = ModelUtil.resolveSrcTypeDeclaration(system, instType);
      return instType;
   }

   void changeCodeTypes(EnumSet<CodeType> newSet) {
      codeTypes = new ArrayList<CodeType>(newSet);
   }

   List<ConstructorProperty> getConstructorProperties(InstanceWrapper wrapper, TypeDeclaration typeDecl) {
      AbstractMethodDefinition createMeth = typeDecl.getEditorCreateMethod();
      ArrayList<ConstructorProperty> props = new ArrayList<ConstructorProperty>();
      if (createMeth != null) {
         List<Parameter> paramList = createMeth.getParameterList();
         String constrParamStr = typeDecl.getConstructorParamNames();
         String[] constrParamNames = constrParamStr != null ? constrParamStr.split(",") : null;
         if (paramList != null) {
            int nps = paramList.size();
            if (constrParamNames != null && constrParamNames.length != nps) {
               System.err.println("*** Ignoring EditorCreate.constructorParamNames for createMethod: mismatching parameters");
               constrParamNames = null;
            }
            for (int pix = 0; pix < nps; pix++) {
               Parameter param = paramList.get(pix);
               String paramName = constrParamNames != null ? constrParamNames[pix].trim() : param.variableName;
               Object paramType = DynUtil.findType(param.parameterTypeName);
               if (paramType == null) {
                  System.err.println("*** Unable to find parameter type: " + param.parameterTypeName);
               }
               props.add(new ConstructorProperty(paramName, paramType, null, wrapper));
            }
         }
         return props;
      }
      return null;
   }

   public String getLayerPrefixForLayerName(String layerName) {
      Layer layer = system.getLayerByDirName(layerName);
      if (layer != null)
         return "Package: " + layer.packagePrefix +
                (layer.packagePrefix != null && layer.packagePrefix.length() > 0 ? "." : "");
      return "";
   }

   public List<String> getMatchingLayerNamesForType(String typeName) {
      if (system.layers == null)
         return java.util.Arrays.asList(new String[]{"No layers loaded"});
      ArrayList<String> res = new ArrayList<String>();
      for (Layer layer:system.layers) {
         if (typeName == null || typeName.length() == 0 ||
             layer.packagePrefix == null || typeName.startsWith(layer.packagePrefix + "."))
            res.add(layer.getLayerName());
      }
      return res;
   }

   BodyTypeDeclaration getOrFetchTypeByName(String typeName, IResponseListener listener) {
      BodyTypeDeclaration type = system.getSrcTypeDeclaration(typeName, null);
      if (type instanceof BodyTypeDeclaration) {
         return (BodyTypeDeclaration) type;
      }
      else {
         system.fetchRemoteTypeDeclaration(typeName, listener);
      }
      return null;
   }

   void clearSearch() {
      searchText = null;
      searchResults = null;
      searchTypeName = null;
   }

   String getCurrentTypeClassName() {
      return currentTypeName == null ? null : CTypeUtil.getClassName(currentTypeName);
   }
}
Here the model layer for TypeTreeModel which is the domain model for the tree widget that browses the types. Different management UIs or aspects of management UIs might use one or the other or both in combination to provide different easily manageable views of the same types for different audiences. For example, an admin might browse the type tree to find the 'User' object and look at all of the properties combined in all layers. Or use the layered organization of the user properties to find some desired aspect. A back-office employee might instead navigate the layer tree to find the specific view of only the types and properties they need for their task at hand.
file: editor/model/TypeTreeModel.sc
import sc.util.ArrayList;
import sc.util.LinkedHashMap;

@sc.obj.Component
@sc.obj.CompilerSettings(constructorProperties="editorModel,viewType")
class TypeTreeModel {
   EditorModel editorModel;
   ViewType viewType;

   LayeredSystem system;

   ArrayList<CodeType> codeTypes :=: editorModel.codeTypes;

   // Adds to the set of layers you include in the index.  These will be in active layers.
   String[] specifiedLayerNames;

   boolean createMode = false;
   boolean propertyMode = false; // when create mode is true, are we creating properties or types?

   CreateMode currentCreateMode;
   boolean addLayerMode = false;  // Exclusive with the other two
   boolean createLayerMode = false; // When layerMode is true, are we including or creating?
   boolean layerMode := createLayerMode || addLayerMode;

   /** Should we also include instances in the type tree.  When included, they are children under their type */
   @Sync(syncMode=SyncMode.Disabled)
   boolean includeInstances = true;

   transient boolean valid = true;
   transient boolean rebuildFirstTime = true;
   transient boolean refreshInProgress = false;

   transient boolean uiBuilt = false;

   transient boolean needsUpdateSelection = false;

   public final static String PKG_INDEX_PREFIX = "<pkg>:";

   @Constant
   ArrayList<String> includePackages;
   @Constant
   ArrayList<String> excludePackages;

   //TypeTree typeTree = new TypeTree(this);
   //ByLayerTypeTree byLayerTypeTree = new ByLayerTypeTree(this);

   @sc.obj.Component
   object typeTree extends TypeTree {
      treeModel = TypeTreeModel.this;
   }

   @sc.obj.Component
   object byLayerTypeTree extends ByLayerTypeTree {
      treeModel = TypeTreeModel.this;
   }

   List<TypeTree> typeTrees = new ArrayList<TypeTree>();
   {
      typeTrees.add(typeTree);
      typeTrees.add(byLayerTypeTree);
   }

   // Rules controlling when to refresh.  
   codeTypes =: refresh();

   // When the current type in the model changes, if we're in create mode we need to refresh to reflect the newly visible/highlighted elements.
   editorModel =: createMode || layerMode ? refresh() : null;

   createMode =: onSelectionChanged();
   addLayerMode =: onSelectionChanged();
   createLayerMode =: onSelectionChanged();
   currentCreateMode =: onSelectionChanged();

   // Need to refresh when any new instances are created.  TODO performance: we could reduce the scope of the refresh if necessary here since this might happen a lot in some applications
   int refreshInstancesCt := editorModel.refreshInstancesCt;
   refreshInstancesCt =: refreshInstances();

   void onSelectionChanged() {
      editorModel.selectionChanged++;
      markSelectionChanged();
   }

   int selectionChanged := editorModel.selectionChanged;
   selectionChanged =: markSelectionChanged();

   void markSelectionChanged() {
      needsUpdateSelection = true;
      refresh();
   }

   void refreshInstances() {
      if (rebuildFirstTime || !valid)
         return;
      refresh();
   }

   void refresh() {
      if (refreshInProgress)
         return;
      // IF we have an empty tree during initialization it resets the "open" state for the startup node
      if (rebuildFirstTime) {
         valid = false;
         rebuildFirstTime = false;
         rebuild();
         return;
      }
      if (valid) {
         valid = false;

         scheduleBuild();
      }
   }

   // On the client, this will run after a 0 millisecond timeout.  
   // On the server, this runs at the end of the request.
   void scheduleBuild() {
      DynUtil.invokeLater(new Runnable() {
         public void run() {
            rebuild();
         }
      }, 9);
   }

   void rebuild() {
      if (refreshInProgress || valid)
         return;

      //includeInstances = !propertyMode && !createMode && !addLayerMode && !createLayerMode == ViewType.DataViewType;
      includeInstances = true;

      refreshInProgress = true;
      valid = true;

      try {
         for (TypeTree typeTree:typeTrees) {
            typeTree.refreshTree();
         }

         if (needsUpdateSelection) {
            for (TypeTree typeTree:typeTrees) {
               if (typeTree.selectionListener != null)
                  typeTree.selectionListener.updateListSelection();
            }
         }
      }
      catch (RuntimeException exc) {
         System.err.println("*** error refreshing tree model: " + exc.toString());
         exc.printStackTrace();
      }
      finally {
         refreshInProgress = false;
      }
   }

   void updateInstancesForType(String typeName, boolean byLayer) {
      List<TypeTree.TreeNode> typeNodes = (byLayer ? byLayerTypeTree : typeTree).rootTreeIndex.get(typeName);
      if (typeNodes != null) {
         for (TypeTree.TreeNode typeNode:typeNodes) {
            typeNode.ent.instanceSelected = true;
            typeNode.ent.updateInstances();
            typeNode.ent.refreshChildren();
         }
      }
   }

   boolean nodeExists(String typeName) {
      return typeTree.rootTreeIndex.get(typeName) != null;
   }
}
Here's the implementation class for the EditorModel. It runs in both the server and desktop processes. It's not included in the client version of the EditorModel class, but instead is synchronized with the client version. It uses data binding expressions to listen for changes made when synchronization events are received on the client or server. In some situations, those changes will trigger subsequent changes to synchronized properties that will be queued up until the next 'sync' with the other side. This establishes a nice declarative dialog, using a 'do later' on the client and 'end of request' flush on the server.
file: editor/modelImpl/EditorModel.sc
import java.lang.reflect.Field;

import sc.type.IBeanMapper;
import java.util.TreeSet;
import java.util.TreeMap;

import sc.lang.java.JavaSemanticNode;
import sc.lang.java.DeclarationType;
import sc.lang.java.InterfaceDeclaration;
import sc.lang.java.ConstructorDefinition;
import sc.lang.JavaLanguage;
import sc.lang.sc.PropertyAssignment;

import sc.lang.IUndoOp;
import sc.type.Type;

import sc.parser.ParseUtil;

import sc.db.DBTypeDescriptor;

EditorModel {
   system = LayeredSystem.getCurrent();

   /** Set this to true so the command line interpreter and UI do not share the same current type, etc. */
   boolean separateContext = false;
   private EditorContext theSeparateCtx = null;
   private EditorContext getTheSeparateContext() {
      if (theSeparateCtx == null)
         theSeparateCtx = new EditorContext(system);
      return theSeparateCtx;
   }
   ctx := separateContext ? getTheSeparateContext() : system.getDefaultEditorContext();

   typeNames =: invalidateModel();
   currentLayer =: invalidateModel();

   currentProperty =: validateCurrentProperty();
   currentProperty =: currentPropertyIcon = GlobalResources.lookupUIIcon(currentProperty);

   importedPropertyType := getImportedPropertyType(currentProperty);

   searchOrderByProps =: refreshSearch();

   boolean modelsValid = true; // start out true so the first invalidate kicks in.... when nothing is selected, we are valid
   boolean modelValidating = false;

   void invalidateModel() {
      if (modelValidating) {
         return; // in rebuildModel, we change currentLayer which may call this when we are in the midst of updating the model so just ignore it
      }

      if (modelsValid) {
         modelsValid = false;

         DynUtil.invokeLater(new Runnable() {
            public void run() {
               rebuildModel();
               }}, 0);
      }
   }

   void rebuildModel() {
      if (modelsValid)
         return;

      modelValidating = true;

      if (!triggeredByUndo) {
         // Add a type navigation to the undo stack if the type changed
         boolean typesChanged = !StringUtil.arraysEqual(oldTypeNames,typeNames);

         if (oldTypeNames != null && typesChanged) {
            ctx.addOp(new IUndoOp() {
               String[] prevTypeNames = oldTypeNames;
               String[] newTypeNames = typeNames;

               void undo() {
                  triggeredByUndo = true;
                  typeNames = prevTypeNames;
                  oldTypeNames = newTypeNames;
               }
               void redo() {
                  triggeredByUndo = true;
                  oldTypeNames = prevTypeNames;
                  typeNames = newTypeNames;
               }

            });
         }
      }
      else
         triggeredByUndo = false;

      oldTypeNames = typeNames;

      ArrayList<Layer> newFilteredLayers = new ArrayList<Layer>();
      ArrayList<Layer> newTypeLayers = new ArrayList<Layer>();
      types = new ArrayList<Object>();
      inheritedTypes = new ArrayList<Object>();
      typesPerLayer = new ArrayList<List<Object>>();
      filteredTypes = new ArrayList<Object>();
      filteredTypesByLayer = new LinkedHashMap<String,List<Object>>();

      selectedFileIndex = new LinkedHashMap<String, SelectedFile>();
      selectedFileList = new ArrayList<SelectedFile>();

      for (String typeName:typeNames) {
         boolean isLayerType = false;

         Object type = system.getTypeDeclaration(typeName);
         if (type == null) {
            Layer layer = system.getLayerByTypeName(typeName);
            if (layer == null) {
               System.err.println("*** Can't find type or layer named: " + typeName);
               continue;
            }
            type = layer.model.getModelTypeDeclaration();
            isLayerType = true;
         }

         // Don't try to preserve the current layer when it goes from visible to invisible
         if (currentLayer != null && !currentLayer.matchesFilter(codeTypes))
            currentLayer = null;

         // Pick the first visible layer in the type... if none are visible, then skip this type
         if (currentLayer == null) {
            if (type instanceof BodyTypeDeclaration) {
               BodyTypeDeclaration btd = (BodyTypeDeclaration) type;
               Layer typeLayer = btd.layer;
               while (typeLayer != null && !typeLayer.matchesFilter(codeTypes)) {
                  btd = btd.getModifiedType();
                  if (btd == null)
                     break;
                  typeLayer = btd.layer;
               }
               // There is no version of this type in the selected layer
               if (btd == null)
                  continue;
               type = btd;
            }
         }

         Layer typeLayer = type instanceof BodyTypeDeclaration ? ((BodyTypeDeclaration) type).getLayer() : null;

         // Going to walk the type hierarchy twice - the first time to gather the set of layers in the type
         // the second time to compute the visible set of types once we've decided whether or not to reset
         // the current layer.
         addNewTypeLayer(typeLayer, newTypeLayers);
         if (typeLayer != null) {
            List<Layer> transLayers = typeLayer.getTransparentLayers();
            if (transLayers != null) {
               for (int i = 0; i < transLayers.size(); i++) {
                  addNewTypeLayer(transLayers.get(i), newTypeLayers);
               }
            }
         }

         int lastTypeLayerIx = -1;
         if (type instanceof TypeDeclaration) {
            TypeDeclaration rootTD = (TypeDeclaration) type;
            BodyTypeDeclaration modType = rootTD.getModifiedType();
            while (modType != null) {
               addNewTypeLayer(modType.getLayer(), newTypeLayers);
               modType = modType.getModifiedType();
            }
            // If we are resetting the current layer, pick the last layer that still has this type in it.
            lastTypeLayerIx = newTypeLayers.size() - 1;
            //if (inheritTypeCt > 0) {
               Object extType = rootTD.getExtendsTypeDeclaration();
               //int ct = inheritTypeCt;
               while (/*ct > 0 && */ extType != null && extType instanceof BodyTypeDeclaration) {
                  BodyTypeDeclaration eTD = (BodyTypeDeclaration) extType;
                  // Use this method to just add the layer to the layer indexes.  When type is null, no type is added.
                  addNewTypeLayer(eTD.getLayer(), newTypeLayers);

                  BodyTypeDeclaration eTDRoot = eTD.getModifiedByRoot();

                  BodyTypeDeclaration extModType;
                  BodyTypeDeclaration mtype = eTD;
                  while ((extModType = mtype.getModifiedType()) != null) {
                     addNewTypeLayer(extModType.getLayer(), newTypeLayers);
                     mtype = extModType;
                  }
                  extType = eTD.getExtendsTypeDeclaration();
                  //ct--;
               }

               addInterfaceNewTypeLayers(rootTD, newTypeLayers);
            //}
         }

         // Now that we have the newTypeLayers, we need to determine if we are going to reset the
         // currentLayer or not. If this type does not overlap with the current layer, we'll do the
         // reset. Once we do that, we are ready to call addLayerType which computes the filteredTypes
         // used to set the currentType.
         boolean resetCurrentLayer = true;
         if (newTypeLayers != null) {
            if (currentLayer != null) {
               if (newTypeLayers.contains(currentLayer))
                  resetCurrentLayer = false;
            }
            if (resetCurrentLayer && newTypeLayers.size() > lastTypeLayerIx) {
               currentLayer = newTypeLayers.get(lastTypeLayerIx);
               // There is a binding to set this but it might be queued and we need this updated for processVisibleTypes below
               if (ctx.currentLayer != currentLayer)
                  ctx.currentLayer = currentLayer;
            }
         }

         /** --- */

         addLayerType(type, typeLayer, newFilteredLayers);

         if (typeLayer != null) {
            List<Layer> transLayers = typeLayer.getTransparentLayers();
            if (transLayers != null) {
               for (int i = 0; i < transLayers.size(); i++) {
                  addLayerType(type, transLayers.get(i), newFilteredLayers);
               }
            }
         }

         // Add this root type to the global list of types
         types.add(type);
         inheritedTypes.add(type);

         if (type instanceof TypeDeclaration) {
            TypeDeclaration rootTD = (TypeDeclaration) type;
            BodyTypeDeclaration modType = rootTD.getModifiedType();
            while (modType != null) {
               addLayerType(modType, modType.getLayer(), newFilteredLayers);
               modType = modType.getModifiedType();
            }
            //if (inheritTypeCt > 0) {
               Object extType = rootTD.getExtendsTypeDeclaration();
               //int ct = inheritTypeCt;
               while (/*ct > 0 && */ extType != null && extType instanceof BodyTypeDeclaration) {
                  BodyTypeDeclaration eTD = (BodyTypeDeclaration) extType;
                  // Use this method to just add the layer to the layer indexes.  When type is null, no type is added.
                  addLayerType(eTD, eTD.getLayer(), newFilteredLayers);

                  BodyTypeDeclaration eTDRoot = eTD.getModifiedByRoot();

                  if (!inheritedTypes.contains(eTDRoot))
                     inheritedTypes.add(eTDRoot);

                  BodyTypeDeclaration extModType;
                  BodyTypeDeclaration mtype = eTD;
                  while ((extModType = mtype.getModifiedType()) != null) {
                     addLayerType(extModType, extModType.getLayer(), newFilteredLayers);
                     mtype = extModType;
                  }
                  extType = eTD.getExtendsTypeDeclaration();
                  //ct--;
               }

               addInterfaceLayerTypes(rootTD, newFilteredLayers, inheritedTypes);
            //}
         }
      }

      filteredTypeLayers = newFilteredLayers;
      // Make sure this does not change when just currentLayer changes...
      if (typeLayers == null || !typeLayers.equals(newTypeLayers))
         typeLayers = newTypeLayers;

      Object filteredType;

      if (filteredTypes.size() > 0)
         filteredType = filteredTypes.get(0);
      else
         filteredType = null;

      if (filteredType instanceof BodyTypeDeclaration) {
         Object ctxCurrentType = ctx.currentType;
         Layer layer = ctxCurrentType == null ? null : ModelUtil.getLayerForType(system, ctxCurrentType);
         if (ctxCurrentType == null || layer == null || !newFilteredLayers.contains(layer)) {
            if (currentInstance != null)
               ctx.setDefaultCurrentObj((BodyTypeDeclaration) filteredType, currentInstance);
            else if (ctxCurrentType == null || !ModelUtil.sameTypes(filteredType, ctxCurrentType)) {
               ctx.currentType = (BodyTypeDeclaration) filteredType;
               setCurrentCtxTypeNoEvent((BodyTypeDeclaration) filteredType);
            }
            else {
               // there was a more specific type in the context so we'll use that here
            }
         }
      }
      // else - we are not updating ctx.currentType here - so these two are not in sync when it's a compiled class or the matched type is not in a visible layer.  TODO: not sure this is right.

      currentType = filteredType;

      currentProperty = null;
      currentPropertyType = currentType;
      savedPropertyValue = currentPropertyValue = null;
      savedPropertyOperator = currentPropertyOperator = null;

      if (currentType != null) {
         currentTypeIsLayer = ModelUtil.isLayerType(currentType);
         if (currentTypeIsLayer)
            currentPackage = currentLayer.packagePrefix;
         else
            currentPackage = ModelUtil.getPackageName(currentType);
         if (currentInstance != null && !ModelUtil.isInstance(currentType, currentInstance))
            currentInstance = null;
         currentTypeName = ModelUtil.getTypeName(currentType);
      }
      else {
         currentPackage = "";
         currentTypeIsLayer = false;
         currentInstance = null;
         currentTypeName = null;
      }

      ArrayList newVisibleTypes = new ArrayList();
      if (types != null) {
         for (Object type:types) {
            type = processVisibleType(type);
            if (type != null) {
               newVisibleTypes.add(type);
            }
         }
      }
      setVisibleTypesNoEvent(newVisibleTypes);

      refreshTypeChanged();

      // Do this at the end in case any of our changes trigger the model
      modelsValid = true;

      modelValidating = false;

      updateCurrentJavaModel();

      //System.out.println("*** finished in - refreshModel: " + StringUtil.arrayToString(oldTypeNames) + " -> " + StringUtil.arrayToString(typeNames) + " currentType: " + currentType + " thread: " + sc.type.PTypeUtil.getThreadName());

      // Send an event so people can listen on this value and update dependent data structures, but wait until we've
      // updated the model
      Bind.sendEvent(sc.bind.IListener.VALUE_CHANGED, this, "visibleTypes");
      Bind.sendEvent(sc.bind.IListener.VALUE_CHANGED, this, null);

      //System.out.println("*** finished sending events in - refreshModel: " + StringUtil.arrayToString(oldTypeNames) + " -> " + StringUtil.arrayToString(typeNames) + " currentType: " + currentType + " thread: " + sc.type.PTypeUtil.getThreadName());
   }

   @Bindable(crossScope=true, sameValueCheck=true)
   BodyTypeDeclaration currentCtxType := ctx.currentType;

   override @Bindable(crossScope=true)
   currentCtxType =: !pendingCreate ? changeCurrentType(currentCtxType, ctx.currentObject, null) : null;

   // Match and change the current type from the value entered in the search box
   currentTypeSearch =: findCurrentType(currentTypeSearch);

   @sc.obj.ManualGetSet
   private void setVisibleTypesNoEvent(ArrayList<Object> visTypes) {
      visibleTypes = visTypes;
   }

   @sc.obj.ManualGetSet
   private void setCurrentCtxTypeNoEvent(BodyTypeDeclaration type) {
      currentCtxType = type;
   }

   private void addInterfaceLayerTypes(BodyTypeDeclaration rootTD, ArrayList<Layer> newFilteredLayers, List<Object> inheritedTypes) {
      Object[] implTypes = rootTD.getImplementsTypeDeclarations();
      if (implTypes != null) {
         for (Object implTypeObj:implTypes) {
            if (implTypeObj instanceof TypeDeclaration) {
               TypeDeclaration implType = (TypeDeclaration) implTypeObj;

               addLayerType(implType, implType.getLayer(), newFilteredLayers);

               BodyTypeDeclaration iTDRoot = implType.getModifiedByRoot();

               if (!inheritedTypes.contains(iTDRoot))
                  inheritedTypes.add(iTDRoot);

               BodyTypeDeclaration extModType;
               BodyTypeDeclaration mtype = implType;
               while ((extModType = mtype.getModifiedType()) != null) {
                  addLayerType(extModType, extModType.getLayer(), newFilteredLayers);
                  mtype = extModType;
               }

               addInterfaceLayerTypes(implType, newFilteredLayers, inheritedTypes);
            }
         }
      }
   }

   private void addLayerType(Object type, Layer layer, ArrayList<Layer> newFilteredLayers) {
      Layer prevLayer;
      if (type instanceof TypeDeclaration) {
         TypeDeclaration td = (TypeDeclaration) type;

         ParseUtil.initAndStartComponent(td);

         BodyTypeDeclaration prevType = td.getModifiedByType();
         prevLayer = prevType == null ? null : prevType.getLayer();
      }
      else {
         prevLayer = null;
      }

      boolean isTypeLayer = true;

      // When we filter a layer, we remove it from the allLayers attribute as well as not showing any types from it.
      if (layer != null && !layer.matchesFilter(codeTypes))
         return;

      // Don't show this layer if we have a currentLayer set and depending on the "mergeLayers" flag we should or not
      if (layer != null && currentLayer != null && !currentLayer.transparentToLayer(layer, 0)) {
         isTypeLayer = false;
      }

      if (isTypeLayer) {
         int layerIx = newFilteredLayers.indexOf(layer);
         List<Object> layerTypes;
         if (layerIx == -1) {
            layerIx = newFilteredLayers.size();
            // Keep layers sorted with null as the very first layer if it is present
            int pos;

            if (layer == null) {
               pos = 0;
            }
            else {
               for (pos = 0; pos < newFilteredLayers.size(); pos++) {
                  Layer cur = newFilteredLayers.get(pos);
                  if (cur != null && cur.layerPosition > layer.layerPosition)
                     break;
               }
            }
            newFilteredLayers.add(pos, layer);
            typesPerLayer.add(pos, layerTypes = new ArrayList<Object>());
         }
         else
            layerTypes = typesPerLayer.get(layerIx);

         // Also associate ths type with its layer
         if (type != null) {
            layerTypes.add(type);

            String typeName = ModelUtil.getTypeName(type);
            List<Object> typeList = filteredTypesByLayer.get(typeName);
            if (typeList == null) {
               typeList = new ArrayList<Object>();
               filteredTypesByLayer.put(typeName, typeList);
            }
            typeList.add(type);

            // If the previous type is null or the previous type's position is outside the selected region, do not include it
            // Otherwise, this type was already added to the global list in a previous layer's type
            if (prevLayer == null || (currentLayer != null && prevLayer.getLayerPosition() > currentLayer.getLayerPosition()))
               filteredTypes.add(type);

            if (type instanceof BodyTypeDeclaration) {
               BodyTypeDeclaration btd = (BodyTypeDeclaration) type;
               JavaModel javaModel = btd.getJavaModel();
               SrcEntry ent = javaModel.getSrcFile();

               // We want to record instances for this type by default since we are manipulating it from the management UI
               btd.liveDynType = true;

               if (ent != null) {
                  SelectedFile f = selectedFileIndex.get(ent.absFileName);
                  if (f == null) {
                     f = new SelectedFile();
                     f.file = ent;
                     f.types = new ArrayList<Object>();
                     f.model = javaModel;
                     f.layer = currentLayer;
                     selectedFileIndex.put(ent.absFileName, f);

                     if (editAllFiles || selectedFileList.size() == 0)
                        selectedFileList.add(f);
                  }
                  if (!f.types.contains(type))
                     f.types.add(type);
               }
            }
         }
      }
   }

   private void addInterfaceNewTypeLayers(BodyTypeDeclaration rootTD, ArrayList<Layer> newTypeLayers) {
      Object[] implTypes = rootTD.getImplementsTypeDeclarations();
      if (implTypes != null) {
         for (Object implTypeObj:implTypes) {
            if (implTypeObj instanceof TypeDeclaration) {
               TypeDeclaration implType = (TypeDeclaration) implTypeObj;

               addNewTypeLayer(implType.getLayer(), newTypeLayers);

               BodyTypeDeclaration iTDRoot = implType.getModifiedByRoot();

               BodyTypeDeclaration extModType;
               BodyTypeDeclaration mtype = implType;
               while ((extModType = mtype.getModifiedType()) != null) {
                  addNewTypeLayer(extModType.getLayer(), newTypeLayers);
                  mtype = extModType;
               }

               addInterfaceNewTypeLayers(implType, newTypeLayers);
            }
         }
      }
   }

   private static void addNewTypeLayer(Layer layer, List<Layer> newTypeLayers) {
      if (layer.hidden)
         return;
      int allIx = newTypeLayers.indexOf(layer);
      if (allIx == -1) {
         int i;
         for (i = 0; i < newTypeLayers.size(); i++) {
            if (newTypeLayers.get(i).layerPosition < layer.layerPosition) {
               newTypeLayers.add(i, layer);
               break;
            }
         }
         if (i == newTypeLayers.size())
            newTypeLayers.add(layer);
      }
   }

   // TODO: this is duplicated in BodyTypeDeclaration - we need to filter the version we serialize to the client - the
   // declaredProperties property of ClientTypeDeclaration
   private static HashSet<String> filteredProps = new HashSet<String>();
   static {
      filteredProps.add("class");
      filteredProps.add("initState");
      filteredProps.add("serialVersionUID");
      filteredProps.add("DBTypeDescriptor");
      filteredProps.add("DBObject");
      filteredProps.add("DBId");
   }

   public boolean filteredProperty(Object type, Object p, boolean perLayer, boolean instanceMode) {
      if (super.filteredProperty(type, p, perLayer, instanceMode))
         return true;

      if (!instanceMode) {
         // For now, only StrataCode members
         if (p instanceof java.lang.reflect.Member)
            return true;

         if (p instanceof IBeanMapper && ((IBeanMapper) p).getPropertyMember() instanceof java.lang.reflect.Member)
            return true;
      }

      Object ownerType = ModelUtil.getEnclosingType(p);

      if (type instanceof ClientTypeDeclaration)
         type = ((ClientTypeDeclaration) type).getOriginal();

      // Normally !inherit mode only uses the declared properties.  But for transparent layers we have to get all of them and filter them here
      /* This rule does not take into account the new inheritProperties flag on a type and so excluded all properties in that case
         Probably should just build in the transparent layer visibility rules into the getProperties method so it's all in one place
      if (inheritTypeCt == 0 && !ModelUtil.sameTypes(ownerType, type))
         return true;
      */

      // In threeD view, we don't want to merge the properties as we go up the layer stack unlike form view.
      if (perLayer && ModelUtil.getLayerForType(null, type) != ModelUtil.getLayerForType(null, ownerType))
         return true;

      String pname;
      return p == null || (pname = ModelUtil.getPropertyName(p)).startsWith("_") || filteredProps.contains(pname);
   }

   public Object[] getPropertiesForType(Object type, sc.type.IResponseListener listener) {
      if (type instanceof ClientTypeDeclaration)
         type = ((ClientTypeDeclaration) type).getOriginal();
      Object[] props;

      // Transparent layers need to grab all of the properties so we can filter them in the code
      if ((currentLayer == null || !currentLayer.transparent))
         props = ModelUtil.getDeclaredPropertiesAndTypes(type, "public", system);
       else
         props = ModelUtil.getPropertiesAndTypes(type, "public");
      return props;
   }

   public Object[] toClientTypeDeclarations(Object[] types) {
      if (types == null)
         return null;
      int i = 0;
      for (Object type:types) {
         if (type instanceof ClientTypeDeclaration)
            continue;
         if (type instanceof BodyTypeDeclaration)
            types[i] = ((BodyTypeDeclaration) type).getClientTypeDeclaration();
      }
      return types;
   }

   String setElementValue(Object type, Object inst, Object prop, String expr, boolean updateType, boolean updateInstances, boolean valueIsExpr) {
      if (type instanceof ClientTypeDeclaration)
         type = ((ClientTypeDeclaration) type).getOriginal();

      Object elemValue = expr;
      if (pendingCreate || !updateType) {
         if (prop != null) {
            Object propertyType;
            if (prop instanceof CustomProperty)
               propertyType = ((CustomProperty) prop).propertyType;
            else
               propertyType = ModelUtil.getPropertyType(prop);
            if (propertyType instanceof Class) {
               try {
                  elemValue = Type.propertyStringToValue(propertyType, expr);
               }
               catch (RuntimeException exc) {
                  return "Invalid value for: " + prop + ": " + expr + ": " + exc.toString();
               }
            }
         }
      }

      if (prop instanceof CustomProperty) {
         String error = updateCustomProperty((CustomProperty) prop, inst, elemValue);
         return error;
      }
      else if (pendingCreate) {
         return updatePendingProperty(ModelUtil.getPropertyName(prop), elemValue);
      }

      // The first time they are changing the object in a transparent layer.  We need to create it in this case.
      if (currentLayer != null && currentLayer.transparent && ModelUtil.getLayerForType(null, type) != currentLayer) {
         String typeName = ModelUtil.getTypeName(type);
         type = ctx.addTopLevelType(null, CTypeUtil.getPackageName(typeName), currentLayer, CTypeUtil.getClassName(typeName), null);
         invalidateModel();
      }
      return ctx.setElementValue(type, inst, prop, expr, updateType, updateInstances, valueIsExpr);
   }

   String validatePropertyTypeName(String typeName) {
      typeName = typeName == null ? "" : typeName.trim();
      if (typeName.length() == 0) {
         return null;
      }
      String err = ModelUtil.validateElement(JavaLanguage.getJavaLanguage().type, typeName, false);
      if (err != null) {
         return err;
      }
      else {
         if (ctx.isCreateInstType(typeName))
            return null;
         String fullTypeName = ctx.getCreateInstFullTypeName(typeName);
         if (fullTypeName != null)
            return null;

         if (findType(typeName) != null) {
            return null;
         }

         if (Type.getPrimitiveType(typeName) != null)
            return null;

         if (system.getTypeDeclaration(typeName) != null)
            return null;

         if (system.getTypeDeclaration(CTypeUtil.prefixPath("java.lang", typeName)) != null)
            return null;

         return "No property type named: " + typeName;
      }
   }

   String validateTypeText(String text, boolean instType) {
      String typeName = text == null ? "" : text.trim();
      if (typeName.length() == 0) {
         return null;
      }
      String err = ModelUtil.validateElement(JavaLanguage.getJavaLanguage().type, text, false);
      if (err != null) {
         return err;
      }
      else {
         if (ctx.isCreateInstType(typeName))
            return null;
         String fullTypeName = ctx.getCreateInstFullTypeName(typeName);
         if (fullTypeName != null)
            return null;

         Object type = findType(typeName);
         if (!instType) {
            if (type != null) {
               return null;
            }
         }
         if (instType && type != null)
            return "Type: " + typeName + " missing EditorCreate annotation";
         else
            return "No type named: " + typeName;
      }
   }

   String validateNameText(String text) {
      if (text.trim().length() == 0) {
         return null;
      }
      JavaLanguage lang = JavaLanguage.getJavaLanguage();
      String err = ModelUtil.validateElement(lang.identifier, text, false);
      if (err != null) {
         return err;
      }
      return null;
   }


   String updateCurrentProperty(Object operator, String value, boolean instanceMode) {
      return setElementValue(currentPropertyType, null, currentProperty, operator + value, !instanceMode, true, true);
   }

   void validateCurrentProperty() {
      Object prop = currentProperty;
      if (prop == null) {
         currentPropertyType = null;
         currentPropertyName = null;
      }
      else if (prop instanceof CustomProperty) {
         currentPropertyType = null;
         currentPropertyName = ((CustomProperty) prop).name;
      }
      else {
         currentPropertyType = ModelUtil.getEnclosingType(prop);
         savedPropertyValue = currentPropertyValue = ctx.propertyValueString(currentType, null, prop);
         savedPropertyOperator = currentPropertyOperator = ModelUtil.getOperator(currentProperty);
         if (savedPropertyOperator == null)
            savedPropertyOperator = currentPropertyOperator = "=";
         currentPropertyName = ModelUtil.getPropertyName(prop);
      }
      propertySelectionChanged();
   }

   // Called when the current JavaModel changes
   private object modelEventListener extends AbstractListener {
      public boolean valueValidated(Object obj, Object prop, Object eventDetail, boolean apply) {
         if (currentType != null && currentProperty != null) {
            savedPropertyValue = currentPropertyValue = ctx.propertyValueString(currentType, null, currentProperty);
            savedPropertyOperator = currentPropertyOperator = ModelUtil.getOperator(currentProperty);
            if (savedPropertyOperator == null)
               savedPropertyOperator = currentPropertyOperator = "=";
         }

         // If the model has changed, the type itself may have changed
         if (currentType instanceof BodyTypeDeclaration) {
            BodyTypeDeclaration typeDecl = (BodyTypeDeclaration) currentType;

            BodyTypeDeclaration newTypeDecl = typeDecl.resolve(false);
            // When the type has changed, update the current model which will trigger the rebuilding of the form
            if (newTypeDecl != typeDecl) {
               currentType = newTypeDecl;
               currentTypeName = newTypeDecl == null ? null : ModelUtil.getTypeName(newTypeDecl);

               invalidateModel();

/*
               int i = 0;
               for (Object type:types) {
                  if (type instanceof BodyTypeDeclaration)
                     types.set(i, ((BodyTypeDeclaration) type).resolve(false));
                  i++;
               }
*/
            }
         }

         return true;
      }
   }

   void removeCurrentListener() {
      if (currentJavaModel != null) {
         Bind.removeListener(currentJavaModel, null, modelEventListener, IListener.VALUE_CHANGED);
      }
   }

   void updateCurrentJavaModel() {
      JavaModel newModel = ModelUtil.getJavaModel(currentType);
      if (newModel != currentJavaModel) {
         removeCurrentListener();
         currentJavaModel = newModel;
         if (newModel != null) {
            Bind.addListener(currentJavaModel, null, modelEventListener, IListener.VALUE_CHANGED);
         }
      }
   }

   void changeCurrentType(Object type, Object inst, InstanceWrapper wrapper) {
      super.changeCurrentType(type, inst, wrapper);

      BodyTypeDeclaration ctxType = ctx.getCurrentType(false);
      if (ctxType != type) {
         if (ctxType != null)
            ctx.popCurrentType();
         if (type instanceof BodyTypeDeclaration)
            ctx.pushCurrentType((BodyTypeDeclaration) type, inst);
      }

      if (inst != ctx.currentObject)
         ctx.setDefaultCurrentObj(type, inst);

      // Push this back if the change is coming from the editor model side
      if (currentCtxType != type && type instanceof BodyTypeDeclaration) {
         currentCtxType = (BodyTypeDeclaration) type;
      }

      updateCurrentJavaModel();
   }

   void clearCurrentType() {
      super.clearCurrentType();
      updateCurrentJavaModel();
   }

   String getPropertySelectionName() {
      if (currentProperty != null) {
         String dynPrefix = ModelUtil.isDynamicProperty(currentProperty) ? " Dynamic" : " Compiled";
         if (currentProperty instanceof VariableDefinition) {
            return dynPrefix + " Field";
         }
         else if (currentProperty instanceof PropertyAssignment) {
            return dynPrefix + " Property Assignment";
         }
         else if (currentProperty instanceof Field)
            return " Native Field";
         else
            return " ???"; // method?
      }
      else
         return null;
   }

   String getTypeSelectionName() {
      if (currentType != null) {
         DeclarationType declType = ModelUtil.getDeclarationType(currentType);
         String name = declType.name;
         String prefix;
         if (currentType instanceof BodyTypeDeclaration) {
            prefix = ModelUtil.isDynamicType(currentType) ? " Dynamic" : " Compiled";
         }
         else
            prefix = " Native";

         return " " + Character.toUpperCase(name.charAt(0)) + name.substring(1);
      }
      else
         return null;
   }

   public String deleteCurrentProperty() {
      if (currentType != null && currentProperty != null && currentType instanceof BodyTypeDeclaration && currentProperty instanceof JavaSemanticNode) {
         ctx.removeProperty((BodyTypeDeclaration) currentType, (JavaSemanticNode) currentProperty, true);
         clearCurrentProperty();
         return null;
      }
      else
         return "Unable to delete current property";
   }


   final String NeedsConfirmDeleteLayers = "Layer is in use";

   public String deleteCurrentLayer() {
      if (currentLayer != null) {
         List<String> usedByLayerNames = currentLayer.getUsedByLayerNames();
         if (usedByLayerNames.size() == 0) {
            ctx.removeLayer(currentLayer, true);
            clearCurrentType();
         }
         else if (confirmDeleteAllLayers) {
            ctx.removeLayers(usedByLayerNames.toArray(new String[0]));
            confirmDeleteAllLayers = false;
         }
         else
            return NeedsConfirmDeleteLayers;

         return null;
      }
      else
         return "No current layer to delete";
   }

   public String deleteCurrentType() {
      if (currentType != null || !(currentType instanceof BodyTypeDeclaration)) {
         ctx.removeType((BodyTypeDeclaration) currentType, true);
         clearCurrentType();
         return null;
      }
      else
         return "*** no current type to delete";
   }

   public String deleteCurrentSelection() {
      String error = null;
      if (currentProperty != null) {
          error = deleteCurrentProperty();
      }
      else if (currentTypeIsLayer) {
         error = deleteCurrentLayer();
      }
      else if (currentType != null) {
         error = deleteCurrentType();
      }
      else
         error = "No current selection";
      return error;

   }

   public Object findType(String typeName) {
      return system.getSrcTypeDeclaration(typeName, null, true);
   }

   public void updateCurrentType(String typeName) {
      if (currentType == null || !StringUtil.equalStrings(typeNames[0], typeName))
         findCurrentType(typeName);
   }

   public String findCurrentType(String rootName) {
      if (rootName == null)
         return null;

      // First see if they specified the whole name
      BodyTypeDeclaration theType = system.getSrcTypeDeclaration(rootName, null, true);

      if (theType == null) {
         List<BodyTypeDeclaration> types = system.findTypesByRootName(rootName);
         if (types == null || types.size() == 0) {
            return "No types named: " + rootName;
         }
         theType = types.get(0);
      }
      changeCurrentType(theType, null, null);
      return null;
   }

   public void commitMemorySessionChanges() {
      ctx.commitMemorySessionChanges();
      invalidateModel();
   }

   void clearCurrentProperty() {
      currentProperty = null;
      currentPropertyType = null;
      currentPropertyValue = null;
      currentPropertyOperator = null;
      currentInstance = null;

      propertySelectionChanged();
   }

   void propertySelectionChanged() {
      // Need to manually change these properties when the current property changes cause rebuildModel does not send the "model changed" event in this case
      if (editSelectionEnabled != getEditableProperty())
         editSelectionEnabled = !editSelectionEnabled;
      Bind.sendDynamicEvent(IListener.VALUE_CHANGED, this, "currentSelectionName");;
   }

   public boolean getEditableProperty() {
      if (currentProperty != null) {
         if (currentProperty instanceof VariableDefinition) {
            return true;
         }
         else if (currentProperty instanceof PropertyAssignment) {
            return true;
         }
         else if (currentProperty instanceof Field)
            return false;
         else
            return false;
      }
      else if (currentTypeIsLayer) {
         return true;
      }
      else if (currentType != null) {
         return true;
      }
      else {
         return false;
      }
   }

   public String addTopLevelType(String mode, String currentPackage, Layer layer, String name, String extType) {
      Object res = ctx.addTopLevelType(mode, currentPackage, layer, name, extType);
      if (res instanceof String)
         return (String) res;

      changeCurrentType(res, null, null);

      return null;
   }

   String createProperty(String ownerTypeName, String propertyTypeName, String propertyName, String operator, String propertyValue, boolean addBefore, String relPropertyName) {
      String name = propertyName.trim();
      if (StringUtil.isEmpty(ownerTypeName)) {
         return "Select a type to hold the new property";
      }
      if (StringUtil.isEmpty(propertyTypeName)) {
         return "Select a data type for the new property";
      }
      Object ownerType = findTypeWithClassName(ownerTypeName);
      if (ownerType == null) {
         return "No owner type for property: " + ownerTypeName;
      }
      String err = ctx.addProperty(ownerType, propertyTypeName, name, operator, propertyValue, addBefore, relPropertyName);
      return err;
   }

   String startOrCompleteCreate(String typeName) {
      if (!pendingCreate) {
         String err = createInstance(typeName);
         if (err != null) {
            return err;
         }
         else {
            // Need to potentially refresh the editor - and if the current type is already selected, it's a mode changed requiring a full refresh of the form
            //newInstSelected++;
            instanceModeChanged++;

            return pendingCreateError;
         }
      }
      else {
         String err = completeCreateInstance(true);
         if (err != null) {
            return err;
         }
         else {
            createMode = false; // Set this back to non-create state
            invalidateModel(); // Rebuild it so it says instance instead of new
            return null;
         }
      }
   }

   private Object findTypeWithClassName(String classOrTypeName) {
      String fullTypeName = ctx.getCreateInstFullTypeName(classOrTypeName);
      if (fullTypeName != null)
         return DynUtil.findType(fullTypeName);
      return DynUtil.findType(classOrTypeName);
   }

   public String createInstance(String typeName) {
      Object typeObj = DynUtil.findType(typeName);
      String fullTypeName = null;
      if (typeObj == null) {
         // Implements the rule where you can just type in the class name
         fullTypeName = ctx.getCreateInstFullTypeName(typeName);
         if (fullTypeName != null) {
            typeName = fullTypeName;
            typeObj = DynUtil.findType(fullTypeName);
         }
         if (typeObj == null) {
            if (currentPackage != null) {
               fullTypeName = CTypeUtil.prefixPath(currentPackage, typeName);
               typeObj = DynUtil.findType(fullTypeName);
               if (typeObj != null)
                  typeName = fullTypeName;
            }
            if (typeObj == null) {
               return "No type: " + typeName + (currentPackage != null ? " with current package: " + currentPackage : "");
            }
         }

         if (!ctx.isCreateInstType(typeName)) {
            return "Type: " + typeName + " not able to create types missing @EditorCreate";
         }
      }
      if (!(typeObj instanceof BodyTypeDeclaration)) {
         typeObj = ModelUtil.resolveSrcTypeDeclaration(system, typeObj);
      }
      if (typeObj instanceof TypeDeclaration) {
         TypeDeclaration typeDecl = (TypeDeclaration) typeObj;
         InstanceWrapper instWrap = new InstanceWrapper(ctx, null, typeName, "<unset>", false);
         instWrap.pendingValues = new TreeMap<String,Object>();
         instWrap.pendingCreate = true;
         constructorProps = getConstructorProperties(instWrap, typeDecl);
         currentLayer = null; // If this type is not in the current layer, it gets removed from the list of visible types
         changeCurrentType(typeDecl, null, instWrap);

         pendingCreateError = completeCreateInstance(false);
         if (fullTypeName != null)
            createModeTypeName = fullTypeName;
      }
      else
         return "Unable to create instance of type: " + typeName + " - no type metadata found";
      return null;
   }

   public String createType(CreateMode mode, String name, String outerTypeName, String extendsTypeName, String pkg, Layer destLayer) {
      String err;

      Object outerType;
      if (outerTypeName == null) {
         outerType = null;
      }
      else {
         outerType = findTypeWithClassName(outerTypeName);
         if (outerType == null) {
            return "No outer type: " + outerTypeName;
         }
      }

      if (name.length() == 0)
         err = "No name specified for new " + mode.toString().toLowerCase();
      else if (outerType != null)
         err = ctx.addInnerType(mode == CreateMode.Object ? "Object" : "Class", outerType, name, extendsTypeName, false, null);
      else {
         if (destLayer == null)
            err = "No layer specified for new " + mode.toString().toLowerCase();
         err = addTopLevelType(mode == CreateMode.Object ? "Object" : "Class", pkg, destLayer, name, extendsTypeName);
      }
      return err;
   }

   public String createLayer(String layerName, String pkgIdentifier, String extendsTypeList, boolean isDynamic, boolean isPublic, boolean isTransparent) {
      try {
         String layerPackage = pkgIdentifier == null || pkgIdentifier.trim().length() == 0 ? "" : ctx.validateIdentifier(pkgIdentifier);
         String[] extendsNames = ctx.validateExtends(extendsTypeList);

         Layer layer = ctx.createLayer(layerName, layerPackage, extendsNames, isDynamic, isPublic, isTransparent, true);

         if (layer != null) {
            changeCurrentTypeName(layer.layerModelTypeName);
            createMode = false;
         }

         return null;
      }
      catch (IllegalArgumentException exc) {
         return exc.toString();
      }
   }

   public String addLayer(String layerNameList, boolean isDynamic) {
      try {
         String[] addNames = ctx.validateExtends(layerNameList);

         Layer origLastLayer = system.lastLayer;

         ctx.addLayers(addNames, isDynamic, true);

         Layer newLastLayer = system.lastLayer;
         if (newLastLayer != origLastLayer) {
            if (newLastLayer != null) {
               changeCurrentTypeName(newLastLayer.layerModelTypeName);
               createMode = false;
            }
         }
         return null;
      }
      catch (IllegalArgumentException exc) {
         return exc.toString();
      }
   }

   public void cancelCreate() {
      changeCurrentType(null, null, null);
      pendingCreate = false;
      pendingCreateError = null;
   }

   public String updateCustomProperty(CustomProperty custProp, Object inst, Object propVal) {
      String err = custProp.updateInstance(inst, propVal);
      if (pendingCreate) // In this case this was the last value we needed, update the error
         pendingCreateError = completeCreateInstance(false);
      return err;
   }

   public String updatePendingProperty(String propName, Object value) {
      if (pendingCreate) {
         if (currentWrapper != null) {
            currentWrapper.pendingValues.put(propName, value);
         }
         pendingCreateError = completeCreateInstance(false);
      }
      return null;
   }

   public String completeCreateInstance(boolean doCreate) {
      if (pendingCreate) {
         if (currentType instanceof TypeDeclaration) {
            TypeDeclaration typeDecl = (TypeDeclaration) currentType;
            AbstractMethodDefinition createMeth = typeDecl.getEditorCreateMethod();
            ArrayList<Object> args = new ArrayList<Object>();
            if (constructorProps != null) {
               ArrayList<String> missingPropNames = null;
               for (ConstructorProperty prop:constructorProps) {
                  String paramName = prop.name;

                  Object val = currentWrapper.pendingValues == null ? null : currentWrapper.pendingValues.get(paramName);
                  if (!isValid(val)) {
                     if (missingPropNames == null)
                        missingPropNames = new ArrayList<String>();
                     missingPropNames.add(paramName);
                  }
                  args.add(val);
               }
            
               // Ready to create the new instance
               if (doCreate && missingPropNames == null) {
                  Object[] argsArr = args.toArray();
                  Object inst;
                  if (createMeth instanceof ConstructorDefinition) {
                     inst = DynUtil.newInnerInstance(typeDecl, null, createMeth.getTypeSignature(), argsArr);
                  }
                  else {
                     inst = DynUtil.invokeMethod(null, createMeth, argsArr);
                  }
                  if (inst != null) {
                     pendingCreate = false;

                     currentInstance = inst;
                     ArrayList<InstanceWrapper> selInsts = new ArrayList<InstanceWrapper>();
                     Map<String,Object> pendingValues = currentWrapper.pendingValues;

                     currentWrapper.pendingCreate = false;

                     // Currently we don't serialize changes made to the InstanceWrapper... they get created on either side so maybe it's simpler just to create a new one
                     currentWrapper = currentWrapper.copyWithInstance(inst);
                     selInsts.add(currentWrapper);
                     selectedInstances = selInsts; // NOTE: make sure the value is defined before setting because it's bound to another property

                     // Register the instance with the dynamic type system so it is returned by getInstancesOfType
                     DynUtil.addDynInstance(currentWrapper.typeName, inst);

                     // Now we've created the instance. For any pendingValues the user entered that were not
                     // constructor properties, set the property to the value in the instance.
                     for (Map.Entry<String,Object> pendingEnt:pendingValues.entrySet()) {
                        String propName = pendingEnt.getKey();
                        boolean skipProp = false;
                        for (int pix = 0; pix < constructorProps.size(); pix++) {
                           ConstructorProperty cprop = constructorProps.get(pix);
                           if (cprop.name.equals(propName)) {
                              skipProp = true;
                              break;
                           }
                        }
                        if (!skipProp) {
                           Object value = pendingEnt.getValue();
                           try {
                              DynUtil.setPropertyValue(currentWrapper.theInstance, propName, value);
                           }
                           catch (IllegalArgumentException exc) {
                              return "Failed to set property after create: " + propName + " to: " + value + " error: " + exc.toString();
                           }
                        }
                     }

                     // Now add it to the type tree
                     instanceAdded(inst);
                     constructorProps = null;

                     // Signal to the form view to refresh it's current instance
                     newInstSelected++;
                  }
                  else
                     return "Failed to create instance";
               }
               else if (missingPropNames != null) {
                   return "Missing values for properties: " + missingPropNames;
               }
            }
         }
      }
      return null;
   }

   private Object convertStringPropertyValue(Object propC, String propName, Object elementValue) {
      Object propType = ModelUtil.getPropertyType(propC);
      if (ModelUtil.sameTypes(propType, java.math.BigDecimal.class) && elementValue instanceof String) {
         try {
            elementValue = new java.math.BigDecimal((String) elementValue);
         }
         catch (Exception exc) {
            return exc.toString();
         }
      }
      if (ModelUtil.sameTypes(propType, java.util.Date.class) && elementValue instanceof String) {
         try {
            elementValue = DynUtil.parseDate((String) elementValue);
         }
         catch (Exception exc) {
            System.err.println("*** Error parsing date string: " + elementValue + ": " + exc);
            return exc.toString();
         }
      }
      return elementValue;
   }

   public String updateInstanceProperty(Object propC, String propName, Object instance, InstanceWrapper wrapper, Object elementValue) {
      if (propC instanceof CustomProperty) {
         return updateCustomProperty((CustomProperty) propC, instance, elementValue);
      }
      if (instance != null) {
         elementValue = convertStringPropertyValue(propC, propName, elementValue);
         try {
            DynUtil.setPropertyValue(instance, propName, elementValue);
         }
         catch (IllegalArgumentException exc) {
            return exc.toString();
         }
         catch (UnsupportedOperationException exc1) {
            return exc1.toString();
         }
      }
      else if (pendingCreate && wrapper == currentWrapper) {
         currentWrapper.pendingValues.put(propName, elementValue);
      }
      else
         return "No instance to update";
      return null;
   }

   private boolean isValid(Object obj) {
      if (obj instanceof String)
         return ((String) obj).trim().length() > 0;
      return obj != null;
   }

   void removeLayers(ArrayList<String> layers) {
      ctx.removeLayers(layers.toArray(new String[layers.size()]));
   }

   ClientTypeDeclaration toClientType(Object type) {
      if (type instanceof BodyTypeDeclaration) {
         if (type instanceof ClientTypeDeclaration)
            return (ClientTypeDeclaration) type;
         return ((BodyTypeDeclaration) type).getClientTypeDeclaration();
      }
      return null;
   }

   BodyTypeDeclaration processVisibleType(Object typeObj) {
      if (typeObj instanceof BodyTypeDeclaration) {
         return toClientType(((BodyTypeDeclaration) typeObj).getDeclarationForLayer(ctx.currentLayers, 0, 0));
      }
      return null;
   }


   void stop() {
      removeCurrentListener();
   }

   int[] getTypeOffsets(SelectedFile file) {
      if (file == null)
         return null;
      ArrayList<Integer> arr = new ArrayList<Integer>();
      for (int i = 0; i < file.types.size(); i++) {
         arr.add(ModelUtil.getTypeOffset(file.types.get(i)));
      }
      int[] res = new int[arr.size()];
      for (int i = 0; i < res.length; i++)
         res[i] = arr.get(i);
      return res;
   }

   void findEditorSearch(String text) {
      if (currentTypeName == null) {
         searchResults = null;
         return;
      }
      DBTypeDescriptor dbType = DBTypeDescriptor.getByName(currentTypeName, true);
      if (dbType == null) {
         Object[] insts = DynUtil.getInstancesOfTypeAndSubTypes(currentTypeName);
         // TODO: filter string properties by text matching
         if (insts == null)
            searchResults = null;
         else
            searchResults = new ArrayList(java.util.Arrays.asList(insts));
      }
      else {
         List<String> useOrderByProps = new ArrayList<String>(searchOrderByProps);
         if (useOrderByProps.size() == 0 && ModelUtil.isAssignableFrom(sc.db.IDBObject.class, currentType))
            useOrderByProps.add("-lastModified");
         searchResults = new ArrayList<Object>(dbType.searchQuery(text, null, null, null, useOrderByProps, searchStartIx, searchMaxResults));
         if (searchResults.size() == searchMaxResults) {
            numSearchResults = dbType.searchCountQuery(text, null, null);
         }
         else
            numSearchResults = searchResults.size();
      }
      searchText = text;
      searchTypeName = currentTypeName;
   }

   void refreshSearch() {
      findEditorSearch(searchText);
   }

   String getImportedPropertyType(Object propType) {
      if (propType instanceof CustomProperty)
         return CTypeUtil.getClassName(ModelUtil.getTypeName(((CustomProperty) propType).propertyType));
      return ctx.getImportedPropertyType(propType);
   }

   void refreshTypeChanged() {
      super.refreshTypeChanged();

      if (!StringUtil.equalStrings(currentTypeName, searchTypeName)) {
         searchResults = null; // TODO: dispose of these here?
         searchText = null;
      }
      else {
         // This won't refresh the grid - we should be listening in the grid for the layer change and refresh
         // the properties and redisplay that way
         //findEditorSearch(searchText);
      }
   }
}
Here the server/desktop layer of TypeTreeModel:
file: editor/modelImpl/TypeTreeModel.sc
import sc.layer.IModelListener;
import sc.layer.LayerIndexInfo;

import sc.type.Type;

import sc.lang.java.ITypeDeclaration;

import sc.lang.ILanguageModel;

TypeTreeModel {

   // TODO: not implemented yet Populated from specifiedLayerNames.  Defines the layers from which we are doing source files.
   ArrayList<Layer> specifiedLayers;

   system = LayeredSystem.getCurrent();

   // TODO: fix this!  Ideally we do not load the types until you click on them.  At that point, we need to load all types, including those which extend the selected type.
   // It might be simpler to optimize this at the package level.  We'll load the inner types of all types when we initialize the type.  The obstacle now is that we need to
   // create DirEnt's for each type once it's been selected.  Maybe we use the addTypeToLayer and addModelType methods?  Essentially re-adding these types... that will add new entries to the subDirs for the parent and push those changes to the client.
   static boolean loadInnerTypesAtStartup = true;

   IModelListener listener;

   void start() {
      system.addNewModelListener(listener = new IModelListener() {
         void modelAdded(ILanguageModel m) {
            addNewModel(m);
         }
         void innerTypeAdded(ITypeDeclaration td) {
            addNewType(td);
         }
         void layerAdded(Layer l) {
            addNewLayer(l);
         }
         void modelRemoved(ILanguageModel m) {
            removeModel(m);
         }
         void innerTypeRemoved(ITypeDeclaration td) {
            removeType(td);
         }
         void layerRemoved(Layer l) {
            removeLayer(l);
         }
         void runtimeAdded(LayeredSystem sys) {
         }
         /*
         void instancedAdded(ITypeDeclaration td, Object inst) {
            addNewInstance(td, inst);
         }
         void instancedRemoved(ITypeDeclaration td, Object inst) {
         }
         */
      });
   }

   ArrayList<String> getExtendedPrimitiveTypeNames() {
      Set<String> primNames = Type.getPrimitiveTypeNames();
      ArrayList<String> res = new ArrayList<String>(primNames.size()+1);
      res.addAll(primNames);
      res.remove("void");
      res.add("String");
      return res;
   }


   // For testing use these to cut down the number of types or layers
   static final int MAX_TYPES = 20000; // 100;
   static final int MAX_LAYERS = 20000; // 10;

   boolean includeInactive = false;
   boolean includePrimitives = false;

   boolean isFilteredType(String typeName) {
      if (includePackages == null && excludePackages == null)
         return false;
      if (typeName == null)
         return true;
      if (excludePackages != null) {
         for (String pkg:excludePackages) {
            if (typeName.startsWith(pkg))
               return true;
         }
      }
      if (includePackages != null) {
         for (String pkg:includePackages) {
            if (typeName.startsWith(pkg))
               return false;
         }
      }
      else
         return false;
      return true;
   }

   boolean isFilteredPackage(String pkgName) {
      if (includePackages == null)
         return false;
      if (pkgName == null)
         return false;
      for (String pkgFilter:includePackages) {
         if (pkgName.startsWith(pkgFilter) || pkgFilter.startsWith(pkgName))
            return false;
      }
      return true;
   }

   Set<String> getSrcTypeNames() {
      Set<String> srcTypeNames = system.getSrcTypeNames(true, loadInnerTypesAtStartup, false, false, true);
      if (specifiedLayerNames != null) {
         specifiedLayers = new ArrayList<Layer>(specifiedLayerNames.length);
         for (int i = 0; i < specifiedLayerNames.length; i++) {
            Layer layer = system.getInactiveLayer(specifiedLayerNames[i], true, true, true, false);
            if (layer == null)
               System.err.println("*** TypeTreeModel: Unable to find layer with specifiedLayerName: " + specifiedLayerNames[i]);
            else {
               // TODO: we should put these into dependency order but we can't use position cause these are inactive.
               specifiedLayers.add(layer);
            }
         }
         Set<String> additionalNames = system.getSrcTypeNames(specifiedLayers, true, loadInnerTypesAtStartup, false, true);
         if (additionalNames != null) {
            if (srcTypeNames == null)
               srcTypeNames = additionalNames;
            else
               srcTypeNames.addAll(additionalNames);
         }
      }
      return srcTypeNames;
   }

   void addNewModel(ILanguageModel m) {
      if (!uiBuilt)
         return;

      TypeDeclaration td = m.getUnresolvedModelTypeDeclaration();
      boolean needsRefresh = false;
      if (td != null) {
         String typeName = td.fullTypeName;
         if (!nodeExists(typeName)) {
            for (TypeTree typeTree:typeTrees) {
               // TODO: hide this difference inside of the TypeTree implementation?
               if (!(typeTree instanceof ByLayerTypeTree)) {
                  TypeTree.TreeEnt e = typeTree.addModel(typeName, m.getPrependPackage());
                  if (e != null) {
                     e.processEntry();
                     needsRefresh = true;
                  }
               }
               else {
                  TypeTree.TreeEnt childEnt = ((ByLayerTypeTree) typeTree).findType(td);
                  if (childEnt != null) {
                     if (childEnt.transparent) {
                        childEnt.transparent = false;
                     }
                  }
                  else {
                     TypeTree.TreeEnt e = ((ByLayerTypeTree) typeTree).addModel(m, m.getPrependPackage());
                     if (e != null) {
                        e.processEntry();
                        needsRefresh = true;
                     }
                  }
               }
            }
         }
      }
      // Now still need to go and refresh the visible nodes so we add a new one for this guy.
      if (needsRefresh)
         refresh();
   }

   void pruneChildren(BodyTypeDeclaration td) {
      Object[] innerTypes = ModelUtil.getAllInnerTypes(td, null, true, false);
      if (innerTypes != null) {
         for (Object innerType:innerTypes) {
            if (innerType instanceof BodyTypeDeclaration) {
               BodyTypeDeclaration btd = (BodyTypeDeclaration) innerType;
               String fullTypeName = btd.getFullTypeName();
               if (system.getSrcTypeDeclaration(fullTypeName, null, true) == null) {
                  for (TypeTree typeTree:typeTrees)
                     typeTree.removeType(fullTypeName);
               }
               else
                  pruneChildren(btd);
            }
         }
      }
   }

   void removeModel(ILanguageModel m) {
      if (!uiBuilt)
         return;

      // Has been removed so use the unresolved type here
      TypeDeclaration td = m.getUnresolvedModelTypeDeclaration();
      boolean needsRefresh = false;
      if (td != null) {
         String typeName = td.fullTypeName;
         if (!nodeExists(typeName))
            return;

         TypeTree.TreeEnt e;

         // Only remove from the type tree if this is the last file defining this type
         if (system.getSrcTypeDeclaration(typeName, null, true) == null) {
            for (TypeTree typeTree:typeTrees) {
               e = typeTree.removeType(typeName);
               if (e != null) {
                  needsRefresh = true;
               }
            }

            // In this case, not removing any of the inner types - we detach the tree parent tree node and so discard the children automatically.
         }
         else {
            // But if there's another version of the same type, we do need to see if any sub-objects have been removed.
            pruneChildren(td);
         }

         for (TypeTree typeTree:typeTrees) {
            // TODO: move down this method into TypeTree
            if (typeTree instanceof ByLayerTypeTree) {
               e = ((ByLayerTypeTree)typeTree).removeModel(m);
               if (e != null)
                  needsRefresh = true;
            }
         }

      }
      // Now still need to go and refresh the visible nodes so we add a new one for this guy.
      if (needsRefresh)
         refresh();
   }

   void addNewType(ITypeDeclaration itd) {
      if (!uiBuilt || !(itd instanceof BodyTypeDeclaration))
         return;

      BodyTypeDeclaration td = (BodyTypeDeclaration) itd;

      boolean needsRefresh = false;
      if (td != null) {
         String typeName = td.fullTypeName;
         for (TypeTree typeTree:typeTrees) {
            if (!(typeTree instanceof ByLayerTypeTree)) {
               if (!nodeExists(typeName)) {
                  TypeTree.TreeEnt e = typeTree.addModel(typeName, true);
                  if (e != null) {
                     e.processEntry();
                     needsRefresh = true;
                  }
               }
            }
            else {
               ByLayerTypeTree blTree = (ByLayerTypeTree) typeTree;
               TypeTree.TreeEnt childEnt = blTree.findType(td);

               if (childEnt != null) {
                  if (childEnt.transparent) {
                     childEnt.transparent = false;
                  }
               }
               else {
                  TypeTree.TreeEnt e = blTree.addType(td);
                  if (e != null) {
                     e.processEntry();
                     needsRefresh = true;
                  }
               }
            }
         }
      }
      // Now still need to go and refresh the visible nodes so we add a new one for this guy.
      if (needsRefresh)
         refresh();
   }

   void removeType(ITypeDeclaration td) {
      if (!uiBuilt)
         return;

      boolean needsRefresh = false;
      if (td != null) {
         String typeName = td.fullTypeName;
         if (!nodeExists(typeName))
            return;

         TypeTree.TreeEnt e;

         for (TypeTree typeTree:typeTrees) {
            if (!(typeTree instanceof ByLayerTypeTree)) {
               // Only remove from the type tree if this is the last file defining this type
               if (system.getSrcTypeDeclaration(typeName, null, true) == null) {
                  e = typeTree.removeType(typeName);
                  if (e != null) {
                     needsRefresh = true;
                  }
               }
            }
            else {
               e = ((ByLayerTypeTree) typeTree).removeType(td);
               if (e != null)
                  needsRefresh = true;
            }
         }
      }
      // Now still need to go and refresh the visible nodes so we add a new one for this guy.
      if (needsRefresh)
         refresh();
   }


   void removeLayer(Layer layer) {
      if (!uiBuilt)
         return;

      boolean needsRefresh = false;

      for (TypeTree typeTree:typeTrees) {
         TypeTree.TreeEnt e = typeTree.removeLayer(layer, true);
         if (e != null) {
            needsRefresh = true;
         }
      }

      // Now still need to go and refresh the visible nodes so we add a new one for this guy.
      if (needsRefresh)
         refresh();
   }

   int typesCreated = 0;
   int layersCreated = 0;

   void addNewLayer(Layer layer) {
      if (!uiBuilt)
         return;

      if (byLayerTypeTree != null) {
         TypeTree.TreeEnt ent = byLayerTypeTree.addLayerDirEnt(layer);
         ent.processEntry();
      }

      if (typeTree != null) {
         // Then build our DirEnt structure from the Set of src type names we get back
         Set<String> srcTypeNames = layer.getSrcTypeNames(true, true, false, true);
         for (String srcTypeName:srcTypeNames) {
            // TODO: need to fix the setting of prependPackage - to handle files in the type tree
            TypeTree.TreeEnt e = typeTree.addModel(srcTypeName, true);
         }
         // Re-process everything?
         typeTree.rootDirEnt.processEntry();
      }

      refresh();
   }


   /*
   void addNewInstance(ITypeDeclaration itd, Object inst) {
      if (!uiBuilt || !(itd instanceof BodyTypeDeclaration))
         return;

      BodyTypeDeclaration td = (BodyTypeDeclaration) itd;

      boolean needsRefresh = false;
      if (td != null) {
         String typeName = td.fullTypeName;
         for (TypeTree typeTree:typeTrees) {
            List<TypeTree.TreeNode> typeEnts = typeTree.rootTreeIndex.get(typeName);
            if (typeEnts != null) {
               for (TypeTree.TreeNode node:typeEnts)
                  if (node.ent.updateInstances())
                     needsRefresh = true;
            }
         }
      }

      if (needsRefresh)
         refresh();
   }
   */

   void stop() {
      if (listener != null) {
         system.removeNewModelListener(listener);
      }
   }
}
Tree Widget

Here's the code for the TreeView component for the client/server version. It generates the HTML for each of the two trees in StrataCode on both the client and the server. For an schtml file, the id of the top-level tag is the name of the file so we do not have to specify id='TreeView' here. Data binding is used to specify the css class of the outer li tag and the span which defines the name of the tree element. It's used to call 'toggleOpen' when you click on the +/- iconand whether to show the children list for a given node.

file: editor/html/core/TreeView.schtml
<%@
   @sc.obj.CompilerSettings(constructorProperties="tree")
%>
<li class=':= "sctTreeNode " + (tree.hasChildren ?
       (tree.ent.open ? "sctParentOpen" : "sctParentClosed") : "sctLeaf")'>
   <%! TypeTree.TreeNode tree; %>

   <span class=':= tree.ent.selected ? "selectedItem" : ""'>
      <a href="#" clickEvent="=: tree.ent.toggleOpen()">
         <img height="18" width="18"
              src=':= tree.ent.open ? "/images/listClose.png" : "/images/listOpen.png"'
              visible=':= tree.needsOpenClose' style="vertical-align:-4px">
      </a>
      <a href="#" clickEvent="=: tree.ent.selectType(false)">
         <img src='= tree.ent.icon == null ? "" : tree.ent.icon.path'
              visible=":= tree.ent.icon != null"
              style="vertical-align:-3px">
         <%= tree.ent.nodeDisplayName %>
      </a>
   </span>
   <ol visible=":= tree.hasChildren" class=':= tree.ent.open ? "sctOpen" : "sctClosed"'>
      <li repeat=":= tree.children" extends="TreeView"
          repeatVarName="childTree" tree=":= childTree"/>
   </ol>
</li>

The last 'li' tag in the file is the most interesting one. It has the 'repeat' attribute which means it's rendered once for each element in the array. To implement a recursive tree, it extends the TreeView component which also is its enclosing class. It has an optional set of children of its own and provides unlimited levels of nesting in a simple declarative description.