377790 - Add action on class to "Add to persistence.xml"
diff --git a/jpa/plugins/org.eclipse.jpt.jpa.core/src/org/eclipse/jpt/jpa/core/context/persistence/PersistenceUnit.java b/jpa/plugins/org.eclipse.jpt.jpa.core/src/org/eclipse/jpt/jpa/core/context/persistence/PersistenceUnit.java
index c0b248c..929eec7 100644
--- a/jpa/plugins/org.eclipse.jpt.jpa.core/src/org/eclipse/jpt/jpa/core/context/persistence/PersistenceUnit.java
+++ b/jpa/plugins/org.eclipse.jpt.jpa.core/src/org/eclipse/jpt/jpa/core/context/persistence/PersistenceUnit.java
@@ -827,6 +827,11 @@
 	 */
 	int findInsertLocationForMappingFileRef();
 	
+	/**
+	 * Add the given list of class names to the persistence.xml excluding 
+	 * those that already exist.
+	 */
+	void addClasses(Iterable<String> classNames, IProgressMonitor monitor);
 
 	// ********** validation **********
 
diff --git a/jpa/plugins/org.eclipse.jpt.jpa.core/src/org/eclipse/jpt/jpa/core/internal/context/persistence/AbstractPersistenceUnit.java b/jpa/plugins/org.eclipse.jpt.jpa.core/src/org/eclipse/jpt/jpa/core/internal/context/persistence/AbstractPersistenceUnit.java
index 7d2742c..714e219 100644
--- a/jpa/plugins/org.eclipse.jpt.jpa.core/src/org/eclipse/jpt/jpa/core/internal/context/persistence/AbstractPersistenceUnit.java
+++ b/jpa/plugins/org.eclipse.jpt.jpa.core/src/org/eclipse/jpt/jpa/core/internal/context/persistence/AbstractPersistenceUnit.java
@@ -2181,6 +2181,34 @@
 		}
 		throw new IllegalArgumentException("Illegal type mapping key: " + key); //$NON-NLS-1$
 	}
+	
+	
+	// ********** add classes to persistence unit **********
+
+	public void addClasses(Iterable<String> classNames, IProgressMonitor monitor) {
+		SubMonitor sm = SubMonitor.convert(monitor, IterableTools.size(classNames));
+		for (String className : classNames) {
+			if(!classRefExists(className)) {
+				this.addSpecifiedClassRef(className);
+			}
+			sm.worked(1);
+		}
+		if (sm.isCanceled()) {
+			return;
+		}
+		
+		this.getXmlPersistenceUnit().sortClasses();
+		sm.worked(1);
+	}
+	
+	private boolean classRefExists(String className) {
+		for (ClassRef classRef : this.getSpecifiedClassRefs()) {
+			if( classRef.getClassName().equals(className)) {
+				return true;
+			}
+		}
+		return false;
+	}
 
 	// ********** misc **********
 
diff --git a/jpa/plugins/org.eclipse.jpt.jpa.ui/plugin.properties b/jpa/plugins/org.eclipse.jpt.jpa.ui/plugin.properties
index e8ce1ce..5804085 100644
--- a/jpa/plugins/org.eclipse.jpt.jpa.ui/plugin.properties
+++ b/jpa/plugins/org.eclipse.jpt.jpa.ui/plugin.properties
@@ -62,6 +62,7 @@
 synchronizeClasses = Synchronize Class List
 makePersistent = Make Persistent...
 persistenceEditor = Persistence XML Editor
+addToPersistenceUnit = Add to Persistence Unit
 
 javaPersistenceNode = Java Persistence
 jpaNode = JPA
diff --git a/jpa/plugins/org.eclipse.jpt.jpa.ui/plugin.xml b/jpa/plugins/org.eclipse.jpt.jpa.ui/plugin.xml
index 231c989..707b64f 100644
--- a/jpa/plugins/org.eclipse.jpt.jpa.ui/plugin.xml
+++ b/jpa/plugins/org.eclipse.jpt.jpa.ui/plugin.xml
@@ -360,6 +360,11 @@
 			categoryId="org.eclipse.jpt.jpa.ui.jpaMetadataConversionCommands">
 		</command>
 
+  <command
+        id="org.eclipse.jpt.jpa.ui.addToPersistenceUnit"
+        name="%addToPersistenceUnit">
+  </command>
+
 	</extension>
 
 
@@ -636,6 +641,27 @@
 			</activeWhen>
 		</handler>
 
+		<handler
+			commandId="org.eclipse.jpt.jpa.ui.addToPersistenceUnit"
+			class="org.eclipse.jpt.jpa.ui.internal.handlers.AddToPersistenceUnitHandler">
+			<enabledWhen>
+				<with variable="selection">
+					<count value="+"/>
+					<iterate>
+						<and>
+							<adapt type="org.eclipse.jdt.core.IJavaElement" />
+							<or>
+								<instanceof value="org.eclipse.jdt.core.ICompilationUnit"/>
+								<instanceof value="org.eclipse.jdt.core.IType"/>
+								<instanceof value="org.eclipse.jdt.core.IPackageFragment"/>
+								<instanceof value="org.eclipse.jdt.core.IPackageFragmentRoot"/>
+							</or>
+						</and>
+					</iterate>
+				</with>
+			</enabledWhen>
+		</handler>
+		
 	</extension>
 
 
@@ -828,6 +854,12 @@
 				<visibleWhen checkEnabled="true"/>
 			</command>
 		</menuContribution>
+		
+		<menuContribution locationURI="popup:org.eclipse.jpt.jpa.ui.menu.JpaTools">
+			<command commandId="org.eclipse.jpt.jpa.ui.addToPersistenceUnit">
+				<visibleWhen checkEnabled="true"/>
+			</command>
+		</menuContribution>
 
 		<!-- contributions to the project configure menu -->
 		<menuContribution locationURI="popup:org.eclipse.ui.projectConfigure?after=additions">
diff --git a/jpa/plugins/org.eclipse.jpt.jpa.ui/property_files/jpt_jpa_ui.properties b/jpa/plugins/org.eclipse.jpt.jpa.ui/property_files/jpt_jpa_ui.properties
index 2d371a6..50c6b0c 100644
--- a/jpa/plugins/org.eclipse.jpt.jpa.ui/property_files/jpt_jpa_ui.properties
+++ b/jpa/plugins/org.eclipse.jpt.jpa.ui/property_files/jpt_jpa_ui.properties
@@ -12,6 +12,10 @@
 
 ACCESS_TYPE_COMPOSITE_ACCESS=Access:
 
+ADD_CLASSES_TO_PERSISTENCE_UNIT_MESSAGE_TEXT=No persistence.xml found in the project
+ADD_CLASSES_TO_PERSISTENCE_UNIT_MESSAGE_TITLE=Add Classes to Persistence Unit
+ADD_CLASSES_TO_PERSISTENCE_UNIT_TASK_NAME=Adding classes to persistence unit...
+
 ADD_TO_EAR_COMPOSITE_EAR_MEMBER_SHIP=EAR membership
 ADD_TO_EAR_COMPOSITE_ADD_TO_EAR_LABEL=&Add project to an EAR
 ADD_TO_EAR_COMPOSITE_EAR_PROJECT_LABEL=EAR pr&oject name:
diff --git a/jpa/plugins/org.eclipse.jpt.jpa.ui/src/org/eclipse/jpt/jpa/ui/JptJpaUiMessages.java b/jpa/plugins/org.eclipse.jpt.jpa.ui/src/org/eclipse/jpt/jpa/ui/JptJpaUiMessages.java
index c85addc..dafe0ea 100644
--- a/jpa/plugins/org.eclipse.jpt.jpa.ui/src/org/eclipse/jpt/jpa/ui/JptJpaUiMessages.java
+++ b/jpa/plugins/org.eclipse.jpt.jpa.ui/src/org/eclipse/jpt/jpa/ui/JptJpaUiMessages.java
@@ -23,6 +23,10 @@
 	}
 
 	public static String ACCESS_TYPE_COMPOSITE_ACCESS;
+	public static String ADD_CLASSES_TO_PERSISTENCE_UNIT_MESSAGE_TEXT;
+	public static String ADD_CLASSES_TO_PERSISTENCE_UNIT_MESSAGE_TITLE;
+	public static String ADD_CLASSES_TO_PERSISTENCE_UNIT_TASK_NAME;
+
 	public static String ADD_TO_EAR_COMPOSITE_EAR_MEMBER_SHIP;
 	public static String ADD_TO_EAR_COMPOSITE_ADD_TO_EAR_LABEL;
 	public static String ADD_TO_EAR_COMPOSITE_EAR_PROJECT_LABEL;
diff --git a/jpa/plugins/org.eclipse.jpt.jpa.ui/src/org/eclipse/jpt/jpa/ui/internal/handlers/AddToPersistenceUnitHandler.java b/jpa/plugins/org.eclipse.jpt.jpa.ui/src/org/eclipse/jpt/jpa/ui/internal/handlers/AddToPersistenceUnitHandler.java
new file mode 100644
index 0000000..1a7455e
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.jpa.ui/src/org/eclipse/jpt/jpa/ui/internal/handlers/AddToPersistenceUnitHandler.java
@@ -0,0 +1,346 @@
+/*******************************************************************************
+ * Copyright (c) 2013 Oracle. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0, which accompanies this distribution
+ * and is available at http://www.eclipse.org/legal/epl-v10.html.
+ * 
+ * Contributors:
+ *     Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.jpa.ui.internal.handlers;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRunnable;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IPackageFragmentRoot;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.operation.IRunnableWithProgress;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.StructuredSelection;
+import org.eclipse.jpt.common.core.resource.xml.JptXmlResource;
+import org.eclipse.jpt.common.ui.internal.swt.widgets.DisplayTools;
+import org.eclipse.jpt.common.ui.internal.utility.SynchronousUiCommandContext;
+import org.eclipse.jpt.common.utility.command.Command;
+import org.eclipse.jpt.common.utility.internal.ObjectTools;
+import org.eclipse.jpt.common.utility.internal.iterable.TransformationIterable;
+import org.eclipse.jpt.common.utility.internal.transformer.TransformerAdapter;
+import org.eclipse.jpt.jpa.core.JpaProject;
+import org.eclipse.jpt.jpa.core.JpaProjectManager;
+import org.eclipse.jpt.jpa.core.context.persistence.Persistence;
+import org.eclipse.jpt.jpa.core.context.persistence.PersistenceUnit;
+import org.eclipse.jpt.jpa.core.context.persistence.PersistenceXml;
+import org.eclipse.jpt.jpa.ui.JptJpaUiMessages;
+import org.eclipse.jpt.jpa.ui.internal.plugin.JptJpaUiPlugin;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+public class AddToPersistenceUnitHandler extends AbstractHandler
+{
+	private IFile persistenceXmlFile;
+
+	public Object execute(ExecutionEvent event) throws ExecutionException {
+		ISelection selection = HandlerUtil.getCurrentSelectionChecked(event);
+
+		for (Map.Entry<IProject, Set<IType>> entry : this.buildSelectedTypes(selection).entrySet()) {
+			IProject project = entry.getKey();
+			Set<IType> types = entry.getValue();
+			JpaProject jpaProject = (JpaProject) project.getAdapter(JpaProject.class);
+			if (jpaProject != null) {
+				try {
+					PersistenceXml persistenceXml = jpaProject.getContextRoot().getPersistenceXml();
+					if (persistenceXml == null) {
+						MessageDialog.openInformation(DisplayTools.getShell(), JptJpaUiMessages.ADD_CLASSES_TO_PERSISTENCE_UNIT_MESSAGE_TITLE, JptJpaUiMessages.ADD_CLASSES_TO_PERSISTENCE_UNIT_MESSAGE_TEXT);
+					} else {
+						persistenceXmlFile = (IFile) persistenceXml.getResource();
+						IRunnableWithProgress runnable = new AddClassesRunnable(persistenceXmlFile, convertToTypeNames(types));
+						this.buildProgressMonitorDialog().run(true, true, runnable);  // true => fork; true => cancellable
+					}
+				} catch (InvocationTargetException ex) {
+					JptJpaUiPlugin.instance().logError(ex);
+				} catch (InterruptedException ex) {
+					Thread.currentThread().interrupt();
+					JptJpaUiPlugin.instance().logError(ex);
+				}
+			}
+		}
+		return null;
+	}
+	
+	protected Iterable<String> convertToTypeNames(Iterable<IType> types) {
+		return new TransformationIterable<IType, String>(types, new NameTransformer());
+	}
+	
+	/* CU private */ class NameTransformer
+		extends TransformerAdapter<IType, String> {
+		@Override
+		public String transform(IType type) {
+			return type.getFullyQualifiedName('$');
+		}
+	}
+
+	// ********** selected types **********
+
+	/**
+	 * Return a map containing lists of types, keyed by project.
+	 * <p>
+	 * The action is contributed for:<ul>
+	 * <li>{@link IType}
+	 * <li>{@link ICompilationUnit}
+	 * <li>{@link IPackageFragment}
+	 * <li>{@link IPackageFragmentRoot} that is a source folder
+	 * </ul>
+	 */
+	private Map<IProject, Set<IType>> buildSelectedTypes(ISelection currentSelection) {
+		if ( ! (currentSelection instanceof StructuredSelection)) {
+			return Collections.emptyMap();
+		}
+		HashMap<IProject, Set<IType>> types = new HashMap<IProject, Set<IType>>();
+		for (Object sel : ((StructuredSelection) currentSelection).toList()) {
+			switch (((IJavaElement) sel).getElementType()) {
+			case IJavaElement.PACKAGE_FRAGMENT_ROOT :
+				this.addSelectedTypes((IPackageFragmentRoot) sel, types);
+				break;
+			case IJavaElement.PACKAGE_FRAGMENT :
+				this.addSelectedTypes((IPackageFragment) sel, types);
+				break;
+			case IJavaElement.COMPILATION_UNIT :
+				this.addSelectedTypes((ICompilationUnit) sel, types);
+				break;
+			case IJavaElement.TYPE :
+				this.addSelectedType((IType) sel, types);
+				break;
+			default :
+				break;
+			}
+		}
+		return types;
+	}
+
+	private void addSelectedTypes(IPackageFragmentRoot packageFragmentRoot, Map<IProject, Set<IType>> types) {
+		for (IJavaElement pkgFragment : this.getPackageFragments(packageFragmentRoot)) {
+			this.addSelectedTypes((IPackageFragment) pkgFragment, types);
+		}
+	}
+
+	private void addSelectedTypes(IPackageFragment packageFragment, Map<IProject, Set<IType>> types) {
+		for (ICompilationUnit compUnit : this.getCompilationUnits(packageFragment)) {
+			this.addSelectedTypes(compUnit, types);
+		}
+	}
+
+	private void addSelectedTypes(ICompilationUnit compilationUnit, Map<IProject, Set<IType>> types) {
+		IType primaryType = compilationUnit.findPrimaryType();
+		if (primaryType != null) {
+			this.addSelectedType(primaryType, types);
+		}
+	}
+
+	private void addSelectedType(IType primaryType, Map<IProject, Set<IType>> typesMap) {
+		IProject project = primaryType.getJavaProject().getProject();
+		Set<IType> types = typesMap.get(project);
+		if (types == null) {
+			types = new HashSet<IType>();
+			typesMap.put(project, types);
+		}
+		types.add(primaryType);
+	}
+
+	private ICompilationUnit[] getCompilationUnits(IPackageFragment packageFragment) {
+		try {
+			return packageFragment.getCompilationUnits();
+		}
+		catch (JavaModelException e) {
+			JptJpaUiPlugin.instance().logError(e);
+		}
+		return new ICompilationUnit[0];
+	}
+
+	private IJavaElement[] getPackageFragments(IPackageFragmentRoot packageFragmentRoot) {
+		try {
+			return packageFragmentRoot.getChildren();
+		}
+		catch (JavaModelException e) {
+			JptJpaUiPlugin.instance().logError(e);
+		}
+		return new IJavaElement[0];
+	}
+
+	private ProgressMonitorDialog buildProgressMonitorDialog() {
+		return new ProgressMonitorDialog(null);
+	}
+
+	// ********** add classes runnable **********
+
+	/**
+	 * This is dispatched to the progress monitor dialog.
+	 */
+	/* CU private */ static class AddClassesRunnable implements IRunnableWithProgress
+	{
+		private final IFile persistenceXmlFile;
+		private final Iterable<String> typeNames;
+
+		AddClassesRunnable(IFile persistenceXmlFile, Iterable<String> typeNames) {
+			super();
+			this.persistenceXmlFile = persistenceXmlFile;
+			this.typeNames = typeNames;
+		}
+
+		public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+			try {
+				this.run_(monitor);
+			} catch (CoreException ex) {
+				throw new InvocationTargetException(ex);
+			}
+		}
+
+		private void run_(IProgressMonitor monitor) throws CoreException {
+			IWorkspace workspace = ResourcesPlugin.getWorkspace();
+			// lock the entire project, since we might modify the metamodel classes
+			ISchedulingRule rule = workspace.getRuleFactory().modifyRule(this.persistenceXmlFile.getProject());
+			workspace.run(
+					new AddClassesWorkspaceRunnable(this.persistenceXmlFile, typeNames),
+					rule,
+					IWorkspace.AVOID_UPDATE,
+					monitor
+					);
+		}
+	}
+
+
+	// ********** add classes workspace runnable **********
+
+	/**
+	 * This is dispatched to the Eclipse workspace.
+	 */
+	/* CU private */ static class AddClassesWorkspaceRunnable implements IWorkspaceRunnable
+	{
+		private final IFile persistenceXmlFile;
+		private final Iterable<String> typeNames;
+
+		AddClassesWorkspaceRunnable(IFile persistenceXmlFile, Iterable<String> typeNames) {
+			super();
+			this.persistenceXmlFile = persistenceXmlFile;
+			this.typeNames = typeNames;
+		}
+
+		public void run(IProgressMonitor monitor) throws CoreException {
+			if (monitor.isCanceled()) {
+				return;
+			}
+			SubMonitor sm = SubMonitor.convert(monitor, JptJpaUiMessages.ADD_CLASSES_TO_PERSISTENCE_UNIT_TASK_NAME, 20);
+
+			JpaProject jpaProject = this.getJpaProject();
+			if (jpaProject == null) {
+				return;
+			}
+
+			JptXmlResource resource = jpaProject.getPersistenceXmlResource();
+			if (resource == null) {
+				// the resource can be null if the persistence.xml file has an invalid content type
+				return;
+			}
+
+			if (sm.isCanceled()) {
+				return;
+			}
+			sm.worked(1);
+
+			PersistenceXml persistenceXml = jpaProject.getContextRoot().getPersistenceXml();
+			if (persistenceXml == null) {
+				return;  // unlikely...
+			}
+
+			Persistence persistence = persistenceXml.getRoot();
+			if (persistence == null) {
+				return;  // unlikely...
+			}
+
+			PersistenceUnit persistenceUnit = (persistence.getPersistenceUnitsSize() == 0) ?
+					persistence.addPersistenceUnit() :
+						persistence.getPersistenceUnit(0);
+					if (sm.isCanceled()) {
+						return;
+					}
+					sm.worked(1);
+
+					Command addClassesCommand = new AddClassesCommand(persistenceUnit, typeNames, sm.newChild(17));
+					JpaProjectManager mgr = this.getJpaProjectManager();
+					try {
+						if (mgr != null) {
+							mgr.execute(addClassesCommand, SynchronousUiCommandContext.instance());
+						}
+					} catch (InterruptedException ex) {
+						Thread.currentThread().interrupt();
+						throw new RuntimeException(ex);
+					}
+
+					resource.save();
+					sm.worked(1);
+		}
+
+		private JpaProjectManager getJpaProjectManager() {
+			return (JpaProjectManager) this.getWorkspace().getAdapter(JpaProjectManager.class);
+		}
+
+		private IProject getProject() {
+			return this.persistenceXmlFile.getProject();
+		}
+
+		private JpaProject getJpaProject() {
+			return (JpaProject) this.getProject().getAdapter(JpaProject.class);
+		}
+
+		private IWorkspace getWorkspace() {
+			return this.persistenceXmlFile.getWorkspace();
+		}
+	}
+
+
+	// ********** add classes command **********
+
+	/**
+	 * This is dispatched to the JPA project manager.
+	 */
+	/* CU private */ static class AddClassesCommand implements Command
+	{
+		private final PersistenceUnit persistenceUnit;
+		private final Iterable<String> typeNames;
+		private final IProgressMonitor monitor;
+
+		AddClassesCommand(PersistenceUnit persistenceUnit, Iterable<String> typeNames, IProgressMonitor monitor) {
+			super();
+			this.persistenceUnit = persistenceUnit;
+			this.typeNames = typeNames;
+			this.monitor = monitor;
+		}
+
+		public void execute() {
+			this.persistenceUnit.addClasses(typeNames, this.monitor);
+		}
+
+		@Override
+		public String toString() {
+			return ObjectTools.toString(this, this.persistenceUnit);
+		}
+	}
+}