diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml
index 2eee35173..cb127831f 100644
--- a/resources/META-INF/plugin.xml
+++ b/resources/META-INF/plugin.xml
@@ -68,6 +68,7 @@
+
@@ -214,6 +215,8 @@
+
+
diff --git a/resources/fileTemplates/internal/Magento Data Model Interface.php.ft b/resources/fileTemplates/internal/Magento Data Model Interface.php.ft
new file mode 100644
index 000000000..78fde3427
--- /dev/null
+++ b/resources/fileTemplates/internal/Magento Data Model Interface.php.ft
@@ -0,0 +1,42 @@
+getData(self::$propertyUpperSnake);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function set$propertyUpperCamel($$propertyLowerCamel)
+ {
+ return $this->setData(self::$propertyUpperSnake, $$propertyLowerCamel);
+ }
+ #end
+ #end
+}
diff --git a/resources/fileTemplates/internal/Magento Data Model.php.html b/resources/fileTemplates/internal/Magento Data Model.php.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/resources/magento2/validation.properties b/resources/magento2/validation.properties
index 77afecd47..d836d2655 100644
--- a/resources/magento2/validation.properties
+++ b/resources/magento2/validation.properties
@@ -29,3 +29,4 @@ validator.mustNotBeEmptyShouldContainLettersOrNumbers=Must not be empty, should
validator.magentoRouteIdInvalid=The route id is invalid
validator.magentoAclResourceIdInvalid=The ACL resource id is invalid
validator.lowercaseCharacters={0} must contain lowercase characters only
+validator.lowerSnakeCase=The {0} field must be of the lower snake case format
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/NewDataModelAction.java b/src/com/magento/idea/magento2plugin/actions/generation/NewDataModelAction.java
new file mode 100644
index 000000000..e15ff1551
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/NewDataModelAction.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation;
+
+import com.intellij.ide.IdeView;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.LangDataKeys;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.magento.idea.magento2plugin.MagentoIcons;
+import com.magento.idea.magento2plugin.actions.generation.dialog.NewDataModelDialog;
+import org.jetbrains.annotations.NotNull;
+
+public class NewDataModelAction extends AnAction {
+ public static final String ACTION_NAME = "Magento 2 Data Model";
+ public static final String ACTION_DESCRIPTION = "Create a new Magento 2 Data Model";
+
+ /**
+ * Constructor.
+ */
+ public NewDataModelAction() {
+ super(ACTION_NAME, ACTION_DESCRIPTION, MagentoIcons.MODULE);
+ }
+
+ @Override
+ public void actionPerformed(@NotNull final AnActionEvent event) {
+ final DataContext dataContext = event.getDataContext();
+
+ final IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext);
+ if (view == null) {
+ return;
+ }
+
+ final Project project = CommonDataKeys.PROJECT.getData(dataContext);
+ if (project == null) {
+ return;
+ }
+
+ final PsiDirectory directory = view.getOrChooseDirectory();
+ if (directory == null) {
+ return;
+ }
+
+ NewDataModelDialog.open(project, directory);
+ }
+
+ @Override
+ public boolean isDumbAware() {
+ return false;
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/data/DataModelData.java b/src/com/magento/idea/magento2plugin/actions/generation/data/DataModelData.java
new file mode 100644
index 000000000..30a288057
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/data/DataModelData.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation.data;
+
+public class DataModelData {
+ private final String namespace;
+ private final String name;
+ private final String moduleName;
+ private final String fqn;
+ private final String interfaceFQN;
+ private final String properties;
+
+ /**
+ * Constructor.
+ */
+ public DataModelData(
+ final String namespace,
+ final String name,
+ final String moduleName,
+ final String fqn,
+ final String interfaceFQN,
+ final String properties
+ ) {
+ this.namespace = namespace;
+ this.name = name;
+ this.moduleName = moduleName;
+ this.fqn = fqn;
+ this.interfaceFQN = interfaceFQN;
+ this.properties = properties;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ public String getFQN() {
+ return fqn;
+ }
+
+ public String getInterfaceFQN() {
+ return interfaceFQN;
+ }
+
+ public String getProperties() {
+ return properties;
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/data/DataModelInterfaceData.java b/src/com/magento/idea/magento2plugin/actions/generation/data/DataModelInterfaceData.java
new file mode 100644
index 000000000..8b7e269b5
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/data/DataModelInterfaceData.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation.data;
+
+public class DataModelInterfaceData {
+ private final String namespace;
+ private final String name;
+ private final String moduleName;
+ private final String fqn;
+ private final String properties;
+
+ /**
+ * Constructor.
+ */
+ public DataModelInterfaceData(
+ final String namespace,
+ final String name,
+ final String moduleName,
+ final String fqn,
+ final String properties
+ ) {
+ this.namespace = namespace;
+ this.name = name;
+ this.moduleName = moduleName;
+ this.fqn = fqn;
+ this.properties = properties;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getModuleName() {
+ return moduleName;
+ }
+
+ public String getFQN() {
+ return fqn;
+ }
+
+ public String getProperties() {
+ return properties;
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/data/code/ClassPropertyData.java b/src/com/magento/idea/magento2plugin/actions/generation/data/code/ClassPropertyData.java
new file mode 100644
index 000000000..9c703dada
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/data/code/ClassPropertyData.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation.data.code;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang.StringUtils;
+
+public class ClassPropertyData {
+ private final List data = new ArrayList<>();
+
+ /**
+ * Constructor.
+ */
+ public ClassPropertyData(
+ final String type,
+ final String lowerCamelName,
+ final String upperCamelName,
+ final String lowerSnakeName,
+ final String upperSnakeName
+ ) {
+ data.add(upperSnakeName);
+ data.add(lowerSnakeName);
+ data.add(type);
+ data.add(upperCamelName);
+ data.add(lowerCamelName);
+ }
+
+ public String string() {
+ return StringUtils.join(data, ";");
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewDataModelDialog.form b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewDataModelDialog.form
new file mode 100644
index 000000000..fc1d36c99
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewDataModelDialog.form
@@ -0,0 +1,132 @@
+
+
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewDataModelDialog.java b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewDataModelDialog.java
new file mode 100644
index 000000000..07296ae35
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/dialog/NewDataModelDialog.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation.dialog;
+
+import com.google.common.base.CaseFormat;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.ComboBoxTableRenderer;
+import com.intellij.psi.PsiDirectory;
+import com.magento.idea.magento2plugin.actions.generation.NewDataModelAction;
+import com.magento.idea.magento2plugin.actions.generation.OverrideClassByAPreferenceAction;
+import com.magento.idea.magento2plugin.actions.generation.data.DataModelData;
+import com.magento.idea.magento2plugin.actions.generation.data.DataModelInterfaceData;
+import com.magento.idea.magento2plugin.actions.generation.data.PreferenceDiXmFileData;
+import com.magento.idea.magento2plugin.actions.generation.data.code.ClassPropertyData;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.FieldValidation;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.annotation.RuleRegistry;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.NotEmptyRule;
+import com.magento.idea.magento2plugin.actions.generation.dialog.validator.rule.PhpClassRule;
+import com.magento.idea.magento2plugin.actions.generation.generator.DataModelGenerator;
+import com.magento.idea.magento2plugin.actions.generation.generator.DataModelInterfaceGenerator;
+import com.magento.idea.magento2plugin.actions.generation.generator.PreferenceDiXmlGenerator;
+import com.magento.idea.magento2plugin.actions.generation.generator.util.NamespaceBuilder;
+import com.magento.idea.magento2plugin.bundles.CommonBundle;
+import com.magento.idea.magento2plugin.bundles.ValidatorBundle;
+import com.magento.idea.magento2plugin.magento.files.DataModel;
+import com.magento.idea.magento2plugin.magento.files.DataModelInterface;
+import com.magento.idea.magento2plugin.ui.table.ComboBoxEditor;
+import com.magento.idea.magento2plugin.ui.table.DeleteRowButton;
+import com.magento.idea.magento2plugin.ui.table.TableButton;
+import com.magento.idea.magento2plugin.util.GetPhpClassByFQN;
+import com.magento.idea.magento2plugin.util.RegExUtil;
+import com.magento.idea.magento2plugin.util.magento.GetModuleNameByDirectoryUtil;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.ArrayList;
+import java.util.List;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableColumn;
+import org.apache.commons.lang.StringUtils;
+
+@SuppressWarnings({
+ "PMD.ExcessiveImports",
+ "PMD.TooManyMethods",
+})
+public class NewDataModelDialog extends AbstractDialog {
+ private final Project project;
+ private final String moduleName;
+ private final ValidatorBundle validatorBundle;
+ private final CommonBundle commonBundle;
+ private final List properties;
+ private NamespaceBuilder interfaceNamespace;
+ private NamespaceBuilder modelNamespace;
+
+ private static final String MODEL_NAME = "Model Name";
+ private static final String PROPERTY_NAME = "Name";
+ private static final String PROPERTY_TYPE = "Type";
+ private static final String PROPERTY_ACTION = "Action";
+ private static final String PROPERTY_DELETE = "Delete";
+
+ private static final String[] PROPERTY_TYPES = {"int", "float", "string", "bool"};
+
+ private JPanel contentPanel;
+ private JButton buttonOK;
+ private JButton buttonCancel;
+ private JTable propertyTable;
+ private JButton addProperty;
+
+ @FieldValidation(rule = RuleRegistry.NOT_EMPTY,
+ message = {NotEmptyRule.MESSAGE, MODEL_NAME})
+ @FieldValidation(rule = RuleRegistry.PHP_CLASS,
+ message = {PhpClassRule.MESSAGE, MODEL_NAME})
+ private JTextField modelName;
+
+ /**
+ * Constructor.
+ */
+ public NewDataModelDialog(final Project project, final PsiDirectory directory) {
+ super();
+
+ this.project = project;
+ this.moduleName = GetModuleNameByDirectoryUtil.execute(directory, project);
+ this.validatorBundle = new ValidatorBundle();
+ this.commonBundle = new CommonBundle();
+ this.properties = new ArrayList<>();
+
+ setContentPane(contentPanel);
+ setModal(true);
+ setTitle(NewDataModelAction.ACTION_DESCRIPTION);
+ getRootPane().setDefaultButton(buttonOK);
+
+ buttonOK.addActionListener((final ActionEvent event) -> onOK());
+ buttonCancel.addActionListener((final ActionEvent event) -> onCancel());
+
+ // call onCancel() on dialog close
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(final WindowEvent event) {
+ onCancel();
+ }
+ });
+
+ initPropertiesTable();
+
+ // call onCancel() on ESCAPE KEY press
+ contentPanel.registerKeyboardAction(
+ (final ActionEvent event) -> onCancel(),
+ KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
+ JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
+ );
+ }
+
+ /**
+ * Opens the dialog window.
+ */
+ public static void open(final Project project, final PsiDirectory directory) {
+ final NewDataModelDialog dialog = new NewDataModelDialog(project, directory);
+ dialog.pack();
+ dialog.centerDialog(dialog);
+ dialog.setVisible(true);
+ }
+
+ private void onOK() {
+ if (validateFormFields()) {
+ buildNamespaces();
+ formatProperties();
+ generateModelInterfaceFile();
+ generateModelFile();
+ generatePreference();
+ this.setVisible(false);
+ }
+ }
+
+ @Override
+ protected boolean validateFormFields() {
+ boolean valid = false;
+ if (super.validateFormFields()) {
+ valid = true;
+ final String errorTitle = commonBundle.message("common.error");
+ final int column = 0;
+ for (int row = 0; row < propertyTable.getRowCount(); row++) {
+ final String propertyName = ((String) propertyTable.getValueAt(row, column)).trim();
+ if (propertyName.isEmpty()) {
+ valid = false;
+ final String errorMessage = validatorBundle.message(
+ "validator.notEmpty", "name"
+ );
+ JOptionPane.showMessageDialog(
+ null,
+ errorMessage,
+ errorTitle,
+ JOptionPane.ERROR_MESSAGE
+ );
+ break;
+ } else if (!propertyName.matches(RegExUtil.LOWER_SNAKE_CASE)) {
+ valid = false;
+ final String errorMessage = validatorBundle.message(
+ "validator.lowerSnakeCase", "name"
+ );
+ JOptionPane.showMessageDialog(
+ null,
+ errorMessage,
+ errorTitle,
+ JOptionPane.ERROR_MESSAGE
+ );
+ break;
+ }
+ }
+ }
+
+ return valid;
+ }
+
+ @Override
+ public void onCancel() {
+ dispose();
+ }
+
+ private void generateModelInterfaceFile() {
+ new DataModelInterfaceGenerator(project, new DataModelInterfaceData(
+ getInterfaceNamespace(),
+ getInterfaceName(),
+ getModuleName(),
+ getInterfaceFQN(),
+ getProperties()
+ )).generate(NewDataModelAction.ACTION_NAME, true);
+ }
+
+ private void generateModelFile() {
+ new DataModelGenerator(project, new DataModelData(
+ getModelNamespace(),
+ getModelName(),
+ getModuleName(),
+ getModelFQN(),
+ getInterfaceFQN(),
+ getProperties()
+ )).generate(NewDataModelAction.ACTION_NAME, true);
+ }
+
+ private void generatePreference() {
+ new PreferenceDiXmlGenerator(new PreferenceDiXmFileData(
+ getModuleName(),
+ GetPhpClassByFQN.getInstance(project).execute(getInterfaceFQN()),
+ getModelFQN(),
+ getModelNamespace(),
+ "base"
+ ), project).generate(OverrideClassByAPreferenceAction.ACTION_NAME);
+ }
+
+ private void buildNamespaces() {
+ interfaceNamespace = new NamespaceBuilder(
+ getModuleName(), getInterfaceName(), DataModelInterface.DIRECTORY
+ );
+ modelNamespace = new NamespaceBuilder(
+ getModuleName(), getModelName(), DataModel.DIRECTORY
+ );
+ }
+
+ /**
+ * Formats properties into an array of ClassPropertyData objects.
+ */
+ private void formatProperties() {
+ final DefaultTableModel propertiesTable = getPropertiesTable();
+ final int rowCount = propertiesTable.getRowCount();
+ String name;
+ String type;
+
+ for (int index = 0; index < rowCount; index++) {
+ name = propertiesTable.getValueAt(index, 0).toString();
+ type = propertiesTable.getValueAt(index, 1).toString();
+ properties.add(new ClassPropertyData(// NOPMD
+ type,
+ CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name),
+ CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name),
+ name,
+ CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, name)
+ ).string());
+ }
+ }
+
+ private String getModuleName() {
+ return moduleName;
+ }
+
+ private String getInterfaceNamespace() {
+ return interfaceNamespace.getNamespace();
+ }
+
+ private String getInterfaceName() {
+ return modelName.getText().trim().concat("Interface");
+ }
+
+ private String getInterfaceFQN() {
+ return interfaceNamespace.getClassFqn();
+ }
+
+ private String getModelNamespace() {
+ return modelNamespace.getNamespace();
+ }
+
+ private String getModelName() {
+ return modelName.getText().trim();
+ }
+
+ private String getModelFQN() {
+ return modelNamespace.getClassFqn();
+ }
+
+ /**
+ * Gets properties as a string, ready for templating.
+ * "UPPER_SNAKE;lower_snake;type;UpperCamel;lowerCamel".
+ */
+ private String getProperties() {
+ return StringUtils.join(properties, ",");
+ }
+
+ private void initPropertiesTable() {
+ final DefaultTableModel propertiesTable = getPropertiesTable();
+ propertiesTable.setDataVector(
+ new Object[][]{},
+ new Object[]{
+ PROPERTY_NAME,
+ PROPERTY_TYPE,
+ PROPERTY_ACTION
+ }
+ );
+
+ final TableColumn column = propertyTable.getColumn(PROPERTY_ACTION);
+ column.setCellRenderer(new TableButton(PROPERTY_DELETE));
+ column.setCellEditor(new DeleteRowButton(new JCheckBox()));
+
+ addProperty.addActionListener(e -> {
+ propertiesTable.addRow(new Object[]{
+ "",
+ PROPERTY_TYPES[0],
+ PROPERTY_DELETE
+ });
+ });
+
+ initPropertyTypeColumn();
+ }
+
+ private void initPropertyTypeColumn() {
+ final TableColumn formElementTypeColumn = propertyTable.getColumn(PROPERTY_TYPE);
+ formElementTypeColumn.setCellEditor(new ComboBoxEditor(PROPERTY_TYPES));
+ formElementTypeColumn.setCellRenderer(new ComboBoxTableRenderer<>(PROPERTY_TYPES));
+ }
+
+ private DefaultTableModel getPropertiesTable() {
+ return (DefaultTableModel) propertyTable.getModel();
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/DataModelGenerator.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/DataModelGenerator.java
new file mode 100644
index 000000000..a80e43d01
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/DataModelGenerator.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation.generator;
+
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.php.lang.psi.PhpFile;
+import com.jetbrains.php.lang.psi.elements.PhpClass;
+import com.magento.idea.magento2plugin.actions.generation.data.DataModelData;
+import com.magento.idea.magento2plugin.actions.generation.generator.util.DirectoryGenerator;
+import com.magento.idea.magento2plugin.actions.generation.generator.util.FileFromTemplateGenerator;
+import com.magento.idea.magento2plugin.actions.generation.generator.util.PhpClassGeneratorUtil;
+import com.magento.idea.magento2plugin.bundles.CommonBundle;
+import com.magento.idea.magento2plugin.bundles.ValidatorBundle;
+import com.magento.idea.magento2plugin.indexes.ModuleIndex;
+import com.magento.idea.magento2plugin.magento.files.DataModel;
+import com.magento.idea.magento2plugin.util.GetFirstClassOfFile;
+import com.magento.idea.magento2plugin.util.GetPhpClassByFQN;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import javax.swing.JOptionPane;
+
+public class DataModelGenerator extends FileGenerator {
+ private final Project project;
+ private final DataModelData modelData;
+ private final DirectoryGenerator directoryGenerator;
+ private final FileFromTemplateGenerator fileFromTemplateGenerator;
+ private final GetFirstClassOfFile getFirstClassOfFile;
+ private final ValidatorBundle validatorBundle;
+ private final CommonBundle commonBundle;
+
+ /**
+ * Constructor.
+ */
+ public DataModelGenerator(final Project project, final DataModelData modelData) {
+ super(project);
+
+ this.project = project;
+ this.modelData = modelData;
+ this.directoryGenerator = DirectoryGenerator.getInstance();
+ this.fileFromTemplateGenerator = FileFromTemplateGenerator.getInstance(project);
+ this.getFirstClassOfFile = GetFirstClassOfFile.getInstance();
+ this.validatorBundle = new ValidatorBundle();
+ this.commonBundle = new CommonBundle();
+ }
+
+ @Override
+ public PsiFile generate(final String actionName) {
+ final PsiFile[] files = new PsiFile[1];
+
+ WriteCommandAction.runWriteCommandAction(project, () -> {
+ PhpClass model = GetPhpClassByFQN.getInstance(project).execute(
+ modelData.getFQN()
+ );
+
+ if (model == null) {
+ model = createModel(actionName);
+
+ if (model == null) {
+ final String errorMessage = this.validatorBundle.message(
+ "validator.file.cantBeCreated",
+ "Data Model"
+ );
+ JOptionPane.showMessageDialog(
+ null,
+ errorMessage,
+ commonBundle.message("common.error"),
+ JOptionPane.ERROR_MESSAGE
+ );
+ } else {
+ files[0] = model.getContainingFile();
+ }
+ } else {
+ final String errorMessage = this.validatorBundle.message(
+ "validator.file.alreadyExists",
+ "Data Model"
+ );
+ JOptionPane.showMessageDialog(
+ null,
+ errorMessage,
+ commonBundle.message("common.error"),
+ JOptionPane.ERROR_MESSAGE
+ );
+ }
+ });
+
+ return files[0];
+ }
+
+ @Override
+ protected void fillAttributes(final Properties attributes) {
+ final List uses = getUses();
+ attributes.setProperty("NAMESPACE", modelData.getNamespace());
+ attributes.setProperty("USES", PhpClassGeneratorUtil.formatUses(uses));
+ attributes.setProperty("NAME", modelData.getName());
+ attributes.setProperty(
+ "EXTENDS",
+ PhpClassGeneratorUtil.getNameFromFqn(DataModel.DATA_OBJECT)
+ );
+ attributes.setProperty(
+ "IMPLEMENTS",
+ PhpClassGeneratorUtil.getNameFromFqn(modelData.getInterfaceFQN())
+ );
+ attributes.setProperty("PROPERTIES", modelData.getProperties());
+ }
+
+ private List getUses() {
+ return Arrays.asList(
+ DataModel.DATA_OBJECT,
+ modelData.getInterfaceFQN()
+ );
+ }
+
+ private PhpClass createModel(final String actionName) {
+ PsiDirectory parentDirectory = ModuleIndex.getInstance(project)
+ .getModuleDirectoryByModuleName(modelData.getModuleName());
+ final PsiFile interfaceFile;
+ final Properties attributes = getAttributes();
+
+ for (final String directory: DataModel.DIRECTORY.split("/")) {
+ parentDirectory = directoryGenerator.findOrCreateSubdirectory(
+ parentDirectory, directory
+ );
+ }
+
+ interfaceFile = fileFromTemplateGenerator.generate(
+ new DataModel(modelData.getName()),
+ attributes,
+ parentDirectory,
+ actionName
+ );
+
+ return interfaceFile == null ? null : getFirstClassOfFile.execute((PhpFile) interfaceFile);
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/DataModelInterfaceGenerator.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/DataModelInterfaceGenerator.java
new file mode 100644
index 000000000..65865d23f
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/DataModelInterfaceGenerator.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.actions.generation.generator;
+
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.php.lang.psi.PhpFile;
+import com.jetbrains.php.lang.psi.elements.PhpClass;
+import com.magento.idea.magento2plugin.actions.generation.data.DataModelInterfaceData;
+import com.magento.idea.magento2plugin.actions.generation.generator.util.DirectoryGenerator;
+import com.magento.idea.magento2plugin.actions.generation.generator.util.FileFromTemplateGenerator;
+import com.magento.idea.magento2plugin.bundles.CommonBundle;
+import com.magento.idea.magento2plugin.bundles.ValidatorBundle;
+import com.magento.idea.magento2plugin.indexes.ModuleIndex;
+import com.magento.idea.magento2plugin.magento.files.DataModelInterface;
+import com.magento.idea.magento2plugin.util.GetFirstClassOfFile;
+import com.magento.idea.magento2plugin.util.GetPhpClassByFQN;
+import java.util.Properties;
+import javax.swing.JOptionPane;
+
+public class DataModelInterfaceGenerator extends FileGenerator {
+ private final Project project;
+ private final DataModelInterfaceData interfaceData;
+ private final DirectoryGenerator directoryGenerator;
+ private final FileFromTemplateGenerator fileFromTemplateGenerator;
+ private final GetFirstClassOfFile getFirstClassOfFile;
+ private final ValidatorBundle validatorBundle;
+ private final CommonBundle commonBundle;
+
+ /**
+ * Constructor.
+ */
+ public DataModelInterfaceGenerator(
+ final Project project,
+ final DataModelInterfaceData interfaceData
+ ) {
+ super(project);
+
+ this.project = project;
+ this.interfaceData = interfaceData;
+ this.directoryGenerator = DirectoryGenerator.getInstance();
+ this.fileFromTemplateGenerator = FileFromTemplateGenerator.getInstance(project);
+ this.getFirstClassOfFile = GetFirstClassOfFile.getInstance();
+ this.validatorBundle = new ValidatorBundle();
+ this.commonBundle = new CommonBundle();
+ }
+
+ @Override
+ public PsiFile generate(final String actionName) {
+ final PsiFile[] files = new PsiFile[1];
+
+ WriteCommandAction.runWriteCommandAction(project, () -> {
+ PhpClass modelInterface = GetPhpClassByFQN.getInstance(project).execute(
+ interfaceData.getFQN()
+ );
+
+ if (modelInterface == null) {
+ modelInterface = createInterface(actionName);
+
+ if (modelInterface == null) {
+ final String errorMessage = this.validatorBundle.message(
+ "validator.file.cantBeCreated",
+ "Data Model Interface"
+ );
+ JOptionPane.showMessageDialog(
+ null,
+ errorMessage,
+ commonBundle.message("common.error"),
+ JOptionPane.ERROR_MESSAGE
+ );
+ } else {
+ files[0] = modelInterface.getContainingFile();
+ }
+ } else {
+ final String errorMessage = this.validatorBundle.message(
+ "validator.file.alreadyExists",
+ "Data Model Interface"
+ );
+ JOptionPane.showMessageDialog(
+ null,
+ errorMessage,
+ commonBundle.message("common.error"),
+ JOptionPane.ERROR_MESSAGE
+ );
+ }
+ });
+
+ return files[0];
+ }
+
+ @Override
+ protected void fillAttributes(final Properties attributes) {
+ attributes.setProperty("NAME", interfaceData.getName());
+ attributes.setProperty("NAMESPACE", interfaceData.getNamespace());
+ attributes.setProperty("PROPERTIES", interfaceData.getProperties());
+ }
+
+ private PhpClass createInterface(final String actionName) {
+ PsiDirectory parentDirectory = ModuleIndex.getInstance(project)
+ .getModuleDirectoryByModuleName(interfaceData.getModuleName());
+ final PsiFile interfaceFile;
+ final Properties attributes = getAttributes();
+
+ for (final String directory: DataModelInterface.DIRECTORY.split("/")) {
+ parentDirectory = directoryGenerator.findOrCreateSubdirectory(
+ parentDirectory, directory
+ );
+ }
+
+ interfaceFile = fileFromTemplateGenerator.generate(
+ new DataModelInterface(interfaceData.getName()),
+ attributes,
+ parentDirectory,
+ actionName
+ );
+
+ return interfaceFile == null ? null : getFirstClassOfFile.execute((PhpFile) interfaceFile);
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/actions/generation/generator/util/PhpClassGeneratorUtil.java b/src/com/magento/idea/magento2plugin/actions/generation/generator/util/PhpClassGeneratorUtil.java
index 2ad8ab5ed..40677f142 100644
--- a/src/com/magento/idea/magento2plugin/actions/generation/generator/util/PhpClassGeneratorUtil.java
+++ b/src/com/magento/idea/magento2plugin/actions/generation/generator/util/PhpClassGeneratorUtil.java
@@ -13,7 +13,7 @@ public final class PhpClassGeneratorUtil {
private PhpClassGeneratorUtil() {}
/**
- * Format PHP class uses.
+ * Formats PHP class uses.
*
* @param uses List
* @return String
@@ -25,7 +25,7 @@ public static String formatUses(final List uses) {
}
/**
- * Fetches class name from a fully qualified name.
+ * Fetches the class name from a fully qualified name.
*
* @param fqn FQN
* @return String
diff --git a/src/com/magento/idea/magento2plugin/magento/files/DataModel.java b/src/com/magento/idea/magento2plugin/magento/files/DataModel.java
new file mode 100644
index 000000000..5158f55f4
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/magento/files/DataModel.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.magento.files;
+
+import com.intellij.lang.Language;
+import com.jetbrains.php.lang.PhpLanguage;
+
+public class DataModel implements ModuleFileInterface {
+ public static final String DIRECTORY = "Model/Data";
+ public static final String DATA_OBJECT = "Magento\\Framework\\DataObject";
+ private final String className;
+
+ public DataModel(final String className) {
+ this.className = className.concat(".php");
+ }
+
+ @Override
+ public String getFileName() {
+ return className;
+ }
+
+ @Override
+ public String getTemplate() {
+ return "Magento Data Model";
+ }
+
+ @Override
+ public Language getLanguage() {
+ return PhpLanguage.INSTANCE;
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/magento/files/DataModelInterface.java b/src/com/magento/idea/magento2plugin/magento/files/DataModelInterface.java
new file mode 100644
index 000000000..8b79cda6d
--- /dev/null
+++ b/src/com/magento/idea/magento2plugin/magento/files/DataModelInterface.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+package com.magento.idea.magento2plugin.magento.files;
+
+import com.intellij.lang.Language;
+import com.jetbrains.php.lang.PhpLanguage;
+
+public class DataModelInterface implements ModuleFileInterface {
+ public static final String DIRECTORY = "Api/Data";
+ private final String className;
+
+ public DataModelInterface(final String className) {
+ this.className = className.concat(".php");
+ }
+
+ @Override
+ public String getFileName() {
+ return className;
+ }
+
+ @Override
+ public String getTemplate() {
+ return "Magento Data Model Interface";
+ }
+
+ @Override
+ public Language getLanguage() {
+ return PhpLanguage.INSTANCE;
+ }
+}
diff --git a/src/com/magento/idea/magento2plugin/util/RegExUtil.java b/src/com/magento/idea/magento2plugin/util/RegExUtil.java
index 66a1e7036..2ca243d6a 100644
--- a/src/com/magento/idea/magento2plugin/util/RegExUtil.java
+++ b/src/com/magento/idea/magento2plugin/util/RegExUtil.java
@@ -22,6 +22,9 @@ public class RegExUtil {
public static final String IDENTIFIER
= "[a-zA-Z0-9_\\-]*";
+ public static final String LOWER_SNAKE_CASE
+ = "[a-z][a-z0-9_]*";
+
public static final String CLI_COMMAND_NAME
= "[a-zA-Z0-9_\\-\\:]*";
diff --git a/testData/actions/generation/generator/DataModelGenerator/generateDataModel/Sample.php b/testData/actions/generation/generator/DataModelGenerator/generateDataModel/Sample.php
new file mode 100644
index 000000000..5d677058b
--- /dev/null
+++ b/testData/actions/generation/generator/DataModelGenerator/generateDataModel/Sample.php
@@ -0,0 +1,25 @@
+getData(self::SAMPLE_PROPERTY);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setSampleProperty($sampleProperty)
+ {
+ return $this->setData(self::SAMPLE_PROPERTY, $sampleProperty);
+ }
+}
diff --git a/testData/actions/generation/generator/DataModelInterfaceGenerator/generateDataModelInterface/SampleInterface.php b/testData/actions/generation/generator/DataModelInterfaceGenerator/generateDataModelInterface/SampleInterface.php
new file mode 100644
index 000000000..03ca695c1
--- /dev/null
+++ b/testData/actions/generation/generator/DataModelInterfaceGenerator/generateDataModelInterface/SampleInterface.php
@@ -0,0 +1,22 @@
+