1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * anjuta-token-file.c
4  * Copyright (C) Sébastien Granjoux 2009 <seb.sfo@free.fr>
5  *
6  * This program is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "anjuta-token-file.h"
21 
22 #include "anjuta-debug.h"
23 
24 #include <glib-object.h>
25 
26 #include <stdio.h>
27 #include <string.h>
28 
29 /* Types declarations
30  *---------------------------------------------------------------------------*/
31 
32 struct _AnjutaTokenFile
33 {
34 	GObject parent;
35 
36 	GFile* file;				/* Corresponding GFile */
37 
38 	AnjutaToken *content;		/* Current file content */
39 
40 	AnjutaToken *save;			/* List of memory block used */
41 
42 	gboolean dirty;					/* Set when the file has been modified */
43 };
44 
45 struct _AnjutaTokenFileClass
46 {
47 	GObjectClass parent_class;
48 };
49 
50 static GObjectClass *parent_class = NULL;
51 
52 /* Helpers functions
53  *---------------------------------------------------------------------------*/
54 
55 /* Private functions
56  *---------------------------------------------------------------------------*/
57 
58 static AnjutaToken*
anjuta_token_file_find_position(AnjutaTokenFile * file,AnjutaToken * token)59 anjuta_token_file_find_position (AnjutaTokenFile *file, AnjutaToken *token)
60 {
61 	AnjutaToken *start;
62 	const gchar *pos;
63 	const gchar *ptr;
64 	const gchar *end;
65 
66 	if (token == NULL) return NULL;
67 
68 	if (anjuta_token_get_length (token) == 0)
69 	{
70 		AnjutaToken *last = anjuta_token_last (token);
71 
72 		for (; (token != NULL) && (token != last); token = anjuta_token_next (token))
73 		{
74 			if (anjuta_token_get_length (token) != 0) break;
75 		}
76 
77 		if (anjuta_token_get_length (token) == 0) return NULL;
78 	}
79 
80 	pos = anjuta_token_get_string (token);
81 	for (start = file->content; start != NULL; start = anjuta_token_next (start))
82 	{
83 		guint len = anjuta_token_get_length (start);
84 
85 		if (len)
86 		{
87 			ptr = anjuta_token_get_string (start);
88 			end = ptr + len;
89 
90 			if ((pos >= ptr) && (pos < end)) break;
91 		}
92 	}
93 	if ((start != NULL) && (ptr != pos))
94 	{
95 		start = anjuta_token_split (start, pos - ptr);
96 		start = anjuta_token_next (start);
97 	}
98 
99 	return start;
100 }
101 
102 /* Public functions
103  *---------------------------------------------------------------------------*/
104 
105 AnjutaToken*
anjuta_token_file_load(AnjutaTokenFile * file,GError ** error)106 anjuta_token_file_load (AnjutaTokenFile *file, GError **error)
107 {
108 	gchar *content;
109 	gsize length;
110 
111 	anjuta_token_file_unload (file);
112 
113 	if (g_file_load_contents (file->file, NULL, &content, &length, NULL, error))
114 	{
115 		AnjutaToken *token;
116 
117 		file->save = anjuta_token_new_static (ANJUTA_TOKEN_FILE,  NULL);
118 		file->content = anjuta_token_new_static (ANJUTA_TOKEN_FILE,  NULL);
119 
120 		token =	anjuta_token_new_string_len (ANJUTA_TOKEN_FILE, content, length);
121 		anjuta_token_prepend_child (file->save, token);
122 
123 		token =	anjuta_token_new_static (ANJUTA_TOKEN_FILE, content);
124 		anjuta_token_prepend_child (file->content, token);
125 		file->dirty = FALSE;
126 	}
127 
128 	return file->content;
129 }
130 
131 gboolean
anjuta_token_file_unload(AnjutaTokenFile * file)132 anjuta_token_file_unload (AnjutaTokenFile *file)
133 {
134 	if (file->content != NULL) anjuta_token_free (file->content);
135 	file->content = NULL;
136 
137 	if (file->save != NULL) anjuta_token_free (file->save);
138 	file->save = NULL;
139 
140 	return TRUE;
141 }
142 
143 gboolean
anjuta_token_file_save(AnjutaTokenFile * file,GError ** error)144 anjuta_token_file_save (AnjutaTokenFile *file, GError **error)
145 {
146 	GFileOutputStream *stream;
147 	gboolean ok = TRUE;
148 	GError *err = NULL;
149 	AnjutaToken *token;
150 
151 	stream = g_file_replace (file->file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &err);
152 	if (stream == NULL)
153 	{
154 		if (g_error_matches (err, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
155 		{
156 			/* Perhaps parent directory is missing, try to create it */
157 			GFile *parent = g_file_get_parent (file->file);
158 
159 			if (g_file_make_directory_with_parents (parent, NULL, NULL))
160 			{
161 				g_object_unref (parent);
162 				g_clear_error (&err);
163 				stream = g_file_replace (file->file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error);
164 				if (stream == NULL) return FALSE;
165 			}
166 			else
167 			{
168 				g_object_unref (parent);
169 				g_propagate_error (error, err);
170 
171 				return FALSE;
172 			}
173 		}
174 		else
175 		{
176 			g_propagate_error (error, err);
177 			return FALSE;
178 		}
179 	}
180 
181 	for (token = file->content; token != NULL; token = anjuta_token_next (token))
182 	{
183 		if (!(anjuta_token_get_flags (token) & ANJUTA_TOKEN_REMOVED) && (anjuta_token_get_length (token)))
184 		{
185 			if (g_output_stream_write (G_OUTPUT_STREAM (stream), anjuta_token_get_string (token), anjuta_token_get_length (token) * sizeof (char), NULL, error) < 0)
186 			{
187 				ok = FALSE;
188 				break;
189 			}
190 		}
191 	}
192 
193 	ok = ok && g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL);
194 	g_object_unref (stream);
195 	file->dirty = FALSE;
196 
197 	return ok;
198 }
199 
200 void
anjuta_token_file_move(AnjutaTokenFile * file,GFile * new_file)201 anjuta_token_file_move (AnjutaTokenFile *file, GFile *new_file)
202 {
203 	if (file->file) g_object_unref (file->file);
204 	file->file = new_file != NULL ? g_object_ref (new_file) : NULL;
205 	file->dirty = new_file != NULL;
206 }
207 
208 static void
remove_raw_token(AnjutaToken * token,gpointer user_data)209 remove_raw_token (AnjutaToken *token, gpointer user_data)
210 {
211 	AnjutaTokenFile *file = (AnjutaTokenFile *)user_data;
212 
213 	if ((anjuta_token_get_length (token) > 0))
214 	{
215 		AnjutaToken *pos = anjuta_token_file_find_position (file, token);
216 		guint len = anjuta_token_get_length (token);
217 
218 		if (pos != NULL)
219 		{
220 			while (len != 0)
221 			{
222 				guint flen = anjuta_token_get_length (pos);
223 				if (len < flen)
224 				{
225 					pos = anjuta_token_split (pos, len);
226 					flen = len;
227 				}
228 				pos = anjuta_token_free (pos);
229 				len -= flen;
230 			}
231 		}
232 	}
233 }
234 
235 static AnjutaToken *
anjuta_token_file_remove_token(AnjutaTokenFile * file,AnjutaToken * token)236 anjuta_token_file_remove_token (AnjutaTokenFile *file, AnjutaToken *token)
237 {
238 	AnjutaToken *next = NULL;
239 
240 	if (token != NULL) next = anjuta_token_foreach_post_order (token, remove_raw_token, file);
241 	file->dirty = TRUE;
242 
243 	return next;
244 }
245 
246 /**
247  * anjuta_token_file_update:
248  * @file: a #AnjutaTokenFile derived class object.
249  * @token: Token to update.
250  *
251  * Update the file with all changed token starting from @token. The function can
252  * return an error if the token is not in the file.
253  *
254  * Return value: %TRUE is the update is done without error.
255  */
256 gboolean
anjuta_token_file_update(AnjutaTokenFile * file,AnjutaToken * token)257 anjuta_token_file_update (AnjutaTokenFile *file, AnjutaToken *token)
258 {
259 	AnjutaToken *prev;
260 	AnjutaToken *next;
261 	AnjutaToken *last;
262 	guint added;
263 	gchar *value;
264 
265 	/* Find all token needing an update */
266 
267 	/* Find following tokens */
268 	for (last = token; last != NULL; last = anjuta_token_next (last))
269 	{
270 		/* Get all tokens in group */
271 		last = anjuta_token_last (last);
272 
273 		gint flags = anjuta_token_get_flags (last);
274 		if (!(flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))) break;
275 	}
276 
277 	/* Find first modified token */
278 	for (;;)
279 	{
280 		gint flags = anjuta_token_get_flags (token);
281 		if (flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))
282 		{
283 			/* Check previous token */
284 			for (prev = token; prev != NULL; prev = anjuta_token_previous (prev))
285 			{
286 				gint flags = anjuta_token_get_flags (prev);
287 				if (!(flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))) break;
288 				token = prev;
289 			}
290 
291 			break;
292 		}
293 		if (token == last)
294 		{
295 			/* No changed */
296 			return TRUE;
297 		}
298 		token = anjuta_token_next (token);
299 	}
300 
301 	/* Find previous token */
302 	for (prev = token; prev != NULL; prev = anjuta_token_previous (prev))
303 	{
304 		gint flags = anjuta_token_get_flags (prev);
305 		if ((anjuta_token_get_length (prev) != 0) && !(flags & (ANJUTA_TOKEN_ADDED | ANJUTA_TOKEN_REMOVED))) break;
306 	}
307 
308 	/* Delete removed token and compute length of added token */
309 	added = 0;
310 	for (next = token; (next != NULL) && (next != last);)
311 	{
312 		gint flags = anjuta_token_get_flags (next);
313 
314 		if (flags & ANJUTA_TOKEN_ADDED)
315 		{
316 			added += anjuta_token_get_length (next);
317 		}
318 		next = anjuta_token_next (next);
319 	}
320 
321 	/* Add new token */
322 	if (added != 0)
323 	{
324 		AnjutaToken *add;
325 		AnjutaToken *start = NULL;
326 
327 		value = g_new (gchar, added);
328 		add = anjuta_token_prepend_child (file->save, anjuta_token_new_string_len (ANJUTA_TOKEN_NAME, value, added));
329 
330 		/* Find token position */
331 		if (prev != NULL)
332 		{
333 			start = anjuta_token_file_find_position (file, prev);
334 			if (start != NULL) start = anjuta_token_split (start, anjuta_token_get_length (prev));
335 		}
336 
337 		/* Insert token */
338 		add = anjuta_token_new_static_len (ANJUTA_TOKEN_NAME, value, added);
339 		if (start == NULL)
340 		{
341 			anjuta_token_prepend_child (file->content, add);
342 		}
343 		else
344 		{
345 			anjuta_token_insert_after (start, add);
346 		}
347 	}
348 
349 	for (next = token; (next != NULL) && (next != last);)
350 	{
351 		gint flags = anjuta_token_get_flags (next);
352 
353 		if (flags & ANJUTA_TOKEN_ADDED)
354 		{
355 			guint len = anjuta_token_get_length (next);
356 
357 			if (len > 0)
358 			{
359 				memcpy(value, anjuta_token_get_string (next), len);
360 				anjuta_token_set_string (next, value, len);
361 				value += len;
362 			}
363 			anjuta_token_clear_flags (next, ANJUTA_TOKEN_ADDED);
364 		}
365 		else if (flags & ANJUTA_TOKEN_REMOVED)
366 		{
367 			next = anjuta_token_file_remove_token (file, next);
368 			continue;
369 		}
370 		next = anjuta_token_next (next);
371 	}
372 
373 	file->dirty = TRUE;
374 
375 	return TRUE;
376 }
377 
378 /**
379  * anjuta_token_file_get_token_position:
380  * @file: #AnjutaTokenFile object
381  * @token: token
382  *
383  * Returns the position of the token in the file. This position is a number
384  * which doesn't correspond to a line number or a character but respect the
385  * order of token in the file.
386  *
387  * Returns: The position of the token or 0 if the token is not in the file.
388  */
389 gsize
anjuta_token_file_get_token_position(AnjutaTokenFile * file,AnjutaToken * token)390 anjuta_token_file_get_token_position (AnjutaTokenFile *file, AnjutaToken *token)
391 {
392 	AnjutaToken *content;
393 	const gchar *string;
394 	gsize pos = 1;
395 
396 
397 	do
398 	{
399 		string = anjuta_token_get_string (token);
400 		if (string != NULL) break;
401 
402 		/* token is a group or an empty token, looks for group members or
403 		 * following token */
404 		token = anjuta_token_next_after_children (token);
405 	} while (token != NULL);
406 
407 	for (content = file->content; content != NULL; content = anjuta_token_next (content))
408 	{
409 		const gchar *start;
410 		gsize len;
411 
412 		start = anjuta_token_get_string (content);
413 		len = anjuta_token_get_length (content);
414 
415 		if ((string >= start) && ((string - start) < len))
416 		{
417 			pos += string - start;
418 			break;
419 		}
420 		else
421 		{
422 			pos += len;
423 		}
424 	}
425 
426 	return content == NULL ? 0 : pos;
427 }
428 
429 
430 gboolean
anjuta_token_file_get_token_location(AnjutaTokenFile * file,AnjutaTokenFileLocation * location,AnjutaToken * token)431 anjuta_token_file_get_token_location (AnjutaTokenFile *file, AnjutaTokenFileLocation *location, AnjutaToken *token)
432 {
433 	AnjutaTokenFileLocation loc = {NULL, 1, 1};
434 	AnjutaToken *pos;
435 	const gchar *target;
436 
437 	anjuta_token_dump (token);
438 	do
439 	{
440 		target = anjuta_token_get_string (token);
441 		if (target != NULL) break;
442 
443 		/* token is a group or an empty token, looks for group members or
444 		 * following token */
445 		token = anjuta_token_next_after_children (token);
446 	} while (token != NULL);
447 
448 	for (pos = file->content; pos != NULL; pos = anjuta_token_next (pos))
449 	{
450 		if (!(anjuta_token_get_flags (pos) & ANJUTA_TOKEN_REMOVED) && (anjuta_token_get_length (pos) > 0))
451 		{
452 			const gchar *ptr;
453 			const gchar *end;
454 
455 			ptr = anjuta_token_get_string (pos);
456 			end = ptr + anjuta_token_get_length (pos);
457 
458 			for (; ptr != end; ptr++)
459 			{
460 				if (*ptr == '\n')
461 				{
462 					/* New line */
463 					loc.line++;
464 					loc.column = 1;
465 				}
466 				else
467 				{
468 					loc.column++;
469 				}
470 
471 				if (ptr == target)
472 				{
473 					if (location != NULL)
474 					{
475 						location->filename = file->file == NULL ? NULL : g_file_get_parse_name (file->file);
476 						location->line = loc.line;
477 						location->column = loc.column;
478 					}
479 
480 					return TRUE;
481 				}
482 			}
483 		}
484 	}
485 
486 	return FALSE;
487 }
488 
489 GFile*
anjuta_token_file_get_file(AnjutaTokenFile * file)490 anjuta_token_file_get_file (AnjutaTokenFile *file)
491 {
492 	return file->file;
493 }
494 
495 AnjutaToken*
anjuta_token_file_get_content(AnjutaTokenFile * file)496 anjuta_token_file_get_content (AnjutaTokenFile *file)
497 {
498 	if (file->content == NULL)
499 	{
500 		anjuta_token_file_load (file, NULL);
501 	}
502 
503 	return file->content;
504 }
505 
506 gboolean
anjuta_token_file_is_dirty(AnjutaTokenFile * file)507 anjuta_token_file_is_dirty (AnjutaTokenFile *file)
508 {
509 	return file->dirty;
510 }
511 
512 /* GObject functions
513  *---------------------------------------------------------------------------*/
514 
515 /* dispose is the first destruction step. It is used to unref object created
516  * with instance_init in order to break reference counting cycles. This
517  * function could be called several times. All function should still work
518  * after this call. It has to called its parents.*/
519 
520 static void
anjuta_token_file_dispose(GObject * object)521 anjuta_token_file_dispose (GObject *object)
522 {
523 	AnjutaTokenFile *file = ANJUTA_TOKEN_FILE (object);
524 
525 	anjuta_token_file_unload (file);
526 
527 	if (file->file) g_object_unref (file->file);
528 	file->file = NULL;
529 
530 	G_OBJECT_CLASS (parent_class)->dispose (object);
531 }
532 
533 /* instance_init is the constructor. All functions should work after this
534  * call. */
535 
536 static void
anjuta_token_file_instance_init(AnjutaTokenFile * file)537 anjuta_token_file_instance_init (AnjutaTokenFile *file)
538 {
539 	file->file = NULL;
540 	file->content = NULL;
541 	file->save = NULL;
542 }
543 
544 /* class_init intialize the class itself not the instance */
545 
546 static void
anjuta_token_file_class_init(AnjutaTokenFileClass * klass)547 anjuta_token_file_class_init (AnjutaTokenFileClass * klass)
548 {
549 	GObjectClass *gobject_class;
550 
551 	g_return_if_fail (klass != NULL);
552 
553 	parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
554 	gobject_class = G_OBJECT_CLASS (klass);
555 
556 	gobject_class->dispose = anjuta_token_file_dispose;
557 }
558 
559 GType
anjuta_token_file_get_type(void)560 anjuta_token_file_get_type (void)
561 {
562 	static GType type = 0;
563 
564 	if (!type)
565 	{
566 		static const GTypeInfo type_info =
567 		{
568 			sizeof (AnjutaTokenFileClass),
569 			(GBaseInitFunc) NULL,
570 			(GBaseFinalizeFunc) NULL,
571 			(GClassInitFunc) anjuta_token_file_class_init,
572 			(GClassFinalizeFunc) NULL,
573 			NULL,           /* class_data */
574 			sizeof (AnjutaTokenFile),
575 			0,              /* n_preallocs */
576 			(GInstanceInitFunc) anjuta_token_file_instance_init,
577 			NULL            /* value_table */
578 		};
579 
580 		type = g_type_register_static (G_TYPE_OBJECT,
581 		                            "AnjutaTokenFile", &type_info, 0);
582 	}
583 
584 	return type;
585 }
586 
587 
588 /* Constructor & Destructor
589  *---------------------------------------------------------------------------*/
590 
591 AnjutaTokenFile *
anjuta_token_file_new(GFile * gfile)592 anjuta_token_file_new (GFile *gfile)
593 {
594 	AnjutaTokenFile *file = g_object_new (ANJUTA_TOKEN_FILE_TYPE, NULL);
595 
596 	if (gfile) file->file =  g_object_ref (gfile);
597 	file->dirty = gfile != NULL;
598 
599 	return file;
600 };
601 
602 void
anjuta_token_file_free(AnjutaTokenFile * tfile)603 anjuta_token_file_free (AnjutaTokenFile *tfile)
604 {
605 	g_object_unref (tfile);
606 }
607