Java tutorial
/** * Copyright 2013-2018 the original author or authors from the Jeddict project (https://jeddict.github.io/). * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package io.github.jeddict.reveng.klass; import com.github.javaparser.ParseProblemException; import com.github.javaparser.resolution.UnsolvedSymbolException; import io.github.jeddict.analytics.JeddictLogger; import static io.github.jeddict.jcode.util.Constants.JAVA_EXT_SUFFIX; import static io.github.jeddict.jcode.util.ProjectHelper.findSourceGroupForFile; import static io.github.jeddict.jcode.util.ProjectHelper.getFolderSourceGroup; import io.github.jeddict.jpa.modeler.initializer.JPAFileActionListener; import io.github.jeddict.jpa.modeler.initializer.JPAModelerUtil; import io.github.jeddict.jpa.spec.Embeddable; import io.github.jeddict.jpa.spec.Entity; import io.github.jeddict.jpa.spec.EntityMappings; import io.github.jeddict.jpa.spec.ManagedClass; import io.github.jeddict.jpa.spec.MappedSuperclass; import io.github.jeddict.jpa.spec.bean.BeanClass; import io.github.jeddict.jpa.spec.extend.IPersistenceAttributes; import io.github.jeddict.jpa.spec.extend.JavaClass; import io.github.jeddict.jpa.spec.extend.RelationAttribute; import io.github.jeddict.reveng.BaseWizardDescriptor; import io.github.jeddict.reveng.JCREProcessor; import static io.github.jeddict.reveng.settings.RevengPanel.isIncludeReferencedClasses; import io.github.jeddict.source.ClassExplorer; import io.github.jeddict.source.JavaSourceParserUtil; import io.github.jeddict.source.SourceExplorer; import java.awt.Window; import java.awt.event.ActionEvent; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import static java.util.Collections.singleton; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import static java.util.logging.Level.FINE; import static java.util.logging.Level.INFO; import java.util.logging.Logger; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toSet; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JOptionPane; import static javax.swing.JOptionPane.showInputDialog; import javax.swing.SwingUtilities; import javax.swing.UIManager; import static org.apache.commons.lang.StringUtils.isEmpty; import org.netbeans.api.progress.aggregate.AggregateProgressFactory; import org.netbeans.api.progress.aggregate.AggregateProgressHandle; import org.netbeans.api.progress.aggregate.ProgressContributor; import org.netbeans.api.project.Project; import org.netbeans.api.project.SourceGroup; import org.netbeans.api.templates.TemplateRegistration; import org.netbeans.modeler.core.ModelerFile; import org.netbeans.modeler.core.exception.ProcessInterruptedException; import org.netbeans.modules.j2ee.persistence.api.EntityClassScope; import org.netbeans.modules.j2ee.persistence.dd.common.PersistenceUnit; import org.netbeans.modules.j2ee.persistence.provider.InvalidPersistenceXmlException; import org.netbeans.modules.j2ee.persistence.provider.ProviderUtil; import org.netbeans.modules.j2ee.persistence.wizard.EntityClosure; import org.netbeans.modules.j2ee.persistence.wizard.PersistenceClientEntitySelection; import org.netbeans.modules.j2ee.persistence.wizard.Util; import static org.netbeans.modules.j2ee.persistence.wizard.WizardProperties.CREATE_PERSISTENCE_UNIT; import static org.netbeans.modules.j2ee.persistence.wizard.WizardProperties.ENTITY_CLASS; import org.netbeans.modules.j2ee.persistence.wizard.fromdb.ProgressPanel; import org.netbeans.modules.j2ee.persistence.wizard.jpacontroller.ProgressReporter; import org.netbeans.modules.j2ee.persistence.wizard.jpacontroller.ProgressReporterDelegate; import org.netbeans.modules.j2ee.persistence.wizard.unit.PersistenceUnitWizardDescriptor; import org.netbeans.modules.j2ee.persistence.wizard.unit.PersistenceUnitWizardPanel.TableGeneration; import org.netbeans.spi.project.ui.templates.support.Templates; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import static org.openide.NotifyDescriptor.ERROR_MESSAGE; import static org.openide.NotifyDescriptor.OK_CANCEL_OPTION; import org.openide.WizardDescriptor; import org.openide.awt.NotificationDisplayer; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataFolder; import org.openide.util.ImageUtilities; import static org.openide.util.NbBundle.getMessage; import org.openide.util.RequestProcessor; import org.openide.util.lookup.ServiceProvider; import org.openide.windows.WindowManager; @ServiceProvider(service = JCREProcessor.class) @TemplateRegistration(folder = "Persistence", position = 2, displayName = "#RevEngWizardDescriptor_displayName", iconBase = "io/github/jeddict/reveng/klass/resources/JPA_FILE_ICON.png", description = "resources/JPA_JCRE_DESC.html", category = "persistence") public final class RevEngWizardDescriptor extends BaseWizardDescriptor implements JCREProcessor { private PersistenceUnitWizardDescriptor puPanel; private WizardDescriptor wizard; private Project project; private static final Logger LOG = Logger.getLogger(RevEngWizardDescriptor.class.getName()); @Override public void initialize(WizardDescriptor wizard) { this.wizard = wizard; index = 0; // obtaining target folder if (project == null) { project = Templates.getProject(wizard); } WizardDescriptor.Panel secondPanel = new ValidationPanel(project, new PersistenceClientEntitySelection( getMessage(RevEngWizardDescriptor.class, "LBL_EntityClasses"), null, wizard)); // NOI18N WizardDescriptor.Panel thirdPanel = new JPAModelSetupPanel(project, wizard); String names[]; boolean noPuNeeded = true; try { noPuNeeded = ProviderUtil.persistenceExists(project) || !ProviderUtil.isValidServerInstanceOrNone(project); } catch (InvalidPersistenceXmlException ex) { LOG.log(FINE, "Invalid persistence.xml: {0}", ex.getPath()); //NOI18N } if (!noPuNeeded) { puPanel = new PersistenceUnitWizardDescriptor(project); panels = new ArrayList<>(); panels.add(secondPanel); panels.add(thirdPanel); panels.add(puPanel); names = new String[] { getMessage(RevEngWizardDescriptor.class, "LBL_EntityClasses"), getMessage(RevEngWizardDescriptor.class, "LBL_JPA_Model"), getMessage(RevEngWizardDescriptor.class, "LBL_PersistenceUnitSetup") }; } else { panels = new ArrayList<>(); panels.add(secondPanel); panels.add(thirdPanel); names = new String[] { getMessage(RevEngWizardDescriptor.class, "LBL_EntityClasses"), getMessage(RevEngWizardDescriptor.class, "LBL_JPA_Model") }; } wizard.putProperty("NewFileWizard_Title", getMessage(RevEngWizardDescriptor.class, "NewFileWizard_Title")); org.netbeans.modeler.component.Wizards.mergeSteps(wizard, panels.toArray(new WizardDescriptor.Panel[0]), names); } /** * Create new diagram via class reverse engineering * * @return * @throws IOException */ @Override public Set<?> instantiate() throws IOException { final Set<String> entities = new HashSet<>((List) wizard.getProperty(ENTITY_CLASS)); if (project == null) { project = Templates.getProject(wizard); } final FileObject targetFilePath = Templates.getTargetFolder(wizard); FileObject sourcePackage = getFolderSourceGroup(targetFilePath).getRootFolder(); final String targetFileName = Templates.getTargetName(wizard); boolean createPersistenceUnit = (Boolean) wizard.getProperty(CREATE_PERSISTENCE_UNIT); if (createPersistenceUnit) { PersistenceUnit persistenceUnit = Util.buildPersistenceUnitUsingData(project, puPanel.getPersistenceUnitName(), nonNull(puPanel.getPersistenceConnection()) ? puPanel.getPersistenceConnection().getName() : puPanel.getDatasource(), TableGeneration.NONE, puPanel.getSelectedProvider()); if (nonNull(persistenceUnit)) { ProviderUtil.setTableGeneration(persistenceUnit, puPanel.getTableGeneration(), puPanel.getSelectedProvider()); Util.addPersistenceUnitToProject(project, persistenceUnit); } } final String title = getMessage(RevEngWizardDescriptor.class, "TITLE_Progress_JPA_Model"); EntityMappings entityMappings = EntityMappings.getNewInstance(JPAModelerUtil.getModelerFileVersion()); entityMappings.setGenerated(); instantiateJCREProcess(title, entityMappings, sourcePackage, entities, targetFilePath, targetFileName, isIncludeReferencedClasses(), true, null); return singleton(DataFolder.findFolder(targetFilePath)); } /** * Update the complete existing diagram with existing class * * @param modelerFile */ @Override public void syncExistingDiagram(ModelerFile modelerFile) { try { this.project = modelerFile.getProject(); EntityMappings entityMappings = (EntityMappings) modelerFile.getDefinitionElement(); FileObject targetFilePath = modelerFile.getFileObject().getParent(); FileObject sourcePackage = getFolderSourceGroup(targetFilePath).getRootFolder(); String targetFileName = modelerFile.getFileObject().getName(); Set<String> entities = entityMappings.getEntity().stream().map(JavaClass::getFQN).collect(toSet()); EntityClassScope entityClassScope = EntityClassScope.getEntityClassScope(project.getProjectDirectory()); EntityClosure entityClosure = EntityClosure.create(entityClassScope, project); entityClosure.setClosureEnabled(true); entityClosure.addEntities(entities); if (entityClosure.getSelectedEntities().size() > entities.size()) { showInputDialog("Entity closure have more entity"); } FileObject backupFile = null; try { String backupFileName = targetFileName + "_backup"; backupFile = FileUtil.copyFile(modelerFile.getFileObject(), targetFilePath, backupFileName); } catch (IOException ex) { } EntityMappings newEntityMappings = EntityMappings .getNewInstance(JPAModelerUtil.getModelerFileVersion()); newEntityMappings.setGenerated(); instantiateJCREProcess("Updating Design", newEntityMappings, sourcePackage, entities, targetFilePath, targetFileName, isIncludeReferencedClasses(), false, null); notifyBackupCreation(modelerFile, backupFile); } catch (IOException ex) { modelerFile.handleException(ex); } } /** * Drop classes in existing diagram * * @param modelerFile * @param entityFiles */ @Override public void processDropedClasses(ModelerFile modelerFile, List<File> entityFiles) { List<FileObject> fileObjects = entityFiles.stream().map(FileUtil::toFileObject).collect(toList()); Map<SourceGroup, Set<String>> sourceGroups = new HashMap<>(); for (FileObject fileObject : fileObjects) { SourceGroup sourceGroup = findSourceGroupForFile(fileObject); if (isNull(sourceGroup)) { continue; } if (!sourceGroups.containsKey(sourceGroup)) { sourceGroups.put(sourceGroup, new HashSet<>()); } String entity = fileObject.getPath().substring(sourceGroup.getRootFolder().getPath().length() + 1, fileObject.getPath().lastIndexOf(JAVA_EXT_SUFFIX)).replace('/', '.'); sourceGroups.get(sourceGroup).add(entity); } try { EntityMappings entityMappings = (EntityMappings) modelerFile.getModelerScene().getBaseElementSpec(); int totalSource = sourceGroups.keySet().size(); int sourceIndex = 0; for (SourceGroup sourceGroup : sourceGroups.keySet()) { int currentSource = ++sourceIndex; Set<String> entities = sourceGroups.get(sourceGroup); instantiateJCREProcess("Importing classes", entityMappings, sourceGroup.getRootFolder(), entities, null, null, isIncludeReferencedClasses(), false, () -> { if (totalSource == currentSource) { modelerFile.save(true); modelerFile.close(); JPAFileActionListener.open(modelerFile); } }); } } catch (IOException ex) { modelerFile.handleException(ex); } } private void instantiateJCREProcess(final String title, final EntityMappings entityMappings, final FileObject sourcePackage, final Set<String> entities, final FileObject targetFilePath, final String targetFileName, final boolean includeReference, final boolean softWrite, final Runnable callback) throws IOException { final ProgressContributor progressContributor = AggregateProgressFactory.createProgressContributor(title); final AggregateProgressHandle handle = AggregateProgressFactory.createHandle(title, new ProgressContributor[] { progressContributor }, null, null); final ProgressPanel progressPanel = new ProgressPanel(); final JComponent progressComponent = AggregateProgressFactory.createProgressComponent(handle); final ProgressReporter reporter = new ProgressReporterDelegate(progressContributor, progressPanel); final Runnable action = () -> { try { handle.start(); int progressStepCount = getProgressStepCount(entities.size()); progressContributor.start(progressStepCount); generateJPAModel(reporter, entityMappings, sourcePackage, entities, targetFilePath, targetFileName, includeReference, softWrite, true); progressContributor.progress(progressStepCount); } catch (IOException | UnsolvedSymbolException ex) { LOG.log(INFO, null, ex); notifyError(ex.getLocalizedMessage()); } catch (ParseProblemException ex) { LOG.log(INFO, null, ex); String message = ex.getLocalizedMessage().substring(0, ex.getLocalizedMessage().indexOf("Problem stacktrace")); notifyError(message); } catch (UnsupportedOperationException ex) { LOG.log(INFO, null, ex); String message = ex.getMessage().isEmpty() ? ex.toString() : ex.getMessage(); notifyError(message); } catch (ProcessInterruptedException ex) { LOG.log(INFO, null, ex); } finally { progressContributor.finish(); SwingUtilities.invokeLater(progressPanel::close); JeddictLogger.createModelerFile("JPA-REV-ENG"); handle.finish(); } }; SwingUtilities.invokeLater(new Runnable() { private boolean first = true; @Override public void run() { if (!first) { RequestProcessor.getDefault().post(action); progressPanel.open(progressComponent, title); if (nonNull(callback)) { callback.run(); } } else { first = false; SwingUtilities.invokeLater(this); } } }); } private void notifyError(String errorMessage) { NotifyDescriptor nd = new NotifyDescriptor.Message( "Please report the issue with NetBeans IDE log : " + errorMessage, ERROR_MESSAGE); DialogDisplayer.getDefault().notify(nd); } private EntityMappings generateJPAModel(final ProgressReporter reporter, EntityMappings entityMappings, final FileObject sourcePackage, final Set<String> entities, final FileObject targetFilePath, final String targetFileName, final boolean includeReference, final boolean softWrite, final boolean autoOpen) throws IOException, ProcessInterruptedException { int progressIndex = 0; String progressMsg = getMessage(RevEngWizardDescriptor.class, "MSG_Progress_JPA_Model_Pre"); //NOI18N; reporter.progress(progressMsg, progressIndex++); List<String> missingEntities = new ArrayList<>(); SourceExplorer source = new SourceExplorer(sourcePackage, entityMappings, entities, includeReference); for (String entityClassFQN : entities) { try { source.createClass(entityClassFQN); } catch (FileNotFoundException ex) { ex.printStackTrace(); missingEntities.add(entityClassFQN); } } progressIndex = loadJavaClasses(reporter, progressIndex, source.getClasses(), entityMappings); List<ClassExplorer> classes = checkReferencedClasses(source, missingEntities, includeReference); while (!classes.isEmpty()) { progressIndex = loadJavaClasses(reporter, progressIndex, classes, entityMappings); classes = checkReferencedClasses(source, missingEntities, includeReference); } if (!missingEntities.isEmpty()) { final String title, _package; StringBuilder message = new StringBuilder(); if (missingEntities.size() == 1) { title = "Conflict detected - Entity not found"; message.append(JavaSourceParserUtil.simpleClassName(missingEntities.get(0))).append(" Entity is "); } else { title = "Conflict detected - Entities not found"; message.append("Entities ").append(missingEntities.stream() .map(e -> JavaSourceParserUtil.simpleClassName(e)).collect(toList())).append(" are "); } if (isEmpty(entityMappings.getPackage())) { _package = "<default_root_package>"; } else { _package = entityMappings.getPackage(); } message.append("missing in Project classpath[").append(_package) .append("]. \n Would like to cancel the process ?"); SwingUtilities.invokeLater(() -> { JButton cancel = new JButton("Cancel import process (Recommended)"); JButton procced = new JButton("Procced"); cancel.addActionListener((ActionEvent e) -> { Window w = SwingUtilities.getWindowAncestor(cancel); if (w != null) { w.setVisible(false); } StringBuilder sb = new StringBuilder(); sb.append('\n').append("You have following option to resolve conflict :").append('\n') .append('\n'); sb.append( "1- New File > Persistence > JPA Diagram from Reverse Engineering (Manually select entities)") .append('\n'); sb.append( "2- Recover missing entities manually > Reopen diagram file > Import entities again"); NotifyDescriptor nd = new NotifyDescriptor.Message(sb.toString(), NotifyDescriptor.INFORMATION_MESSAGE); DialogDisplayer.getDefault().notify(nd); }); procced.addActionListener(e -> { Window window = SwingUtilities.getWindowAncestor(cancel); if (nonNull(window)) { window.setVisible(false); } manageEntityMapping(entityMappings); if (nonNull(targetFilePath) && nonNull(targetFileName)) { JPAModelerUtil.createNewModelerFile(entityMappings, targetFilePath, targetFileName, softWrite, autoOpen); } }); JOptionPane.showOptionDialog(WindowManager.getDefault().getMainWindow(), message.toString(), title, OK_CANCEL_OPTION, ERROR_MESSAGE, UIManager.getIcon("OptionPane.errorIcon"), new Object[] { cancel, procced }, cancel); }); } else { manageEntityMapping(entityMappings); if (nonNull(targetFilePath) && nonNull(targetFileName)) { JPAModelerUtil.createNewModelerFile(entityMappings, targetFilePath, targetFileName, softWrite, autoOpen); } return entityMappings; } throw new ProcessInterruptedException(); } private int loadJavaClasses(final ProgressReporter reporter, int progressIndex, final List<ClassExplorer> selectedClasses, final EntityMappings entityMappings) { List<ClassExplorer> classes = new CopyOnWriteArrayList<>(selectedClasses); for (ClassExplorer clazz : classes) { if (clazz.isEntity() || clazz.isMappedSuperclass()) { String progressMsg = getMessage(RevEngWizardDescriptor.class, "MSG_Progress_JPA_Class_Parsing", clazz.getName() + JAVA_EXT_SUFFIX);//NOI18N reporter.progress(progressMsg, progressIndex++); parseJavaClass(entityMappings, clazz); } } for (ClassExplorer clazz : classes) { if (!clazz.isEntity() && !clazz.isMappedSuperclass()) { String progressMsg = getMessage(RevEngWizardDescriptor.class, "MSG_Progress_JPA_Class_Parsing", clazz.getName() + JAVA_EXT_SUFFIX);//NOI18N reporter.progress(progressMsg, progressIndex++); parseJavaClass(entityMappings, clazz); } } return progressIndex; } private List<ClassExplorer> checkReferencedClasses(SourceExplorer source, List<String> missingEntities, boolean includeReference) { List<ClassExplorer> newReferencedClass = new ArrayList<>(); EntityMappings entityMappings = source.getEntityMappings(); if (includeReference) { // manageSiblingAttribute for MappedSuperClass and Embeddable is not required for (DBRE) DB REV ENG CASE for (ManagedClass<IPersistenceAttributes> managedClass : entityMappings.getAllManagedClass()) { for (RelationAttribute attribute : new ArrayList<>( managedClass.getAttributes().getRelationAttributes())) { String entityClass = attribute.getTargetEntity(); String entityClassFQN = attribute.getTargetEntityFQN(); if (entityMappings.findAllJavaClass(entityClass).isEmpty()) { try { source.createClass(entityClassFQN).ifPresent(newReferencedClass::add); } catch (FileNotFoundException ex) { ex.printStackTrace(); missingEntities.add(entityClassFQN); } } } } } return newReferencedClass; } private Optional<JavaClass> parseJavaClass(final EntityMappings entityMappings, final ClassExplorer clazz) { JavaClass javaClass = null; String className = clazz.getName(); if (clazz.isEntity()) { if (!entityMappings.findEntity(className).isPresent()) { Entity entity; javaClass = entity = new Entity(); javaClass.setClazz(clazz.getName()); entityMappings.addEntity(entity); entity.load(clazz); } } else if (clazz.isMappedSuperclass()) { if (!entityMappings.findMappedSuperclass(className).isPresent()) { MappedSuperclass mappedSuperclass; javaClass = mappedSuperclass = new MappedSuperclass(); javaClass.setClazz(clazz.getName()); entityMappings.addMappedSuperclass(mappedSuperclass); mappedSuperclass.load(clazz); } } else if (clazz.isEmbeddable()) { if (!entityMappings.isCompositePrimaryKeyClass(clazz.getName()) && !entityMappings.findEmbeddable(className).isPresent()) { Embeddable embeddable; javaClass = embeddable = new Embeddable(); javaClass.setClazz(clazz.getName()); entityMappings.addEmbeddable(embeddable); embeddable.load(clazz); } } else if (!clazz.isEnum() && !clazz.isInterface()) { if (!entityMappings.isCompositePrimaryKeyClass(clazz.getName()) && !entityMappings.findBeanClass(className).isPresent()) { BeanClass beanClass; javaClass = beanClass = new BeanClass(); javaClass.setClazz(clazz.getName()); entityMappings.addBeanClass(beanClass); beanClass.load(clazz); } } return Optional.ofNullable(javaClass); } @Override public String name() { return getMessage(RevEngWizardDescriptor.class, "LBL_WizardTitle"); } public static int getProgressStepCount(int entityCount) { return entityCount + 2; } private static void manageEntityMapping(EntityMappings entityMappings) { entityMappings.manageRefId(); entityMappings.repairDefinition(JPAModelerUtil.IO, true); // entityMappingsSpec.manageJoinColumnRefName(); } private static void notifyBackupCreation(ModelerFile file, FileObject backupFile) { if (backupFile == null) { return; } NotificationDisplayer.getDefault().notify("Backup created", ImageUtilities.image2Icon(file.getIcon()), "Previous state of file has been saved to " + backupFile.getName() + ". Click here to delete it", (ActionEvent e) -> { try { if (backupFile.isValid()) { backupFile.delete(); } } catch (IOException ex) { file.handleException(ex); } }, NotificationDisplayer.Priority.NORMAL, NotificationDisplayer.Category.INFO); } /** * A panel which checks that the target project has a valid server set * otherwise it delegates to the real panel. */ private static class ValidationPanel extends DelegatingWizardDescriptorPanel { private ValidationPanel(Project project, WizardDescriptor.Panel delegate) { super(project, delegate); } } }