content: /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *    https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   ****************************************************************/
  package org.apache.cayenne.modeler.util.combo;
  
  import java.lang.reflect.Method;
  
  import javax.swing.ComboBoxModel;
  import javax.swing.JComboBox;
  import javax.swing.plaf.basic.BasicComboBoxEditor;
  
  import org.apache.cayenne.modeler.util.CellRenderers;
  
  /**
   * CustomTypeComboBoxEditor is used as an editor of a combobox, when
   * custom type (such as Entity) is to be used. BasicComboBoxEditor
   * cannot be used, because it converts String values to other types
   * incorrectly (in fact, only classes with valueOf(String) methods
   * are supported).
   * 
   */
  public class CustomTypeComboBoxEditor extends BasicComboBoxEditor {
      /**
       * 'oldValue' property is private somewhy, so we make our local
       * copy
       */
      protected Object localOldValue;
      
      /**
       * The combobox being edited
       */
      protected final JComboBox combo;
      
      /**
       * Whether non-present items are allowed
       */
      protected final boolean allowsUserValues;
      
      /**
       * Creates new editor
       * @param combo ComboBox being edited
       */
      public CustomTypeComboBoxEditor(JComboBox combo, boolean allowsUserValues) {
          editor = new EditorTextField(combo);
          this.combo = combo;
          this.allowsUserValues = allowsUserValues;
      }
      
      /** 
       * Sets the item that should be edited. 
       *
       * @param anObject the displayed value of the editor
       */
      @Override
      public void setItem(Object anObject) {
          localOldValue = anObject;
          super.setItem(anObject == null ? null : CellRenderers.asString(anObject));
      }
      
      /**
       * @return edited item
       */
      @Override
      public Object getItem() {
          Object newValue = editor.getText();
          
          if (localOldValue != null && !(localOldValue instanceof String))  {
              // The original value is not a string. Should return the value in it's
              // original type.
              if (newValue.equals(localOldValue.toString())) {
                  return localOldValue;
              } else {
                  // Must take the value from the editor and get the value and cast it to the new type.
                  Class cls = localOldValue.getClass();
                  try {
                      newValue = convert((String)newValue, cls);
                  }
                  catch (Exception ignored) {}
              }
          }
          
          if (!allowsUserValues && newValue != null) {
              boolean contains = false;
              
              for (int i = 0; i < combo.getItemCount(); i++) {
                  if (newValue.equals(combo.getItemAt(i))) {
                      contains = true;
                      break;
                  }
              }
                     
              if (!contains) {
                  return null;
              }
          }
          
          return newValue;
      }
      
      /**
       * Converts String value to specified type
       *
       * @param value String value of textfield
       * @param classTo type of result item
       * 
       * @return value of classTo type, or null if conversion is impossible
       */
      protected Object convert(String value, Class<?> classTo) {
          if (classTo == String.class) {
              return value;
          }
          
          /*
           * We still try to it in BasicComboBox's way, so that primary object
           * types (such as numbers) would still be supported 
           */
          try {
              Method method = classTo.getMethod("valueOf", String.class);
              return method.invoke(null, value);
          } catch (Exception ignored) {}
          
          /*
           * We could manually convert strings to dbentities, attrs and other, but
           * in this implementation we use reverse operation instead, and convert 
           * combobox model's items to String.
           * All string values are assumed unique is one model.
           */
          ComboBoxModel model = combo.getModel();
          for (int i = 0; i < model.getSize(); i++) {
              if (value.equals(CellRenderers.asString(model.getElementAt(i)))) {
                  return model.getElementAt(i);
              }
          }
          
          //we return null, since String will not be appreciated
          return null;
      }
  }
  