/* Functions for handling dependency tracking when reading .class files.

   Copyright (C) 1998  Free Software Foundation, Inc.

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU CC; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  

Java and all Java-based marks are trademarks or registered trademarks
of Sun Microsystems, Inc. in the United States and other countries.
The Free Software Foundation is independent of Sun Microsystems, Inc.  */

/* Written by Tom Tromey <tromey@cygnus.com>, October 1998.  */

#include <config.h>
#include "system.h"

#include <assert.h>

#include "jcf.h"



/* We keep a linked list of all the files we've already read.  */
struct entry
{
  char *file;
  struct entry *next;
};

/* List of files.  */
static struct entry *dependencies = NULL;

/* Name of targets.  We support multiple targets when writing .class
   files.  */
static struct entry *targets = NULL;

/* Number of columns in output.  */
#define MAX_OUTPUT_COLUMNS 72

/* The output file, or NULL if we aren't doing dependency tracking.  */
static FILE *dep_out = NULL;

/* Nonzero if system files should be added.  */
static int system_files;



/* Helper to free an entry list.  */
static void
free_entry (entp)
     struct entry **entp;
{
  struct entry *ent, *next;

  for (ent = *entp; ent != NULL; ent = next)
    {
      next = ent->next;
      free (ent->file);
      free (ent);
    }
  *entp = NULL;
}

/* Helper to add to entry list.  */
static void
add_entry (entp, name)
     struct entry **entp;
     char *name;
{
  struct entry *ent;

  for (ent = *entp; ent != NULL; ent = ent->next)
    if (! strcmp (ent->file, name))
      return;

  ent = (struct entry *) malloc (sizeof (struct entry));
  ent->file = strdup (name);
  ent->next = *entp;
  *entp = ent;
}

/* Call this to reset the dependency module.  This is required if
   multiple dependency files are being generated from a single tool
   invocation.  */
void
jcf_dependency_reset ()
{
  free_entry (&dependencies);
  free_entry (&targets);

  if (dep_out != NULL)
    {
      if (dep_out != stdout)
	fclose (dep_out);
      dep_out = NULL;
    }
}

void
jcf_dependency_set_target (name)
     char *name;
{
  free_entry (&targets);
  if (name != NULL)
    add_entry (&targets, name);
}

void
jcf_dependency_add_target (name)
     char *name;
{
  add_entry (&targets, name);
}

void
jcf_dependency_set_dep_file (name)
     const char *name;
{
  assert (dep_out != stdout);
  if (dep_out)
    fclose (dep_out);
  if (! strcmp (name, "-"))
    dep_out = stdout;
  else
    dep_out = fopen (name, "w");
}

void
jcf_dependency_add_file (filename, system_p)
     const char *filename;
     int system_p;
{
  /* Just omit system files.  */
  if (system_p && ! system_files)
    return;

  add_entry (&dependencies, filename);
}

void
jcf_dependency_init (system_p)
     int system_p;
{
  system_files = system_p;
}

/* FIXME: this is taken almost directly from cccp.c.  Such duplication
   is bad.  */
static char *
munge (filename)
     char *filename;
{
  static char *buffer = NULL;
  static int buflen = 0;

  int len = 2 * strlen (filename) + 1;
  char *p, *dst;

  if (buflen < len)
    {
      buflen = len;
      if (buffer == NULL)
	buffer = malloc (buflen);
      else
	buffer = realloc (buffer, buflen);
    }

  dst = buffer;
  for (p = filename; *p; ++p)
    {
      switch (*p)
	{
	case ' ':
	case '\t':
	  {
	    /* GNU make uses a weird quoting scheme for white space.
	       A space or tab preceded by 2N+1 backslashes represents
	       N backslashes followed by space; a space or tab
	       preceded by 2N backslashes represents N backslashes at
	       the end of a file name; and backslashes in other
	       contexts should not be doubled.  */
	    char *q;
	    for (q = p - 1; filename < q && q[-1] == '\\';  q--)
	      *dst++ = '\\';
	  }
	  *dst++ = '\\';
	  goto ordinary_char;

	case '$':
	  *dst++ = '$';
	  /* Fall through.  This can mishandle things like "$(" but
	     there's no easy fix.  */
	default:
	ordinary_char:
	  /* This can mishandle characters in the string "\0\n%*?[\\~";
	     exactly which chars are mishandled depends on the `make' version.
	     We know of no portable solution for this;
	     even GNU make 3.76.1 doesn't solve the problem entirely.
	     (Also, '\0' is mishandled due to our calling conventions.)  */
	  *dst++ = *p;
	  break;
	}
    }

  *dst++ = '\0';
  return buffer;
}

/* Helper to print list of files.  */
static int
print_ents (ent, column)
     struct entry *ent;
     int column;
{
  int first = 1;

  for (; ent != NULL; ent = ent->next)
    {
      char *depname = munge (ent->file);
      int len = strlen (depname);

      if (column + len + 2 > MAX_OUTPUT_COLUMNS)
	{
	  fprintf (dep_out, " \\\n ");
	  column = 1;
	}

      if (! first)
	fputs (" ", dep_out);
      fputs (depname, dep_out);
      first = 0;
      column += len + 1;
    }

  return column;
}

void
jcf_dependency_write ()
{
  int column = 0;

  if (! dep_out)
    return;

  assert (targets);
  column = print_ents (targets, 0);
  fputs (" : ", dep_out);

  print_ents (dependencies, column);
  fputs ("\n", dep_out);
  fflush (dep_out);
}
