public IntrospectedProperty<?> @NotNull [] getIntrospectedProperties(@NotNull Class<?> aClass, @NotNull Class<?> delegeeClass) {
    // Try the cache first
    // TODO[vova, anton] update cache after class reloading (its properties could be hanged).
    if (myClass2Properties.containsKey(aClass)) {
      return myClass2Properties.get(aClass);
    }

    List<IntrospectedProperty<?>> result = new ArrayList<>();
    try {
      final BeanInfo beanInfo = Introspector.getBeanInfo(aClass);
      final PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
      for (final PropertyDescriptor descriptor : descriptors) {
        Method readMethod = descriptor.getReadMethod();
        Method writeMethod = descriptor.getWriteMethod();
        Class<?> propertyType = descriptor.getPropertyType();
        if (writeMethod == null || readMethod == null || propertyType == null) {
          continue;
        }

        boolean storeAsClient = false;
        try {
          delegeeClass.getMethod(readMethod.getName(), readMethod.getParameterTypes());
          delegeeClass.getMethod(writeMethod.getName(), writeMethod.getParameterTypes());
        }
        catch (NoSuchMethodException e) {
          storeAsClient = true;
        }

        final @NonNls String name = descriptor.getName();

        IntrospectedProperty<?> property;
        Properties properties = (myProject == null) ? new Properties() : Properties.getInstance();
        if (int.class.equals(propertyType)) { // int
          IntEnumEditor.Pair[] enumPairs = properties.getEnumPairs(aClass, name);
          if (enumPairs != null) {
            property = createIntEnumProperty(name, readMethod, writeMethod, enumPairs);
          }
          else if (JLabel.class.isAssignableFrom(aClass)) { // special handling for javax.swing.JLabel
            if (JLabel.class.isAssignableFrom(aClass) &&
                ("displayedMnemonic".equals(name) || "displayedMnemonicIndex".equals(name))) { // skip JLabel#displayedMnemonic and JLabel#displayedMnemonicIndex
              continue;
            }
            else {
              property = new IntroIntProperty(name, readMethod, writeMethod, storeAsClient);
            }
          }
          else if (AbstractButton.class.isAssignableFrom(aClass)) {  // special handling AbstractButton subclasses
            if ("mnemonic".equals(name) || "displayedMnemonicIndex".equals(name)) { // AbstractButton#mnemonic
              continue;
            }
            else {
              property = new IntroIntProperty(name, readMethod, writeMethod, storeAsClient);
            }
          }
          else if (JTabbedPane.class.isAssignableFrom(aClass)) {
            if (SwingProperties.SELECTED_INDEX.equals(name)) {
              continue;
            }
            property = new IntroIntProperty(name, readMethod, writeMethod, storeAsClient);
          }
          else {
            property = new IntroIntProperty(name, readMethod, writeMethod, storeAsClient);
          }
        }
        else if (boolean.class.equals(propertyType)) { // boolean
          property = new IntroBooleanProperty(name, readMethod, writeMethod, storeAsClient);
        }
        else if (double.class.equals(propertyType)) {
          property = new IntroPrimitiveTypeProperty<>(name, readMethod, writeMethod, storeAsClient, Double.class);
        }
        else if (float.class.equals(propertyType)) {
          property = new IntroPrimitiveTypeProperty<>(name, readMethod, writeMethod, storeAsClient, Float.class);
        }
        else if (long.class.equals(propertyType)) {
          property = new IntroPrimitiveTypeProperty<>(name, readMethod, writeMethod, storeAsClient, Long.class);
        }
        else if (byte.class.equals(propertyType)) {
          property = new IntroPrimitiveTypeProperty<>(name, readMethod, writeMethod, storeAsClient, Byte.class);
        }
        else if (short.class.equals(propertyType)) {
          property = new IntroPrimitiveTypeProperty<>(name, readMethod, writeMethod, storeAsClient, Short.class);
        }
        else if (char.class.equals(propertyType)) { // java.lang.String
          property = new IntroCharProperty(name, readMethod, writeMethod, storeAsClient);
        }
        else if (String.class.equals(propertyType)) { // java.lang.String
          property = new IntroStringProperty(name, readMethod, writeMethod, myProject, storeAsClient);
        }
        else if (Insets.class.equals(propertyType)) { // java.awt.Insets
          property = new IntroInsetsProperty(name, readMethod, writeMethod, storeAsClient);
        }
        else if (Dimension.class.equals(propertyType)) { // java.awt.Dimension
          property = new IntroDimensionProperty(name, readMethod, writeMethod, storeAsClient);
        }
        else if (Rectangle.class.equals(propertyType)) { // java.awt.Rectangle
          property = new IntroRectangleProperty(name, readMethod, writeMethod, storeAsClient);
        }
        else if (Component.class.isAssignableFrom(propertyType)) {
          if (JSplitPane.class.isAssignableFrom(aClass) && (name.equals("leftComponent") || name.equals("rightComponent") ||
                                                            name.equals("topComponent") || name.equals("bottomComponent"))) {
            // these properties are set through layout
            continue;
          }
          if (JTabbedPane.class.isAssignableFrom(aClass) && name.equals(SwingProperties.SELECTED_COMPONENT)) {
            // can't set selectedComponent because of set property / add child sequence
            continue;
          }
          if (JMenuBar.class.isAssignableFrom(propertyType) || JPopupMenu.class.isAssignableFrom(propertyType)) {
            // no menu editing yet
            continue;
          }
          Condition<RadComponent> filter = null;
          if (name.equals(SwingProperties.LABEL_FOR)) {
            filter = t -> {
              ComponentItem item = getItem(t.getComponentClassName());
              return item != null && item.isCanAttachLabel();
            };
          }
          property = new IntroComponentProperty(name, readMethod, writeMethod, propertyType, filter, storeAsClient);
        }
        else if (Color.class.equals(propertyType)) {
          property = new IntroColorProperty(name, readMethod, writeMethod, storeAsClient);
        }
        else if (Font.class.equals(propertyType)) {
          property = new IntroFontProperty(name, readMethod, writeMethod, storeAsClient);
        }
        else if (Icon.class.equals(propertyType)) {
          property = new IntroIconProperty(name, readMethod, writeMethod, storeAsClient);
        }
        else if (ListModel.class.isAssignableFrom(propertyType)) {
          property = new IntroListModelProperty(name, readMethod, writeMethod, storeAsClient);
        }
        else if (Enum.class.isAssignableFrom(propertyType)) {
          property = new IntroEnumProperty(name, readMethod, writeMethod, storeAsClient, propertyType);
        }
        else {
          // other types are not supported (yet?)
          continue;
        }

        result.add(property);
      }
    }
    catch (IntrospectionException e) {
      throw new RuntimeException(e);
    }

    IntrospectedProperty<?>[] properties = result.toArray(new IntrospectedProperty[0]);
    myClass2Properties.put(aClass, properties);
    return properties;
  }
public IntrospectedProperty<?> @NotNull [] getIntrospectedProperties(@NotNull Class<?> aClass, @NotNull Class<?> delegeeClass)