1 /***************************************************************************
2                           robjectlist  -  description
3                              -------------------
4     begin                : Wed Aug 18 2004
5     copyright            : (C) 2004-2019 by Thomas Friedrichsmeier
6     email                : thomas.friedrichsmeier@kdemail.net
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 #include "robjectlist.h"
18 
19 #define UPDATE_DELAY_INTERVAL 500
20 
21 #define ROBJECTLIST_UPDATE_ENVIRONMENTS_COMMAND 1
22 #define ROBJECTLIST_UPDATE_COMPLETE_COMMAND 2
23 
24 #include <qtimer.h>
25 #include <qstringlist.h>
26 
27 #include <KLocalizedString>
28 
29 #include "renvironmentobject.h"
30 #include "../rbackend/rkrinterface.h"
31 #include "rkmodificationtracker.h"
32 #include "../misc/rkprogresscontrol.h"
33 #include "../settings/rksettingsmoduler.h"
34 #include "rkpseudoobjects.h"
35 
36 #include "../rkglobals.h"
37 
38 #include "../debug.h"
39 
40 // static
41 RObjectList *RObjectList::object_list = 0;
42 
RObjectList()43 RObjectList::RObjectList () : RContainerObject (0, QString ()) {
44 	RK_TRACE (OBJECTS);
45 	object_list = this;
46 
47 	update_timer = new QTimer (this);
48 	update_timer->setSingleShot (true);
49 	connect (update_timer, &QTimer::timeout, this, &RObjectList::timeout);
50 
51 	//update_timer->start (AUTO_UPDATE_INTERVAL, true);
52 
53 	type = RObject::Workspace;
54 	name = "search()";
55 
56 	update_chain = 0;
57 
58 	globalenv = new REnvironmentObject (0, ".GlobalEnv");
59 	globalenv->updateFromR (update_chain);
60 
61    // TODO: Do we really need tracker notification at this stage?
62 	RKOrphanNamespacesObject *obj = new RKOrphanNamespacesObject (this);
63 	RKGlobals::tracker ()->beginAddObject (obj, this, 0);      // first child after GlobalEnv
64 	orphan_namespaces = obj;
65 	RKGlobals::tracker ()->endAddObject (obj, this, 0);
66 }
67 
~RObjectList()68 RObjectList::~RObjectList () {
69 	RK_TRACE (OBJECTS);
70 	delete orphan_namespaces;
71 	delete globalenv;
72 }
73 
getObjectDescription() const74 QString RObjectList::getObjectDescription () const {
75 	return i18n ("This section contains environments that are not part of <i>.GlobalEnv</i> / your \"workspace\". Most importantly, this includes loaded packages, but also objects added to R's <i>search()<i>-path using <i>attach()</i>.");
76 }
77 
detachPackages(const QStringList & packages,RCommandChain * chain,RKProgressControl * control)78 QStringList RObjectList::detachPackages (const QStringList &packages, RCommandChain *chain, RKProgressControl* control) {
79 	RK_TRACE (OBJECTS);
80 
81 	QStringList remove;
82 	QStringList reject;
83 	for (int i = 0; i < packages.size(); ++i) {
84 		QString shortname = packages[i];
85 		shortname.remove ("package:");
86 		if (RKSettingsModuleRPackages::essentialPackages ().contains (shortname)) {
87 			reject.append (i18n ("Did not unload package %1. It is required in RKWard. If you really want to do this, do so on the R Console.", shortname));
88 		} else if (!findChildByName (packages[i])) {
89 			RK_ASSERT (false);
90 			reject.append (i18n ("Package %1 appears not to have been loaded", shortname));
91 		} else {
92 			remove.append (packages[i]);
93 		}
94 	}
95 	for (int i = 0; i < remove.size (); ++i) {
96 		RCommand *command = new RCommand ("detach (" + rQuote (remove[i]) + ')', RCommand::App | RCommand::ObjectListUpdate);
97 
98 		if (control) control->addRCommand (command);
99 		RKGlobals::rInterface()->issueCommand (command, chain);
100 	}
101 
102 	return reject;
103 }
104 
updateFromR(RCommandChain * chain)105 void RObjectList::updateFromR (RCommandChain *chain) {
106 	RK_TRACE (OBJECTS);
107 
108 	if (update_chain) {
109 		// gee, looks like another update is still on the way. lets schedule one for later:
110 		update_timer->start (UPDATE_DELAY_INTERVAL);
111 		RK_DEBUG (OBJECTS, DL_DEBUG, "another object-list update is already running. Rescheduling a further update for later");
112 		return;
113 	}
114 
115 	emit (updateStarted ());
116 	update_chain = RKGlobals::rInterface ()->startChain (chain);
117 
118 	RCommand *command = new RCommand ("list (search (), loadedNamespaces ())", RCommand::App | RCommand::Sync | RCommand::GetStructuredData, QString (), this, ROBJECTLIST_UPDATE_ENVIRONMENTS_COMMAND);
119 	RKGlobals::rInterface ()->issueCommand (command, update_chain);
120 }
121 
updateFromR(RCommandChain * chain,const QStringList & current_searchpath,const QStringList & current_namespaces)122 void RObjectList::updateFromR (RCommandChain *chain, const QStringList &current_searchpath, const QStringList &current_namespaces) {
123 	RK_TRACE (OBJECTS);
124 
125 // TODO: can this happen? when?
126 	if (update_chain) {
127 		// gee, looks like another update is still on the way. lets schedule one for later:
128 		update_timer->start (UPDATE_DELAY_INTERVAL);
129 		RK_DEBUG (OBJECTS, DL_DEBUG, "another object-list update is already running. Rescheduling a further update for later");
130 		return;
131 	}
132 
133 	emit (updateStarted ());
134 	update_chain = RKGlobals::rInterface ()->startChain (chain);
135 
136 	updateEnvironments (current_searchpath, false);
137 	updateNamespaces (current_namespaces);
138 
139 	RKGlobals::rInterface ()->issueCommand (QString (), RCommand::App | RCommand::Sync | RCommand::EmptyCommand, QString (), this, ROBJECTLIST_UPDATE_COMPLETE_COMMAND, update_chain);
140 }
141 
rCommandDone(RCommand * command)142 void RObjectList::rCommandDone (RCommand *command) {
143 	RK_TRACE (OBJECTS);
144 
145 	if (command->getFlags () == ROBJECTLIST_UPDATE_ENVIRONMENTS_COMMAND) {
146 		RK_ASSERT (command->getDataType () == RData::StructureVector);
147 		const RData::RDataStorage & data = command->structureVector ();
148 		RK_ASSERT (data.size () == 2);
149 
150 		QStringList new_environments = data[0]->stringVector ();
151 		RK_ASSERT (new_environments.size () >= 2);
152 
153 		updateEnvironments (new_environments, true);
154 		updateNamespaces (data[1]->stringVector ());
155 
156 		RKGlobals::rInterface ()->issueCommand (QString (), RCommand::App | RCommand::Sync | RCommand::EmptyCommand, QString (), this, ROBJECTLIST_UPDATE_COMPLETE_COMMAND, update_chain);
157 	} else if (command->getFlags () == ROBJECTLIST_UPDATE_COMPLETE_COMMAND) {
158 		RK_ASSERT (update_chain);
159 		RKGlobals::rInterface ()->closeChain (update_chain);
160 		update_chain = 0;
161 
162 		RK_DEBUG (OBJECTS, DL_DEBUG, "object list update complete");
163 		emit (updateComplete ());
164 	} else {
165 		RK_ASSERT (false);
166 	}
167 }
168 
updateEnvironments(const QStringList & _env_names,bool force_globalenv_update)169 void RObjectList::updateEnvironments (const QStringList &_env_names, bool force_globalenv_update) {
170 	RK_TRACE (OBJECTS);
171 
172 	RObjectMap newchildmap;
173 	QStringList env_names = _env_names;
174 	if (!env_names.isEmpty ()) {
175 		QString dummy = env_names.takeFirst ();
176 		RK_ASSERT (dummy == ".GlobalEnv");
177 		if (force_globalenv_update) {
178 			// for now, we only update the .GlobalEnv. All others we assume to be static
179 			getGlobalEnv ()->updateFromR (update_chain);
180 		}
181 	} else {
182 		RK_ASSERT (!env_names.isEmpty ());
183 	}
184 
185 	// find which items are new, and copy the old ones
186 	for (int i = 0; i < env_names.count (); ++i) {
187 		QString name = env_names[i];
188 
189 		RObject *obj = findChildByName (name);
190 		if (obj && (i > 0) && (env_names.lastIndexOf (name, i-1) > -1)) {		// duplicate environment names can happen (e.g. if a data.frame is attached multiple times)
191 			obj = 0;	// only copy the old item once
192 		}
193 		if (!obj) {
194 			obj = createTopLevelEnvironment (name);
195 			RKGlobals::tracker ()->beginAddObject (obj, this, i);
196 			childmap.insert (i, obj);
197 			RKGlobals::tracker ()->endAddObject (obj, this, i);
198 		}
199 		newchildmap.insert (i, obj);
200 	}
201 
202 	// check which envs have been removed or changed position
203 	for (int i = 0; i < childmap.size (); ++i) {	// do *not* cache the childmap.size ()! We may change it in the loop.
204 		RObject *obj = childmap[i];
205 		int new_pos = newchildmap.indexOf (obj);
206 
207 		if (new_pos < 0) {	// environment is gone
208 			RK_DEBUG (OBJECTS, DL_INFO, "removing toplevel environment %s from list", obj->getShortName ().toLatin1 ().data ());
209 			if (RKGlobals::tracker ()->removeObject (obj, 0, true)) --i;
210 			else (newchildmap.insert (i, obj));
211 		} else if (new_pos != i) {
212 			// this call is rather expensive, all in all, but fortunately called very rarely
213 			moveChild (obj, i, new_pos);
214 		}
215 	}
216 
217 	RK_DO (RK_ASSERT (childmap == newchildmap), OBJECTS, DL_DEBUG);	// this is an expensive assert, hence wrapping it inside RK_DO
218 }
219 
updateNamespaces(const QStringList namespace_names)220 void RObjectList::updateNamespaces (const QStringList namespace_names) {
221 	RK_TRACE (OBJECTS);
222 
223 	QStringList orphan_namespace_names;
224 	for (int i = 0; i < namespace_names.size (); ++i) {
225 		if (!findPackage (namespace_names[i])) orphan_namespace_names.append (namespace_names[i]);
226 	}
227 	orphan_namespaces->updateFromR (update_chain, orphan_namespace_names);
228 }
229 
createTopLevelEnvironment(const QString & name)230 REnvironmentObject *RObjectList::createTopLevelEnvironment (const QString &name) {
231 	RK_TRACE (OBJECTS);
232 
233 	REnvironmentObject *envobj = new REnvironmentObject (this, name);
234 	envobj->updateFromR (update_chain);
235 	return envobj;
236 }
237 
findObjects(const QStringList & path,bool partial,const QString & op)238 RObject::ObjectList RObjectList::findObjects (const QStringList &path, bool partial, const QString &op) {
239 	RK_TRACE (OBJECTS);
240 	RK_ASSERT (op == "$");
241 
242 	RObject::ObjectList ret;
243 	if (path.value (1) == "::") {
244 		RObject *environment = findPackage (path[0]);
245 		if (environment) return (environment->findObjects (path.mid (2), partial, "$"));
246 		return ret;
247 	} else if (path.value (1) == ":::") {
248 		RObject *environment = findPackage (path[0]);
249 		if (environment) environment = static_cast<REnvironmentObject*> (environment)->namespaceEnvironment ();
250 		if (!environment) environment = orphan_namespaces->findOrphanNamespace (path[0]);
251 		if (environment) return (environment->findObjects (path.mid (2), partial, "$"));
252 		return ret;
253 	} else if (path.value (0) == ".GlobalEnv") {
254 		if (path.length () > 1) return getGlobalEnv ()->findObjects (path.mid (2), partial, "$");
255 		// else we'll find base::.GlobalEnv, below
256 	}
257 
258 	// no namespace given. Search all environments for matches, .GlobalEnv, first
259 	ret = getGlobalEnv ()->findObjects (path, partial, "$");
260 	for (int i = 0; i < childmap.size (); ++i) {
261 		if (!(partial || ret.isEmpty ())) return ret;
262 
263 		ret.append (childmap[i]->findObjects (path, partial, "$"));
264 	}
265 	return ret;
266 }
267 
getFullNames(const RObject::ObjectList & matches,int options)268 QStringList RObject::getFullNames (const RObject::ObjectList &matches, int options) {
269 	RK_TRACE (OBJECTS);
270 
271 	QStringList ret;
272 	QSet<QString> unique_names;
273 	for (int i = 0; i < matches.count (); ++i) {
274 		if (options & IncludeEnvirIfMasked) {
275 			// - If the name is *not* masked (yet), return the plain name.
276 			// - If the name *is* masked, return the full qualitfied name.
277 			// NOTE: This assumes objects are given in search order!
278 			QString base_name = matches[i]->getFullName (options);
279 			if (unique_names.contains (base_name)) {
280 				base_name = matches[i]->getFullName (options | IncludeEnvirIfNotGlobalEnv | IncludeEnvirForGlobalEnv);
281 			}
282 			RK_ASSERT (!unique_names.contains (base_name));
283 			unique_names.insert (base_name);
284 			ret.append (base_name);
285 		} else {
286 			ret.append (matches[i]->getFullName (options));
287 		}
288 	}
289 	return ret;
290 }
291 
findPackage(const QString & namespacename) const292 REnvironmentObject* RObjectList::findPackage (const QString &namespacename) const {
293 	RK_TRACE (OBJECTS);
294 
295 	for (int i = childmap.size () - 1; i >= 0; --i) {
296 		RObject* child = childmap[i];
297 		if (!child->isType (PackageEnv)) continue;	// Skip Autoloads
298 		REnvironmentObject* env = static_cast<REnvironmentObject *> (child);
299 		if (env->packageName () == namespacename) {
300 			return env;
301 		}
302 	}
303 	return 0;
304 }
305 
updateStructure(RData *)306 bool RObjectList::updateStructure (RData *) {
307 	RK_TRACE (OBJECTS);
308 
309 	RK_ASSERT (false);
310 
311 	return true;
312 }
313 
timeout()314 void RObjectList::timeout () {
315 	RK_TRACE (OBJECTS);
316 
317 	updateFromR (0);
318 }
319 
renameChildCommand(RObject * object,const QString & new_name) const320 QString RObjectList::renameChildCommand (RObject *object, const QString &new_name) const {
321 	RK_TRACE (OBJECTS);
322 
323 	return (makeChildName (new_name, false, IncludeEnvirIfNotGlobalEnv) + " <- " + object->getFullName () + '\n' + removeChildCommand (object));
324 }
325 
removeChildCommand(RObject * object) const326 QString RObjectList::removeChildCommand (RObject *object) const {
327 	RK_TRACE (OBJECTS);
328 
329 	return ("remove (" + object->getFullName () + ')');
330 }
331 
332 
333