Index: META-INF/MANIFEST.MF
===================================================================
--- META-INF/MANIFEST.MF (revision 23382)
+++ META-INF/MANIFEST.MF (working copy)
@@ -68,6 +68,9 @@
org.eclipse.core.variables,
org.junit,
org.eclipse.ui.views.properties.tabbed,
- org.jboss.tools.jbpm.common
+ org.jboss.tools.jbpm.common,
+ org.eclipse.ui.navigator,
+ org.eclipse.ltk.ui.refactoring;bundle-version="3.5.0",
+ org.eclipse.ltk.core.refactoring;bundle-version="3.5.100"
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Index: src/org/jbpm/gd/jpdl/refactoring/AbstractProcessHandler.java
===================================================================
--- src/org/jbpm/gd/jpdl/refactoring/AbstractProcessHandler.java (revision 0)
+++ src/org/jbpm/gd/jpdl/refactoring/AbstractProcessHandler.java (revision 0)
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * 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:
+ * IBM Corporation - initial API and implementation
+ ******************************************************************************/
+package org.jbpm.gd.jpdl.refactoring;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.mapping.ResourceMapping;
+import org.eclipse.core.resources.mapping.ResourceMappingContext;
+import org.eclipse.core.resources.mapping.ResourceTraversal;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IAdapterManager;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.jbpm.gd.jpdl.Logger;
+
+public abstract class AbstractProcessHandler extends AbstractHandler {
+
+ protected IResource[] getSelectedResources(IStructuredSelection sel) {
+ List resources= new ArrayList(sel.size());
+ for (Iterator e= sel.iterator(); e.hasNext();) {
+ Object next= e.next();
+ if (next instanceof IResource) {
+ resources.add(next);
+ continue;
+ } else if (next instanceof IAdaptable) {
+ Object resource= ((IAdaptable) next).getAdapter(IResource.class);
+ if (resource != null) {
+ resources.add(resource);
+ continue;
+ }
+ } else {
+ IAdapterManager adapterManager= Platform.getAdapterManager();
+ ResourceMapping mapping= (ResourceMapping) adapterManager.getAdapter(next, ResourceMapping.class);
+
+ if (mapping != null) {
+
+ ResourceTraversal[] traversals= null;
+ try {
+ traversals= mapping.getTraversals(ResourceMappingContext.LOCAL_CONTEXT, new NullProgressMonitor());
+ } catch (CoreException exception) {
+ Logger.log(exception.getStatus());
+ }
+
+ if (traversals != null) {
+ for (int i= 0; i < traversals.length; i++) {
+ IResource[] traversalResources= traversals[i].getResources();
+ if (traversalResources != null) {
+ for (int j= 0; j < traversalResources.length; j++) {
+ resources.add(traversalResources[j]);
+ }// for
+ }// if
+ }// for
+ }// if
+ }// if
+ }
+ }
+ return (IResource[]) resources.toArray(new IResource[resources.size()]);
+ }
+
+}
Index: src/org/jbpm/gd/jpdl/refactoring/DeleteProcessHandler.java
===================================================================
--- src/org/jbpm/gd/jpdl/refactoring/DeleteProcessHandler.java (revision 0)
+++ src/org/jbpm/gd/jpdl/refactoring/DeleteProcessHandler.java (revision 0)
@@ -0,0 +1,15 @@
+package org.jbpm.gd.jpdl.refactoring;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+
+public class DeleteProcessHandler extends AbstractHandler {
+
+ @Override
+ public Object execute(ExecutionEvent arg0) throws ExecutionException {
+ System.out.println("hello, delete world");
+ return null;
+ }
+
+}
Index: src/org/jbpm/gd/jpdl/refactoring/JavaNavigatorRefactorActionProvider.java
===================================================================
--- src/org/jbpm/gd/jpdl/refactoring/JavaNavigatorRefactorActionProvider.java (revision 0)
+++ src/org/jbpm/gd/jpdl/refactoring/JavaNavigatorRefactorActionProvider.java (revision 0)
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * Copyright (c) 2003, 2008 IBM Corporation and others.
+ * 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:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.jbpm.gd.jpdl.refactoring;
+
+import org.eclipse.jdt.ui.actions.RefactorActionGroup;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.actions.ActionContext;
+import org.eclipse.ui.navigator.CommonActionProvider;
+import org.eclipse.ui.navigator.ICommonActionExtensionSite;
+import org.eclipse.ui.navigator.ICommonViewerWorkbenchSite;
+
+/**
+ * Contributes the following actions to the menu on behalf of the JDT content
+ * extension.
+ *
+ *
+ * - {@link RefactorActionGroup}. Contributes the "Refactor>" and "Source>" submenus to the context menu.
+ *
+ */
+public class JavaNavigatorRefactorActionProvider extends CommonActionProvider {
+
+ private RefactorActionGroup fRefactorGroup;
+
+ public void fillActionBars(IActionBars actionBars) {
+ if (fRefactorGroup != null) {
+ fRefactorGroup.fillActionBars(actionBars);
+ fRefactorGroup.retargetFileMenuActions(actionBars);
+ }
+ }
+
+ public void fillContextMenu(IMenuManager menu) {
+ if (fRefactorGroup != null) {
+ fRefactorGroup.fillContextMenu(menu);
+ }
+ }
+
+ public void init(ICommonActionExtensionSite site) {
+ ICommonViewerWorkbenchSite workbenchSite= null;
+ if (site.getViewSite() instanceof ICommonViewerWorkbenchSite)
+ workbenchSite= (ICommonViewerWorkbenchSite) site.getViewSite();
+
+ // we only initialize the refactor group when in a view part
+ // (required for the constructor)
+ if (workbenchSite != null) {
+ if (workbenchSite.getPart() != null && workbenchSite.getPart() instanceof IViewPart) {
+ IViewPart viewPart= (IViewPart) workbenchSite.getPart();
+
+ fRefactorGroup= new RefactorActionGroup(viewPart);
+ }
+ }
+ }
+
+ public void setContext(ActionContext context) {
+ if (fRefactorGroup != null) {
+ fRefactorGroup.setContext(context);
+ }
+ }
+
+ /*
+ * @see org.eclipse.ui.actions.ActionGroup#dispose()
+ * @since 3.5
+ */
+ public void dispose() {
+ if (fRefactorGroup != null)
+ fRefactorGroup.dispose();
+ super.dispose();
+ }
+}
Index: src/org/jbpm/gd/jpdl/refactoring/MoveProcessHandler.java
===================================================================
--- src/org/jbpm/gd/jpdl/refactoring/MoveProcessHandler.java (revision 0)
+++ src/org/jbpm/gd/jpdl/refactoring/MoveProcessHandler.java (revision 0)
@@ -0,0 +1,15 @@
+package org.jbpm.gd.jpdl.refactoring;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+
+public class MoveProcessHandler extends AbstractHandler {
+
+ @Override
+ public Object execute(ExecutionEvent arg0) throws ExecutionException {
+ System.out.println("hello, move world");
+ return null;
+ }
+
+}
Index: src/org/jbpm/gd/jpdl/refactoring/RenameProcessHandler.java
===================================================================
--- src/org/jbpm/gd/jpdl/refactoring/RenameProcessHandler.java (revision 0)
+++ src/org/jbpm/gd/jpdl/refactoring/RenameProcessHandler.java (revision 0)
@@ -0,0 +1,39 @@
+package org.jbpm.gd.jpdl.refactoring;
+
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+public class RenameProcessHandler extends AbstractProcessHandler {
+
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ Shell activeShell= HandlerUtil.getActiveShell(event);
+ ISelection sel= HandlerUtil.getCurrentSelection(event);
+ if (sel instanceof IStructuredSelection) {
+ IResource resource= getCurrentResource((IStructuredSelection) sel);
+ if (resource != null) {
+ RenameProcessWizard refactoringWizard= new RenameProcessWizard(resource);
+ RefactoringWizardOpenOperation op= new RefactoringWizardOpenOperation(refactoringWizard);
+ try {
+ op.run(activeShell, "Rename Process");
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ }
+ return null;
+ }
+
+ private IResource getCurrentResource(IStructuredSelection sel) {
+ IResource[] resources= getSelectedResources(sel);
+ if (resources.length == 1) {
+ return resources[0];
+ }
+ return null;
+ }
+}
Index: src/org/jbpm/gd/jpdl/refactoring/RenameProcessProcessor.java
===================================================================
--- src/org/jbpm/gd/jpdl/refactoring/RenameProcessProcessor.java (revision 0)
+++ src/org/jbpm/gd/jpdl/refactoring/RenameProcessProcessor.java (revision 0)
@@ -0,0 +1,239 @@
+package org.jbpm.gd.jpdl.refactoring;
+
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceStatus;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.resources.mapping.IResourceChangeDescriptionFactory;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.MultiStatus;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.CompositeChange;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant;
+import org.eclipse.ltk.core.refactoring.participants.RenameProcessor;
+import org.eclipse.ltk.core.refactoring.participants.ResourceChangeChecker;
+import org.eclipse.ltk.core.refactoring.participants.SharableParticipants;
+import org.eclipse.ltk.core.refactoring.resource.RenameResourceChange;
+
+public class RenameProcessProcessor extends RenameProcessor {
+
+ private IResource jpdlResource;
+ private IResource gpdResource;
+ private String newProcessName;
+ private boolean updateReferences;
+
+ public RenameProcessProcessor(IResource resource) {
+ if (resource == null || !resource.exists()) {
+ throw new IllegalArgumentException("resource must not be null and must exist"); //$NON-NLS-1$
+ }
+
+ if (isJpdlFile(resource)) {
+ jpdlResource = resource;
+ gpdResource = getGpdResource(resource);
+ } else if (isGpdFile(resource)) {
+ jpdlResource = getJpdlResource(resource);
+ gpdResource = resource;
+ }
+
+ if (jpdlResource != null && jpdlResource.exists() && gpdResource != null && gpdResource.exists()) {
+
+ updateReferences= true;
+
+ String newName = jpdlResource.getName();
+ int i = newName.indexOf(".jpdl.xml");
+ if (i != -1) {
+ newName = newName.substring(0, i);
+ }
+ setNewProcessName(newName);
+
+ } else {
+ throw new IllegalArgumentException("both jpdlFile and gpdFile must not be null and must exist");
+ }
+ }
+
+ private String getProcessName(IResource resource) {
+ String name = resource.getName();
+ if (name.startsWith(".") && name.endsWith(".gpd.xml")) {
+ return name.substring(1, name.indexOf(".gpd.xml"));
+ } else if (name.endsWith(".jpdl.xml")) {
+ return name.substring(0, name.indexOf(".jpdl.xml"));
+ } else {
+ throw new IllegalArgumentException("resource must be either a jpdlFile or gpdFile");
+ }
+ }
+
+ private boolean isJpdlFile(IResource resource) {
+ return resource.getName().endsWith(".jpdl.xml");
+ }
+
+ private boolean isGpdFile(IResource resource) {
+ String name = resource.getName();
+ return name.startsWith(".") && name.endsWith(".gpd.xml");
+ }
+
+ private IResource getGpdResource(IResource resource) {
+ if (!resource.getName().endsWith(".jpdl.xml")) {
+ throw new IllegalArgumentException("gpd resource can only be obtained for a jpdl file");
+ }
+ return resource.getParent().getFile(new Path("." + getProcessName(resource) + ".gpd.xml"));
+ }
+
+ private IResource getJpdlResource(IResource resource) {
+ if (!resource.getName().endsWith(".gpd.xml")) {
+ throw new IllegalArgumentException("jpdl resource can only be obtained for a gpd file");
+ }
+ return resource.getParent().getFile(new Path(getProcessName(resource) + ".jpdl.xml"));
+ }
+
+ public String getNewProcessName() {
+ return newProcessName;
+ }
+
+ public void setNewProcessName(String newName) {
+ Assert.isNotNull(newName);
+ newProcessName= newName;
+ }
+
+ public boolean isUpdateReferences() {
+ return updateReferences;
+ }
+
+ public void setUpdateReferences(boolean updateReferences) {
+ this.updateReferences= updateReferences;
+ }
+
+ public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
+ IStatus result = null;
+ if (!jpdlResource.isSynchronized(IResource.DEPTH_INFINITE)) {
+ result = new Status (
+ IStatus.ERROR,
+ ResourcesPlugin.PI_RESOURCES,
+ IResourceStatus.OUT_OF_SYNC_LOCAL,
+ "Resource " + jpdlResource.getName() + "is out of sync with file system",
+ null);
+ }
+ if (!gpdResource.isSynchronized(IResource.DEPTH_INFINITE)) {
+ IStatus temp = new Status (
+ IStatus.ERROR,
+ ResourcesPlugin.PI_RESOURCES,
+ IResourceStatus.OUT_OF_SYNC_LOCAL,
+ "Resource " + jpdlResource.getName() + "is out of sync with file system",
+ null);
+ if (result == null) {
+ result = temp;
+ } else {
+ MultiStatus multi = new MultiStatus(
+ ResourcesPlugin.PI_RESOURCES,
+ IResourceStatus.OUT_OF_SYNC_LOCAL,
+ "both resources are out of sync with file system",
+ null);
+ multi.add(result);
+ multi.add(temp);
+ result = multi;
+ }
+ }
+ if (result == null) {
+ result = Status.OK_STATUS;
+ }
+ return RefactoringStatus.create(result);
+ }
+
+ public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context) throws CoreException {
+ pm.beginTask("", 1);
+ try {
+ ResourceChangeChecker checker= (ResourceChangeChecker) context.getChecker(ResourceChangeChecker.class);
+ IResourceChangeDescriptionFactory deltaFactory= checker.getDeltaFactory();
+ IPath newJpdlPath= jpdlResource.getFullPath().removeLastSegments(1).append(getNewProcessName() + ".jpdl.xml");
+ deltaFactory.move(
+ jpdlResource, newJpdlPath);
+ IPath newGpdPath= gpdResource.getFullPath().removeLastSegments(1).append("." + getNewProcessName() + ".gpd.xml");
+ deltaFactory.move(
+ gpdResource, newGpdPath);
+ return new RefactoringStatus();
+ } finally {
+ pm.done();
+ }
+ }
+
+ public RefactoringStatus validateNewElementName(String newName) {
+ Assert.isNotNull(newName, "new name");
+ IContainer c= jpdlResource.getParent();
+ if (c == null)
+ return RefactoringStatus.createFatalErrorStatus("Internal Error");
+ if ("".equals(newName)) {
+ return RefactoringStatus.createFatalErrorStatus("The name of the process cannot be the empty string");
+ }
+ if (c.findMember(newName + ".jpdl.xml") != null || c.findMember("." + newName + ".gpd.xml") != null)
+ return RefactoringStatus.createFatalErrorStatus("A process with this name already exists");
+
+ if (!c.getFullPath().isValidSegment(newName + ".jpdl.xml") || !c.getFullPath().isValidSegment("." + newName + ".gpd.xml"))
+ return RefactoringStatus.createFatalErrorStatus("This is an invalid name for a process");
+
+ RefactoringStatus result= RefactoringStatus.create(c.getWorkspace().validateName(newName, jpdlResource.getType()));
+ if (!result.hasFatalError()) {
+ result.merge(RefactoringStatus.create(c.getWorkspace().validateName(newName, gpdResource.getType())));
+ }
+ if (!result.hasFatalError())
+ result.merge(RefactoringStatus.create(c.getWorkspace().validatePath(createNewPath(jpdlResource, newName), jpdlResource.getType())));
+ if (!result.hasFatalError())
+ result.merge(RefactoringStatus.create(c.getWorkspace().validatePath(createNewPath(gpdResource, newName), gpdResource.getType())));
+ return result;
+ }
+
+ public Change createChange(IProgressMonitor pm) throws CoreException {
+ pm.beginTask("", 1);
+ try {
+ CompositeChange compositeChange = new CompositeChange("process rename");
+ compositeChange.add(new RenameResourceChange(jpdlResource.getFullPath(), getNewProcessName() + ".jpdl.xml"));
+ compositeChange.add(new RenameResourceChange(gpdResource.getFullPath(), "." + getNewProcessName() + ".gpd.xml"));
+ return compositeChange;
+ } finally {
+ pm.done();
+ }
+ }
+
+ private String createNewPath(IResource resource, String newName) {
+ return resource.getFullPath().removeLastSegments(1).append(newName).toString();
+ }
+
+ public Object[] getElements() {
+ return new Object[] { jpdlResource, gpdResource};
+ }
+
+ public String getIdentifier() {
+ return "org.jbpm.gd.jpdl.refactoring.renameProcessProcessor";
+ }
+
+ public String getProcessorName() {
+ return "Rename Process";
+ }
+
+ public boolean isApplicable() {
+ if (jpdlResource == null)
+ return false;
+ if (!jpdlResource.exists())
+ return false;
+ if (!jpdlResource.isAccessible())
+ return false;
+ if (gpdResource == null)
+ return false;
+ if (!gpdResource.exists())
+ return false;
+ if (!gpdResource.isAccessible())
+ return false;
+ return true;
+ }
+
+ public RefactoringParticipant[] loadParticipants(RefactoringStatus status, SharableParticipants shared) throws CoreException {
+ return new RefactoringParticipant[0];
+ }
+
+}
\ No newline at end of file
Index: src/org/jbpm/gd/jpdl/refactoring/RenameProcessWizard.java
===================================================================
--- src/org/jbpm/gd/jpdl/refactoring/RenameProcessWizard.java (revision 0)
+++ src/org/jbpm/gd/jpdl/refactoring/RenameProcessWizard.java (revision 0)
@@ -0,0 +1,98 @@
+package org.jbpm.gd.jpdl.refactoring;
+
+import org.eclipse.core.resources.IResource;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
+import org.eclipse.ltk.ui.refactoring.UserInputWizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+public class RenameProcessWizard extends RefactoringWizard {
+
+ public RenameProcessWizard(IResource resource) {
+ super(new RenameRefactoring(new RenameProcessProcessor(resource)), DIALOG_BASED_USER_INTERFACE);
+ setDefaultPageTitle("Rename Process");
+ setWindowTitle("Rename Process");
+ }
+
+ protected void addUserInputPages() {
+ RenameProcessProcessor processor= (RenameProcessProcessor) getRefactoring().getAdapter(RenameProcessProcessor.class);
+ addPage(new RenameProcessRefactoringConfigurationPage(processor));
+ }
+
+ private static class RenameProcessRefactoringConfigurationPage extends UserInputWizardPage {
+
+ private final RenameProcessProcessor refactoringProcessor;
+ private Text nameField;
+
+ public RenameProcessRefactoringConfigurationPage(RenameProcessProcessor processor) {
+ super("RenameResourceRefactoringInputPage");
+ refactoringProcessor= processor;
+ }
+
+ public void createControl(Composite parent) {
+ Composite composite= new Composite(parent, SWT.NONE);
+ composite.setLayout(new GridLayout(2, false));
+ composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
+ composite.setFont(parent.getFont());
+
+ Label label= new Label(composite, SWT.NONE);
+ label.setText("New na&me");
+ label.setLayoutData(new GridData());
+
+ nameField= new Text(composite, SWT.BORDER);
+ nameField.setText(refactoringProcessor.getNewProcessName());
+ nameField.setFont(composite.getFont());
+ nameField.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, true, false));
+ nameField.addModifyListener(new ModifyListener() {
+ public void modifyText(ModifyEvent e) {
+ validatePage();
+ }
+ });
+
+ nameField.selectAll();
+ setPageComplete(false);
+ setControl(composite);
+ }
+
+ public void setVisible(boolean visible) {
+ if (visible) {
+ nameField.setFocus();
+ }
+ super.setVisible(visible);
+ }
+
+ protected final void validatePage() {
+ String text= nameField.getText();
+ RefactoringStatus status= refactoringProcessor.validateNewElementName(text);
+ setPageComplete(status);
+ }
+
+ protected boolean performFinish() {
+ initializeRefactoring();
+ storeSettings();
+ return super.performFinish();
+ }
+
+ public IWizardPage getNextPage() {
+ initializeRefactoring();
+ storeSettings();
+ return super.getNextPage();
+ }
+
+ private void storeSettings() {
+ }
+
+ private void initializeRefactoring() {
+ refactoringProcessor.setNewProcessName(nameField.getText());
+ }
+ }
+}
Index: plugin.xml
===================================================================
--- plugin.xml (revision 23532)
+++ plugin.xml (working copy)
@@ -1577,9 +1577,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+