@Inject
  protected PropertyWidgetFactory(
      AbstractBeanJobBuilder<?, ?, ?> beanJobBuilder, InjectorBuilder injectorBuilder) {
    _beanJobBuilder = beanJobBuilder;
    _injectorBuilder = injectorBuilder;
    _propertyWidgetMappings =
        new IdentityHashMap<ConfiguredPropertyDescriptor, PropertyWidgetMapping>();

    Set<ConfiguredPropertyDescriptor> mappedProperties =
        beanJobBuilder.getDescriptor().getConfiguredPropertiesByAnnotation(MappedProperty.class);
    for (ConfiguredPropertyDescriptor mappedProperty : mappedProperties) {
      MappedProperty annotation = mappedProperty.getAnnotation(MappedProperty.class);
      String mappedToName = annotation.value();
      ConfiguredPropertyDescriptor mappedToProperty =
          beanJobBuilder.getDescriptor().getConfiguredProperty(mappedToName);

      PropertyWidgetMapping propertyWidgetMapping =
          buildMappedPropertyWidget(mappedProperty, mappedToProperty);

      _propertyWidgetMappings.put(mappedProperty, propertyWidgetMapping);
      _propertyWidgetMappings.put(mappedToProperty, propertyWidgetMapping);
    }
  }
  protected PropertyWidgetMapping buildMappedPropertyWidget(
      ConfiguredPropertyDescriptor mappedProperty, ConfiguredPropertyDescriptor mappedToProperty) {
    if (mappedProperty.isArray()
        && mappedToProperty.isArray()
        && mappedToProperty.isInputColumn()) {
      // mapped strings
      if (mappedProperty.getBaseType() == String.class) {
        final MultipleMappedStringsPropertyWidget propertyWidget =
            new MultipleMappedStringsPropertyWidget(
                getBeanJobBuilder(), mappedToProperty, mappedProperty);
        final PropertyWidgetMapping mapping = new PropertyWidgetMapping();
        mapping.putMapping(mappedProperty, propertyWidget);
        mapping.putMapping(mappedToProperty, propertyWidget.getMappedStringsPropertyWidget());
        return mapping;
      }

      // mapped enums
      if (mappedProperty.getBaseType().isEnum()) {
        final MultipleMappedEnumsPropertyWidget<Enum<?>> propertyWidget =
            new MultipleMappedEnumsPropertyWidget<Enum<?>>(
                getBeanJobBuilder(), mappedToProperty, mappedProperty);
        final PropertyWidgetMapping mapping = new PropertyWidgetMapping();
        mapping.putMapping(mappedProperty, propertyWidget);
        mapping.putMapping(mappedToProperty, propertyWidget.getMappedEnumsPropertyWidget());
        return mapping;
      }
    }

    // schema structure mapping
    if (mappedProperty.getBaseType() == String.class && !mappedToProperty.isArray()) {
      // save the "mappedToPropertyWidget" since it may be need to be
      // reused when there is a chain of dependencies between mapped
      // properties
      final PropertyWidgetMapping propertyWidgetMapping =
          _propertyWidgetMappings.get(mappedToProperty);
      final PropertyWidget<?> mappedToPropertyWidget;
      if (propertyWidgetMapping == null) {
        mappedToPropertyWidget = null;
      } else {
        mappedToPropertyWidget = propertyWidgetMapping.getMapping(mappedToProperty);
      }

      // mapped schema name
      if (mappedProperty.getAnnotation(SchemaProperty.class) != null
          && (mappedToProperty.getBaseType() == Datastore.class
              || mappedToProperty.getBaseType() == UpdateableDatastore.class)) {
        final SchemaNamePropertyWidget schemaPropertyWidget =
            new SchemaNamePropertyWidget(getBeanJobBuilder(), mappedProperty);
        final SingleDatastorePropertyWidget datastorePropertyWidget;
        if (mappedToPropertyWidget == null) {
          final DatastoreCatalog datastoreCatalog =
              getBeanJobBuilder().getAnalysisJobBuilder().getConfiguration().getDatastoreCatalog();
          datastorePropertyWidget =
              new SingleDatastorePropertyWidget(
                  getBeanJobBuilder(), mappedToProperty, datastoreCatalog);
        } else {
          datastorePropertyWidget = (SingleDatastorePropertyWidget) mappedToPropertyWidget;
        }

        datastorePropertyWidget.addComboListener(
            new DCComboBox.Listener<Datastore>() {
              @Override
              public void onItemSelected(Datastore item) {
                schemaPropertyWidget.setDatastore(item);
              }
            });

        final PropertyWidgetMapping mapping = new PropertyWidgetMapping();
        mapping.putMapping(mappedProperty, schemaPropertyWidget);
        mapping.putMapping(mappedToProperty, datastorePropertyWidget);
        return mapping;
      }

      // mapped table name
      if (mappedProperty.getAnnotation(TableProperty.class) != null
          && mappedToProperty.getAnnotation(SchemaProperty.class) != null) {

        final TableNamePropertyWidget tablePropertyWidget =
            new TableNamePropertyWidget(getBeanJobBuilder(), mappedProperty);
        final SchemaNamePropertyWidget schemaPropertyWidget;
        if (mappedToPropertyWidget == null) {
          schemaPropertyWidget =
              new SchemaNamePropertyWidget(getBeanJobBuilder(), mappedToProperty);
        } else {
          schemaPropertyWidget = (SchemaNamePropertyWidget) mappedToPropertyWidget;
        }

        schemaPropertyWidget.addComboListener(
            new DCComboBox.Listener<Schema>() {
              @Override
              public void onItemSelected(Schema item) {
                tablePropertyWidget.setSchema(item);
              }
            });

        final PropertyWidgetMapping mapping = new PropertyWidgetMapping();
        mapping.putMapping(mappedProperty, tablePropertyWidget);
        mapping.putMapping(mappedToProperty, schemaPropertyWidget);
        return mapping;
      }

      // mapped column name(s)
      if (mappedProperty.getAnnotation(ColumnProperty.class) != null
          && mappedToProperty.getAnnotation(TableProperty.class) != null) {

        final TableNamePropertyWidget tablePropertyWidget;
        if (mappedToPropertyWidget == null) {
          tablePropertyWidget = new TableNamePropertyWidget(getBeanJobBuilder(), mappedToProperty);
        } else {
          tablePropertyWidget = (TableNamePropertyWidget) mappedToPropertyWidget;
        }

        if (mappedProperty.isArray()) {
          // multiple mapped column names

          // TODO: Not yet implemented. This case needs to take care
          // of the fact that usually this is then ALSO mapped to an
          // array of input columns.
        } else {
          // mapped column name

          final ColumnNamePropertyWidget columnPropertyWidget =
              new ColumnNamePropertyWidget(mappedProperty, getBeanJobBuilder());
          tablePropertyWidget.addComboListener(
              new DCComboBox.Listener<Table>() {
                @Override
                public void onItemSelected(Table item) {
                  columnPropertyWidget.setTable(item);
                }
              });

          final PropertyWidgetMapping mapping = new PropertyWidgetMapping();
          mapping.putMapping(mappedProperty, columnPropertyWidget);
          mapping.putMapping(mappedToProperty, tablePropertyWidget);
          return mapping;
        }
      }
    }

    return null;
  }
  @Inject
  public SingleResourcePropertyWidget(
      ConfiguredPropertyDescriptor propertyDescriptor,
      AbstractBeanJobBuilder<?, ?, ?> beanJobBuilder,
      UserPreferences userPreferences) {
    super(beanJobBuilder, propertyDescriptor);
    _userPreferences = userPreferences;

    boolean openFileDialog = true;
    String[] extensions = null;

    FileProperty fileProperty = propertyDescriptor.getAnnotation(FileProperty.class);
    if (fileProperty != null) {
      openFileDialog = fileProperty.accessMode() == FileAccessMode.OPEN;

      extensions = fileProperty.extension();
    }

    _resourceTypeComboBox =
        new DCComboBox<String>(new String[] {"file", "url", "classpath", "vfs"});
    _filenameField =
        new FilenameTextField(_userPreferences.getConfiguredFileDirectory(), openFileDialog);
    _otherPathTextField = WidgetFactory.createTextField();

    if (extensions != null && extensions.length > 0) {
      List<FileFilter> filters = new ArrayList<FileFilter>(extensions.length);
      for (String extension : extensions) {
        String extensionWithDot;
        if (extension.startsWith(".")) {
          extensionWithDot = extension;
        } else {
          extensionWithDot = "." + extension;
        }
        FileFilter filter =
            new ExtensionFilter(extension.toUpperCase() + " file", extensionWithDot);
        filters.add(filter);
        _filenameField.addChoosableFileFilter(filter);
      }
      if (filters.size() == 1) {
        _filenameField.setSelectedFileFilter(filters.get(0));
      } else {
        FileFilter filter =
            FileFilters.combined(
                "All suggested file formats", filters.toArray(new FileFilter[filters.size()]));
        _filenameField.setSelectedFileFilter(filter);
      }
    } else {
      _filenameField.setSelectedFileFilter(FileFilters.ALL);
    }

    final Resource currentValue = getCurrentValue();
    if (currentValue == null) {
      _otherPathTextField.setVisible(false);
      _immutableValue = null;
    } else if (currentValue instanceof FileResource) {
      _otherPathTextField.setVisible(false);
      _filenameField.setFile(((FileResource) currentValue).getFile());
      _immutableValue = null;
    } else if (currentValue instanceof UrlResource
        || currentValue instanceof VfsResource
        || currentValue instanceof ClasspathResource) {
      _filenameField.setVisible(false);
      _immutableValue = null;
    } else {
      _filenameField.setVisible(false);
      _immutableValue = currentValue;
    }

    if (_immutableValue == null) {
      _filenameField
          .getTextField()
          .getDocument()
          .addDocumentListener(
              new DCDocumentListener() {
                @Override
                protected void onChange(DocumentEvent e) {
                  fireValueChanged();
                }
              });

      _filenameField.addFileSelectionListener(
          new FileSelectionListener() {
            @Override
            public void onSelected(FilenameTextField filenameTextField, File file) {
              File dir = file.getParentFile();
              _userPreferences.setConfiguredFileDirectory(dir);
              fireValueChanged();
            }
          });

      _otherPathTextField
          .getDocument()
          .addDocumentListener(
              new DCDocumentListener() {
                @Override
                protected void onChange(DocumentEvent event) {
                  fireValueChanged();
                }
              });
      _resourceTypeComboBox.addListener(
          new Listener<String>() {
            @Override
            public void onItemSelected(String item) {
              boolean isFileMode = "file".equals(item);
              _filenameField.setVisible(isFileMode);
              _otherPathTextField.setVisible(!isFileMode);

              fireValueChanged();
            }
          });

      final DCPanel panel =
          DCPanel.flow(
              Alignment.LEFT, 0, 0, _resourceTypeComboBox, _filenameField, _otherPathTextField);
      add(panel);
    } else {
      add(DCLabel.dark("- Resource: " + _immutableValue.getName() + " -"));
    }
  }