@State(
	name = "OpenCmsPluginConfigurationData",
	storages = {
		@Storage( StoragePathMacros.WORKSPACE_FILE),
		@Storage( "opencms.xml")
	}
)
public class OpenCmsPlugin implements ProjectComponent, PersistentStateComponent<OpenCmsPluginConfigurationData> {

	private static final Logger LOG = Logger.getInstance(OpenCmsPlugin.class);

	public static final String TOOLWINDOW_ID = "OpenCms";
	public static final String OPENCMS_MODULE_CONFIG_FILE = "opencms-module-config.json";

	private static final String OPENCMS_MENU_ID = "OpenCmsPlugin.ActionMenu";
	private static final String PROJECT_POPUP_MENU_ID = "OpenCmsPlugin.ProjectPopupMenu";
	private static final String EDITOR_POPUP_MENU_ID = "OpenCmsPlugin.EditorPopupMenu";
	private static final String TAB_POPUP_MENU_ID = "OpenCmsPlugin.TabsPopupGroup";

	private static final Icon MENU_ICON = new ImageIcon(OpenCmsPlugin.class.getResource("/icons/opencms_menu.png"));

	/** After IntelliJ module changes we'll wait a while before OpenCms modules get updated */
	private static final int MODULE_CHANGE_UPDATE_DELAY = 1000;

	/** The IntelliJ project */
	private Project project;
	
	private VirtualFileManagerListener virtualFileManagerListener;
	
	/** Helper object to retrieve configuration data from the OpenCms configuration */
	private OpenCmsConfiguration openCmsConfiguration;

	/** Configuration data from the configuration form, is stored and loaded via PersistentStateComponent methods */
	private OpenCmsPluginConfigurationData configurationData;

	/** Container for all OpenCms modules configured in the project */
	private OpenCmsModules openCmsModules;

	/** Adapter used to sync the RFS with the OpenCms VFS */
	private VfsAdapter vfsAdapter;

	/**
	 * Connector used to retrieve module or resource information from OpenCms and execute actions in OpenCms
	 * (e.g. publishing), backed by the OpenCms module "com.mediaworx.opencms.ideconnector"
	 */
	private OpenCmsPluginConnector pluginConnector;

	/**
	 * Client used to execute actions in OpenCms (new with version 1.7). Right now it runs side by side with the old
	 * {@link #pluginConnector} and adds some additional functionality (importing modules). In a future release the
	 * connectorClient is planned to replace the old pluginConnector.
	 */
	private IDEConnectorClient connectorClient;

	/**
	 * The main menu
	 */
	private OpenCmsMainMenu openCmsMainMenu;

	/**
	 * ToolWindow for the OpenCms plugin
	 */
	private ToolWindow toolWindow;

	/**
	 * Console used to log OpenCms actions like sync or publish
	 */
	private OpenCmsToolWindowConsole console;

	/** IntelliJ's action manager */
	private ActionManager actionManager;

	/** json parser used to parse the OpenCms module configuration file content */
	private final JSONParser jsonParser = new JSONParser();


	/**
	 * timer used to execute delayed refreshes of OpenCms modules after IntelliJ modules have been changed
	 */
	private final java.util.Timer moduleUpdateTimer = new java.util.Timer();

	/**
	 * task used to execute delayed OpenCms module updates after IntelliJ modules have been changed
	 */
	private TimerTask currentModuleUpdateTimerTask;

	/**
	 * Set to <code>true</code> the first time the OpenCms plugin is enabled
	 */
	private boolean wasInitialized = false;

	/**
	 * Creates a new plugin instance, called by IntelliJ
	 * @param project   the IntelliJ project
	 */
	public OpenCmsPlugin(Project project) {
		LOG.info("OpenCmsPlugin: Initializing OpenCmsPlugin with project " + project.getName());
		this.project = project;
		openCmsModules = new OpenCmsModules(this);
	}

	/**
	 * Initializes the plugin, called by IntelliJ
	 */
	@Override
	public void initComponent() {
		LOG.info("OpenCmsPlugin: initComponent called, project: " + project.getName());
		actionManager = ActionManager.getInstance();
	}

	private void initOpenCmsConfiguration() {
		clearOpenCmsConfiguration();
		OpenCmsPluginConfigurationData config = getPluginConfiguration();
		openCmsConfiguration = new OpenCmsConfiguration(config);
		openCmsConfiguration.startMonitoringConfigurationChanges();
	}

	private void clearOpenCmsConfiguration() {
		if (openCmsConfiguration != null) {
			openCmsConfiguration.stopMonitoringConfigurationChanges();
			openCmsConfiguration = null;
		}
	}


	/**
	 * Enables the plugin (if it is enabled in the project level configuration), called by IntelliJ whenever a project
	 * is opened.
	 */
	@Override
	public void projectOpened() {
		LOG.info("OpenCmsPlugin: projectOpened called, project: " + project.getName());
		OpenCmsPluginConfigurationData config = getPluginConfiguration();
		if (config != null && config.isOpenCmsPluginEnabled()) {
			enable();
		}
	}

	/**
	 * Disables the plugin, unregisters module actions and stops monitoring configuration changes, called by IntelliJ
	 * whenever a project is closed.
	 */
	@Override
	public void projectClosed() {
		LOG.info("OpenCmsPlugin: projectClosed called, project: " + project.getName());
		if (wasInitialized) {
			openCmsMainMenu.unregisterModuleActions();
			openCmsConfiguration.stopMonitoringConfigurationChanges();
			disable();
		}
	}

	/**
	 * Enables the plugin for the current project. Initializes the plugin and its actions if initialization was not
	 * done before. If the plugin connector is activated in the project level configuration it gets initialized as well.
	 */
	public void enable() {
		initOpenCmsConfiguration();
		initializeOpenCmsModules();

		if (!wasInitialized) {
			OpenCmsPluginConfigurationData config = getPluginConfiguration();
			if (config != null && config.isOpenCmsPluginEnabled()) {

				if (config.isPluginConnectorEnabled()) {
					pluginConnector =   new OpenCmsPluginConnector(
												config.getConnectorUrl(),
												config.getUsername(),
												config.getPassword(),
												config.isUseMetaDateVariablesEnabled(),
												config.isUseMetaIdVariablesEnabled()
										);
				}
			}
			registerListeners();
			registerMenus();
			wasInitialized = true;

			checkWebappRootConfiguration(true);
		}
		else {
			setToolWindowAvailable(true);
		}
	}

	/**
	 * Disables the plugin. The only thing done here is deactivating the plugin's tool window, menus are disabled
	 * automatically because all actions are hidden if the plugin is deactivated
	 */
	public void disable() {
		clearOpenCmsConfiguration();

		if (wasInitialized) {
			setToolWindowAvailable(false);
		}
	}

	/**
	 * initializes all OpenCms modules that are configured in the IntelliJ modules (option "Is OpenCms module" checked)
	 * and removes modules that aren't configured as OpenCms modules
	 */
	private synchronized void initializeOpenCmsModules() {
		LOG.info("OpenCmsPlugin: initializeOpenCmsModules called, project: " + project.getName());

		ModuleManager moduleManager = ModuleManager.getInstance(project);
		Module[] modules = moduleManager.getModules();

		// put all modules in a tree map with their name as key and the module as value to be able to get an alphabetically sorted module list
		Map<String, Module> ideaModuleMap = new TreeMap<>();
		for (Module module : modules) {
			ideaModuleMap.put(module.getName(), module);
		}


		for (Module ideaModule : ideaModuleMap.values()) {
			String moduleBasePath = PluginTools.getModuleContentRoot(ideaModule);
			if (StringUtils.isNotBlank(moduleBasePath)) {
				OpenCmsModuleConfigurationData moduleConfig = null;

				boolean isModuleConfigReadFromFile = false;
				String moduleConfigFilePath = moduleBasePath + "/" + OPENCMS_MODULE_CONFIG_FILE;
				File moduleConfigFile = new File(moduleConfigFilePath);
				if (moduleConfigFile.exists() && moduleConfigFile.isFile()) {
					try {
						String moduleConfigurationJson = FileUtils.readFileToString(moduleConfigFile, StandardCharsets.UTF_8);
						try {
							JSONObject moduleJson = (JSONObject)jsonParser.parse(moduleConfigurationJson);
							moduleConfig = readOpenCmsModuleConfiguration(moduleJson, ideaModule.getName());
							isModuleConfigReadFromFile = true;
						}
						catch (ParseException e) {
							LOG.error("Can't parse the module configuration file " + moduleConfigFilePath, e);
						}
						catch (Exception e) {
							LOG.error("Unexpected exception parsing the OpenCms module configuration " + moduleConfigFilePath, e);
						}
					}
					catch (IOException e) {
						// this should never happen, since UTF-8 should exist. Let's log it anyway
						LOG.warn("Charset UTF-8 not found, can't read " + moduleConfigFilePath);
					}
				}

				if (!isModuleConfigReadFromFile) {
					OpenCmsModuleConfigurationComponent configurationComponent = ideaModule.getComponent(OpenCmsModuleConfigurationComponent.class);
					moduleConfig = configurationComponent.getState();
				}

				boolean validModule = false;
				if (moduleConfig != null && moduleConfig.isOpenCmsModuleEnabled()) {
					getOpenCmsModules().registerModule(moduleBasePath, moduleConfig);
					validModule = true;
				}
				if (!validModule) {
					getOpenCmsModules().unregisterModule(moduleBasePath);
				}
			}
			else {
				LOG.warn(String.format("Module %s doesn't have a valid content root", ideaModule.getName()));
			}
		}
	}

	/**
	 * converts the json object containing the OpenCms module configuration to an instance of
	 * OpenCmsModuleConfigurationData
	 * @param moduleJson the json object containing the OpenCms module configuration
	 * @return OpenCmsModuleConfigurationData
	 */
	private OpenCmsModuleConfigurationData readOpenCmsModuleConfiguration(JSONObject moduleJson, String intelliJModuleName) {
		OpenCmsModuleConfigurationData moduleConfig;
		String openCmsModuleName = (String)moduleJson.get("openCmsModuleName");
		String localVfsRoot = (String)moduleJson.get("localVfsRoot");
		String exportImportSiteRoot = (String)moduleJson.get("exportImportSiteRoot");
		String syncMode = (String)moduleJson.get("syncMode");
		String moduleVersion = (String)moduleJson.get("moduleVersion");

		moduleConfig = new OpenCmsModuleConfigurationData();
		moduleConfig.setOpenCmsModuleEnabled(true);

		// handle the OpenCms module name (default or specific)
		moduleConfig.setUseProjectDefaultModuleNameEnabled(StringUtils.isBlank(openCmsModuleName) || "default".equals(openCmsModuleName));
		if (!moduleConfig.isUseProjectDefaultModuleNameEnabled()) {
			moduleConfig.setModuleName(openCmsModuleName);
		}

		// handle the local VFS root (default or specific)
		moduleConfig.setUseProjectDefaultVfsRootEnabled(StringUtils.isBlank(localVfsRoot) || "default".equals(localVfsRoot));
		if (!moduleConfig.isUseProjectDefaultVfsRootEnabled()) {
			moduleConfig.setLocalVfsRoot(localVfsRoot);
		}

		// handle the export/import site root
		moduleConfig.setExportImportSiteRoot(StringUtils.isNotEmpty(exportImportSiteRoot) ? exportImportSiteRoot : "/");

		// handle the sync mode (default, PUSH, SYNC or PULL)
		if (
				StringUtils.isEmpty(syncMode) ||
				!(syncMode.equals("default") || syncMode.equals("PUSH") || syncMode.equals("SYNC") || syncMode.equals("PULL"))
			) {
			LOG.warn("invalid sync mode in opencms module configuration for module " + intelliJModuleName);
			syncMode = "default";
		}
		moduleConfig.setUseProjectDefaultSyncModeEnabled("default".equals(syncMode));
		if (!moduleConfig.isUseProjectDefaultSyncModeEnabled()) {
			moduleConfig.setSyncMode(SyncMode.valueOf(syncMode));
		}

		// handle the module version
		moduleConfig.setSetSpecificModuleVersionEnabled(StringUtils.isNotBlank(syncMode));
		if (moduleConfig.isSetSpecificModuleVersionEnabled()) {
			moduleConfig.setModuleVersion(moduleVersion);
		}

		return moduleConfig;
	}

	/**
	 * Handles delayed refresh of the OpenCms modules after IntelliJ module configuration changes. If multiple IntelliJ
	 * modules have been changed, the delayed execution is stopped and a new scheduled task is queued, so the refresh is
	 * done only once.
	 */
	public void queueOpenCmsModuleUpdate() {
		if (currentModuleUpdateTimerTask != null) {
			currentModuleUpdateTimerTask.cancel();
		}
		currentModuleUpdateTimerTask = new TimerTask() {
			@Override
			public void run() {
				LOG.info("OpenCmsPlugin: running timed OpenCms module refresh now: " + project.getName());
				currentModuleUpdateTimerTask = null;
				initializeOpenCmsModules();
				if (openCmsMainMenu != null) {
					openCmsMainMenu.registerModuleActions();
				}
			}
		};
		moduleUpdateTimer.schedule(currentModuleUpdateTimerTask, MODULE_CHANGE_UPDATE_DELAY);
	}


	/**
	 * Registers the OpenCms menus for MainMenu, ProjectPopup, EditorPopup and EditorTabPopup
	 */
	private void registerMenus() {
		registerToolsMenu();
		registerProjectPopupMenu();
		registerEditorPopupMenu();
		registerEditorTabPopupMenu();
	}

	/**
	 * Registers listeners for IntelliJ events. The listeners only listen for file change events and only file
	 * deletions, renames and moves are handled (depending on the project level configuration these changes may
	 * be synced back to OpenCms).
	 */
	private void registerListeners() {
		MessageBus bus = ApplicationManager.getApplication().getMessageBus();
		MessageBusConnection connection = bus.connect();
		OpenCmsModuleFileChangeListener fileChangeListener = new OpenCmsModuleFileChangeListener(this);
		connection.subscribe(VirtualFileManager.VFS_CHANGES, fileChangeListener);
	}

	/**
	 * Adds a menu action to a given menu group.
	 * @param group     the menu group the action should be added to
	 * @param id        the action's identifier
	 * @param action    the action itself
	 * @param text      text to be displayed in the menu
	 */
	public void addAction(DefaultActionGroup group, String id, AnAction action, String text) {
		addAction(group, id, action, text, null, null);
	}

	/**
	 * Adds a menu action to a given menu group.
	 * @param group     the menu group the action should be added to
	 * @param id        the action's identifier
	 * @param action    the action itself
	 * @param text      text to be displayed in the menu
	 * @param icon      icon to be displayed with the menu action (usually displayed befor the text)
	 */
	private void addAction(DefaultActionGroup group, String id, AnAction action, String text, Icon icon) {
		addAction(group, id, action, text, icon, null);
	}

	/**
	 *
	 * Adds a menu action to a given menu group.
	 * @param group         the menu group the action should be added to
	 * @param id            the action's identifier
	 * @param action        the action itself
	 * @param text          text to be displayed in the menu
	 * @param icon          icon to be displayed with the menu action (usually displayed befor the text)
	 * @param constraints   constraints telling IntelliJ where to position the menu action
	 */
	private void addAction(DefaultActionGroup group, String id, AnAction action, String text, Icon icon, Constraints constraints) {
		action.getTemplatePresentation().setText(text);
		if (icon != null) {
			action.getTemplatePresentation().setIcon(icon);
		}
		if (actionManager.getAction(id) != null) {
			actionManager.unregisterAction(id);
		}
		try {
			actionManager.registerAction(id, action);
		}
		catch(Throwable t) {
			LOG.warn("Error adding action " + id, t);
		}
		if (constraints == null) {
			group.add(action);
		}
		else {
			group.add(action, constraints);
		}
	}

	/**
	 * Creates and registers the OpenCms menu for the tools menu as an action group
	 */
	private void registerToolsMenu() {
		openCmsMainMenu = (OpenCmsMainMenu)actionManager.getAction(OPENCMS_MENU_ID);
		if (openCmsMainMenu != null) {
			openCmsMainMenu.setPlugin(this);
		}
		if (openCmsMainMenu == null) {
			DefaultActionGroup toolsMenu = (DefaultActionGroup)actionManager.getAction("ToolsMenu");
			openCmsMainMenu = OpenCmsMainMenu.getInstance(this);
			addAction(toolsMenu, OPENCMS_MENU_ID, openCmsMainMenu, "O_penCms", null, null);
		}
		openCmsMainMenu.registerModuleActions();
	}

	/**
	 * Creates and registers the OpenCms menu for the project popup as an action group
	 */
	private void registerProjectPopupMenu() {
		OpenCmsProjectPopupMenu openCmsProjectPopupMenu = (OpenCmsProjectPopupMenu)actionManager.getAction(PROJECT_POPUP_MENU_ID);
		if (openCmsProjectPopupMenu == null) {
			DefaultActionGroup projectPopup = (DefaultActionGroup)actionManager.getAction(IdeActions.GROUP_PROJECT_VIEW_POPUP);
			openCmsProjectPopupMenu = new OpenCmsProjectPopupMenu(this);
			addAction(projectPopup, PROJECT_POPUP_MENU_ID, openCmsProjectPopupMenu, "_OpenCms", MENU_ICON, new Constraints(Anchor.BEFORE, "RevealIn"));
			projectPopup.add(Separator.getInstance(), new Constraints(Anchor.AFTER, PROJECT_POPUP_MENU_ID));
		}
	}

	/**
	 * Creates and registers the OpenCms menu for the editor popup as an action group
	 */
	private void registerEditorPopupMenu() {
		OpenCmsEditorPopupMenu openCmsEditorPopupMenu = (OpenCmsEditorPopupMenu)actionManager.getAction(EDITOR_POPUP_MENU_ID);
		if (openCmsEditorPopupMenu == null) {
			DefaultActionGroup editorPopup = (DefaultActionGroup)actionManager.getAction(IdeActions.GROUP_EDITOR_POPUP);
			openCmsEditorPopupMenu = new OpenCmsEditorPopupMenu(this);
			addAction(editorPopup, EDITOR_POPUP_MENU_ID, openCmsEditorPopupMenu, "_OpenCms", MENU_ICON, new Constraints(Anchor.AFTER, "ChangeFileEncodingAction"));
			editorPopup.addAction(Separator.getInstance(), new Constraints(Anchor.BEFORE, EDITOR_POPUP_MENU_ID));
		}
	}

	/**
	 * Creates and registers the OpenCms menu for the editor tab popup as an action group
	 */
	private void registerEditorTabPopupMenu() {
		OpenCmsEditorTabPopupMenu openCmsEditorTabPopupMenu = (OpenCmsEditorTabPopupMenu)actionManager.getAction(TAB_POPUP_MENU_ID);
		if (openCmsEditorTabPopupMenu == null) {
			// DefaultActionGroup editorTabPopup = (DefaultActionGroup)actionManager.getAction(IdeActions.GROUP_EDITOR_TAB_POPUP);
			DefaultActionGroup editorTabPopup = (DefaultActionGroup)actionManager.getAction("EditorTabPopupMenuEx");
			openCmsEditorTabPopupMenu = new OpenCmsEditorTabPopupMenu(this);
			editorTabPopup.addAction(Separator.getInstance());
			// addAction(editorTabPopup, TAB_POPUP_MENU_ID, openCmsEditorTabPopupMenu, "_OpenCms", MENU_ICON, new Constraints(Anchor.AFTER, "UnsplitAll"));
			addAction(editorTabPopup, TAB_POPUP_MENU_ID, openCmsEditorTabPopupMenu, "_OpenCms", MENU_ICON, new Constraints(Anchor.BEFORE, "RunContextPopupGroup"));
			// editorTabPopup.addAction(Separator.getInstance(), new Constraints(Anchor.BEFORE, TAB_POPUP_MENU_ID));
			editorTabPopup.addAction(Separator.getInstance(), new Constraints(Anchor.AFTER, TAB_POPUP_MENU_ID));
		}
	}

	public boolean checkWebappRootConfiguration(boolean showDialog) {
		boolean configOK = true;
		OpenCmsPluginConfigurationData config = getPluginConfiguration();
		if (config != null && config.isOpenCmsPluginEnabled()) {
			File file = new File(config.getWebappRoot());
			// show an error message if the webapp root folder was not found
			if (!file.exists()) {
				configOK = false;
				if (showDialog) {
					Messages.showDialog("The Webapp Root was not found.\nPlease check the OpenCms Webapp Root in the OpenCms Plugin settings.", "OpenCms Plugin - Configuration Error", new String[]{"Ok"}, 0, Messages.getErrorIcon());
				}
			}
			else {
				// check if the OpenCms configuration path exists
				file = new File(config.getWebappRoot() + OpenCmsConfiguration.CONFIGPATH);
				if (!file.exists()) {
					configOK = false;
					if (showDialog) {
						Messages.showDialog("The OpenCms configuration was not found.\nPlease check the OpenCms Webapp Root in the OpenCms Plugin settings.", "Error", new String[]{"Ok"}, 0, Messages.getErrorIcon());
					}
				}
			}
		}
		return configOK;
	}

	/**
	 * Does some cleanup, called by IntelliJ.
	 */
	@Override
	public void disposeComponent() {
		LOG.info("OpenCmsPlugin: disposeComponent called, project: " + project.getName());
		project = null;
		configurationData = null;
		clearOpenCmsConfiguration();
		openCmsModules = null;
		vfsAdapter = null;
		pluginConnector = null;
		toolWindow = null;
		console = null;
		actionManager = null;
	}

	/**
	 * Returns the component's name.
	 * @return  "OpenCmsPlugin.MainComponent"
	 */
	@NotNull
	@Override
	public String getComponentName() {
		LOG.info("OpenCmsPlugin: getComponentName called, project: " + project.getName());
		return "OpenCmsPlugin.MainComponent";
	}

	/**
	 * Returns the IntelliJ project
	 * @return  the IntelliJ project
	 */
	public Project getProject() {
		return project;
	}

	/**
	 * Returns the helper object to retrieve configuration data from the OpenCms configuration
	 * @return  the OpenCms configuration helper object
	 */
	public OpenCmsConfiguration getOpenCmsConfiguration() {
		if (openCmsConfiguration == null) {
			initOpenCmsConfiguration();
		}
		return openCmsConfiguration;
	}

	/**
	 * Returns the plugin's project level configuration data
	 * @return  the project level configuration data
	 */
	public OpenCmsPluginConfigurationData getPluginConfiguration() {
		if (configurationData == null) {
			configurationData = new OpenCmsPluginConfigurationData();
		}
		return configurationData;
	}

	/**
	 * Returns the container for all OpenCms modules configured in the project
	 * @return  the OpenCms module container
	 */
	public OpenCmsModules getOpenCmsModules() {
		return openCmsModules;
	}

	/**
	 * refreshes all OpenCms modules (e.g. after configuration changes)
	 */
	public void refreshOpenCmsModules() {
		openCmsModules.refreshAllModules();
	}

	/**
	 * Returns the adapter used to sync the RFS with the OpenCms VFS
	 * @return  the VFS adapter for sync actions
	 */
	public VfsAdapter getVfsAdapter() {
		if (vfsAdapter == null) {
			OpenCmsPluginConfigurationData config = getPluginConfiguration();
			if (config != null && config.isOpenCmsPluginEnabled() && config.getPassword() != null && config.getPassword().length() > 0) {
				vfsAdapter = new VfsAdapter(config.getRepository(), config.getUsername(), config.getPassword());
			}
		}
		return vfsAdapter;
	}

	/**
	 * Returns the connector used to retrieve module or resource information from OpenCms and execute actions in
	 * OpenCms (e.g. publishing)
	 * @return the OpenCms plugin connector for information retrieval and publishing
	 * @deprecated the old connector (via JSP) will be removed once all functionality is moved to the new connector
	 *             service (via Servlet)
	 */
	@Deprecated
	public OpenCmsPluginConnector getPluginConnector() {
		return pluginConnector;
	}

	/**
	 * Sets the connector used to retrieve module or resource information from OpenCms and execute actions in
	 * OpenCms (e.g. publishing)
	 *
	 * @param pluginConnector the OpenCms plugin connector for information retrieval and publishing
	 * @deprecated the old connector (via JSP) will be removed once all functionality is moved to the new connector
	 *             service (via Servlet)
	 */
	@Deprecated
	public void setPluginConnector(OpenCmsPluginConnector pluginConnector) {
		this.pluginConnector = pluginConnector;
	}

	/**
	 * Returns the IDE connector client that may be used to execute actions on the local OpenCms instance (must be
	 * running) (new with version 1.7). Right now the connector client runs side by side with the old
	 * {@link #pluginConnector} and adds some additional functionality (importing modules). In a future release the
	 * connectorClient is planned to replace the old pluginConnector.
	 * @return the IDE connector client
	 */
	public IDEConnectorClient getConnectorClient() {
		if (connectorClient == null) {
			initConnectorClient();
		}
		return connectorClient;
	}

	/**
	 * Initializes the connector client
	 */
	private void initConnectorClient() {
		OpenCmsPluginConfigurationData config = getPluginConfiguration();
		if (config != null && config.isOpenCmsPluginEnabled() && config.isPluginConnectorServiceEnabled() && StringUtils.isNotBlank(config.getConnectorServiceUrl())) {
			IDEConnectorClientConfiguration clientConfiguration = new IDEConnectorClientConfiguration();
			clientConfiguration.setConnectorServiceBaseUrl(config.getConnectorServiceUrl());
			connectorClient = new IDEConnectorClient(clientConfiguration);
		}
	}
	
	
	
	/**
	 * Sets the connector client used to access the new IDE Connector Service (e.g. for importing modules)
	 * @param connectorClient
	 */
	public void setConnectorClient(IDEConnectorClient connectorClient) {
		this.connectorClient = connectorClient;
	}

	/**
	 * Initializes the plugin's tool window
	 */
	private void initToolWindow() {
		if (project == null) {
			LOG.info("Unable to initialize the tool window since the project is null");
			return;
		}
		ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
		if (toolWindowManager == null) {
			LOG.info("ToolWindowManager could not be retrieved from the project, it may not be registered yet.");
			return;
		}
		toolWindow = toolWindowManager.getToolWindow(OpenCmsPlugin.TOOLWINDOW_ID);
		if (toolWindow == null) {
			OpenCmsPluginConfigurationData config = getPluginConfiguration();
			toolWindow = toolWindowManager.registerToolWindow(OpenCmsPlugin.TOOLWINDOW_ID, false, ToolWindowAnchor.BOTTOM);
			toolWindow.setIcon(new ImageIcon(this.getClass().getResource("/icons/opencms_13.png")));
			toolWindow.setAvailable(config != null && config.isOpenCmsPluginEnabled(), null);
			OpenCmsPluginToolWindowFactory toolWindowFactory = new OpenCmsPluginToolWindowFactory();
			toolWindowFactory.createToolWindowContent(project, toolWindow);
		}
	}

	/**
	 * Returns the plugin's tool window
	 * @return  the plugin's tool window
	 */
	public ToolWindow getToolWindow() {
		if (toolWindow == null) {
			initToolWindow();
		}
		return toolWindow;
	}

	/**
	 * Activates or deactivates the plugin's tool window
	 * @param available <code>true</code> to activate and <code>false</code> to deactivate the tool window
	 */
	public void setToolWindowAvailable(boolean available) {
		ToolWindow toolWindow = getToolWindow();
		try {
			toolWindow.setAvailable(available, null);
		}
		catch (Exception e) {
			LOG.warn("Exception activating the ToolWindow: " + e.getMessage());
		}
	}

	/**
	 * Activates the plugin's tool window to display the console
	 */
	public void showConsole() {
		if (toolWindow == null || !toolWindow.isActive()) {
			getToolWindow().activate(null);
		}
	}

	/**
	 * Returns the console used to log OpenCms actions like sync or publish
	 * @return  the console used to log OpenCms actions like sync or publish
	 */
	public OpenCmsToolWindowConsole getConsole() {
		// if the console has not been initialized ...
		if (console == null) {
			initToolWindow();  // ... initialize the tool window (containing the console)
		}
		return console;
	}

	/**
	 * Sets the console used to log OpenCms actions like sync or publish
	 * @param console  the console used to log OpenCms actions like sync or publish
	 */
	public void setConsole(OpenCmsToolWindowConsole console) {
		this.console = console;
	}

	/**
	 * Returns the current project level configuration state.
	 *
	 * @return the OpenCmsPluginConfigurationData object
	 */
	@Nullable
	@Override
	public OpenCmsPluginConfigurationData getState() {
		LOG.info("OpenCmsPlugin: getState called, project: " + project.getName());
		return configurationData;
	}

	/**
	 * Loads the project level configuration state contained in the given configuration data.
	 *
	 * @param configurationData the project level configuration data to load
	 */
	@Override
	public void loadState(OpenCmsPluginConfigurationData configurationData) {
		LOG.info("OpenCmsPlugin: loadState called, project: " + project.getName());
		XmlSerializerUtil.copyBean(configurationData, getPluginConfiguration());
	}

}
 public class OpenCmsPluginConfigurationData {

	private boolean openCmsPluginEnabled = false;
    private String repository;
    private String username;
    private String password;
    private String webappRoot;
	private String defaultLocalVfsRoot;
	private String moduleNamingScheme;
	private SyncMode defaultSyncMode;
	private String ignoredFiles;
	private String[] ignoredFilesArray;
	private String ignoredPaths;
	private String[] ignoredPathsArray;
	private String moduleZipTargetFolderPath;
	private boolean pluginConnectorEnabled;
	private String connectorUrl;
	private boolean pluginConnectorServiceEnabled;
	private String connectorServiceUrl;
	private AutoPublishMode autoPublishMode;
	private boolean pullMetadataEnabled;
	private String manifestRoot;
	private boolean useMetaDateVariablesEnabled;
	private boolean useMetaIdVariablesEnabled;
	private boolean useMetaVariablesEnabled;

	/**
	 * Flag denoting if the plugin is enabled for the project.
	 * @return  <code>true</code> if the plugin is enabled for the project, <code>false</code> otherwise
	 */
	public boolean isOpenCmsPluginEnabled() {
		return openCmsPluginEnabled;
	}

	/**
	 * Sets the flag denoting if the plugin is enabled for the project.
	 * @param openCmsPluginEnabled  <code>true</code> if the plugin is to be enabled for the project,
	 *                              <code>false</code> otherwise
	 */
	public void setOpenCmsPluginEnabled(boolean openCmsPluginEnabled) {
		this.openCmsPluginEnabled = openCmsPluginEnabled;
	}

	/**
	 * Returns the configured CMIS-Repository used to sync files and/or folders to and from the OpenCms VFS.
	 * @return the configured CMIS-Repository, e.g. "http://localhost:8080/opencms/cmisatom/cmis-offline/"
	 */
	public String getRepository() {
        return repository;
    }

	/**
	 * Sets the CMIS-Repository to be used to sync files and/or folders to and from the OpenCms VFS.
	 * @param repository the CMIS-Repository, e.g. "http://localhost:8080/opencms/cmisatom/cmis-offline/"
	 */
    public void setRepository(String repository) {
        this.repository = repository;
    }

	/**
	 * Returns the configured OpenCms username
	 * @return the configured OpenCms username (e.g. "Admin")
	 */
    public String getUsername() {
        return username;
    }

	/**
	 * Sets the OpenCms username to be used to login to OpenCms
	 * @param username an OpenCms username (e.g. "Admin")
	 */
    public void setUsername(String username) {
        this.username = username;
    }

	/**
	 * Returns the configured OpenCms password
	 * @return the configured OpenCms password (e.g. "admin")
	 */
   public String getPassword() {
        return password;
    }

	/**
	 * Sets the OpenCms password to be used to login to OpenCms
	 * @param password the OpenCms user's password (e.g. "admin")
	 */
    public void setPassword(String password) {
        this.password = password;
    }

	/**
	 * Returns the configured webapp root
	 * @return the configured webapp root
	 */
    public String getWebappRoot() {
	    return stripTrailingSeparator(webappRoot);
    }

	/**
	 * Sets the webapp root
	 * @param webappRoot the root of the local OpenCms webapp
	 */
    public void setWebappRoot(String webappRoot) {
        this.webappRoot = webappRoot;
    }

	/**
	 * Returns the configured default local VFS root. That's where VFS files are stored under the module root. Modules
	 * can configure differing VFS roots.
	 * @return the configured default local VFS root (e.g. "src/main/vfs")
	 * @see OpenCmsModuleConfigurationData#getLocalVfsRoot()
	 */
	public String getDefaultLocalVfsRoot() {
		return defaultLocalVfsRoot;
	}

	/**
	 * Sets the default local VFS root. That's where VFS files are stored under the module root. Modules can configure
	 * differing VFS roots.
	 * @param defaultLocalVfsRoot the default local VFS root (e.g. "src/main/vfs")
	 * @see OpenCmsModuleConfigurationData#setLocalVfsRoot(String)
	 */
	public void setDefaultLocalVfsRoot(String defaultLocalVfsRoot) {
		this.defaultLocalVfsRoot = defaultLocalVfsRoot;
	}

	/**
	 * Returns the configured naming scheme for modules. If no specific module name is provided in the module
	 * configuration, that's the scheme used to generate the OpenCms module name by replacing ${modulename} with the
	 * IntelliJ module name.
	 * @return the module naming scheme (e.g. "com.yourcompany.opencms.${modulename})
	 * @see com.mediaworx.intellij.opencmsplugin.configuration.OpenCmsModuleConfigurationData#getModuleName() ()
	 */
	public String getModuleNamingScheme() {
		return moduleNamingScheme;
	}

	/**
	 * Sets the naming scheme to be used to generate OpenCms module names. The variable ${modulename} is replaced by
	 * the IntelliJ module name.
	 * @param moduleNamingScheme the naming scheme for OpenCms module names
	 */
	public void setModuleNamingScheme(String moduleNamingScheme) {
		this.moduleNamingScheme = moduleNamingScheme;
	}

	/**
	 * Returns the configured default Sync Mode. The Sync Mode can be PUSH, SYNC or PULL. Modules can configure
	 * differing Sync Modes.
	 * @return the configured Sync Mode
	 * @see OpenCmsModuleConfigurationData#getSyncMode()
	 */
	public SyncMode getDefaultSyncMode() {
		return defaultSyncMode != null ? defaultSyncMode : SyncMode.SYNC;
	}

	/**
	 * Sets the default Sync Mode. The Sync Mode can be PUSH, SYNC or PULL. Modules can configure differing Sync Modes.
	 * @param defaultSyncMode the SyncMode (PUSH, SYNC or PULL)
	 * @see OpenCmsModuleConfigurationData#setSyncMode(SyncMode)
	 */
	public void setDefaultSyncMode(SyncMode defaultSyncMode) {
		this.defaultSyncMode = defaultSyncMode;
	}

	/**
	 * Gets the configured ignored file regex list (one String, separated by Newlines). Files matching one of these
	 * regular expressions are not synced to/from the OpenCms VFS.
	 * @return  the configured ignored file list
	 * @see #getIgnoredFilesArray()
	 */
	public String getIgnoredFiles() {
		return ignoredFiles;
	}

	/**
	 * Gets the configured ignored file regex list as a String Array. Files matching one of these regular expressions
	 * are not synced to/from the OpenCms VFS.
	 * @return  the configured ignored file list split into a String Array.
	 * @see #getIgnoredFiles()
	 */
	public String[] getIgnoredFilesArray() {
		return ignoredFilesArray;
	}

	/**
	 * Sets the ignored file regex list. Files matching one of these regular expressions are not synced to/from the
	 * OpenCms VFS.
	 * @param ignoredFiles the ignored file regex list (one String, separated by Newlines)
	 */
	public void setIgnoredFiles(String ignoredFiles) {
		this.ignoredFiles = ignoredFiles;
		if (ignoredFiles != null && ignoredFiles.length() > 0) {
			ignoredFilesArray = StringUtils.split(ignoredFiles.trim());
		}
		else {
			ignoredFilesArray = new String[0];
		}
	}

	/**
	 * Gets the configured ignored folders regex list (one String, separated by Newlines). Folders matching one of these
	 * regular expressions are not synced to/from the OpenCms VFS.
	 * @return  the configured ignored folders list
	 * @see #getIgnoredFilesArray()
	 */
	public String getIgnoredPaths() {
		return ignoredPaths;
	}

	/**
	 * Gets the configured ignored folders regex list as a String Array. Folders matching one of these regular
	 * expressions are not synced to/from the OpenCms VFS.
	 * @return  the configured ignored folders list split into a String Array.
	 * @see #getIgnoredFiles()
	 */
	public String[] getIgnoredPathsArray() {
		return ignoredPathsArray;
	}

	/**
	 * Sets the ignored folders regex list. Folders matching one of these regular expressions are not synced to/from the
	 * OpenCms VFS.
	 * @param ignoredPaths the ignored folders regex list (one String, separated by Newlines)
	 */
	public void setIgnoredPaths(String ignoredPaths) {
		this.ignoredPaths = ignoredPaths;
		if (ignoredPaths != null && ignoredPaths.length() > 0) {
			ignoredPathsArray = StringUtils.split(ignoredPaths.trim());
		}
		else {
			ignoredPathsArray = new String[0];
		}
	}

	/**
	 * Gets the configured folder that is used to store packaged module zips
	 * @return folder that is used to store packaged module zips
	 */
	public String getModuleZipTargetFolderPath() {
		return moduleZipTargetFolderPath;
	}

	public void setModuleZipTargetFolderPath(String moduleZipTargetFolderPath) {
		this.moduleZipTargetFolderPath = moduleZipTargetFolderPath;
	}

	/**
	 * Flag denoting if the Plugin Connector is enabled
	 * @return  <code>true</code> if the Plugin Connector is enabled, <code>false</code> otherwise
	 */
	public boolean isPluginConnectorEnabled() {
		return pluginConnectorEnabled;
	}

	/**
	 * Sets the flag denoting if the Plugin Connector is enabled
	 * @param pluginConnectorEnabled <code>true</code> if the Plugin Connector should be enabled, <code>false</code>
	 *                               otherwise
	 */
	public void setPluginConnectorEnabled(boolean pluginConnectorEnabled) {
		this.pluginConnectorEnabled = pluginConnectorEnabled;
	}


	/**
	 * Flag denoting if the Plugin Connector Service is enabled (requires Plugin Connector 1.5)
	 * @return  <code>true</code> if the Plugin Connector is enabled, <code>false</code> otherwise
	 */
	public boolean isPluginConnectorServiceEnabled() {
		return pluginConnectorServiceEnabled;
	}

	/**
	 * Sets the flag denoting if the Plugin Connector Service is enabled
	 * @param pluginConnectorServiceEnabled <code>true</code> if the Plugin Connector Service should be enabled,
	 *                                      <code>false</code> otherwise
	 */
	public void setPluginConnectorServiceEnabled(boolean pluginConnectorServiceEnabled) {
		this.pluginConnectorServiceEnabled = pluginConnectorServiceEnabled;
	}


	/**
	 * Gets the configured Url under which the old Plugin Connector JSP can be called.
	 *
	 * @return the configured Url under which the Plugin Connector can be called
	 */
	public String getConnectorUrl() {
		return connectorUrl;
	}

	/**
	 * Sets the Url under which the old Plugin Connector JSP can be called.
	 *
	 * @param connectorUrl the Plugin Connector JSP's Url (e.g.
	 *                     http://localhost:8080/opencms/opencms/system/modules/com.mediaworx.opencms.ideconnector/connector.jsp)
	 */
	public void setConnectorUrl(String connectorUrl) {
		this.connectorUrl = connectorUrl;
	}

	/**
	 * Gets the configured Url under which the new Plugin Connector Service can be called.
	 *
	 * @return the configured Url under which the Plugin Connector can be called
	 */
	public String getConnectorServiceUrl() {
		return connectorServiceUrl;
	}

	/**
	 * Sets the Url under which the new Plugin Connector Service can be called.
	 *
	 * @param connectorServiceUrl the Plugin Connector Service Url (e.g. http://localhost:8080/opencms/ideConnector)
	 */
	public void setConnectorServiceUrl(String connectorServiceUrl) {
		this.connectorServiceUrl = connectorServiceUrl;
	}

	/**
	 * Gets the configured Auto Publish Mode
	 * @return the configured Auto Publish Mode (OFF, FILECHANGE, ALL)
	 */
	public AutoPublishMode getAutoPublishMode() {
		return autoPublishMode != null ? autoPublishMode : AutoPublishMode.FILECHANGE;
	}

	/**
	 * Sets the Auto Publish Mode
	 * @param autoPublishMode the Auto Publish Mode to be used by the plugin (OFF, FILECHANGE, ALL)
	 */
	public void setAutoPublishMode(AutoPublishMode autoPublishMode) {
		this.autoPublishMode = autoPublishMode;
	}


	/**
	 * Flag denoting if pulling OpenCms module and resource meta data is enabled
	 * @return  <code>true</code> if pulling meta data is enabled, <code>false</code> otherwise
	 */
	public boolean isPullMetadataEnabled() {
		return pullMetadataEnabled;
	}

	/**
	 * Sets the flag denoting if pulling OpenCms module and resource meta data is enabled
	 * @param pullMetadataEnabled <code>true</code> if pulling meta data should be enabled, <code>false</code> otherwise
	 */
	public void setPullMetadataEnabled(boolean pullMetadataEnabled) {
		this.pullMetadataEnabled = pullMetadataEnabled;
	}

	/**
	 * Gets the configured path under which OpenCms module manifest files (<code>manifest.xml</code>) and resource
	 * meta data are to be stored (relative to the module's root path).
	 * @return  the configured manifest root path
	 */
	public String getManifestRoot() {
		return manifestRoot;
	}

	/**
	 * Sets the path under which OpenCms module manifest files (<code>manifest.xml</code>) and resource meta data are
	 * to be stored (relative to the module's root path).
	 * @param manifestRoot the manifest root path to be used
	 */
	public void setManifestRoot(String manifestRoot) {
		this.manifestRoot = manifestRoot;
	}

	/**
	 * Gets the flag denoting if using placeholders instead of dates in resource meta data is enabled
	 * @return <code>true</code> if using placeholders is enabled, <code>false</code> otherwise
	 */
	public boolean isUseMetaDateVariablesEnabled() {
		return useMetaVariablesEnabled || useMetaDateVariablesEnabled;
	}

	/**
	 * Sets the flag denoting if using placeholders instead of dates in resource meta data is enabled
	 * @param useMetaDateVariablesEnabled  <code>true</code> if using placeholders should be enabled,
	 *                                     <code>false</code> otherwise
	 */
	public void setUseMetaDateVariablesEnabled(boolean useMetaDateVariablesEnabled) {
		this.useMetaDateVariablesEnabled = useMetaDateVariablesEnabled;
		if (!useMetaDateVariablesEnabled) {
			this.useMetaVariablesEnabled = false;
		}
	}

	/**
	 * Gets the flag denoting if using placeholders instead of UUIDs in resource meta data is enabled
	 *
	 * @return <code>true</code> if using placeholders is enabled, <code>false</code> otherwise
	 */
	public boolean isUseMetaIdVariablesEnabled() {
		return useMetaVariablesEnabled || useMetaIdVariablesEnabled;
	}

	/**
	 * Sets the flag denoting if using placeholders instead of UUIDs in resource meta data is enabled
	 *
	 * @param useMetaIdVariablesEnabled <code>true</code> if using placeholders should be enabled, <code>false</code>
	 *                                  otherwise
	 */
	public void setUseMetaIdVariablesEnabled(boolean useMetaIdVariablesEnabled) {
		this.useMetaIdVariablesEnabled = useMetaIdVariablesEnabled;
		if (!useMetaIdVariablesEnabled) {
			this.useMetaVariablesEnabled = false;
		}
	}

	/**
	 * Gets the flag denoting if using placeholders instead of UUIDs and dates in resource meta data is enabled
	 *
	 * @return <code>true</code> if using placeholders is enabled, <code>false</code> otherwise
	 * @deprecated no longer used, but needed for downward compatibility
	 */
	public boolean isUseMetaVariablesEnabled() {
		return useMetaVariablesEnabled || (this.useMetaDateVariablesEnabled && this.useMetaIdVariablesEnabled);
	}

	/**
	 * Sets the flag denoting if using placeholders instead of UUIDs and dates in resource meta data is enabled
	 *
	 * @param useMetaVariablesEnabled <code>true</code> if using placeholders should be enabled, <code>false</code>
	 *                                otherwise
	 * @deprecated no longer used, but needed for downward compatibility
	 */
	public void setUseMetaVariablesEnabled(boolean useMetaVariablesEnabled) {
		this.useMetaVariablesEnabled = useMetaVariablesEnabled;
		if (useMetaVariablesEnabled) {
			this.useMetaDateVariablesEnabled = true;
			this.useMetaIdVariablesEnabled = true;
		}
	}

	/**
	 * Internal utility method to strip trailing path separators from path Strings.
	 * @param s the path String
	 * @return  the path String with trailing path separators stripped.
	 */
	private String stripTrailingSeparator(String s) {
		if (s != null && (s.endsWith("\\") || s.endsWith("/"))) {
			return s.substring(0, s.length() - 1);
		}
		return s;
	}

} public enum  AutoPublishMode {
	OFF,
	FILECHANGE,
	ALL
} public class ExportEntity {

	/**
	 * Creates a new export entity
	 */
	public ExportEntity() {
	}

	String sourcePath;
	String targetPath;
	String vfsPath;
	String destination;
	boolean toBeDeleted;

	/**
	 * Returns the RFS source path
 	 * @return the RFS source path
	 */
	public String getSourcePath() {
		return sourcePath;
	}

	/**
	 * Sets the RFS source path
	 * @param sourcePath the RFS source path
	 */
	public void setSourcePath(String sourcePath) {
		this.sourcePath = sourcePath;
	}

	/**
	 * Returns the export target path (somewhere in the webapp's folder structure)
	 * @return the export target path
	 */
	public String getTargetPath() {
		return targetPath;
	}

	/**
	 * Sets the export target path
	 * @param targetPath the export target path
	 */
	public void setTargetPath(String targetPath) {
		this.targetPath = targetPath;
	}

	/**
	 * Gets the entity's OpenCms VFS path
	 * @return the entity's OpenCms VFS path
	 */
	public String getVfsPath() {
		return vfsPath;
	}

	/**
	 * Sets the entity's OpenCms VFS path
	 * @param vfsPath the entity's OpenCms VFS path
	 */
	public void setVfsPath(String vfsPath) {
		this.vfsPath = vfsPath;
	}

	/**
	 * Returns the export point's destination (the RFS target configured in OpenCms)
	 * @return the export point's destination
	 */
	public String getDestination() {
		return destination;
	}

	/**
	 * Sets the export point's destination
	 * @param destination the export point's destination (the RFS target configured in OpenCms)
	 */
	public void setDestination(String destination) {
		this.destination = destination;
	}

	/**
	 * Flag denoting if the exported entity is to be deleted
	 * @return <code>true</code> if the exported entity is to be deleted, <code>false</code> otherwise
	 */
	public boolean isToBeDeleted() {
		return toBeDeleted;
	}

	/**
	 * Sets the flag denoting if the exported entity is to be deleted
	 * @param toBeDeleted <code>true</code> if the exported entity is to be deleted, <code>false</code> otherwise
	 */
	public void setToBeDeleted(boolean toBeDeleted) {
		this.toBeDeleted = toBeDeleted;
	}

	/**
	 * Returns a String representation of this export entity (for debugging purposes)
	 * @return a String representation of this export entity
	 */
	@Override
	public String toString() {
		return "ExportEntity {\n" +
				"  sourcePath:  " + sourcePath + "\n" +
				"  targetPath:  " + targetPath + "\n" +
				"  vfsPath:     " + vfsPath + "\n" +
				"  destination: " + destination + "\n" +
				"  toBeDeleted: " + toBeDeleted + "\n" +
				"} " + super.toString();
	}
} public abstract class SyncEntity {

	private OpenCmsModule ocmsModule;
	private String vfsPath;
	private File file;
	private CmisObject vfsObject;
	private SyncAction syncAction;
	private boolean replaceExistingEntity;

	/**
	 * Creates a new sync entity
	 * @param ocmsModule the OpenCms module the entity is contained in
	 * @param vfsPath the entity's VFS path
	 * @param file The java file file representing the entity
	 * @param vfsObject The CMIS object representing the entity
	 * @param syncAction The Sync Action to be used for the entity (PUSH, PULL, DELETE_RFS or DELETE_VFS)
	 * @param replaceExistingEntity <code>true</code> if an existing entity is replaced during sync
	 */
	public SyncEntity(OpenCmsModule ocmsModule, String vfsPath, File file, CmisObject vfsObject, SyncAction syncAction, boolean replaceExistingEntity) {
		setOcmsModule(ocmsModule);
		setVfsPath(vfsPath);
		setFile(file);
		setVfsObject(vfsObject);
		setSyncAction(syncAction);
		setReplaceExistingEntity(replaceExistingEntity);
	}

	/**
	 * Returns the entity's type (FILE or FOLDER)
	 * @return the entity's type (FILE or FOLDER)
	 */
	public abstract Type getType();

	/**
	 * returns the path to the meta data file for this entity
	 * @return the path to the meta data file for this entity
	 */
	public abstract String getMetaInfoFilePath();

	/**
	 * Returns the OpenCms module.
	 * @return the OpenCms module
	 */
	public OpenCmsModule getOcmsModule() {
		return ocmsModule;
	}

	/**
	 * Sets the OpenCms module
	 * @param ocmsModule the OpenCms module
	 */
	public void setOcmsModule(OpenCmsModule ocmsModule) {
		this.ocmsModule = ocmsModule;
	}

	/**
	 * Returns the entity's VFS path.
	 * @return the entity's VFS path
	 */
	public String getVfsPath() {
		return vfsPath;
	}

	/**
	 * Sets the entity's VFS path.
	 * @param vfsPath the entity's VFS path
	 */
	public void setVfsPath(String vfsPath) {
		this.vfsPath = PluginTools.ensureUnixPath(vfsPath);
	}

	/**
	 * Returns the entity's path in the real file System.
	 * @return the entity's RFS path
	 */
	public String getRfsPath() {
		String localPath = PluginTools.stripVfsSiteRootFromVfsPath(ocmsModule, vfsPath);
		return ocmsModule.getLocalVfsRoot() + localPath;
	}

	/**
	 * Returns a File handle for the entity in the real file system
	 * @return a File handle for the entity in the RFS
	 */
	public File getFile() {
		return this.file;
	}

	/**
	 * Sets the File handle handle for the entity in the real file system
	 * @param file the File handle for the entity in the RFS
	 */
	public void setFile(File file) {
		this.file = file;
	}

	/**
	 * Returns the CMIS object representing this entity in the OpenCms VFS
	 * @return the CMIS object representing this entity in the OpenCms VFS
	 */
	public CmisObject getVfsObject() {
		return vfsObject;
	}

	/**
	 * Sets the CMIS object representing this entity in the OpenCms VFS
	 * @param vfsObject the CMIS object representing this entity in the OpenCms VFS
	 */
	public void setVfsObject(CmisObject vfsObject) {
		this.vfsObject = vfsObject;
	}

	/**
	 * Returns the entity's SyncAction (PUSH, PULL, DELETE_RFS or DELETE_VFS)
	 * @return the entity's SyncAction (PUSH, PULL, DELETE_RFS or DELETE_VFS)
	 */
	public SyncAction getSyncAction() {
		return syncAction;
	}

	/**
	 * Sets the entity's SyncAction
	 * @param syncAction the entity's SyncAction (PUSH, PULL, DELETE_RFS or DELETE_VFS)
	 */
	public void setSyncAction(SyncAction syncAction) {
		this.syncAction = syncAction;
	}

	/**
	 * Flag denoting if an existing entity is replaced during sync
	 * @return <code>true</code> if an existing entity is replaced during sync, <code>false</code> otherwise
	 */
	public boolean replaceExistingEntity() {
		return replaceExistingEntity;
	}

	/**
	 * Sets the flag denoting if an existing entity is replaced during sync
	 * @param replaceExistingEntity <code>true</code> if an existing entity is replaced during sync,
	 *                              <code>false</code> otherwise
	 */
	public void setReplaceExistingEntity(boolean replaceExistingEntity) {
		this.replaceExistingEntity = replaceExistingEntity;
	}

	/**
	 * Checks if the entity is a file
	 * @return <code>true</code> if the entity is a file, <code>false</code> otherwise
	 */
	public boolean isFile() {
		return getType() == Type.FILE;
	}

	/**
	 * Checks if the entity is a folder
	 * @return <code>true</code> if the entity is a folder, <code>false</code> otherwise
	 */
	public boolean isFolder() {
		return getType() == Type.FOLDER;
	}

	/**
	 * Possible Types of the entity, FILE and FOLDER
	 */
	public static enum Type {
		FILE,
		FOLDER
	}
} public class SyncFolder extends SyncEntity {

	/**
	 * Creates a new sync folder
	 * @param ocmsModule the OpenCms module the folder is contained in
	 * @param vfsPath the folder's VFS path
	 * @param file Java file instance representing the folder
	 * @param vfsObject The CMIS object representing the folder
	 * @param syncAction The Sync Action to be used for the folder (PUSH, PULL, DELETE_RFS or DELETE_VFS)
	 */
	public SyncFolder(OpenCmsModule ocmsModule, String vfsPath, File file, CmisObject vfsObject, SyncAction syncAction, boolean replaceExistingEntity) {
		super(ocmsModule, vfsPath, file, vfsObject, syncAction, replaceExistingEntity);
	}

	/**
	 * Returns the Type (FOLDER)
	 * @return always returns Type.FOLDER
	 */
	@Override
	public Type getType() {
		return Type.FOLDER;
	}

	/**
	 * returns the path to the meta data file for this folder
	 * @return the path to the meta data file for this folder
	 */
	public String getMetaInfoFilePath() {
		String localPath = PluginTools.stripVfsSiteRootFromVfsPath(getOcmsModule(), getVfsPath());
		return OpenCmsModuleManifestGenerator.getMetaInfoPath(getOcmsModule().getManifestRoot(), localPath, true);
	}

	/**
	 * returns the path to the meta data folder for this folder (containing meta data of child entities)
	 * @return the path to the meta data folder for this folder
	 */
	public String getMetaInfoFolderPath() {
		String localPath = PluginTools.stripVfsSiteRootFromVfsPath(getOcmsModule(), getVfsPath());
		return getOcmsModule().getManifestRoot() + localPath;
	}


} public class CmsPushException extends Exception {

	/**
	 * Creates a new CmsPushException with the given message
	 * @param message the error message
	 */
	public CmsPushException(String message) {
		super(message);
	}

	/**
	 * Creates a new CmsPushException with the given message
	 * @param message the error message
	 * @param cause Original Exception/Throwable that led to the exception
	 */
	public CmsPushException(String message, Throwable cause) {
		super(message, cause);
	}
} public class OpenCmsConnectorException extends Exception {

	/**
	 * Creates a new OpenCmsConnectorException with the given message
	 * @param message the error message
	 */
	public OpenCmsConnectorException(String message) {
		super(message);
	}

	/**
	 * Creates a new OpenCmsConnectorException with the given message
	 * @param message the error message
	 * @param cause Original Exception/Throwable that led to the exception
	 */
	public OpenCmsConnectorException(String message, Throwable cause) {
		super(message, cause);
	}
} public class OpenCmsModule implements OpenCmsConfiguration.ConfigurationChangeListener {

	private static final Logger LOG = LoggerFactory.getLogger(OpenCmsModule.class);

	private OpenCmsPlugin plugin;
	private String moduleBasePath;
	private OpenCmsConfiguration openCmsConfig;
	private OpenCmsPluginConfigurationData pluginConfig;
	private OpenCmsModuleConfigurationData moduleConfig;
	private String moduleName;

	private List<OpenCmsModuleExportPoint> exportPoints;
	private List<String> moduleResources;
	private String localVfsRoot;

	/**
	 * Creates a new OpenCms module
	 * @param plugin            the current plugin instance
	 * @param moduleBasePath    absolute base path of the module on the local file system
	 */
	public OpenCmsModule(OpenCmsPlugin plugin, String moduleBasePath) {
		this.plugin = plugin;
		this.moduleBasePath = moduleBasePath;

		pluginConfig = plugin.getPluginConfiguration();
		openCmsConfig = plugin.getOpenCmsConfiguration();
		openCmsConfig.registerConfigurationChangeListener(this);
	}

	/**
	 * Initializes the module with the given configuration data (from the module's configuration dialog)
	 * @param moduleConfig the module's configuration data
	 */
	public void init(OpenCmsModuleConfigurationData moduleConfig) {
		this.moduleConfig = moduleConfig;

		if (moduleConfig.isUseProjectDefaultModuleNameEnabled()) {
			String moduleNamingScheme = pluginConfig.getModuleNamingScheme();
			String moduleFolderName = StringUtils.substringAfterLast(this.moduleBasePath, "/");
			if (moduleNamingScheme != null && moduleNamingScheme.length() > 0) {
				moduleName = moduleNamingScheme.replaceAll(Pattern.quote("${modulename}"), moduleFolderName);
			}
			else {
				moduleName = moduleFolderName;
			}
		}
		else {
			moduleName = moduleConfig.getModuleName();
		}

		exportPoints = openCmsConfig.getExportPointsForModule(moduleName);
		moduleResources = openCmsConfig.getModuleResourcesForModule(moduleName);

		String relativeVfsRoot;

		if (moduleConfig.isUseProjectDefaultVfsRootEnabled()) {
			relativeVfsRoot = pluginConfig.getDefaultLocalVfsRoot();
		}
		else {
			relativeVfsRoot = moduleConfig.getLocalVfsRoot();
		}
		localVfsRoot = this.moduleBasePath + "/" + relativeVfsRoot;
	}

	/**
	 * refreshes the module after module configuration changes
	 * @param moduleConfig  the updated configuration data
	 */
	public void refresh(OpenCmsModuleConfigurationData moduleConfig) {
		// for now a refresh just does the same as init
		init(moduleConfig);
	}

	/**
	 * refreshes the module with unchanged configuration data
	 */
	public void refresh() {
		// for now a refresh just does the same as init
		init(moduleConfig);
	}

	/**
	 * @return the module's package name name, e.g. com.mycompany.opencms.contenttypes
	 */
	public String getModuleName() {
		return moduleName;
	}

	/**
	 * @return  absolute base path of the module on the local file system
	 */
	public String getModuleBasePath() {
		return moduleBasePath;
	}

	/**
	 * @return absolute local path to the module's vfs folder
	 */
	public String getLocalVfsRoot() {
		return localVfsRoot;
	}

	public String getExportImportSiteRoot() {
		String exportImportSiteRoot = moduleConfig.getExportImportSiteRoot();
		if (StringUtils.isBlank(exportImportSiteRoot)) {
			exportImportSiteRoot = "/";
		}
		return exportImportSiteRoot;
	}

	/**
	 * @return absolute local path to the folder containing the module's meta data
	 */
	public String getManifestRoot() {
		return moduleBasePath + "/" + plugin.getPluginConfiguration().getManifestRoot();
	}

	/**
	 * @return the module's sync mode as configured in either the global plugin configuration or the module's
	 *         configuration
	 */
	public SyncMode getSyncMode() {
		if (moduleConfig.isUseProjectDefaultSyncModeEnabled()) {
			return pluginConfig.getDefaultSyncMode();
		}
		else {
			return moduleConfig.getSyncMode();
		}
	}

	/**
	 * @return <code>true</code> if the module configuration defines a specific module version to use,
	 *         <code>false</code> otherwise
	 */
	public boolean isSetSpecificModuleVersionEnabled() {
		return moduleConfig.isSetSpecificModuleVersionEnabled();
	}

	/**
	 * @return the module version configured for this module (if any)
	 */
	public String getModuleVersion() {
		return moduleConfig.getModuleVersion();
	}

	/**
	 * @return a list of export points configured for this module
	 */
	public List<OpenCmsModuleExportPoint> getExportPoints() {
		return exportPoints;
	}

	/**
	 * @return list of the module resource paths configured for this module (relative to VFS root)
	 */
	public List<String> getModuleResources() {
		return moduleResources;
	}

	/**
	 * Checks if the given file is placed inside a module resource path
	 * @param file  the file to check
	 * @return  <code>true</code> if the file is contained in one of the module's resource paths,
	 *          <code>false</code> otherwise
	 */
	public boolean isFileModuleResource(File file) {
		return isPathModuleResource(file.getPath());
	}

	/**
	 * Checks if the given path is inside a module resource path
	 *
	 * @param resourcePath the path to check
	 * @return <code>true</code> if the path is contained in one of the module's resource paths,
	 * <code>false</code> otherwise
	 */
	public boolean isPathModuleResource(String resourcePath) {
		resourcePath = PluginTools.ensureUnixPath(resourcePath);
		for (String moduleResourcePath : getModuleResources()) {
			String localModuleResourcePath = getLocalVfsRoot() + moduleResourcePath;
			if ((resourcePath + "/").endsWith(localModuleResourcePath)) {
				resourcePath = resourcePath + "/";
			}
			LOG.info("localModuleResourcePath: " + localModuleResourcePath);
			LOG.info("resourcePath:     " + resourcePath);
			if (resourcePath.startsWith(localModuleResourcePath)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * checks if the given file represents the module's root folder
	 * @param file  the file to check
	 * @return <code>true</code> if the file represents the module's root folder, <code>false</code> otherwise
	 */
	public boolean isFileModuleRoot(File file) {
		if (!file.isDirectory()) {
			return false;
		}
		String filePath = PluginTools.ensureUnixPath(file.getPath());
		return filePath.equals(moduleBasePath);
	}

	/**
	 * checks if the given path is the module's root folder
	 *
	 * @param path the path to check
	 * @return <code>true</code> if the path is the module's root folder, <code>false</code> otherwise
	 */
	public boolean isPathModuleRoot(String path) {
		if (StringUtils.isBlank(path)) {
			return false;
		}
		// RTASK: check for trailing slashes on either path. How is moduleBasePath stored? How should it be stored?
		return path.equals(moduleBasePath);
	}

	/**
	 * checks if the given file is placed under the local VFS root
	 * @param file  the file to check
	 * @return <code>true</code> if the file is contained in the local VFS root, <code>false</code> otherwise
	 */
	public boolean isFileInVFSPath(File file) {
		if (VfsFileAnalyzer.fileOrPathIsIgnored(plugin.getPluginConfiguration(), file)) {
			return false;
		}
		String filePath = PluginTools.ensureUnixPath(file.getPath());
		return filePath.startsWith(localVfsRoot);
	}

	/**
	 * checks if the given path is under the local VFS root
	 *
	 * @param path the path to check
	 * @return <code>true</code> if the path is contained in the local VFS root, <code>false</code> otherwise
	 */
	public boolean isPathInVFSPath(String path) {
		String filename = StringUtils.substringAfterLast(path, "/");
		if (VfsFileAnalyzer.fileOrPathIsIgnored(plugin.getPluginConfiguration(), path, filename)) {
			return false;
		}
		return path.startsWith(localVfsRoot);
	}


	/**
	 * @param file the file for which the VFS is to be retrieved
	 * @return the VFS relative path for the given file
	 */
	public String getVfsPathForFile(final File file) {
		String filepath = PluginTools.ensureUnixPath(file.getPath());
		return getVfsPathForRealPath(filepath);
	}

	/**
	 * @param path a local root path
	 * @return the VFS relative path for the given local path
	 */
	public String getVfsPathForRealPath(final String path) {
		String relativeName;
		try {
			relativeName = path.substring(localVfsRoot.length());
		}
		catch (StringIndexOutOfBoundsException e) {
			LOG.warn("There was a problem loading the VFS path for the path " + path + " - localVfsRoot: " + localVfsRoot);
			relativeName = "/";
		}
		if (relativeName.length() == 0) {
			relativeName = "/";
		}
		return PluginTools.addVfsSiteRootToLocalPath(this, relativeName);
	}

	/**
	 * @return the path of the newest module zip in the target folder, null if no module zip exists
	 */
	public String findNewestModuleZipPath() {
		String zipParentPath = getModuleBasePath() + "/" + plugin.getPluginConfiguration().getModuleZipTargetFolderPath();
		Collection<File> moduleZips = FileUtils.listFiles(new File(zipParentPath), new String[]{"zip"}, false);
		File newestModuleZip = null;
		for (File moduleZip : moduleZips) {
			if (newestModuleZip == null || FileUtils.isFileNewer(moduleZip, newestModuleZip)) {
				newestModuleZip = moduleZip;
			}
		}
		if (newestModuleZip != null) {
			return newestModuleZip.getPath();
		}
		else {
			return null;
		}
	}


	/**
	 * handles changes to the module configuration, refreshes export points and module resources
	 * @param changeType    the type of the changed OpenCms configuration (right now only MODULECONFIGURATION)
	 */
	@Override
	public void handleOpenCmsConfigurationChange(OpenCmsConfiguration.ConfigurationChangeType changeType) {
		if (changeType == OpenCmsConfiguration.ConfigurationChangeType.MODULECONFIGURATION) {
			exportPoints = openCmsConfig.getExportPointsForModule(moduleName);
			moduleResources = openCmsConfig.getModuleResourcesForModule(moduleName);
		}
	}

} public class OpenCmsModuleExportPoint {

	private String vfsSource;
	private String rfsTarget;

	/**
	 * Creates a new module export point
	 * @param vfsSource the VFS resource path for this export point
	 * @param rfsTarget the target path in the real file system, relative to the webapp
	 */
	public OpenCmsModuleExportPoint(String vfsSource, String rfsTarget) {
		this.vfsSource = vfsSource;
		this.rfsTarget = rfsTarget;
	}

	/**
	 * Returns the VFS resource path for this export point
	 * @return  the VFS resource path for this export point
	 */
	public String getVfsSource() {
		return vfsSource;
	}

	/**
	 * Returns the path in the real file system, relative to the webapp
	 * @return  the target path in the real file system, relative to the webapp
	 */
	public String getRfsTarget() {
		return rfsTarget;
	}


	/**
	 * returns the export path for  the given resource relative to the webapp.
	 * @param resourcePath  path to the resource within the export point
	 * @return  export path for the resource relative to the webapp
	 */
	public String getTargetPathForVfsResource(String resourcePath) {
		if (!resourcePath.startsWith(vfsSource)) {
			return null;
		}
		return rfsTarget + resourcePath.substring(vfsSource.length());
	}
} public class OpenCmsModuleResource {

	private OpenCmsModule openCmsModule;
	private String resourcePath;

	/**
	 * @param openCmsModule the OpenCms module this resource belongs to
	 * @param resourcePath  the resource path (VFS relative path)
	 */
	public OpenCmsModuleResource(OpenCmsModule openCmsModule, String resourcePath) {
		this.openCmsModule = openCmsModule;
		this.resourcePath = PluginTools.ensureUnixPath(resourcePath);
	}

	/**
	 * @return the OpenCms module this resource belongs to
	 */
	public OpenCmsModule getOpenCmsModule() {
		return openCmsModule;
	}

	/**
	 * @return the resource path (VFS relative path)
	 */
	public String getResourcePath() {
		String resourcePath = this.resourcePath.replaceFirst("/$", ""); // strip trailing slash
		return PluginTools.addVfsSiteRootToLocalPath(openCmsModule, resourcePath);
	}

} public class PluginTools {

	/**
	 * @param module an IntelliJ module
	 * @return the content root for the module. It is assumed that IntelliJ modules representing OpenCms modules
	 *         have only one content root. If multiple content roots exist for the IntelliJ module, only the first is
	 *         returned.
	 */
	public static String getModuleContentRoot(Module module) {
		String moduleContentRoot = null;
		if (module != null) {
			VirtualFile[] moduleRoots = ModuleRootManager.getInstance(module).getContentRoots();
			if (moduleRoots.length == 0) {
				return null;
			}
			// we assume that for OpenCms modules there is only one content root
			moduleContentRoot = moduleRoots[0].getPath();
		}
		return moduleContentRoot;
	}


	/**
	 * Converts an array of IntelliJ's VirtualFiles to a list of standard Java File handles.
	 * @param virtualFiles the IntelliJ VirtualFile array
	 * @return a list of corresponding standard Java File handles
	 */
	public static List<File> getRealFilesFromVirtualFiles(VirtualFile[] virtualFiles) {
		List<File> realFiles = new ArrayList<>(virtualFiles.length);
		for (VirtualFile virtualFile : virtualFiles) {
			realFiles.add(new File(virtualFile.getPath()));
		}
		return realFiles;
	}

	/**
	 * Converts the windows File separator "\" to the Unix/Linux/Mac separator "/"
	 * @param path the path to convert
	 * @return a path woth all backward slashes replaced by forward ones
	 */
	public static String ensureUnixPath(String path) {
		if (path == null || path.length() == 0) {
			return path;
		}
		return path.replaceAll("\\\\", "/");
	}

	/**
	 * converts any newline format (\r\n or \r) to Unix/Linux/Mac newline format (\n)
	 * @param in the String to convert
	 * @return the original String with all newlines converted to \n
	 */
	public static String ensureUnixNewline(String in) {
		return in.replaceAll("\\r\\n|\\r", "\n");
	}

	/**
	 * If the module has a site root other than "/" configured, then the site root is removed from the beginning of
	 * the given vfsPath
	 * @param module  the module the path belongs to
	 * @param vfsPath the VFS path to be stripped of the site root
	 * @return the given vfsPath with the site root removed
	 */
	public static String stripVfsSiteRootFromVfsPath(OpenCmsModule module, String vfsPath) {
		String localPath;
		if ("/".equals(module.getExportImportSiteRoot())) {
			localPath = vfsPath;
		}
		else {
			localPath = vfsPath.replaceFirst("^" + module.getExportImportSiteRoot(), "");
		}
		return localPath;
	}


	/**
	 * If the module has a site root other than "/" configured, then the site root is added to the beginning of
	 * the given local path
	 *
	 * @param module    the module the path belongs to
	 * @param localPath the local path that the site root should be added to
	 * @return the given local path with the site root added as prefix
	 */
	public static String addVfsSiteRootToLocalPath(OpenCmsModule module, String localPath) {
		String vfsRootPath = localPath;
		if (!"/".equals(module.getExportImportSiteRoot())) {
			vfsRootPath = module.getExportImportSiteRoot() + vfsRootPath;
		}
		return vfsRootPath;

	}
} public class OpenCmsToolWindowConsole extends JTextPane {

	private static final Logger LOG = Logger.getInstance(OpenCmsToolWindowConsole.class);

	private JScrollPane scrollPane;
	private StyledDocument consoleDocument;
	private SimpleAttributeSet errorAttributes;
	private SimpleAttributeSet noticeAttributes;

	public OpenCmsToolWindowConsole() {
		super();
		init();
	}

	public void setScrollPane(JScrollPane scrollPane) {
		this.scrollPane = scrollPane;
	}

	private void init() {
		setEditable(false);

		consoleDocument = getStyledDocument();
		errorAttributes = new SimpleAttributeSet();
		StyleConstants.setForeground(errorAttributes, Color.RED);
		StyleConstants.setBold(errorAttributes, true);

		noticeAttributes = new SimpleAttributeSet();
		StyleConstants.setForeground(noticeAttributes, Color.BLUE);
		StyleConstants.setBold(noticeAttributes, true);
	}

	public void clear() {
		setText("");
	}

	public void append(String str) {
		append(str, null);
	}

	public void info(String str) {
		append(str + "\n", null);
	}

	public void notice(String str) {
		append(str + "\n", noticeAttributes);
	}

	public void error(String str) {
		append(str + "\n", errorAttributes);
	}

	private void append(String str, AttributeSet attributeSet) {
		UIUtil.invokeLaterIfNeeded(new ConsoleAppender(str, attributeSet));
	}

	public StyledDocument getConsoleDocument() {
		return consoleDocument;
	}

	private class ConsoleAppender implements Runnable {

		String stringToAppend;
		AttributeSet attributeSet;

		private ConsoleAppender(String stringToAppend, AttributeSet attributeSet) {
			this.stringToAppend = stringToAppend;
			this.attributeSet = attributeSet;
		}

		@Override
		public void run() {
			try {
				consoleDocument.insertString(consoleDocument.getLength(), stringToAppend, attributeSet);
			}
			catch (BadLocationException e) {
				LOG.warn("Exception while appending content to the console", e);
			}
			if (scrollPane != null && scrollPane.getVerticalScrollBar() != null) {
				JScrollBar scrollbar = scrollPane.getVerticalScrollBar();
				scrollPane.getVerticalScrollBar().setValue(scrollbar.getMaximum() - scrollbar.getVisibleAmount());
			}
		}

	}
}
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.mediaworx.intellij.opencmsplugin.OpenCmsPlugin;
import com.mediaworx.intellij.opencmsplugin.configuration.OpenCmsPluginConfigurationData;
import com.mediaworx.intellij.opencmsplugin.connector.AutoPublishMode;
import com.mediaworx.intellij.opencmsplugin.entities.ExportEntity;
import com.mediaworx.intellij.opencmsplugin.entities.SyncEntity;
import com.mediaworx.intellij.opencmsplugin.entities.SyncFolder;
import com.mediaworx.intellij.opencmsplugin.exceptions.CmsPushException;
import com.mediaworx.intellij.opencmsplugin.exceptions.OpenCmsConnectorException;
import com.mediaworx.intellij.opencmsplugin.opencms.OpenCmsModule;
import com.mediaworx.intellij.opencmsplugin.opencms.OpenCmsModuleExportPoint;
import com.mediaworx.intellij.opencmsplugin.opencms.OpenCmsModuleResource;
import com.mediaworx.intellij.opencmsplugin.tools.PluginTools;
import com.mediaworx.intellij.opencmsplugin.toolwindow.OpenCmsToolWindowConsole;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
		boolean success = false;
		String errormessage = null;

		if (entity.isFolder()) {
			try {
				adapter.createFolder(entity.getVfsPath());
				success = true;
			}
			catch (Exception e) {
				errormessage = "Error pushing Folder "+entity.getVfsPath()+"\n"+e.getMessage();
			}
		}
		else if (entity.isFile()) {
			try {
				adapter.pushFile(entity);
				success = true;
			}
			catch (CmsPushException e) {
				errormessage = e.getMessage();
			}
		}

		if (success) {
			StringBuilder confirmation = new StringBuilder();
			confirmation.append("PUSH: ").append(entity.getVfsPath()).append(" pushed to VFS");
			if (entity.replaceExistingEntity()) {
				confirmation.append(" replacing an existing entity");
			}
			console.info(confirmation.toString());
		}
		else {
			console.error("PUSH FAILED! " + errormessage);
		}
	}

	private void doPull(SyncEntity entity) {
		StringBuilder confirmation = new StringBuilder();

		if (entity.isFolder()) {
			try {
				FileUtils.forceMkdir(new File(entity.getRfsPath()));
			} catch (IOException e) {
				console.error("ERROR: couldn't create local directory " + entity.getRfsPath());
				LOG.warn("There was an Exception creating a local directory", e);
		   }
		}
		else {
			adapter.pullFile(entity);
		}

		confirmation.append("PULL: ").append(entity.getVfsPath()).append(" pulled from VFS to ").append(entity.getOcmsModule().getLocalVfsRoot());
		if (entity.replaceExistingEntity()) {
			confirmation.append(" replacing an existing entity");
		}

		console.info(confirmation.toString());
	}

	private void doDeleteFromRfs(SyncEntity entity) {
		StringBuilder confirmation = new StringBuilder("DELETE ").append(entity.getVfsPath()).append(" from ").append(entity.getOcmsModule().getLocalVfsRoot()).append(" (not in the VFS) - ");
		File rfsFile = entity.getFile();
		if (FileUtils.deleteQuietly(rfsFile)) {
			confirmation.append(" SUCCESS");
			console.info(confirmation.toString());
		}
		else {
			confirmation.insert(0, "ERROR: ");
			confirmation.append(" FAILED!");
			console.error(confirmation.toString());
		}
	}

	private void doDeleteFromVfs(SyncEntity entity) {
		StringBuilder confirmation = new StringBuilder("DELETE ").append(entity.getVfsPath()).append(" (not in the RFS) - ");
		if (adapter.deleteResource(entity.getVfsPath())) {
			confirmation.append(" SUCCESS");
			console.info(confirmation.toString());
		}
		else {
			confirmation.insert(0, "ERROR: ");
			confirmation.append(" FAILED!");
			console.error(confirmation.toString());
		}
	}

	private void pullResourceMetaInfos() {
		// build lists with all resources for which meta information is to be pulled / deleted
		ArrayList<String> pullEntityList = new ArrayList<String>();
		for (SyncEntity entity : syncList) {
			if (!entity.getSyncAction().isDeleteAction()) {
				pullEntityList.add(entity.getVfsPath());
			}
		}

		try {
			HashMap<String, String> metaInfos = plugin.getPluginConnector().getResourceInfos(pullEntityList);
			for (SyncEntity entity : syncList) {
				doMetaInfoHandling(console, metaInfos, entity);
			}
		}
		catch (OpenCmsConnectorException e) {
			console.error(e.getMessage());
		}
		catch (IOException e) {
			console.error("There was an error retrieving resource meta infos from OpenCms");
			LOG.warn("IOException while trying to retrieve meta infos", e);
		}
	}

	private void doMetaInfoHandling(OpenCmsToolWindowConsole console, Map<String,String> metaInfos, SyncEntity entity) {
		String metaInfoFilePath = entity.getMetaInfoFilePath();
		File metaInfoFile = new File(metaInfoFilePath);

		if (entity.getSyncAction().isDeleteAction()) {
			FileUtils.deleteQuietly(metaInfoFile);
			console.info("DELETE: " + metaInfoFilePath);
			return;
		}

		if (metaInfos.containsKey(entity.getVfsPath())) {
			if (entity instanceof SyncFolder) {