├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── build.gradle ├── code-of-conduct.md ├── contributing.md ├── gradle.properties ├── license.md ├── readme.md ├── settings.gradle └── src └── main ├── java └── ore │ └── plugins │ └── idea │ ├── lib │ ├── action │ │ └── OrePluginAction.java │ ├── dialog │ │ ├── InputDialog.java │ │ ├── SelectStuffDialog.java │ │ └── base │ │ │ └── OrePluginDialog.java │ ├── exception │ │ ├── CancelException.java │ │ ├── InvalidFileException.java │ │ ├── InvalidStructureException.java │ │ ├── ValidationException.java │ │ └── base │ │ │ ├── ExceptionResolver.java │ │ │ ├── OrePluginException.java │ │ │ └── OrePluginRuntimeException.java │ ├── model │ │ ├── pom │ │ │ ├── PsiBuild.java │ │ │ ├── PsiDependency.java │ │ │ ├── PsiParent.java │ │ │ ├── PsiPlugin.java │ │ │ └── PsiPom.java │ │ └── ui │ │ │ └── NameListCelRenderer.java │ ├── provider │ │ ├── ConstructorProvider.java │ │ └── TemplateProvider.java │ ├── service │ │ ├── JavaCodeGenerator.java │ │ └── base │ │ │ └── OrePluginGenerator.java │ └── utils │ │ └── FormatUtils.java │ └── swip │ ├── action │ └── SwipAction.java │ ├── dialog │ └── PackageInputDialog.java │ ├── model │ └── SwipRequest.java │ └── service │ ├── ControllerGenerator.java │ ├── FreemarkerGenerator.java │ ├── RepositoryGenerator.java │ ├── ResourcePersistableGenerator.java │ ├── ServiceGenerator.java │ └── base │ └── SwipJavaCodeGenerator.java └── resources ├── META-INF └── plugin.xml └── templates ├── freemarker ├── base-resource-view ├── edit-resource-view ├── section │ ├── resource-form-field │ ├── resource-table-body-field │ └── resource-table-head-field └── swip-styles.css └── maven └── spring-web-initializr-dependency /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] - The '...' does not work as expected" 5 | labels: '' 6 | assignees: OrPolyzos 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FR] - The specific title" 5 | labels: '' 6 | assignees: OrPolyzos 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### IntelliJ IDEA ### 2 | .idea 3 | *.iml 4 | *.iws 5 | *.ipr 6 | *.jar 7 | 8 | ### Gradle ### 9 | .gradle 10 | gradle/ 11 | gradlew 12 | gradlew.bat 13 | /build/ 14 | gradle.properties -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | maven { 5 | url "https://oss.sonatype.org/content/repositories/snapshots/" 6 | } 7 | maven { 8 | url 'https://dl.bintray.com/jetbrains/intellij-plugin-service' 9 | } 10 | 11 | } 12 | dependencies { 13 | classpath "org.jetbrains.intellij.plugins:gradle-intellij-plugin:0.5.0-SNAPSHOT" 14 | } 15 | } 16 | 17 | 18 | plugins { 19 | id 'java' 20 | id "org.jetbrains.intellij" version "0.4.9" 21 | } 22 | 23 | apply plugin: 'java' 24 | apply plugin: 'org.jetbrains.intellij' 25 | 26 | group 'ore.plugins.idea' 27 | version '1.1.2' 28 | 29 | sourceCompatibility = javaVersion 30 | targetCompatibility = javaVersion 31 | 32 | tasks.withType(JavaCompile) { 33 | options.encoding = projectEncoding 34 | } 35 | 36 | intellij { 37 | type 'IU' 38 | plugins = ['java'] 39 | version '2019.3' 40 | updateSinceUntilBuild false 41 | } 42 | 43 | publishPlugin { 44 | token publishToken 45 | channels publishChannels.split(',') 46 | host publishHost 47 | } 48 | 49 | repositories { 50 | mavenLocal() 51 | mavenCentral() 52 | } 53 | 54 | dependencies { 55 | compile(group: 'io.github.orpolyzos', name: 'spring-web-initializr', version: '1.1.0') { 56 | exclude(group: 'org.slf4j') 57 | } 58 | compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: '2.9.8' 59 | testCompile group: 'junit', name: 'junit', version: '4.12' 60 | } 61 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at orestes.polyzos@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the readme.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the readme.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of a project maintainer, or if you 17 | do not have permission to do that, you may request one of the the project maintainers to merge it for you. 18 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Building 2 | javaVersion=1.8 3 | projectEncoding=UTF-8 4 | 5 | # Publishing 6 | publishToken= 7 | publishChannels=Stable 8 | publishHost=http://plugins.jetbrains.com/ 9 | 10 | # Proxy 11 | #org.gradle.jvmargs=-Dhttp.proxyHost=172.25.1.1 -Dhttp.proxyPort=8080 -Dhttps.proxyHost=172.25.1.1 -Dhttps.proxyPort=8080 -DsocksproxyHost=172.25.1.1 -DsocksproxyPort=8080 12 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Orestes Polyzos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Swip *(Spring Web Initializr Plugin)* 2 | * Create a fully functional (Spring Boot) WebApp with just a few clicks 3 | * Reduce the boilerplate code 4 | 5 | ## Demonstration 6 | ![Demo gif](/../screenshots/swip-demo.gif?raw=true) 7 | 8 | ## Download 9 | Get it through IntelliJ IDEA by writing *Swip* (Spring Web Initializr Plugin) or else through the [Jetbrains Repo](https://plugins.jetbrains.com/plugin/12239-swip-spring-web-initializr) 10 | 11 | ## How To 12 | 1) Create a Java class for your entity (e.g. User) with all its' desired fields (e.g. firstName, lastName, etc...) 13 | 2) Right click inside the class to get the editor menu 14 | 3) Click the `Swip` option and follow the instructions 15 | 16 | ## TL;DR 17 | Below are some samples to get you started. 18 |
19 | pom.xml 20 | 21 | ```xml 22 | 23 | 25 | 4.0.0 26 | 27 | ore.utils.initializrs 28 | swip-demo 29 | 0.0.1-SNAPSHOT 30 | jar 31 | 32 | Swip Demo 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-parent 37 | 1.5.7.RELEASE 38 | 39 | 40 | 41 | 42 | UTF-8 43 | UTF-8 44 | 1.8 45 | 46 | 47 | 48 | 49 | 50 | io.github.orpolyzos 51 | spring-web-initializr 52 | 1.1.0 53 | 54 | 55 | com.h2database 56 | h2 57 | runtime 58 | 59 | 60 | 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-maven-plugin 66 | 67 | 68 | 69 | 70 | 71 | ``` 72 |
73 | 74 |
75 | User.java (sample pojo) 76 | 77 | ```java 78 | package ore.swip.demo.domain; 79 | 80 | import javax.persistence.*; 81 | import java.util.List; 82 | 83 | @Entity(name = "user") 84 | public class User { 85 | 86 | @Id 87 | @Column(name = "id", nullable = false) 88 | @GeneratedValue(strategy = GenerationType.IDENTITY) 89 | private Long id; 90 | 91 | @Column(name = "first_name", nullable = false) 92 | private String firstName; 93 | 94 | @Column(name = "last_name", nullable = false) 95 | private String lastName; 96 | 97 | @Column(name = "password", nullable = false) 98 | private String password; 99 | 100 | @Column(name = "email", nullable = false, unique = true) 101 | private String email; 102 | 103 | public Long getId() { 104 | return id; 105 | } 106 | 107 | public void setId(Long id) { 108 | this.id = id; 109 | } 110 | 111 | public String getFirstName() { 112 | return firstName; 113 | } 114 | 115 | public void setFirstName(String firstName) { 116 | this.firstName = firstName; 117 | } 118 | 119 | public String getLastName() { 120 | return lastName; 121 | } 122 | 123 | public void setLastName(String lastName) { 124 | this.lastName = lastName; 125 | } 126 | 127 | public String getPassword() { 128 | return password; 129 | } 130 | 131 | public void setPassword(String password) { 132 | this.password = password; 133 | } 134 | 135 | public String getEmail() { 136 | return email; 137 | } 138 | 139 | public void setEmail(String email) { 140 | this.email = email; 141 | } 142 | 143 | } 144 | 145 | ``` 146 |
147 | 148 | ## Prerequisites 149 | 150 | __spring-web-initializr__ (https://github.com/OrPolyzos/spring-web-initializr) 151 | 152 | In order to avoid duplicate code a separate library has been developed, that is being used by Swip. 153 | As such, the following dependency is mandatory and should be added to your pom.xml 154 | ```xml 155 | 156 | io.github.orpolyzos 157 | spring-web-initializr 158 | 1.1.0 159 | 160 | ``` 161 | 162 | ## Release History 163 | * 1.1.0 164 | * Adds support for spring-boot-starter-parent from version '1.5.20.RELEASE' up to LATEST 165 | * Adds support for older IntelliJ IDEA since build '171.4424.54' 166 | 167 | * 1.0.0 - First Release 168 | * Adds support for Maven 169 | * Adds support for spring-boot-starter-web 170 | * Adds support for spring-boot-starter-data-jpa 171 | * Adds support for spring-boot-starter-freemarker 172 | 173 | ## Description 174 | Swip will generate all the code required for a functional WebApp based on your domain classes. 175 | It is based on the __convention over configuration__ paradigm. 176 | During the generation phase, the actual configuration is limited, but after the files have been generated they can obviously be configured/changed as needed. 177 | 178 | Obviously Swip is not going to meet your exact business requirements all the times and tweaking may be required, but it is going to provide you with the base stuff to get you started faster. 179 | 180 | ### Front end (only Freemarker is supported at the moment) 181 | In any web application the end user should be in place to perform the basic CRUD operations through the provided screens for each Entity (e.g. User) 182 | 183 | * Page #1 - Create Read Delete
184 | * Form that provides the required fields to save an Resource (e.g. UserForm) 185 | * Form that provides the required fields to search for a Resource (e.g. UserSearchForm) 186 | * Table that provides the fields of retrieved Entities as well as the option to Delete/Edit a Resource 187 | 188 | * Page #2 - Edit
189 | * Form that provides the required fields to update a Resource (e.g. UserForm) 190 | 191 | Apart from the specific fields of each Entity, all the pages are actually the exact same thing. 192 | 193 | ### ResourceController 194 | Based on the provided front end implementation, the ResourceController should be able to: 195 | * getBaseResourceView() -> serves Page #1 196 | * createResource() -> creates a Resource and serves Page #1 197 | * searchBy() -> searches for Resources (optionally based on a SearchForm) and serves Page #1 filled with the found list of Resources 198 | * deleteResource() -> deletes a Resource and serves Page #1 199 | * getEditResource() -> searches for a specific Resource and serves Page #2 filled with its' fields 200 | * editResource() -> updates a Resource and serves Page #1 201 | 202 | ### ResourceService 203 | Based on the provided ResourceController, the ResourceService should be able to: 204 | * find(ID) -> searches a Resource by its' ID and returns it 205 | * findOptional(ID) -> searches a Resource by its' ID and returns an Optional 206 | * findOrThrow(ID) -> searches a Resource by its' ID and returns it or throws a ResourceNotFoundException 207 | * findAll() -> searches for all Resources and returns an Iterable 208 | * insert(Resource) -> searches for duplicate Resources and throws a DuplicateResourceException if found, or else saves the Resource 209 | * update(Resource) -> searches for the specific Resource and updates it if found, or else throws a ResourceNotFoundException 210 | * searchBy(ResourceSearchForm) -> searches for Resources based on a ResourceSearchForm and returns an Iterable (by default returns findAll()) 211 | 212 | 213 | ## Authors 214 | * [**Orestes Polyzos**](https://github.com/OrPolyzos) - *Initial work* 215 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'swip' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/action/OrePluginAction.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.action; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | import com.intellij.openapi.actionSystem.AnAction; 6 | import com.intellij.openapi.actionSystem.AnActionEvent; 7 | import com.intellij.openapi.actionSystem.LangDataKeys; 8 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 9 | import com.intellij.openapi.editor.Editor; 10 | import com.intellij.psi.PsiClass; 11 | import com.intellij.psi.PsiElement; 12 | import com.intellij.psi.PsiFile; 13 | import com.intellij.psi.util.PsiTreeUtil; 14 | import ore.plugins.idea.lib.exception.InvalidFileException; 15 | import ore.plugins.idea.lib.exception.base.ExceptionResolver; 16 | import ore.plugins.idea.lib.provider.TemplateProvider; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | public abstract class OrePluginAction extends AnAction implements ExceptionResolver, TemplateProvider { 20 | 21 | private static final String JAVA_EXTENSION = "java"; 22 | 23 | @Override 24 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 25 | safeExecute(() -> safeActionPerformed(anActionEvent), anActionEvent); 26 | } 27 | 28 | public abstract void safeActionPerformed(AnActionEvent anActionEvent); 29 | 30 | @Override 31 | public void update(@NotNull AnActionEvent anActionEvent) { 32 | safeExecute(() -> { 33 | extractPsiClass(anActionEvent); 34 | anActionEvent.getPresentation().setEnabled(true); 35 | }, anActionEvent); 36 | } 37 | 38 | protected PsiClass extractPsiClass(@NotNull AnActionEvent anActionEvent) { 39 | PsiFile psiFile = anActionEvent.getData(LangDataKeys.PSI_FILE); 40 | validatePsiFile(psiFile, anActionEvent); 41 | 42 | Editor editor = anActionEvent.getData(PlatformDataKeys.EDITOR); 43 | validateForNull(editor, anActionEvent); 44 | 45 | PsiElement elementAt = requireNonNull(psiFile).findElementAt(requireNonNull(editor).getCaretModel().getOffset()); 46 | validateForNull(elementAt, anActionEvent); 47 | 48 | PsiClass psiClass = PsiTreeUtil.getParentOfType(elementAt, PsiClass.class); 49 | validateForNull(psiClass, anActionEvent); 50 | return psiClass; 51 | } 52 | 53 | private void validatePsiFile(PsiFile psiFile, AnActionEvent anActionEvent) { 54 | validateForNull(psiFile, anActionEvent); 55 | if (!psiFile.getFileType().getDefaultExtension().equals(JAVA_EXTENSION)) { 56 | disableContextMenu(anActionEvent); 57 | } 58 | } 59 | 60 | private void validateForNull(Object object, AnActionEvent anActionEvent) { 61 | if (object == null) { 62 | disableContextMenu(anActionEvent); 63 | } 64 | } 65 | 66 | private void disableContextMenu(AnActionEvent anActionEvent) { 67 | anActionEvent.getPresentation().setEnabled(false); 68 | throw new InvalidFileException(); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/dialog/InputDialog.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.dialog; 2 | 3 | import com.intellij.openapi.ui.LabeledComponent; 4 | import com.intellij.psi.PsiClass; 5 | import com.intellij.ui.components.JBTextField; 6 | import ore.plugins.idea.lib.dialog.base.OrePluginDialog; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import javax.swing.*; 10 | 11 | public class InputDialog extends OrePluginDialog { 12 | 13 | private final LabeledComponent component; 14 | private JBTextField jbTextField; 15 | 16 | public InputDialog(PsiClass psiClass, String title, String componentText) { 17 | super(psiClass.getProject()); 18 | setTitle(title); 19 | JPanel jPanel = new JPanel(); 20 | jbTextField = new JBTextField(); 21 | jbTextField.setEditable(true); 22 | jbTextField.setFocusable(true); 23 | jbTextField.setColumns(40); 24 | jPanel.add(jbTextField); 25 | component = LabeledComponent.create(jPanel, componentText); 26 | showDialog(); 27 | } 28 | 29 | @Nullable 30 | @Override 31 | protected JComponent createCenterPanel() { 32 | return component; 33 | } 34 | 35 | public String getInput() { 36 | return jbTextField.getText().trim(); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/dialog/SelectStuffDialog.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.dialog; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.LabeledComponent; 5 | import com.intellij.ui.CollectionListModel; 6 | import com.intellij.ui.ToolbarDecorator; 7 | import com.intellij.ui.components.JBList; 8 | import ore.plugins.idea.lib.dialog.base.OrePluginDialog; 9 | 10 | import javax.swing.*; 11 | import java.util.Collection; 12 | import java.util.List; 13 | 14 | public class SelectStuffDialog extends OrePluginDialog { 15 | 16 | private String title; 17 | private String message; 18 | private Collection optionsList; 19 | private int listSelectionModel; 20 | private ListCellRenderer listCellRenderer; 21 | 22 | private LabeledComponent labeledComponent; 23 | private JBList optionsJbList; 24 | 25 | public SelectStuffDialog(Project project, String title, String message, Collection optionsList, int listSelectionModel) { 26 | super(project); 27 | setCommonFields(title, message, optionsList, listSelectionModel); 28 | setup(); 29 | showDialog(); 30 | } 31 | 32 | private void setCommonFields(String title, String message, Collection optionsList, int listSelectionModel) { 33 | this.title = title; 34 | this.message = message; 35 | this.optionsList = optionsList; 36 | this.listSelectionModel = listSelectionModel; 37 | } 38 | 39 | public SelectStuffDialog(Project project, String title, String message, Collection optionsList, int listSelectionModel, ListCellRenderer listCellRenderer) { 40 | super(project); 41 | setCommonFields(title, message, optionsList, listSelectionModel); 42 | this.listCellRenderer = listCellRenderer; 43 | setup(); 44 | showDialog(); 45 | } 46 | 47 | 48 | private void setup() { 49 | setTitle(title); 50 | setupOptionsJbList(); 51 | setupLabeledComponent(); 52 | } 53 | 54 | private void setupOptionsJbList() { 55 | optionsJbList = new JBList(new CollectionListModel<>(optionsList)); 56 | optionsJbList.setSelectionMode(listSelectionModel); 57 | if (listCellRenderer != null) { 58 | optionsJbList.setCellRenderer(listCellRenderer); 59 | } 60 | } 61 | 62 | @Override 63 | protected JComponent createCenterPanel() { 64 | return labeledComponent; 65 | } 66 | 67 | 68 | private void setupLabeledComponent() { 69 | ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(optionsJbList); 70 | toolbarDecorator.disableAddAction(); 71 | toolbarDecorator.disableRemoveAction(); 72 | labeledComponent = LabeledComponent.create(toolbarDecorator.createPanel(), message); 73 | } 74 | 75 | @SuppressWarnings("unchecked") 76 | public List getSelectedStuff() { 77 | return optionsJbList.getSelectedValuesList(); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/dialog/base/OrePluginDialog.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.dialog.base; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import ore.plugins.idea.lib.exception.CancelException; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public abstract class OrePluginDialog extends DialogWrapper { 9 | 10 | protected OrePluginDialog(@Nullable Project project) { 11 | super(project); 12 | } 13 | 14 | public void showDialog() { 15 | init(); 16 | show(); 17 | } 18 | 19 | public void waitForInput() { 20 | if (super.isOK()) { 21 | return; 22 | } 23 | throw new CancelException(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/exception/CancelException.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.exception; 2 | 3 | import ore.plugins.idea.lib.exception.base.OrePluginRuntimeException; 4 | 5 | public class CancelException extends OrePluginRuntimeException { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/exception/InvalidFileException.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.exception; 2 | 3 | 4 | import ore.plugins.idea.lib.exception.base.OrePluginRuntimeException; 5 | 6 | public class InvalidFileException extends OrePluginRuntimeException { 7 | 8 | public InvalidFileException() { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/exception/InvalidStructureException.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.exception; 2 | 3 | 4 | import ore.plugins.idea.lib.exception.base.OrePluginRuntimeException; 5 | 6 | public class InvalidStructureException extends OrePluginRuntimeException { 7 | 8 | public InvalidStructureException(String message) { 9 | super(message); 10 | } 11 | 12 | public InvalidStructureException() { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/exception/ValidationException.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.exception; 2 | 3 | import ore.plugins.idea.lib.exception.base.OrePluginRuntimeException; 4 | 5 | public class ValidationException extends OrePluginRuntimeException { 6 | 7 | public ValidationException(String message) { 8 | super(message); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/exception/base/ExceptionResolver.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.exception.base; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.ui.Messages; 5 | import ore.plugins.idea.lib.exception.CancelException; 6 | import ore.plugins.idea.lib.exception.InvalidFileException; 7 | import ore.plugins.idea.lib.exception.ValidationException; 8 | 9 | public interface ExceptionResolver { 10 | 11 | default void safeExecute(Runnable runnable, AnActionEvent anActionEvent) { 12 | try { 13 | runnable.run(); 14 | } catch (InvalidFileException invalidFileException) { 15 | anActionEvent.getPresentation().setEnabled(false); 16 | } catch (ValidationException validationException) { 17 | Messages.showErrorDialog(validationException.getMessage(), "Validation Error"); 18 | } catch (CancelException ignored) { 19 | } catch (OrePluginRuntimeException exception) { 20 | Messages.showErrorDialog(exception.getMessage(), "Error"); 21 | } 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/exception/base/OrePluginException.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.exception.base; 2 | 3 | public class OrePluginException extends Exception { 4 | 5 | public OrePluginException(String message) { 6 | super(message); 7 | } 8 | 9 | public OrePluginException() { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/exception/base/OrePluginRuntimeException.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.exception.base; 2 | 3 | public class OrePluginRuntimeException extends RuntimeException { 4 | 5 | public OrePluginRuntimeException(String message) { 6 | super(message); 7 | } 8 | 9 | public OrePluginRuntimeException() { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/model/pom/PsiBuild.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.model.pom; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | import java.util.List; 6 | 7 | @JsonIgnoreProperties(ignoreUnknown = true) 8 | public class PsiBuild { 9 | 10 | private List plugins; 11 | 12 | public List getPlugins() { 13 | return plugins; 14 | } 15 | 16 | public void setPlugins(List plugins) { 17 | this.plugins = plugins; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/model/pom/PsiDependency.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.model.pom; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class PsiDependency { 7 | 8 | private String groupId; 9 | private String artifactId; 10 | 11 | public String getGroupId() { 12 | return groupId; 13 | } 14 | 15 | public void setGroupId(String groupId) { 16 | this.groupId = groupId; 17 | } 18 | 19 | public String getArtifactId() { 20 | return artifactId; 21 | } 22 | 23 | public void setArtifactId(String artifactId) { 24 | this.artifactId = artifactId; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/model/pom/PsiParent.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.model.pom; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class PsiParent { 7 | 8 | private String groupId; 9 | private String artifactId; 10 | private String version; 11 | 12 | public String getGroupId() { 13 | return groupId; 14 | } 15 | 16 | public void setGroupId(String groupId) { 17 | this.groupId = groupId; 18 | } 19 | 20 | public String getArtifactId() { 21 | return artifactId; 22 | } 23 | 24 | public void setArtifactId(String artifactId) { 25 | this.artifactId = artifactId; 26 | } 27 | 28 | public String getVersion() { 29 | return version; 30 | } 31 | 32 | public void setVersion(String version) { 33 | this.version = version; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/model/pom/PsiPlugin.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.model.pom; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public class PsiPlugin { 7 | 8 | private String groupId; 9 | private String artifactId; 10 | 11 | public String getGroupId() { 12 | return groupId; 13 | } 14 | 15 | public void setGroupId(String groupId) { 16 | this.groupId = groupId; 17 | } 18 | 19 | public String getArtifactId() { 20 | return artifactId; 21 | } 22 | 23 | public void setArtifactId(String artifactId) { 24 | this.artifactId = artifactId; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/model/pom/PsiPom.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.model.pom; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | import javax.xml.bind.annotation.XmlRootElement; 6 | import java.util.List; 7 | 8 | @XmlRootElement(name = "project") 9 | @JsonIgnoreProperties(ignoreUnknown = true) 10 | public class PsiPom { 11 | 12 | private PsiParent parent; 13 | private List dependencies; 14 | private PsiBuild build; 15 | 16 | public PsiParent getParent() { 17 | return parent; 18 | } 19 | 20 | public void setParent(PsiParent parent) { 21 | this.parent = parent; 22 | } 23 | 24 | public List getDependencies() { 25 | return dependencies; 26 | } 27 | 28 | public void setDependencies(List dependencies) { 29 | this.dependencies = dependencies; 30 | } 31 | 32 | public PsiBuild getBuild() { 33 | return build; 34 | } 35 | 36 | public void setBuild(PsiBuild build) { 37 | this.build = build; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/model/ui/NameListCelRenderer.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.model.ui; 2 | 3 | import com.intellij.navigation.NavigationItem; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | 8 | public class NameListCelRenderer extends DefaultListCellRenderer { 9 | 10 | @Override 11 | public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 12 | if (value instanceof NavigationItem) { 13 | value = ((NavigationItem) value).getName(); 14 | } 15 | return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/provider/ConstructorProvider.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.provider; 2 | 3 | import com.intellij.psi.JavaPsiFacade; 4 | import com.intellij.psi.PsiClass; 5 | import com.intellij.psi.PsiField; 6 | import com.intellij.psi.PsiMethod; 7 | 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | public interface ConstructorProvider { 12 | 13 | String CONSTRUCTOR_TEMPLATE = "%s(%s){%s}"; 14 | String CONSTRUCTOR_ARGUMENT_TEMPLATE = "%s %s"; 15 | String CONSTRUCTOR_ASSIGNMENT_TEMPLATE = "this.%s = %s;"; 16 | 17 | default PsiMethod extractConstructorForClass(PsiClass psiClass, List ctrArgs, List ctrArgsToAssign, List superArgs) { 18 | String constructorArgsPart = ctrArgs.stream() 19 | .map(psiField -> String.format(CONSTRUCTOR_ARGUMENT_TEMPLATE, psiField.getType().getCanonicalText(), psiField.getNameIdentifier().getText())) 20 | .collect(Collectors.joining(", ")); 21 | 22 | ctrArgsToAssign.forEach(psiClass::add); 23 | String superPart = !superArgs.isEmpty() ? String.format("super(%s);\n", String.join(", ", superArgs)) : ""; 24 | String constructorArgsAssignPart = ctrArgsToAssign.stream() 25 | .map(psiField -> String.format(CONSTRUCTOR_ASSIGNMENT_TEMPLATE, psiField.getNameIdentifier().getText(), psiField.getNameIdentifier().getText())) 26 | .collect(Collectors.joining("")); 27 | String bodyPart = superPart.concat(constructorArgsAssignPart); 28 | 29 | String constructor = String.format(CONSTRUCTOR_TEMPLATE, psiClass.getName(), constructorArgsPart, bodyPart); 30 | return JavaPsiFacade.getElementFactory(psiClass.getProject()).createMethodFromText(constructor, psiClass); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/provider/TemplateProvider.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.provider; 2 | 3 | import java.io.InputStream; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Scanner; 6 | 7 | public interface TemplateProvider { 8 | 9 | default String provideTemplateContent(String templatePath) { 10 | InputStream inputStream = TemplateProvider.class.getResourceAsStream(templatePath); 11 | String template; 12 | try (Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) { 13 | template = scanner.useDelimiter("\\A").next(); 14 | } 15 | return template.replaceAll("\r\n", "\n"); 16 | } 17 | 18 | default String extractTemplateReplacementValue(String value) { 19 | return "%<".concat(value).concat(">"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/service/JavaCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.service; 2 | 3 | import com.intellij.openapi.vfs.LocalFileSystem; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import com.intellij.psi.*; 6 | import ore.plugins.idea.lib.exception.base.OrePluginRuntimeException; 7 | import ore.plugins.idea.lib.provider.ConstructorProvider; 8 | import ore.plugins.idea.lib.service.base.OrePluginGenerator; 9 | 10 | import java.io.File; 11 | import java.util.Objects; 12 | 13 | public abstract class JavaCodeGenerator extends OrePluginGenerator implements ConstructorProvider { 14 | 15 | protected static final String DEFAULT_JAVA_SRC_PATH = "/src/main/java/"; 16 | 17 | public JavaCodeGenerator(PsiClass psiClass) { 18 | super(psiClass); 19 | } 20 | 21 | public abstract PsiClass generateJavaClass(); 22 | 23 | protected VirtualFile createFolderIfNotExists(String path) { 24 | File packageFile = new File(path); 25 | if (!packageFile.exists() && !packageFile.mkdirs()) { 26 | throw new OrePluginRuntimeException(String.format("Failed to generateJavaClass package at '%s'", packageFile)); 27 | } 28 | return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(packageFile); 29 | } 30 | 31 | protected PsiJavaFile createJavaFileInDirectory(PsiDirectory psiDirectory, String resourceRepositoryName) { 32 | return (PsiJavaFile) psiDirectory.createFile(resourceRepositoryName.concat(".java")); 33 | } 34 | 35 | protected PsiJavaFile createJavaFileInDirectoryWithPackage(PsiDirectory psiDirectory, String resourceServiceName, String fullPackagePath) { 36 | PsiJavaFile resourceServiceFile = createJavaFileInDirectory(psiDirectory, resourceServiceName); 37 | 38 | if (fullPackagePath.length() > 0) { 39 | PsiPackageStatement packageStatement = getElementFactory().createPackageStatement(fullPackagePath); 40 | resourceServiceFile.addAfter(packageStatement, null); 41 | } 42 | return resourceServiceFile; 43 | } 44 | 45 | protected void addQualifiedAnnotationNameTo(String qualifiedAnnotationName, PsiMember psiMember) { 46 | Objects.requireNonNull(psiMember.getModifierList()).addAnnotation(qualifiedAnnotationName); 47 | } 48 | 49 | protected void addOverrideTo(PsiMethod resourceFieldGetter) { 50 | addQualifiedAnnotationNameTo("java.lang.Override", resourceFieldGetter); 51 | } 52 | 53 | protected void addQualifiedExtendsToClass(String qualifiedExtendsName, PsiClass psiClass) { 54 | PsiJavaCodeReferenceElement psiJavaCodeReferenceElement = getElementFactory().createReferenceFromText(qualifiedExtendsName, psiClass); 55 | PsiReferenceList extendsList = psiClass.getExtendsList(); 56 | Objects.requireNonNull(extendsList).add(psiJavaCodeReferenceElement); 57 | } 58 | 59 | protected void addQualifiedImplementsToClass(String qualifiedImplementsName, PsiClass psiClass) { 60 | PsiJavaCodeReferenceElement psiJavaCodeReferenceElement = getElementFactory().createReferenceFromText(qualifiedImplementsName, psiClass); 61 | PsiReferenceList implementsList = psiClass.getImplementsList(); 62 | Objects.requireNonNull(implementsList).add(psiJavaCodeReferenceElement); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/service/base/OrePluginGenerator.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.service.base; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.roots.ProjectRootManager; 5 | import com.intellij.psi.JavaPsiFacade; 6 | import com.intellij.psi.PsiClass; 7 | import com.intellij.psi.PsiElementFactory; 8 | import com.intellij.psi.PsiManager; 9 | import com.intellij.psi.codeStyle.JavaCodeStyleManager; 10 | import com.intellij.psi.search.GlobalSearchScope; 11 | import ore.plugins.idea.lib.provider.TemplateProvider; 12 | 13 | public abstract class OrePluginGenerator implements TemplateProvider { 14 | 15 | protected PsiClass psiClass; 16 | protected Project project; 17 | 18 | public OrePluginGenerator(PsiClass psiClass) { 19 | this.psiClass = psiClass; 20 | this.project = psiClass.getProject(); 21 | } 22 | 23 | public PsiClass getPsiClass() { 24 | return psiClass; 25 | } 26 | 27 | public Project getProject() { 28 | return project; 29 | } 30 | 31 | protected ProjectRootManager getProjectRootManager() { 32 | return ProjectRootManager.getInstance(project); 33 | } 34 | 35 | protected PsiManager getPsiManager() { 36 | return PsiManager.getInstance(project); 37 | } 38 | 39 | protected JavaCodeStyleManager getJavaCodeStyleManager() { 40 | return JavaCodeStyleManager.getInstance(project); 41 | } 42 | 43 | protected JavaPsiFacade getJavaPsiFacade() { 44 | return JavaPsiFacade.getInstance(project); 45 | } 46 | 47 | protected PsiElementFactory getElementFactory() { 48 | return JavaPsiFacade.getInstance(project).getElementFactory(); 49 | } 50 | 51 | protected PsiClass getClassFromQualifiedName(String qualifiedName) { 52 | return JavaPsiFacade.getInstance(project).findClass(qualifiedName, GlobalSearchScope.allScope(project)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/lib/utils/FormatUtils.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.lib.utils; 2 | 3 | public class FormatUtils { 4 | 5 | public static String toFirstLetterUpperCase(String word) { 6 | return word.substring(0, 1).toUpperCase() + word.substring(1); 7 | } 8 | 9 | public static String toFirstLetterLowerCase(String word) { 10 | return word.substring(0, 1).toLowerCase() + word.substring(1); 11 | } 12 | 13 | public static String camelCaseToUpperCaseWithUnderScore(String word) { 14 | if (word.length() == 0) return word; 15 | StringBuilder stringBuilder = new StringBuilder(); 16 | stringBuilder.append(String.valueOf(word.charAt(0)).toUpperCase()); 17 | for (int i = 1; i < word.length(); i++) { 18 | if (Character.isUpperCase(word.charAt(i))) { 19 | stringBuilder.append("_"); 20 | } 21 | stringBuilder.append(String.valueOf(word.charAt(i)).toUpperCase()); 22 | } 23 | return stringBuilder.toString(); 24 | } 25 | 26 | public static String toPlural(String singular) { 27 | String consonants = "bcdfghjklmnpqrstvwxz"; 28 | 29 | switch (singular) { 30 | case "Person": 31 | return "People"; 32 | case "Trash": 33 | return "Trash"; 34 | case "Life": 35 | return "Lives"; 36 | case "Man": 37 | return "Men"; 38 | case "Woman": 39 | return "Women"; 40 | case "Child": 41 | return "Children"; 42 | case "Foot": 43 | return "Feet"; 44 | case "Tooth": 45 | return "Teeth"; 46 | case "Dozen": 47 | return "Dozen"; 48 | case "Hundred": 49 | return "Hundred"; 50 | case "Thousand": 51 | return "Thousand"; 52 | case "Million": 53 | return "Million"; 54 | case "Datum": 55 | return "Data"; 56 | case "Criterion": 57 | return "Criteria"; 58 | case "Analysis": 59 | return "Analyses"; 60 | case "Fungus": 61 | return "Fungi"; 62 | case "Index": 63 | return "Indices"; 64 | case "Matrix": 65 | return "Matrices"; 66 | case "Settings": 67 | return "Settings"; 68 | case "UserSettings": 69 | return "UserSettings"; 70 | default: 71 | if (consonants.contains(String.valueOf(singular.charAt(singular.length() - 2)))) { 72 | // Handle ending with "o" (if preceeded by a consonant, end with -es, otherwise -s: Potatoes and Radios) 73 | if (singular.endsWith("o")) { 74 | return singular + "es"; 75 | } 76 | // Handle ending with "y" (if preceeded by a consonant, end with -ies, otherwise -s: Companies and Trays) 77 | if (singular.endsWith("y")) { 78 | return singular.substring(0, singular.length() - 1) + "ies"; 79 | } 80 | } 81 | 82 | // Ends with a whistling sound: boxes, buzzes, churches, passes 83 | if (singular.endsWith("s") || singular.endsWith("sh") || singular.endsWith("ch") || singular.endsWith("x") || singular.endsWith("z")) { 84 | return singular + "es"; 85 | } 86 | return singular + "s"; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/swip/action/SwipAction.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.swip.action; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.dataformat.xml.XmlMapper; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | import com.intellij.openapi.command.WriteCommandAction; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.roots.ProjectRootManager; 9 | import com.intellij.openapi.ui.Messages; 10 | import com.intellij.psi.JavaPsiFacade; 11 | import com.intellij.psi.PsiClass; 12 | import com.intellij.psi.PsiField; 13 | import com.intellij.psi.PsiModifier; 14 | import com.intellij.psi.search.GlobalSearchScope; 15 | import ore.plugins.idea.lib.action.OrePluginAction; 16 | import ore.plugins.idea.lib.dialog.InputDialog; 17 | import ore.plugins.idea.lib.dialog.SelectStuffDialog; 18 | import ore.plugins.idea.lib.exception.CancelException; 19 | import ore.plugins.idea.lib.exception.InvalidFileException; 20 | import ore.plugins.idea.lib.exception.ValidationException; 21 | import ore.plugins.idea.lib.exception.base.OrePluginRuntimeException; 22 | import ore.plugins.idea.lib.model.pom.PsiPom; 23 | import ore.plugins.idea.lib.model.ui.NameListCelRenderer; 24 | import ore.plugins.idea.swip.dialog.PackageInputDialog; 25 | import ore.plugins.idea.swip.model.SwipRequest; 26 | import ore.plugins.idea.swip.service.*; 27 | import ore.spring.web.initializr.domain.ResourcePersistable; 28 | import org.jetbrains.annotations.NotNull; 29 | 30 | import javax.swing.*; 31 | import java.io.File; 32 | import java.io.IOException; 33 | import java.nio.file.Files; 34 | import java.nio.file.Paths; 35 | import java.util.Arrays; 36 | import java.util.List; 37 | import java.util.Objects; 38 | import java.util.stream.Collectors; 39 | 40 | public class SwipAction extends OrePluginAction { 41 | 42 | private static final String SPRING_WEB_INITIALIZR_DEPENDENCY_TEMPLATE = "/templates/maven/spring-web-initializr-dependency"; 43 | 44 | @Override 45 | public void update(@NotNull AnActionEvent anActionEvent) { 46 | safeExecute(() -> { 47 | super.update(anActionEvent); 48 | PsiClass psiClass = extractPsiClass(anActionEvent); 49 | if (psiClass.getImplementsList() != null 50 | && Arrays.stream(psiClass.getImplementsList().getReferencedTypes()) 51 | .anyMatch(refType -> refType.getClassName().contains(ResourcePersistable.class.getSimpleName()))) { 52 | throw new InvalidFileException(); 53 | } 54 | }, anActionEvent); 55 | } 56 | 57 | @Override 58 | public void safeActionPerformed(AnActionEvent anActionEvent) { 59 | PsiClass resourcePsiClass = extractPsiClass(anActionEvent); 60 | 61 | File pom = requestPomXml(resourcePsiClass); 62 | validateMavenDependencies(pom); 63 | 64 | SwipRequest swipRequest = SwipRequest.SwipRequestBuilder 65 | .aSwipRequest(resourcePsiClass, requestResourceIdField(resourcePsiClass)) 66 | // TODO ADD SUPPORT FOR CUSTOM RESOURCE FORM 67 | .withResourceFormClass(resourcePsiClass) 68 | // TODO ADD SUPPORT FOR CUSTOM RESOURCE SEARCH FORM 69 | .withResourceSearchFormClass(resourcePsiClass) 70 | .withResourceRepositoryPackage(requestPackage(resourcePsiClass, String.format("Package for ResourceRepository (i.e. %sResourceRepository)", resourcePsiClass.getName()), "ResourceRepository")) 71 | .withResourceServicePackage(requestPackage(resourcePsiClass, String.format("Package for ResourceService (i.e. %sResourceService)", resourcePsiClass.getName()), "ResourceService")) 72 | .withResourceControllerPackage(requestPackage(resourcePsiClass, String.format("Package for ResourceController (i.e. %sResourceController)", resourcePsiClass.getName()), "ResourceController")) 73 | .build(); 74 | 75 | act(swipRequest); 76 | } 77 | 78 | private File requestPomXml(PsiClass resourcePsiClass) { 79 | String pomXmlPath = ProjectRootManager.getInstance(resourcePsiClass.getProject()).getContentRoots()[0].getPath().concat("/pom.xml"); 80 | if (!Files.exists(Paths.get(pomXmlPath))) { 81 | Messages.showMessageDialog(resourcePsiClass.getProject(), "Maven project expected. Please convert this project to continue.", "Invalid Structure", Messages.getErrorIcon()); 82 | throw new CancelException(); 83 | } 84 | return new File(pomXmlPath); 85 | } 86 | 87 | private void validateMavenDependencies(File pom) { 88 | ObjectMapper xmlMapper = new XmlMapper(); 89 | try { 90 | PsiPom psiPom = xmlMapper.readValue(pom, PsiPom.class); 91 | if (psiPom.getDependencies() == null || 92 | psiPom.getDependencies() 93 | .stream() 94 | .noneMatch(e -> e.getGroupId().equals("io.github.orpolyzos") && e.getArtifactId().equals("spring-web-initializr"))) { 95 | Messages.showErrorDialog(provideTemplateContent(SPRING_WEB_INITIALIZR_DEPENDENCY_TEMPLATE), "Insufficient Dependencies"); 96 | throw new CancelException(); 97 | } 98 | } catch (IOException e) { 99 | throw new OrePluginRuntimeException(e.getMessage()); 100 | } 101 | 102 | } 103 | 104 | @NotNull 105 | private PsiField requestResourceIdField(PsiClass resourcePsiClass) { 106 | List resourceIdCandidates = Arrays.stream(resourcePsiClass.getFields()) 107 | .filter(this::excludeStaticOrFinal) 108 | .collect(Collectors.toList()); 109 | SelectStuffDialog resourceIdFieldDialog = new SelectStuffDialog<>( 110 | resourcePsiClass.getProject(), 111 | String.format("ID for Resource (i.e. %s)", resourcePsiClass.getName()), 112 | "Choose the field that is going to be the ID (primary key) for the Resource", 113 | resourceIdCandidates, ListSelectionModel.SINGLE_SELECTION, new NameListCelRenderer()); 114 | resourceIdFieldDialog.waitForInput(); 115 | return resourceIdFieldDialog.getSelectedStuff() 116 | .stream() 117 | .findFirst() 118 | .orElseThrow(() -> new ValidationException("Invalid selection for the ID field")); 119 | } 120 | 121 | @NotNull 122 | private String requestPackage(PsiClass resourcePsiClass, String title, String suffix) { 123 | String fullPackage; 124 | do { 125 | InputDialog packageInputDialog = new PackageInputDialog(resourcePsiClass, title, "Place in package (e.g. ore.swip.demo) or leave empty for default"); 126 | packageInputDialog.waitForInput(); 127 | fullPackage = packageInputDialog.getInput(); 128 | } while (!packageExistsAlready(fullPackage, resourcePsiClass.getProject()) 129 | || classExistsAlready(String.format("%s.%s%s", fullPackage, resourcePsiClass.getName(), suffix), resourcePsiClass.getProject())); 130 | return fullPackage; 131 | } 132 | 133 | private boolean packageExistsAlready(String fullPackagePath, Project project) { 134 | boolean packageExists = Files.exists(Paths.get(ProjectRootManager.getInstance(project).getContentRoots()[0].getPath().concat("/src/main/java/").concat(fullPackagePath.replaceAll("\\.", "/")))); 135 | if (!packageExists) { 136 | Messages.showWarningDialog("Package does not exist.", "Invalid Package"); 137 | } 138 | return packageExists; 139 | } 140 | 141 | private boolean classExistsAlready(String qualifiedName, Project project) { 142 | boolean existsAlready = JavaPsiFacade.getInstance(project).findClass(qualifiedName, GlobalSearchScope.allScope(project)) != null; 143 | if (existsAlready) { 144 | Messages.showWarningDialog(String.format("There is already a class %s.", qualifiedName), "Duplicate File"); 145 | } 146 | return existsAlready; 147 | } 148 | 149 | private boolean excludeStaticOrFinal(PsiField psiField) { 150 | return !Objects.requireNonNull(psiField.getModifierList()).hasModifierProperty(PsiModifier.STATIC) || Objects.requireNonNull(psiField.getModifierList()).hasModifierProperty(PsiModifier.FINAL); 151 | } 152 | 153 | private void act(SwipRequest swipRequest) { 154 | WriteCommandAction.runWriteCommandAction(swipRequest.getResourceClass().getProject(), () -> { 155 | 156 | new ResourcePersistableGenerator(swipRequest).generateJavaClass(); 157 | 158 | PsiClass resourceRepositoryClass = new RepositoryGenerator(swipRequest).generateJavaClass(); 159 | 160 | PsiClass resourceServiceClass = new ServiceGenerator(swipRequest, resourceRepositoryClass).generateJavaClass(); 161 | 162 | ControllerGenerator controllerGenerator = new ControllerGenerator(swipRequest, resourceServiceClass); 163 | controllerGenerator.generateJavaClass(); 164 | 165 | FreemarkerGenerator freemarkerGenerator = new FreemarkerGenerator(swipRequest, controllerGenerator); 166 | freemarkerGenerator.generateResources(); 167 | 168 | }); 169 | } 170 | 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/swip/dialog/PackageInputDialog.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.swip.dialog; 2 | 3 | import com.intellij.psi.PsiClass; 4 | import ore.plugins.idea.lib.dialog.InputDialog; 5 | import ore.plugins.idea.lib.exception.ValidationException; 6 | 7 | public class PackageInputDialog extends InputDialog { 8 | private static final String JAVA_PACKAGE_PATTERN = "^[a-z][a-z0-9_]*(\\.[a-z0-9_]+)*[0-9a-z_]?$"; 9 | 10 | public PackageInputDialog(PsiClass psiClass, String title, String componentText) { 11 | super(psiClass, title, componentText); 12 | } 13 | 14 | @Override 15 | public String getInput() { 16 | String packagePath = super.getInput(); 17 | validatePackagePath(packagePath); 18 | return packagePath; 19 | } 20 | 21 | private void validatePackagePath(String packagePath) { 22 | if (packagePath.length() > 0 && !packagePath.toLowerCase().matches(JAVA_PACKAGE_PATTERN)) { 23 | throw new ValidationException("Invalid package name"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/swip/model/SwipRequest.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.swip.model; 2 | 3 | import com.intellij.psi.PsiClass; 4 | import com.intellij.psi.PsiField; 5 | 6 | public class SwipRequest { 7 | 8 | private PsiClass resourceClass; 9 | private PsiField resourceIdPsiField; 10 | 11 | private PsiClass resourceFormClass; 12 | private PsiClass resourceSearchFormClass; 13 | 14 | 15 | private String resourceRepositoryPackage = ""; 16 | private String resourceServicePackage = ""; 17 | private String resourceControllerPackage = ""; 18 | private String resourceIdQualifiedName; 19 | 20 | private SwipRequest(PsiClass resourceClass, PsiField resourceIdPsiField) { 21 | this.resourceClass = resourceClass; 22 | this.resourceIdPsiField = resourceIdPsiField; 23 | this.resourceFormClass = resourceClass; 24 | this.resourceSearchFormClass = resourceClass; 25 | } 26 | 27 | public PsiField getResourceIdPsiField() { 28 | return resourceIdPsiField; 29 | } 30 | 31 | public PsiClass getResourceClass() { 32 | return resourceClass; 33 | } 34 | 35 | public PsiClass getResourceFormClass() { 36 | return resourceFormClass; 37 | } 38 | 39 | public PsiClass getResourceSearchFormClass() { 40 | return resourceSearchFormClass; 41 | } 42 | 43 | public String getResourceRepositoryPackage() { 44 | return resourceRepositoryPackage; 45 | } 46 | 47 | public String getResourceServicePackage() { 48 | return resourceServicePackage; 49 | } 50 | 51 | public String getResourceControllerPackage() { 52 | return resourceControllerPackage; 53 | } 54 | 55 | public void setResourceIdPsiField(PsiField resourceIdPsiField) { 56 | this.resourceIdPsiField = resourceIdPsiField; 57 | } 58 | 59 | public void setResourceClass(PsiClass resourceClass) { 60 | this.resourceClass = resourceClass; 61 | } 62 | 63 | public void setResourceFormClass(PsiClass resourceFormClass) { 64 | this.resourceFormClass = resourceFormClass; 65 | } 66 | 67 | public void setResourceSearchFormClass(PsiClass resourceSearchFormClass) { 68 | this.resourceSearchFormClass = resourceSearchFormClass; 69 | } 70 | 71 | public void setResourceRepositoryPackage(String resourceRepositoryPackage) { 72 | this.resourceRepositoryPackage = resourceRepositoryPackage; 73 | } 74 | 75 | public void setResourceServicePackage(String resourceServicePackage) { 76 | this.resourceServicePackage = resourceServicePackage; 77 | } 78 | 79 | public void setResourceControllerPackage(String resourceControllerPackage) { 80 | this.resourceControllerPackage = resourceControllerPackage; 81 | } 82 | 83 | public String getResourceIdQualifiedName() { 84 | return resourceIdQualifiedName; 85 | } 86 | 87 | public void setResourceIdQualifiedName(String resourceIdQualifiedName) { 88 | this.resourceIdQualifiedName = resourceIdQualifiedName; 89 | } 90 | 91 | public static class SwipRequestBuilder { 92 | private PsiField resourceIdPsiField; 93 | private PsiClass resourceClass; 94 | private PsiClass resourceFormClass; 95 | private PsiClass resourceSearchFormClass; 96 | private String resourceRepositoryPackage; 97 | private String resourceServicePackage; 98 | private String resourceControllerPackage; 99 | 100 | private SwipRequestBuilder(PsiClass resourceClass, PsiField resourceIdPsiField) { 101 | this.resourceClass = resourceClass; 102 | this.resourceIdPsiField = resourceIdPsiField; 103 | } 104 | 105 | public static SwipRequestBuilder aSwipRequest(PsiClass resourceClass, PsiField resourcePsiField) { 106 | return new SwipRequestBuilder(resourceClass, resourcePsiField); 107 | } 108 | 109 | public SwipRequestBuilder withResourceFormClass(PsiClass resourceFormClass) { 110 | this.resourceFormClass = resourceFormClass; 111 | return this; 112 | } 113 | 114 | public SwipRequestBuilder withResourceSearchFormClass(PsiClass resourceSearchFormClass) { 115 | this.resourceSearchFormClass = resourceSearchFormClass; 116 | return this; 117 | } 118 | 119 | public SwipRequestBuilder withResourceRepositoryPackage(String resourceRepositoryPackage) { 120 | this.resourceRepositoryPackage = resourceRepositoryPackage; 121 | return this; 122 | } 123 | 124 | public SwipRequestBuilder withResourceServicePackage(String resourceServicePackage) { 125 | this.resourceServicePackage = resourceServicePackage; 126 | return this; 127 | } 128 | 129 | public SwipRequestBuilder withResourceControllerPackage(String resourceControllerPackage) { 130 | this.resourceControllerPackage = resourceControllerPackage; 131 | return this; 132 | } 133 | 134 | public SwipRequest build() { 135 | SwipRequest swipRequest = new SwipRequest(resourceClass, resourceIdPsiField); 136 | swipRequest.setResourceFormClass(resourceFormClass); 137 | swipRequest.setResourceSearchFormClass(resourceSearchFormClass); 138 | swipRequest.setResourceRepositoryPackage(resourceRepositoryPackage); 139 | swipRequest.setResourceServicePackage(resourceServicePackage); 140 | swipRequest.setResourceControllerPackage(resourceControllerPackage); 141 | swipRequest.setResourceIdQualifiedName(resourceIdPsiField.getType().getCanonicalText()); 142 | return swipRequest; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/swip/service/ControllerGenerator.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.swip.service; 2 | 3 | import com.intellij.openapi.vfs.VirtualFile; 4 | import com.intellij.psi.*; 5 | import com.intellij.psi.util.PsiUtil; 6 | import ore.plugins.idea.swip.model.SwipRequest; 7 | import ore.plugins.idea.swip.service.base.SwipJavaCodeGenerator; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.stream.Collectors; 14 | 15 | import static ore.plugins.idea.lib.utils.FormatUtils.*; 16 | 17 | public class ControllerGenerator extends SwipJavaCodeGenerator { 18 | 19 | private static final String RESOURCE_SERVICE_NAME_TEMPLATE = "%sResourceController"; 20 | private static final String CONTROLLER_ANNOTATION_QN = "org.springframework.stereotype.Controller"; 21 | private static final String MANDATORY_PATH_METHOD_TEMPLATE = "protected String get%s() { return %s; }"; 22 | private static final String GET_MAPPING_QN = "org.springframework.web.bind.annotation.GetMapping"; 23 | private static final String POST_MAPPING_QN = "org.springframework.web.bind.annotation.PostMapping"; 24 | 25 | private PsiClass resourceService; 26 | 27 | private String resourceSingular; 28 | private String resourcePlural; 29 | 30 | public ControllerGenerator(SwipRequest swipRequest, PsiClass resourceService) { 31 | super(swipRequest); 32 | this.resourceService = resourceService; 33 | this.resourceSingular = toFirstLetterLowerCase(Objects.requireNonNull(swipRequest.getResourceClass().getName())); 34 | this.resourcePlural = toPlural(resourceSingular); 35 | } 36 | 37 | @Override 38 | public PsiClass generateJavaClass() { 39 | String fullPackagePath = getProjectRootManager().getContentRoots()[0].getPath().concat(DEFAULT_JAVA_SRC_PATH).concat(swipRequest.getResourceControllerPackage().replaceAll("\\.", "/")); 40 | VirtualFile vfPackage = createFolderIfNotExists(fullPackagePath); 41 | PsiDirectory pdPackage = getPsiManager().findDirectory(vfPackage); 42 | return createResourceController(pdPackage); 43 | } 44 | 45 | private PsiClass createResourceController(PsiDirectory psiDirectory) { 46 | String resourceControllerName = String.format(RESOURCE_SERVICE_NAME_TEMPLATE, swipRequest.getResourceClass().getName()); 47 | PsiJavaFile resourceControllerFile = createJavaFileInDirectoryWithPackage(psiDirectory, resourceControllerName, swipRequest.getResourceControllerPackage()); 48 | 49 | PsiClass resourceController = getElementFactory().createClass(resourceControllerName); 50 | addQualifiedAnnotationNameTo(CONTROLLER_ANNOTATION_QN, resourceController); 51 | 52 | String resourceControllerQualifiedName = String.format("ore.spring.web.initializr.controller.ResourceController<%s, %s, %s, %s>", 53 | swipRequest.getResourceClass().getQualifiedName(), 54 | swipRequest.getResourceIdQualifiedName(), 55 | swipRequest.getResourceFormClass().getQualifiedName(), 56 | swipRequest.getResourceSearchFormClass().getQualifiedName()); 57 | addQualifiedExtendsToClass(resourceControllerQualifiedName, resourceController); 58 | 59 | setupConstructor(resourceController); 60 | setupConstantsAndGetters(resourceController); 61 | setupServletMethods(resourceController); 62 | setupConvertMethods(resourceController); 63 | 64 | getJavaCodeStyleManager().shortenClassReferences(resourceController); 65 | resourceControllerFile.add(resourceController); 66 | 67 | return resourceController; 68 | } 69 | 70 | private void setupConvertMethods(PsiClass resourceController) { 71 | PsiMethod resourceFormToResourceMethod = getElementFactory().createMethodFromText(String.format("protected %s resourceFormToResource(%s %s) { return %s; }", 72 | swipRequest.getResourceClass().getQualifiedName(), 73 | swipRequest.getResourceFormClass().getQualifiedName(), 74 | toFirstLetterLowerCase(Objects.requireNonNull(swipRequest.getResourceFormClass().getName())), 75 | toFirstLetterLowerCase(Objects.requireNonNull(swipRequest.getResourceClass().getName()))), resourceController.getContext()); 76 | addOverrideTo(resourceFormToResourceMethod); 77 | resourceController.add(resourceFormToResourceMethod); 78 | 79 | PsiMethod resourceToResourceFormMethod = getElementFactory().createMethodFromText(String.format("protected %s resourceToResourceForm(%s %s) { return %s; }", 80 | swipRequest.getResourceFormClass().getQualifiedName(), 81 | swipRequest.getResourceClass().getQualifiedName(), 82 | toFirstLetterLowerCase(Objects.requireNonNull(swipRequest.getResourceClass().getName())), 83 | toFirstLetterLowerCase(Objects.requireNonNull(swipRequest.getResourceFormClass().getName()))), resourceController.getContext()); 84 | addOverrideTo(resourceToResourceFormMethod); 85 | resourceController.add(resourceToResourceFormMethod); 86 | } 87 | 88 | private void setupServletMethods(PsiClass resourceController) { 89 | resourceController.add(extractGetResourceViewMethod(resourceController)); 90 | resourceController.add(extractCreateResourceMethod(resourceController)); 91 | resourceController.add(extractDeleteResourceMethod(resourceController)); 92 | resourceController.add(extractGetEditResourceViewMethod(resourceController)); 93 | resourceController.add(extractEditResourceMethod(resourceController)); 94 | resourceController.add(extractSearchByMethod(resourceController)); 95 | 96 | } 97 | 98 | @NotNull 99 | private PsiMethod extractGetResourceViewMethod(PsiClass resourceController) { 100 | PsiAnnotation annotation = getElementFactory().createAnnotationFromText(String.format("@%s(%s)", GET_MAPPING_QN, "RESOURCE_BASE_URI"), resourceController.getContext()); 101 | PsiMethod psiMethod = getElementFactory().createMethodFromText("public String getResourceView(org.springframework.ui.Model model) { return super.getResourceView(model); }", resourceController.getContext()); 102 | addOverrideTo(psiMethod); 103 | psiMethod.getModifierList().addAfter(annotation, psiMethod.getModifierList().getAnnotations()[0]); 104 | return psiMethod; 105 | } 106 | 107 | @NotNull 108 | private PsiMethod extractCreateResourceMethod(PsiClass resourceController) { 109 | PsiAnnotation annotation = getElementFactory().createAnnotationFromText(String.format("@%s(%s)", POST_MAPPING_QN, "RESOURCE_BASE_URI"), resourceController.getContext()); 110 | PsiMethod psiMethod = getElementFactory() 111 | .createMethodFromText(String.format("public String createResource(" + 112 | "@javax.validation.Valid @org.springframework.web.bind.annotation.ModelAttribute(RESOURCE_FORM_HOLDER) %s resourceForm, " + 113 | "org.springframework.validation.BindingResult bindingResult, " + 114 | "org.springframework.ui.Model model, " + 115 | "org.springframework.web.servlet.mvc.support.RedirectAttributes redirectAttributes) { " + 116 | "return super.createResource(resourceForm, bindingResult, model, redirectAttributes); " + 117 | "}", swipRequest.getResourceFormClass().getQualifiedName()) 118 | , resourceController.getContext()); 119 | addOverrideTo(psiMethod); 120 | psiMethod.getModifierList().addAfter(annotation, psiMethod.getModifierList().getAnnotations()[0]); 121 | return psiMethod; 122 | } 123 | 124 | @NotNull 125 | private PsiElement extractDeleteResourceMethod(PsiClass resourceController) { 126 | PsiAnnotation annotation = getElementFactory().createAnnotationFromText(String.format("@%s(\"%s/{resourceId}/delete\")", POST_MAPPING_QN, resourcePlural), resourceController.getContext()); 127 | PsiMethod psiMethod = getElementFactory() 128 | .createMethodFromText(String.format("public String deleteResource(" + 129 | "@org.springframework.web.bind.annotation.PathVariable(\"resourceId\") %s resourceId, " + 130 | "org.springframework.ui.Model model, " + 131 | "org.springframework.web.servlet.mvc.support.RedirectAttributes redirectAttributes) {" + 132 | "return super.deleteResource(resourceId, model, redirectAttributes); }", swipRequest.getResourceIdQualifiedName()), resourceController.getContext()); 133 | addOverrideTo(psiMethod); 134 | psiMethod.getModifierList().addAfter(annotation, psiMethod.getModifierList().getAnnotations()[0]); 135 | return psiMethod; 136 | } 137 | 138 | @NotNull 139 | private PsiElement extractGetEditResourceViewMethod(PsiClass resourceController) { 140 | PsiAnnotation annotation = getElementFactory().createAnnotationFromText(String.format("@%s(\"%s/{resourceId}/edit\")", GET_MAPPING_QN, resourcePlural), resourceController.getContext()); 141 | PsiMethod psiMethod = getElementFactory() 142 | .createMethodFromText(String.format("public String getEditResourceView(" + 143 | "@org.springframework.web.bind.annotation.PathVariable(\"resourceId\") %s resourceId, " + 144 | "org.springframework.ui.Model model, " + 145 | "org.springframework.web.servlet.mvc.support.RedirectAttributes redirectAttributes) {" + 146 | "return super.getEditResourceView(resourceId, model, redirectAttributes); }", swipRequest.getResourceIdQualifiedName()), resourceController.getContext()); 147 | addOverrideTo(psiMethod); 148 | psiMethod.getModifierList().addAfter(annotation, psiMethod.getModifierList().getAnnotations()[0]); 149 | return psiMethod; 150 | } 151 | 152 | @NotNull 153 | private PsiElement extractEditResourceMethod(PsiClass resourceController) { 154 | PsiAnnotation annotation = getElementFactory().createAnnotationFromText(String.format("@%s(\"%s/{resourceId}/edit\")", POST_MAPPING_QN, resourcePlural), resourceController.getContext()); 155 | PsiMethod psiMethod = getElementFactory() 156 | .createMethodFromText(String.format("public String editResource(" + 157 | "@org.springframework.web.bind.annotation.PathVariable(\"resourceId\") %s resourceId, " + 158 | "@javax.validation.Valid @org.springframework.web.bind.annotation.ModelAttribute(RESOURCE_FORM_HOLDER) %s resourceForm, " + 159 | "org.springframework.validation.BindingResult bindingResult, " + 160 | "org.springframework.ui.Model model, " + 161 | "org.springframework.web.servlet.mvc.support.RedirectAttributes redirectAttributes, " + 162 | "javax.servlet.http.HttpServletRequest httpServletRequest) {" + 163 | "return super.editResource(resourceId, resourceForm, bindingResult, model, redirectAttributes, httpServletRequest); }", 164 | swipRequest.getResourceIdQualifiedName(), swipRequest.getResourceFormClass().getQualifiedName()), 165 | resourceController.getContext()); 166 | addOverrideTo(psiMethod); 167 | psiMethod.getModifierList().addAfter(annotation, psiMethod.getModifierList().getAnnotations()[0]); 168 | return psiMethod; 169 | } 170 | 171 | @NotNull 172 | private PsiElement extractSearchByMethod(PsiClass resourceController) { 173 | PsiAnnotation annotation = getElementFactory().createAnnotationFromText(String.format("@%s(\"%s/search\")", POST_MAPPING_QN, resourcePlural), resourceController.getContext()); 174 | PsiMethod psiMethod = getElementFactory() 175 | .createMethodFromText(String.format("public String searchBy(" + 176 | "@javax.validation.Valid @org.springframework.web.bind.annotation.ModelAttribute(RESOURCE_SEARCH_FORM_HOLDER) %s resourceSearchForm, " + 177 | "org.springframework.validation.BindingResult bindingResult, " + 178 | "org.springframework.ui.Model model, " + 179 | "org.springframework.web.servlet.mvc.support.RedirectAttributes redirectAttributes) {" + 180 | "return super.searchBy(resourceSearchForm, bindingResult, model, redirectAttributes); }", 181 | swipRequest.getResourceSearchFormClass().getQualifiedName()), resourceController.getContext()); 182 | addOverrideTo(psiMethod); 183 | psiMethod.getModifierList().addAfter(annotation, psiMethod.getModifierList().getAnnotations()[0]); 184 | return psiMethod; 185 | } 186 | 187 | private void setupConstructor(PsiClass resourceController) { 188 | 189 | String qualifiedResourceServiceName = String.format("ore.spring.web.initializr.service.ResourceService<%s,%s,%s>", 190 | swipRequest.getResourceClass().getQualifiedName(), 191 | swipRequest.getResourceFormClass().getQualifiedName(), 192 | swipRequest.getResourceIdQualifiedName()); 193 | 194 | PsiField resourceServiceElement = getElementFactory().createFieldFromText(String.format("private final %s %s;", qualifiedResourceServiceName, 195 | toFirstLetterLowerCase(Objects.requireNonNull(resourceService.getName()))), resourceService.getContext()); 196 | 197 | 198 | List constructorArguments = Collections.singletonList(resourceServiceElement); 199 | List superArguments = constructorArguments 200 | .stream() 201 | .map(e -> e.getNameIdentifier().getText()) 202 | .collect(Collectors.toList()); 203 | 204 | PsiMethod constructor = extractConstructorForClass(resourceController, constructorArguments, constructorArguments, superArguments); 205 | PsiUtil.setModifierProperty(constructor, PsiModifier.PUBLIC, true); 206 | addAutowiredTo(constructor); 207 | 208 | resourceController.add(constructor); 209 | } 210 | 211 | private void setupConstantsAndGetters(PsiClass resourceController) { 212 | addConstantAndGetter(resourceController, "resourceBaseUri", getResourceBaseUri()); 213 | addConstantAndGetter(resourceController, "resourceViewPath", getResourceViewPath()); 214 | addConstantAndGetter(resourceController, "editResourceViewPath", getEditResourceViewPath()); 215 | addConstantAndGetter(resourceController, "resourceFormHolder", getResourceFormHolder()); 216 | addConstantAndGetter(resourceController, "resourceSearchFormHolder", getResourceSearchFormHolder()); 217 | addConstantAndGetter(resourceController, "resourceListHolder", getResourceListHolder()); 218 | } 219 | 220 | String getResourceListHolder() { 221 | return String.format("%sList", resourceSingular); 222 | } 223 | 224 | String getResourceSearchFormHolder() { 225 | return String.format("%sSearchForm", resourceSingular); 226 | } 227 | 228 | String getResourceFormHolder() { 229 | return String.format("%sForm", resourceSingular); 230 | } 231 | 232 | String getEditResourceViewPath() { 233 | return String.format("/%s/edit-%s", resourceSingular, resourceSingular); 234 | } 235 | 236 | String getResourceViewPath() { 237 | return String.format("/%s/%s", resourceSingular, resourcePlural); 238 | } 239 | 240 | String getResourceBaseUri() { 241 | return String.format("/%s", resourcePlural); 242 | } 243 | 244 | private void addConstantAndGetter(PsiClass resourceController, String name, String value) { 245 | PsiField resourceField = getElementFactory().createFieldFromText(String.format("private static final String %s = \"%s\";", camelCaseToUpperCaseWithUnderScore(name), value), resourceController.getContext()); 246 | resourceController.add(resourceField); 247 | 248 | PsiMethod resourceFieldGetter = getElementFactory().createMethodFromText(String.format(MANDATORY_PATH_METHOD_TEMPLATE, toFirstLetterUpperCase(name), camelCaseToUpperCaseWithUnderScore(name)), resourceController.getContext()); 249 | addOverrideTo(resourceFieldGetter); 250 | resourceController.add(resourceFieldGetter); 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/swip/service/FreemarkerGenerator.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.swip.service; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.roots.ProjectRootManager; 5 | import com.intellij.openapi.ui.Messages; 6 | import com.intellij.openapi.vfs.LocalFileSystem; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.psi.PsiClassType; 9 | import com.intellij.psi.PsiField; 10 | import com.intellij.psi.PsiPrimitiveType; 11 | import com.intellij.psi.PsiType; 12 | import ore.plugins.idea.lib.exception.InvalidStructureException; 13 | import ore.plugins.idea.lib.exception.base.OrePluginRuntimeException; 14 | import ore.plugins.idea.lib.service.base.OrePluginGenerator; 15 | import ore.plugins.idea.swip.model.SwipRequest; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.nio.file.Files; 21 | import java.nio.file.Paths; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import java.util.Objects; 25 | import java.util.function.Predicate; 26 | import java.util.stream.Collectors; 27 | 28 | import static ore.plugins.idea.lib.utils.FormatUtils.*; 29 | 30 | public class FreemarkerGenerator extends OrePluginGenerator { 31 | 32 | private static final String BASE_RESOURCE_VIEW_TEMPLATE = "/templates/freemarker/base-resource-view"; 33 | private static final String EDIT_RESOURCE_VIEW_TEMPLATE = "/templates/freemarker/edit-resource-view"; 34 | private static final String RESOURCE_FORM_FIELD_TEMPLATE = "/templates/freemarker/section/resource-form-field"; 35 | private static final String RESOURCE_TABLE_HEAD_FORM_FIELD_TEMPLATE = "/templates/freemarker/section/resource-table-head-field"; 36 | private static final String RESOURCE_TABLE_BODY_FORM_FIELD_TEMPLATE = "/templates/freemarker/section/resource-table-body-field"; 37 | private static final String CSS_STYLES_TEMPLATE = "/templates/freemarker/swip-styles.css"; 38 | 39 | private SwipRequest swipRequest; 40 | 41 | private ControllerGenerator controllerGenerator; 42 | 43 | private String resourceSingular; 44 | private String resourcePlural; 45 | 46 | public FreemarkerGenerator(SwipRequest swipRequest, ControllerGenerator controllerGenerator) { 47 | super(swipRequest.getResourceClass()); 48 | this.swipRequest = swipRequest; 49 | this.controllerGenerator = controllerGenerator; 50 | this.resourceSingular = toFirstLetterLowerCase(Objects.requireNonNull(swipRequest.getResourceClass().getName())); 51 | this.resourcePlural = toPlural(resourceSingular); 52 | 53 | } 54 | 55 | public void generateResources() { 56 | VirtualFile staticFolder = createBaseStructureTo(swipRequest.getResourceClass().getProject(), "/static"); 57 | createCssStylesFile(staticFolder); 58 | 59 | VirtualFile templates = createBaseStructureTo(swipRequest.getResourceClass().getProject(), "/templates"); 60 | String resourceFolderPath = String.format("%s/%s", templates.getPath(), resourceSingular); 61 | validateFileDoesNotExist(resourceFolderPath); 62 | 63 | createVirtualFileIfNotExists(swipRequest.getResourceClass().getProject(), resourceFolderPath, true); 64 | 65 | createBaseResourceView(templates); 66 | createEditResourceView(templates); 67 | } 68 | 69 | private void createCssStylesFile(VirtualFile staticFolder) { 70 | VirtualFile cssStylesFile = createVirtualFileIfNotExists(swipRequest.getResourceClass().getProject(), String.format("%s/swip-styles.css", staticFolder.getPath()), false); 71 | writeContentToFile(cssStylesFile, provideTemplateContent(CSS_STYLES_TEMPLATE)); 72 | } 73 | 74 | 75 | private void createBaseResourceView(VirtualFile templates) { 76 | VirtualFile baseResourceView = createVirtualFileIfNotExists(swipRequest.getResourceClass().getProject(), String.format("%s/%s.ftl", templates.getPath(), controllerGenerator.getResourceViewPath()), false); 77 | List resourceFields = extractCandidateResourceFields(field -> !field.equals(swipRequest.getResourceIdPsiField())); 78 | 79 | String resourceFormContent = extractResourceFormContent(resourceFields); 80 | 81 | String resourceTableHeadContent = resourceFields 82 | .stream() 83 | .map(field -> provideTemplateContent(RESOURCE_TABLE_HEAD_FORM_FIELD_TEMPLATE) 84 | .replace(extractTemplateReplacementValue("resourceFieldNameUpperCase"), toFirstLetterUpperCase(field.getNameIdentifier().getText()))) 85 | .collect(Collectors.joining("")); 86 | 87 | String resourceTableBodyContent = resourceFields 88 | .stream() 89 | .map(field -> provideTemplateContent(RESOURCE_TABLE_BODY_FORM_FIELD_TEMPLATE) 90 | .replace(extractTemplateReplacementValue("resourceFieldNameLowerCase"), field.getNameIdentifier().getText())) 91 | .collect(Collectors.joining("")); 92 | 93 | String baseResourceViewFtl = provideTemplateContent(BASE_RESOURCE_VIEW_TEMPLATE) 94 | .replaceAll(extractTemplateReplacementValue("baseResourceViewTitle"), toFirstLetterUpperCase(resourcePlural)) 95 | .replaceAll(extractTemplateReplacementValue("resourceBaseUri"), controllerGenerator.getResourceBaseUri()) 96 | .replaceAll(extractTemplateReplacementValue("resourceForm"), controllerGenerator.getResourceFormHolder()) 97 | .replaceAll(extractTemplateReplacementValue("resourceSearchForm"), controllerGenerator.getResourceSearchFormHolder()) 98 | .replaceAll(extractTemplateReplacementValue("resourceList"), controllerGenerator.getResourceListHolder()) 99 | .replace(extractTemplateReplacementValue("resourceFormContent"), resourceFormContent) 100 | .replace(extractTemplateReplacementValue("resourceTableHeadContent"), resourceTableHeadContent) 101 | .replace(extractTemplateReplacementValue("resourceTableBodyContent"), resourceTableBodyContent); 102 | 103 | writeContentToFile(baseResourceView, baseResourceViewFtl); 104 | } 105 | 106 | private void createEditResourceView(VirtualFile templates) { 107 | VirtualFile editResourceView = createVirtualFileIfNotExists(swipRequest.getResourceClass().getProject(), String.format("%s/%s.ftl", templates.getPath(), controllerGenerator.getEditResourceViewPath()), false); 108 | List resourceFields = extractCandidateResourceFields(field -> !field.equals(swipRequest.getResourceIdPsiField())); 109 | String resourceFormContent = extractResourceFormContent(resourceFields); 110 | String editResourceViewContent = provideTemplateContent(EDIT_RESOURCE_VIEW_TEMPLATE) 111 | .replaceAll(extractTemplateReplacementValue("editResourceViewTitle"), String.format("Edit %s", toFirstLetterUpperCase(resourceSingular))) 112 | .replaceAll(extractTemplateReplacementValue("resourceBaseUri"), controllerGenerator.getResourceBaseUri()) 113 | .replaceAll(extractTemplateReplacementValue("resourceForm"), controllerGenerator.getResourceFormHolder()) 114 | .replace(extractTemplateReplacementValue("resourceFormContent"), resourceFormContent); 115 | 116 | writeContentToFile(editResourceView, editResourceViewContent); 117 | } 118 | 119 | private String extractResourceFormContent(List resourceFields) { 120 | return resourceFields 121 | .stream() 122 | .filter(field -> !field.equals(swipRequest.getResourceIdPsiField())) 123 | .map(field -> { 124 | String resourceFieldTemplate = provideTemplateContent(RESOURCE_FORM_FIELD_TEMPLATE); 125 | return resourceFieldTemplate 126 | .replaceAll(extractTemplateReplacementValue("resourceFieldNameLowerCase"), toFirstLetterLowerCase(field.getNameIdentifier().getText())) 127 | .replaceAll(extractTemplateReplacementValue("resourceFieldNameUpperCase"), toFirstLetterUpperCase(field.getNameIdentifier().getText())) 128 | .replaceAll(extractTemplateReplacementValue("resourceFieldName"), field.getNameIdentifier().getText()) 129 | .replaceAll(extractTemplateReplacementValue("resourceForm"), controllerGenerator.getResourceFormHolder()); 130 | }).collect(Collectors.joining("")); 131 | } 132 | 133 | @NotNull 134 | private List extractCandidateResourceFields(Predicate predicate) { 135 | return Arrays.stream(psiClass.getFields()) 136 | .filter(field -> { 137 | if (field.getType() instanceof PsiPrimitiveType && !field.getType().equals(PsiType.VOID)) { 138 | return true; 139 | } 140 | if (field.getType() instanceof PsiClassType) { 141 | switch (field.getType().getPresentableText()) { 142 | case "String": 143 | case "Integer": 144 | case "Long": 145 | case "Double": 146 | case "Float": 147 | case "Short": 148 | case "Character": 149 | case "Boolean": 150 | return true; 151 | default: 152 | return false; 153 | } 154 | } 155 | return false; 156 | }) 157 | .filter(predicate) 158 | .collect(Collectors.toList()); 159 | } 160 | 161 | private VirtualFile createBaseStructureTo(Project project, String afterResources) { 162 | VirtualFile root = ProjectRootManager.getInstance(project).getContentRoots()[0]; 163 | VirtualFile src = createVirtualFileIfNotExists(project, root.getPath() + "/src", true); 164 | VirtualFile main = createVirtualFileIfNotExists(project, src.getPath() + "/main", true); 165 | VirtualFile resources = createVirtualFileIfNotExists(project, main.getPath() + "/resources", true); 166 | return createVirtualFileIfNotExists(project, resources.getPath() + afterResources, true); 167 | } 168 | 169 | private void validateFileDoesNotExist(String path) { 170 | if (new File(path).exists()) { 171 | throw new InvalidStructureException(String.format("There is already a directory '%s'. Skipping freemarker resources creation...", path)); 172 | } 173 | } 174 | 175 | private VirtualFile createVirtualFileIfNotExists(Project project, String path, boolean isDirectory) { 176 | File file = new File(path); 177 | if (!file.exists()) { 178 | if (isDirectory) { 179 | file.mkdir(); 180 | } else { 181 | try { 182 | file.createNewFile(); 183 | } catch (IOException e) { 184 | Messages.showMessageDialog(project, String.format("Could not create '%s'. Aborting.", path), "Error", Messages.getErrorIcon()); 185 | throw new InvalidStructureException(); 186 | } 187 | } 188 | } 189 | return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); 190 | } 191 | 192 | 193 | private void writeContentToFile(VirtualFile virtualFile, String content) { 194 | try { 195 | Files.write(Paths.get(virtualFile.getPath()), content.getBytes()); 196 | } catch (IOException e) { 197 | throw new OrePluginRuntimeException(String.format("Failed to write to file. Message %s: ", e.getMessage())); 198 | } 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/swip/service/RepositoryGenerator.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.swip.service; 2 | 3 | import com.intellij.openapi.vfs.VirtualFile; 4 | import com.intellij.psi.PsiClass; 5 | import com.intellij.psi.PsiDirectory; 6 | import com.intellij.psi.PsiJavaFile; 7 | import ore.plugins.idea.swip.service.base.SwipJavaCodeGenerator; 8 | import ore.plugins.idea.swip.model.SwipRequest; 9 | 10 | public class RepositoryGenerator extends SwipJavaCodeGenerator { 11 | 12 | private static final String RESOURCE_REPOSITORY_NAME_TEMPLATE = "%sResourceRepository"; 13 | private static final String REPOSITORY_ANNOTATION_QN = "org.springframework.stereotype.Repository"; 14 | 15 | public RepositoryGenerator(SwipRequest swipRequest) { 16 | super(swipRequest); 17 | } 18 | 19 | @Override 20 | public PsiClass generateJavaClass() { 21 | String fullPackagePath = getProjectRootManager().getContentRoots()[0].getPath().concat(DEFAULT_JAVA_SRC_PATH).concat(swipRequest.getResourceRepositoryPackage().replaceAll("\\.", "/")); 22 | VirtualFile vfPackage = createFolderIfNotExists(fullPackagePath); 23 | PsiDirectory pdPackage = getPsiManager().findDirectory(vfPackage); 24 | return createRepositoryInterface(pdPackage); 25 | } 26 | 27 | private PsiClass createRepositoryInterface(PsiDirectory psiDirectory) { 28 | String resourceRepositoryName = String.format(RESOURCE_REPOSITORY_NAME_TEMPLATE, swipRequest.getResourceClass().getName()); 29 | PsiJavaFile resourceRepositoryFile = createJavaFileInDirectoryWithPackage(psiDirectory, resourceRepositoryName, swipRequest.getResourceRepositoryPackage()); 30 | 31 | PsiClass resourceRepository = getElementFactory().createInterface(resourceRepositoryName); 32 | 33 | addQualifiedAnnotationNameTo(REPOSITORY_ANNOTATION_QN, resourceRepository); 34 | 35 | String crudRepositoryQn = String.format("org.springframework.data.repository.CrudRepository<%s,%s>", 36 | swipRequest.getResourceClass().getQualifiedName(), 37 | swipRequest.getResourceIdQualifiedName()); 38 | 39 | addQualifiedExtendsToClass(crudRepositoryQn, resourceRepository); 40 | 41 | getJavaCodeStyleManager().shortenClassReferences(resourceRepository); 42 | resourceRepositoryFile.add(resourceRepository); 43 | return resourceRepository; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/swip/service/ResourcePersistableGenerator.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.swip.service; 2 | 3 | import com.intellij.psi.PsiClass; 4 | import com.intellij.psi.PsiMethod; 5 | import ore.plugins.idea.swip.service.base.SwipJavaCodeGenerator; 6 | import ore.plugins.idea.swip.model.SwipRequest; 7 | 8 | public class ResourcePersistableGenerator extends SwipJavaCodeGenerator { 9 | 10 | public ResourcePersistableGenerator(SwipRequest swipRequest) { 11 | super(swipRequest); 12 | } 13 | 14 | @Override 15 | public PsiClass generateJavaClass() { 16 | String resourcePersistableQualifiedName = String.format("ore.spring.web.initializr.domain.ResourcePersistable<%s>", swipRequest.getResourceIdQualifiedName()); 17 | addQualifiedImplementsToClass(resourcePersistableQualifiedName, swipRequest.getResourceClass()); 18 | swipRequest.getResourceClass().add(extractGetIdMethod()); 19 | getJavaCodeStyleManager().shortenClassReferences(swipRequest.getResourceClass()); 20 | return swipRequest.getResourceClass(); 21 | } 22 | 23 | private PsiMethod extractGetIdMethod() { 24 | PsiMethod psiMethod = getElementFactory().createMethodFromText(String.format("public %s getResourcePersistableId() { return this.%s; }", swipRequest.getResourceIdQualifiedName(), swipRequest.getResourceIdPsiField().getNameIdentifier().getText()), swipRequest.getResourceClass()); 25 | addOverrideTo(psiMethod); 26 | return psiMethod; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/swip/service/ServiceGenerator.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.swip.service; 2 | 3 | import com.intellij.openapi.vfs.VirtualFile; 4 | import com.intellij.psi.*; 5 | import com.intellij.psi.util.PsiUtil; 6 | import ore.plugins.idea.swip.model.SwipRequest; 7 | import ore.plugins.idea.swip.service.base.SwipJavaCodeGenerator; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | import static ore.plugins.idea.lib.utils.FormatUtils.toFirstLetterLowerCase; 14 | 15 | public class ServiceGenerator extends SwipJavaCodeGenerator { 16 | 17 | private static final String RESOURCE_SERVICE_NAME_TEMPLATE = "%sResourceService"; 18 | private static final String SERVICE_ANNOTATION_QN = "org.springframework.stereotype.Service"; 19 | 20 | private PsiClass resourceRepository; 21 | 22 | public ServiceGenerator(SwipRequest swipRequest, PsiClass resourceRepository) { 23 | super(swipRequest); 24 | this.resourceRepository = resourceRepository; 25 | } 26 | 27 | 28 | @Override 29 | public PsiClass generateJavaClass() { 30 | String fullPackagePath = getProjectRootManager().getContentRoots()[0].getPath().concat(DEFAULT_JAVA_SRC_PATH).concat(swipRequest.getResourceServicePackage().replaceAll("\\.", "/")); 31 | VirtualFile vfPackage = createFolderIfNotExists(fullPackagePath); 32 | PsiDirectory pdPackage = getPsiManager().findDirectory(vfPackage); 33 | return createResourceService(pdPackage); 34 | } 35 | 36 | private PsiClass createResourceService(PsiDirectory psiDirectory) { 37 | String resourceServiceName = String.format(RESOURCE_SERVICE_NAME_TEMPLATE, swipRequest.getResourceClass().getName()); 38 | PsiJavaFile resourceServiceFile = createJavaFileInDirectoryWithPackage(psiDirectory, resourceServiceName, swipRequest.getResourceServicePackage()); 39 | 40 | PsiClass resourceService = getElementFactory().createClass(resourceServiceName); 41 | addQualifiedAnnotationNameTo(SERVICE_ANNOTATION_QN, resourceService); 42 | 43 | String resourceServiceQualifiedName = String.format("ore.spring.web.initializr.service.ResourceService<%s,%s,%s>", 44 | swipRequest.getResourceClass().getQualifiedName(), 45 | swipRequest.getResourceSearchFormClass().getQualifiedName(), 46 | swipRequest.getResourceIdQualifiedName()); 47 | addQualifiedExtendsToClass(resourceServiceQualifiedName, resourceService); 48 | 49 | String qualifiedResourceRepositoryName = String.format("org.springframework.data.repository.CrudRepository<%s,%s>", 50 | swipRequest.getResourceClass().getQualifiedName(), swipRequest.getResourceIdQualifiedName()); 51 | 52 | PsiField resourceRepositoryElement = getElementFactory().createFieldFromText(String.format("private final %s %s;", qualifiedResourceRepositoryName, toFirstLetterLowerCase(Objects.requireNonNull(resourceRepository.getName()))), resourceService.getContext()); 53 | 54 | List constructorArguments = Collections.singletonList(resourceRepositoryElement); 55 | 56 | PsiMethod constructor = extractConstructorForClass(resourceService, constructorArguments, constructorArguments, Collections.singletonList(resourceRepositoryElement.getNameIdentifier().getText())); 57 | PsiUtil.setModifierProperty(constructor, PsiModifier.PUBLIC, true); 58 | addAutowiredTo(constructor); 59 | resourceService.add(constructor); 60 | 61 | getJavaCodeStyleManager().shortenClassReferences(resourceService); 62 | resourceServiceFile.add(resourceService); 63 | 64 | return resourceService; 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/ore/plugins/idea/swip/service/base/SwipJavaCodeGenerator.java: -------------------------------------------------------------------------------- 1 | package ore.plugins.idea.swip.service.base; 2 | 3 | import com.intellij.psi.PsiMember; 4 | import ore.plugins.idea.lib.service.JavaCodeGenerator; 5 | import ore.plugins.idea.swip.model.SwipRequest; 6 | 7 | public abstract class SwipJavaCodeGenerator extends JavaCodeGenerator { 8 | 9 | protected SwipRequest swipRequest; 10 | 11 | public SwipJavaCodeGenerator(SwipRequest swipRequest) { 12 | super(swipRequest.getResourceClass()); 13 | this.swipRequest = swipRequest; 14 | } 15 | 16 | protected void addAutowiredTo(PsiMember psiMember) { 17 | addQualifiedAnnotationNameTo("org.springframework.beans.factory.annotation.Autowired", psiMember); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | spring.web.initializr.plugin 3 | Swip (Spring Web Initializr) 4 | Orestes Polyzos 5 | 6 | 7 | 8 | 9 | Create a fully functional (Spring Boot) WebApp with just a few clicks 11 |

Swip will generate all the files required for a functional web application based on your domain classes.

12 |

13 | It is based on the convention over configuration paradigm, so during the generation phase the actual 14 | configuration is limited, but after the files have been generated they can obviously be configured/changed as needed. 15 |

16 |
17 |

How To

18 |

Create a Java class for your entity (e.g. User) with all its' desired fields (e.g. firstName, lastName, etc...).

19 |

Right-click inside the class to get the editor menu.

20 |

Click the `Swip` option and follow the instructions.

21 |
22 |

About

23 |

24 | Swip is an open-source project. You can get more information at its' Github Repo. 25 |

26 | ]]> 27 |
28 | 29 | 30 | 32 |
  • 33 | 1.1.2 34 |
      35 |
    • Fixes NPE when caret is inside a .java file but outside of a Java Class
    • 36 |
    • Adds support for IntelliJ IDEA until build '193.*'
    • 37 |
    38 |
  • 39 |
  • 40 | 1.1.1 41 |
      42 |
    • Adds support for IntelliJ IDEA until build '192.*'
    • 43 |
    44 |
  • 45 |
  • 46 | 1.1.0 47 |
      48 |
    • Adds support for spring-boot-starter-parent from version '1.5.20.RELEASE' up to LATEST
    • 49 |
    • Adds support for older IntelliJ IDEA since build '171.4424.54'
    • 50 |
    51 |
  • 52 |
  • 53 | 1.0.0 - First release 54 |
      55 |
    • Adds support/dependency for Maven
    • 56 |
    • Adds support for spring-boot-starter-web
    • 57 |
    • Adds support for spring-boot-starter-data-jpa
    • 58 |
    • Adds support for spring-boot-starter-freemarker
    • 59 |
    60 |
  • 61 | 62 | ]]> 63 |
    64 | 65 | com.intellij.modules.lang 66 | com.intellij.modules.java 67 | com.intellij.modules.ultimate 68 | com.intellij.modules.idea 69 | com.intellij.modules.idea.ultimate 70 | 71 | 72 | 74 | 75 | 76 | 77 | 78 |
    79 | -------------------------------------------------------------------------------- /src/main/resources/templates/freemarker/base-resource-view: -------------------------------------------------------------------------------- 1 | <#import "/spring.ftl" as spring/> 2 | 3 | 4 | %<baseResourceViewTitle> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 17 | <#if infoMessage??> 18 |
    19 |
    ${infoMessage}
    20 |
    21 | 22 | <#if errorMessage??> 23 |
    24 |
    ${errorMessage}
    25 |
    26 | 27 |
    28 | 29 |
    30 |
    31 | % 32 |
    33 | 34 | 35 | 36 | 37 |
    38 | 39 |
    40 |
    41 | 42 |
    43 | 44 |
    45 |
    46 |
    Specific search fields should be added here
    47 |
    48 | 49 | 50 | 51 | 52 |
    53 |
    54 |
    55 |
    56 |
    57 |
    58 | <#if %?has_content> 59 |
    60 | 61 |
    62 |
    63 | 64 | 65 | 66 | 67 | % 68 | 69 | 70 | 71 | 72 | 73 | <#list % as resource> 74 | 75 | 76 | % 77 | 84 | 91 | 92 | 93 | 94 |
    IDEditDelete
    ${resource.resourcePersistableId!""} 78 |
    79 | 82 |
    83 |
    85 |
    86 | 89 |
    90 |
    95 |
    96 |
    97 |
    98 | 99 |
    100 | 101 | -------------------------------------------------------------------------------- /src/main/resources/templates/freemarker/edit-resource-view: -------------------------------------------------------------------------------- 1 | <#import "/spring.ftl" as spring/> 2 | 3 | 4 | %<editResourceViewTitle> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
    14 | 17 | <#if infoMessage??> 18 |
    19 |
    ${infoMessage}
    20 |
    21 | 22 | <#if errorMessage??> 23 |
    24 |
    ${errorMessage}
    25 |
    26 | 27 |
    28 | 29 |
    30 |
    31 |
    32 | 33 | <@spring.bind "%.resourcePersistableId"/> 34 | 35 | <#list spring.status.errorMessages as error> 36 | ${error} 37 | 38 |
    39 | % 40 |
    41 | 42 | 43 | 44 | 45 |
    46 | 47 |
    48 |
    49 |
    50 | 51 | -------------------------------------------------------------------------------- /src/main/resources/templates/freemarker/section/resource-form-field: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 | <@spring.bind "%.%"/> 5 | 6 | <#list spring.status.errorMessages as error> 7 | ${error} 8 | 9 |
    10 | -------------------------------------------------------------------------------- /src/main/resources/templates/freemarker/section/resource-table-body-field: -------------------------------------------------------------------------------- 1 | 2 | ${resource.%!""} 3 | -------------------------------------------------------------------------------- /src/main/resources/templates/freemarker/section/resource-table-head-field: -------------------------------------------------------------------------------- 1 | 2 | % 3 | -------------------------------------------------------------------------------- /src/main/resources/templates/freemarker/swip-styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: mediumseagreen; 3 | } 4 | 5 | div.page-header { 6 | color: white; 7 | } 8 | 9 | .infoMessage { 10 | font-weight: bold; 11 | } 12 | 13 | .errorMessage { 14 | color: red; 15 | font-weight: bold; 16 | } 17 | 18 | .attention { 19 | animation: blinker 1s linear infinite; 20 | font-size: large; 21 | font-weight: bold; 22 | font-style: italic; 23 | color: red; 24 | } 25 | 26 | @keyframes blinker { 27 | 50% { 28 | opacity: 0; 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/resources/templates/maven/spring-web-initializr-dependency: -------------------------------------------------------------------------------- 1 | Swip absolutely requires the spring-web-initializr dependency to work. 2 | Please add it into the pom.xml and try again. 3 | 4 | 5 | io.github.orpolyzos 6 | spring-web-initializr 7 | 1.1.0 8 | 9 | --------------------------------------------------------------------------------