# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8

"""Fonctions utilitaires pour gérer des projets WebObjects
"""

try: True, False
except NameError: True, False = 1, 0

import os, sys, string, re, types
from os import path

from base import basename, dirname, matches_name
from config import ShConfigFile, PListFile

##################################################
# gestion des frameworks systèmes

default_frameworks_for_woapp = ['JavaFoundation.framework',
                                'JavaEOControl.framework',
                                'JavaEOAccess.framework',
                                'JavaWebObjects.framework',
                                'JavaWOExtensions.framework',
                                'JavaXML.framework',
                                'JavaJDBCAdaptor.framework',
                                ]
default_frameworks_for_jcapp = ['JavaEOApplication.framework',
                                'JavaEODistribution.framework',
                                'JavaEOInterface.framework',
                                'JavaEOInterfaceSwing.framework',
                                ]
system_frameworks = default_frameworks_for_woapp + default_frameworks_for_jcapp + \
                    ['JavaDirectToWeb.framework',
                     'JavaDTWGeneration.framework',
                     'JavaEOGeneration.framework',
                     'JavaEOProject.framework',
                     'JavaEORuleSystem.framework',
                     'JavaJNDIAdaptor.framework',
                     'JavaOpenEJBActivation.framework',
                     'JavaOpenEJB.framework',
                     'JavaOpenORB.framework',
                     'JavaOpenTM.framework',
                     'JavaPlot.framework',
                     'JavaWebServicesClient.framework',
                     'JavaWebServicesGeneration.framework',
                     'JavaWebServicesSupport.framework',
                     'JavaWOJSPServlet.framework',
                     'JavaWOSMIL.framework',
                     ]

def is_system_framework(fp):
    """retourner True si fp est un chemin vers un framework système
    """
    return basename(fp) in system_frameworks

def is_default_framework_for_woapp(fp):
    return is_system_framework(fp) and basename(fp) in default_frameworks_for_woapp

def is_default_framework_for_jcapp(fp):
    return is_system_framework(fp) and basename(fp) in default_frameworks_for_jcapp

##################################################
# gestion des exceptions

class WOError(Exception):
    """exception lancée si une erreur se produit dans l'une des fonctions de ce
    module
    """
    pass

##################################################
# gestion des projets

class WOProject:
    """Classe de base des projets webobjects

    Cette classe est utilisée comme interface, pour indiquer les méthodes qui
    existent dans les classes dérivées
    """
    def projname(self):
        """Retourner le nom du projet.

        Ce nom est utilisé comme nom de base pour le projet compilé, par exemple
        projname.woa ou projname.framework
        """
        raise NotImplementedError

    def projsubtype(self):
        """Retourner le type de sous-projet: woapp, jcapp, framework
        """
        raise NotImplementedError

    projtype_map = {'woapp': 'woapplication',
                    'jcapp': 'woapplication',
                    'wofrm': 'woframework',
                }
    def projtype(self):
        """Retourner le type de projet: woapplication ou woframework
        """
        return self.projtype_map[self.projsubtype()]

    def projdir(self):
        """Retourner le répertoire absolu qui contient le projet
        """
        raise NotImplementedError

    def projname_and_projtype(self):
        """retourner le tuple (projname, projtype)
        """
        return self.projname(), self.projtype()

class WOJawotools(WOProject, ShConfigFile):
    """Un projet jawotools
    """
    def __init__(self, dir, raise_exception=True):
        """Initialiser l'objet avec le répertoire dir
        """
        ShConfigFile.__init__(self)
        self.load(path.join(dir, 'build.properties'), raise_exception=raise_exception)

    def PROJECT_NAME(self):
        try: return self['project_name']
        except KeyError: raise WOError, "Propriété project_name non définie dans le projet"
    def PROJECT_TYPE(self):
        try: return self['project_type']
        except KeyError: raise WOError, "Propriété project_type non définie dans le projet"
    
    def projname(self): return self.PROJECT_NAME()
    def projsubtype(self):
        type = string.lower(self.PROJECT_TYPE())
        if type.startswith("application"):
            type = type.find("+javaclient") != -1 and "jcapp" or "woapp"
        elif type.startswith("framework"):
            type = "wofrm"
        return type
            
    def projdir(self): return self.dirname
        
class WOBuildConf(WOProject, ShConfigFile):
    """Un projet wobuild.conf
    """
    def __init__(self, dir, raise_exception=True):
        """Initialiser l'objet avec le répertoire dir
        """
        ShConfigFile.__init__(self)
        self.load(path.join(dir, 'wobuild.conf'), raise_exception=raise_exception)

    def NAME(self):
        try: return self['NAME']
        except KeyError: raise WOError, "Propriété NAME non définie dans le projet"
    def TYPE(self):
        try: return self['TYPE']
        except KeyError: raise WOError, "Propriété TYPE non définie dans le projet"
    def TAG(self): return self.get('TAG', '')
    
    def projname(self): return self.NAME()
    def projsubtype(self):
        type = string.lower(self.TYPE())
        type = {'app': 'woapp', 'framework': 'wofrm'}.get(type, type)
        return type
    def projdir(self): return self.dirname

class WOProjectMacOSX(WOProject, PListFile):
    """Un projet de Project Builder sous Mac OS X
    """
    def __init__(self, dir, raise_exception=True):
        """Initialiser l'objet avec le répertoire dir
        """
        PListFile.__init__(self)

        adir = path.abspath(dir)
        bn = basename(adir)
        if bn == "project.pbxproj" and matches_name("*.pbproj", basename(dirname(adir))):
            # on a donné directement le fichier project.pbxproj
            self.load(dir, raise_exception=raise_exception)
            self._projdir = dirname(dirname(adir))
        elif matches_name("*.pbproj", bn):
            # on a donné le nom du répertoire .pbproj
            self.load(path.join(dir, 'project.pbxproj'), raise_exception=raise_exception)
            self._projdir = dirname(adir)
        else:
            # on a donné le nom du répertoire du projet. il faut chercher le
            # répertoire .pbproj
            files = os.listdir(dir)
            projects_filter = lambda file, bp=dir: matches_name('*.pbproj/', file, bp)
            projects = filter(projects_filter, files)
            if len(projects) != 1:
                if raise_exception:
                    if len(projects):
                        raise WOError, "Il y a plusieurs répertoires .pbproj dans %s" % dir
                    else:
                        raise WOError, "Aucun répertoire .pbproj n'a été trouvé dans %s" % dir
                return
            projfile = path.join(dir, projects[0], 'project.pbxproj')
            self.load(projfile, raise_exception=raise_exception)
            self._projdir = adir

        self.group_for_oid = {}

    def is_oid(self, oid):
        """déterminer si oid est un id d'objet

        n doit être de longeur 24 et n'être composé que de caractères hexadécimaux
        """
        return re.match(r'[0-9a-fA-F]{24,24}$', oid) is not None

    def object(self, oid):
        """retourner un objet si l'oid est valide, ou la valeur inchangée si ce
        n'est pas un oid valide
        """
        if self.is_oid(oid):
            return self['objects'][oid]
        else:
            return oid

    def objects(self, oids):
        """retourner une liste d'objet pour une liste d'oids
        """
        return map(self.object, oids)

    def path(self, p='', o=None, convert_object=True):
        """retourner un objet dont on donne le chemin
        """
        if o is None: o = self.items
        if type(o) is types.StringType:
            o = self.object(o)
        if p:
            ps = string.split(p, '.')
            count = len(ps)
            for i in range(count):
                p = ps[i]
                last = i == count - 1

                # obtenir la clé
                if type(o) is types.DictType:
                    k = p
                elif type(o) is types.ListType:
                    k = int(p)
                else:
                    raise ValueError, "chemin invalide vers une valeur scalaire"

                # obtenir l'objet
                o = o[k]

                # convertir éventuellement les références vers des objets
                if convert_object or not last:
                    if type(o) is types.ListType:
                        o = self.objects(o)
                    elif type(o) is types.StringType:
                        o = self.object(o)
        return o

    def __group_for(self, oid, start_at):
        children = self.path('children', start_at, convert_object=False)
        if oid in children:
            return start_at
        else:
            for child in children:
                group = self.object(child)
                if group['isa'] in ('PBXGroup', 'PBXVariantGroup'):
                    found = self.__group_for(oid, child)
                    if found is not None: return found
            return None
        
    def group_for(self, oid):
        """retourner l'oid du groupe associé à l'objet d'id find_oid
        """
        if not self.group_for_oid.has_key(oid):
            self.group_for_oid[oid] = self.__group_for(oid, self.path('rootObject.mainGroup', convert_object=False))
        return self.group_for_oid[oid]
    
    def PRODUCT_NAME(self):
        try:
            if self.path('rootObject.targets.0.isa') in ('PBXApplicationTarget', 'PBXFrameworkTarget'):
                return self.path('rootObject.targets.0.buildSettings.PRODUCT_NAME')
        except KeyError: pass
        raise WOError, "Impossible de déterminer le nom du produit"

    def TARGET0_ISA(self):
        try: return self.path('rootObject.targets.0.isa')
        except KeyError: pass
        raise WOError, "Impossible de déterminer le type du target principal"

    def projname(self): return self.PRODUCT_NAME()
    
    projsubtype_map = {'PBXApplicationTarget': 'woapp',
                       'PBXFrameworkTarget': 'wofrm',
                       }
    def projsubtype(self):
        subtype = self.projsubtype_map[self.TARGET0_ISA()]
        if subtype == 'woapp':
            # tester si les frameworks pour jcapp sont présents
            pass
        return subtype

    def projdir(self): return self._projdir

class WOProjectWindows(WOProject, PListFile):
    """Un projet de Project Builder sous Windows
    """
    def __init__(self, dir, raise_exception=True):
        """Initialiser l'objet avec le répertoire dir
        """
        PListFile.__init__(self)
        
        adir = path.abspath(dir)
        if basename(adir) == "PB.project":
            # on a donné le nom du fichier PB.project
            self.load(dir, raise_exception=raise_exception)
        else:
            # on a donné le nom du répertoire du projet. il faut chercher le
            # fichier PB.project
            self.load(path.join(dir, 'PB.project'), raise_exception=raise_exception)

    def PROJECTNAME(self): return self['PROJECTNAME']
    def PROJECTTYPE(self): return self['PROJECTTYPE']

    def projname(self): return self.PROJECTNAME()

    projsubtype_map = {'Application': 'woapp', # apparemment, client lourd java / wo4.5
                       'EOApplication': 'woapp', # client lourd objective c / wo4.5
                       'JavaWebObjectsApplication': 'woapp',
                       'JavaWebObjectsFramework': 'wofrm',
                       }
    def projsubtype(self):
        subtype = self.projsubtype_map[self.PROJECTTYPE()]
        if subtype == 'woapp':
            # tester si les frameworks pour jcapp sont présents
            pass
        return subtype

    def projdir(self): return self.dirname

def wo_projname_and_projtype(ap):
    """Obtenir le nom du projet WebObjects pour le répertoire absolu ap et le
    type de projet: app ou framework

    Si ce répertoire ne contient pas de projet WebObjects ou en contient
    plusieurs, retourner None.
    """
    # tester si le projet est un projet jawotools
    proj = WOJawotools(ap, raise_exception=False)
    if proj.valid:
        try: return proj.projname_and_projtype()
        except WOError: pass

    # tester si le projet est un projet wobuild
    proj = WOBuildConf(ap, raise_exception=False)
    if proj.valid:
        try: return proj.projname_and_projtype()
        except WOError: pass

    # tester s'il s'agit d'un projet ProjectBuilder/MacOSX
    # ce test ne fonctione que s'il n'y a qu'un seul projet dans le répertoire
    proj = WOProjectMacOSX(ap, raise_exception=False)
    if proj.valid:
        try: return proj.projname_and_projtype()
        except WOError: pass

    # tester s'il s'agit d'un projet ProjectBuilder/Windows
    proj = WOProjectWindows(ap, raise_exception=False)
    if proj.valid:
        try: return proj.projname_and_projtype()
        except WOError: pass

    # sinon, on déclare forfait
    return None, None