예제 #1
0
    def reversePython(self, umlFrame: UmlClassDiagramsFrame,
                      directoryName: str, files: List[str]):
        """
        Reverse engineering Python files to OglClass's

        Args:
            umlFrame:       The uml frame to display on
            directoryName:  The directory name where the selected files reside
            files:          A list of files to parse
        """
        fileCount: int = len(files)
        dlg = ProgressDialog('Parsing Files',
                             'Starting',
                             parent=umlFrame,
                             style=PD_APP_MODAL | PD_ELAPSED_TIME)
        dlg.SetRange(fileCount)
        currentFileCount: int = 0

        onGoingParents: Parents = Parents({})
        for fileName in files:

            try:
                fqFileName: str = f'{directoryName}{osSep}{fileName}'
                self.logger.info(f'Processing file: {fqFileName}')
                dlg.Update(currentFileCount, f'Processing: {fileName}')

                fileStream: FileStream = FileStream(fqFileName)
                lexer: Python3Lexer = Python3Lexer(fileStream)

                stream: CommonTokenStream = CommonTokenStream(lexer)
                parser: Python3Parser = Python3Parser(stream)

                tree: Python3Parser.File_inputContext = parser.file_input()
                if parser.getNumberOfSyntaxErrors() != 0:
                    self.logger.error(
                        f"File {fileName} contains {parser.getNumberOfSyntaxErrors()} syntax errors"
                    )
                    # TODO:  Put up a dialog
                    continue

                self.visitor = PyutPythonVisitor()
                self.visitor.parents = onGoingParents
                self.visitor.visit(tree)
                self._generatePyutClasses()

                onGoingParents = self.visitor.parents
                currentFileCount += 1
            except (ValueError, Exception) as e:
                from org.pyut.errorcontroller.ErrorManager import ErrorManager
                eMsg: str = f'file: {fileName}\n{e} - {ErrorManager.getErrorInfo()}'
                self.logger.error(eMsg)
                dlg.Destroy()
                raise PythonParseException(eMsg)
        dlg.Destroy()
        self._generateOglClasses(umlFrame)
        self._layoutUmlClasses(umlFrame)
        self._generateInheritanceLinks(umlFrame)
예제 #2
0
    def testRetrieveMethods(self):

        tree: Python3Parser.File_inputContext = self._setupVisitor('Vertex.py')
        visitor: PyutPythonVisitor = PyutPythonVisitor()

        visitor.visit(tree)

        expectedNumberOfFields: int = 3
        self.assertEqual(expectedNumberOfFields, len(visitor.fields),
                         'Not enough fields')
예제 #3
0
    def testRetrieveCodeFromMethods(self):

        tree: Python3Parser.File_inputContext = self._setupVisitor('Vertex.py')
        visitor: PyutPythonVisitor = PyutPythonVisitor()

        visitor.visit(tree)

        expectedNumberOfMethodsWithCode: int = 3
        self.assertEqual(expectedNumberOfMethodsWithCode,
                         len(visitor.methodCode), 'Not enough code')
예제 #4
0
    def testNonPropertyDecorator(self):

        tree: Python3Parser.File_inputContext = self._setupVisitor(
            'NonPropertyDecoratorClass.py')
        visitor: PyutPythonVisitor = PyutPythonVisitor()

        visitor.visit(tree)

        actualNumPops: int = len(visitor.propertyNames)
        self.assertEqual(0, actualNumPops,
                         'There should be no properties in the test class')
예제 #5
0
    def testMultiClassFileWithInheritance(self):
        tree: Python3Parser.File_inputContext = self._setupVisitor('Opie.py')
        visitor: PyutPythonVisitor = PyutPythonVisitor()

        visitor.visit(tree)

        expectedParentName: str = 'Cat'
        expectedChildName: str = 'Opie'

        self.assertTrue(expectedParentName in visitor.parents,
                        'Missing parent')

        actualChildName: str = visitor.parents[expectedParentName][0]

        self.assertEqual(expectedChildName, actualChildName, 'Missing child')
예제 #6
0
    def testOnlyInitHasParameters(self):

        tree: Python3Parser.File_inputContext = self._setupVisitor('Vertex.py')
        visitor: PyutPythonVisitor = PyutPythonVisitor()

        visitor.visit(tree)

        self.assertTrue('Vertex' in visitor.classMethods,
                        'Oops, I am missing my class key')

        self.assertTrue('__init__' in visitor.parameters,
                        'I am missing a method')
        self.assertFalse('surround_faces' in visitor.parameters,
                         'I am missing a method')
        self.assertFalse('surround_half_edges' in visitor.parameters,
                         'I am missing a method')
예제 #7
0
    def testDecoratedProperty(self):

        tree: Python3Parser.File_inputContext = self._setupVisitor(
            'ClassWithProperties.py')
        visitor: PyutPythonVisitor = PyutPythonVisitor()

        visitor.visit(tree)

        self.logger.info(f'{visitor.classMethods=}')
        self.logger.info(f'{visitor.propertyNames=}')
        self.logger.info(f'{visitor.setterProperties=}')
        self.logger.info(f'{visitor.getterProperties=}')

        self.assertTrue(len(visitor.propertyNames) == 2)
        self.assertTrue(len(visitor.setterProperties) == 2)
        self.assertTrue(len(visitor.getterProperties) == 2)
예제 #8
0
    def testInheritanceMultiParentMultiChildren(self):
        tree: Python3Parser.File_inputContext = self._setupVisitor(
            'DeepInheritance.py')
        visitor: PyutPythonVisitor = PyutPythonVisitor()

        visitor.visit(tree)

        expectedParentName1: str = 'ParentClass1'
        expectedParentName2: str = 'ParentClass2'

        self.assertEqual(len(visitor.parents), 2, 'Incorrect parent count')
        self.assertTrue(expectedParentName1 in visitor.parents,
                        f'Missing parent: {expectedParentName1}')
        self.assertTrue(expectedParentName2 in visitor.parents,
                        f'Missing parent: {expectedParentName2}')

        parent1Children: List[str] = visitor.parents[expectedParentName1]

        expectedParent1Child1: str = 'ChildClass1'
        expectedParent1Child2: str = 'ChildClass2'
        self.assertTrue(
            expectedParent1Child1 in parent1Children,
            f'Missing child: {expectedParent1Child1} of parent {expectedParentName1}'
        )
        self.assertTrue(
            expectedParent1Child2 in parent1Children,
            f'Missing child: {expectedParent1Child2} of parent {expectedParentName1}'
        )

        parent2Children: List[str] = visitor.parents[expectedParentName2]

        expectedParent2Child3: str = 'ChildClass3'
        expectedParent2Child4: str = 'ChildClass4'
        self.assertTrue(
            expectedParent2Child3 in parent2Children,
            f'Missing child: {expectedParent2Child3} of parent {expectedParentName2}'
        )
        self.assertTrue(
            expectedParent2Child4 in parent2Children,
            f'Missing child: {expectedParent2Child4} of parent {expectedParentName2}'
        )
예제 #9
0
    def testLargeClassWithMethodsThatHaveParameters(self):

        tree: Python3Parser.File_inputContext = self._setupVisitor(
            'GMLExporter.py')
        visitor: PyutPythonVisitor = PyutPythonVisitor()

        visitor.visit(tree)

        self.assertTrue('GMLExporter' in visitor.classMethods,
                        'Oops, I am missing my class key')

        self.assertTrue('translate' in visitor.parameters,
                        'I am missing a method - translate')
        # self.assertTrue('prettyPrint' in visitor.parameters, 'I am missing a method - prettyPrint')   # this is a property
        self.assertTrue('write' in visitor.parameters,
                        'I am missing a method - write')
        self.assertTrue(
            '_generateNodeGraphicsSection' in visitor.parameters,
            'I am missing a method - _generateNodeGraphicsSection')
        self.assertTrue('__generatePoint' in visitor.parameters,
                        'I am missing a method - __generatePoint')
예제 #10
0
    def testDataClass(self):

        tree: Python3Parser.File_inputContext = self._setupVisitor(
            'DataTestClass.py')
        visitor: PyutPythonVisitor = PyutPythonVisitor()

        visitor.visit(tree)

        self.logger.info(f'{visitor.dataClassNames}')
        self.assertTrue('DataTestClass' in visitor.dataClassNames,
                        'Bad property count')

        actualNumProps: int = len(visitor.dataClassProperties)
        self.assertEqual(
            TestPyutPythonVisitor.EXPECTED_DATA_CLASS_PROPERTY_COUNT,
            actualNumProps,
            f'There should be {TestPyutPythonVisitor.EXPECTED_DATA_CLASS_PROPERTY_COUNT} properties in the test class'
        )

        for dPropertyTuple in visitor.dataClassProperties:
            actualProp: str = dPropertyTuple[1]
            self.assertTrue(actualProp in self.EXPECTED_DATA_CLASS_PROPERTIES,
                            'Missing property')
예제 #11
0
class ReverseEngineerPython2:

    PyutClassName = str
    ClassName     = str
    PyutClasses   = Dict[PyutClassName, PyutClass]
    OglClasses    = Dict[ClassName, OglClass]

    PYTHON_ASSIGNMENT:     str = '='
    PYTHON_TYPE_DELIMITER: str = ':'
    PYTHON_EOL_COMMENT:    str = '#'

    def __init__(self):

        self.logger: Logger = getLogger(__name__)

        self.visitor: PyutPythonVisitor = cast(PyutPythonVisitor, None)

        self._pyutClasses: ReverseEngineerPython2.PyutClasses = {}
        self._oglClasses:  ReverseEngineerPython2.OglClasses  = {}

    def reversePython(self, umlFrame: UmlClassDiagramsFrame, directoryName: str, files: List[str]):
        """
        Reverse engineering Python files to OglClass's

        Args:
            umlFrame:       The uml frame to display on
            directoryName:  The directory name where the selected files reside
            files:          A list of files to parse
        """
        fileCount: int = len(files)
        dlg = ProgressDialog('Parsing Files', 'Starting',  parent=umlFrame, style=PD_APP_MODAL | PD_ELAPSED_TIME)
        dlg.SetRange(fileCount)
        currentFileCount: int = 0

        onGoingParents: PyutPythonVisitor.Parents = {}
        for fileName in files:

            try:
                fqFileName: str = f'{directoryName}{osSep}{fileName}'
                self.logger.info(f'Processing file: {fqFileName}')
                dlg.Update(currentFileCount, f'Processing: {fileName}')

                fileStream: FileStream   = FileStream(fqFileName)
                lexer:      Python3Lexer = Python3Lexer(fileStream)

                stream: CommonTokenStream = CommonTokenStream(lexer)
                parser: Python3Parser     = Python3Parser(stream)

                tree: Python3Parser.File_inputContext = parser.file_input()
                if parser.getNumberOfSyntaxErrors() != 0:
                    self.logger.error(f"File {fileName} contains {parser.getNumberOfSyntaxErrors()} syntax errors")
                    # TODO:  Put up a dialog
                    continue

                self.visitor = PyutPythonVisitor()
                self.visitor.parents = onGoingParents
                self.visitor.visit(tree)
                self._generatePyutClasses()

                onGoingParents = self.visitor.parents
                currentFileCount += 1
            except (ValueError, Exception) as e:
                from org.pyut.errorcontroller.ErrorManager import ErrorManager
                eMsg: str = f'file: {fileName}\n{e} - {ErrorManager.getErrorInfo()}'
                self.logger.error(eMsg)
                dlg.Destroy()
                raise PythonParseException(eMsg)
        dlg.Destroy()
        self._generateOglClasses(umlFrame)
        self._layoutUmlClasses(umlFrame)
        self._generateInheritanceLinks(umlFrame)

    def _generatePyutClasses(self):

        for className in self._classNames():
            pyutClass: PyutClass = PyutClass(name=className)

            pyutClass = self._addFields(pyutClass)

            for methodName in self._methodNames(className):
                pyutMethod: PyutMethod = PyutMethod(name=methodName)
                if methodName[0:2] == "__":
                    pyutMethod.setVisibility(PyutVisibilityEnum.PRIVATE)
                elif methodName[0] == "_":
                    pyutMethod.setVisibility(PyutVisibilityEnum.PROTECTED)
                else:
                    pyutMethod.setVisibility(PyutVisibilityEnum.PUBLIC)
                pyutMethod = self._addParameters(pyutMethod)
                pyutMethod.sourceCode = self.visitor.methodCode[methodName]

                pyutClass.addMethod(pyutMethod)
            setterProperties: PyutPythonVisitor.Parameters = self.visitor.setterProperties
            getterProperties: PyutPythonVisitor.Parameters = self.visitor.getterProperties
            for propName in self.visitor.propertyNames:

                setterParams: List[str] = setterProperties[propName]
                getterParams: List[str] = getterProperties[propName]
                self.logger.info(f'Processing - {propName=} {setterParams=} {getterParams=}')
                setter, getter = self._createProperties(propName=propName, setterParams=setterParams)
                pyutClass.addMethod(setter)
                pyutClass.addMethod(getter)

            self._pyutClasses[className] = pyutClass
        self.logger.info(f'Generated {len(self._pyutClasses)} classes')

    def _createProperties(self, propName: str, setterParams: List[str]) -> Tuple[PyutMethod, PyutMethod]:

        setter: PyutMethod = PyutMethod(name=propName, visibility=PyutVisibilityEnum.PUBLIC)
        getter: PyutMethod = PyutMethod(name=propName, visibility=PyutVisibilityEnum.PUBLIC)

        nameType:          str       = setterParams[0]
        potentialNameType: List[str] = nameType.split(':')

        if len(potentialNameType) == 2:

            param: PyutParam = PyutParam(name=potentialNameType[0], theParameterType=PyutType(value=potentialNameType[1]))
            setter.addParam(param)
            getter.returnType = PyutType(value=potentialNameType[1])
        else:
            param: PyutParam = PyutParam(name=potentialNameType[0])
            setter.addParam(param)

        return setter, getter

    def _addParameters(self, pyutMethod: PyutMethod) -> PyutMethod:

        methodName: str = pyutMethod.name
        if methodName in self.visitor.parameters:
            parameters: PyutPythonVisitor.Parameters = self.visitor.parameters[methodName]
            for parameter in parameters:
                self.logger.debug(f'parameter: {parameter}')
                paramNameType = parameter.split(':')
                #
                # TODO: account for default values
                #
                if len(paramNameType) == 2:         # Somebody is good and did typing
                    pyutType: PyutType = PyutType(paramNameType[1])
                    pyutParam: PyutParam = PyutParam(name=paramNameType[0], theParameterType=pyutType)
                    pyutMethod.addParam(pyutParam)

        return pyutMethod

    def _addFields(self, pyutClass: PyutClass) -> PyutClass:
        """
        Can look like this:

           fieldData: x:int=0
           fieldData = 0

        Args:
            pyutClass:  Where to add the fields

        Returns:  The updated input class

        """
        for fieldData in self.visitor.fields:
            self.logger.debug(f'fieldData: {fieldData}')
            pyutField: PyutField = self._parseFieldToPyut(fieldData)
            pyutClass.addField(pyutField)

        return pyutClass

    def _generateOglClasses(self, umlFrame: UmlClassDiagramsFrame):

        for pyutClassName in self._pyutClasses:
            try:
                pyutClass: PyutClass = self._pyutClasses[pyutClassName]
                oglClass:  OglClass = OglClass(pyutClass)
                umlFrame.addShape(oglClass, 0, 0)
                oglClass.autoResize()

                self._oglClasses[pyutClassName] = oglClass
            except (ValueError, Exception) as e:
                self.logger.error(f"Error while creating class {pyutClassName},  {e}")

    def _generateInheritanceLinks(self, umlFrame: UmlClassDiagramsFrame):

        parents: PyutPythonVisitor.Parents = self.visitor.parents

        for parentName in parents.keys():
            children: PyutPythonVisitor.Children = parents[parentName]
            for childName in children:

                try:
                    parentOglClass: OglClass = self._oglClasses[parentName]
                    childOglClass:  OglClass = self._oglClasses[childName]
                    self.__createInheritanceLink(child=childOglClass, parent=parentOglClass, umlFrame=umlFrame)
                except KeyError as ke:        # Probably there is no parent we are tracking
                    self.logger.error(f'Apparently we are not tracking this parent:  {ke}')
                    continue

    def _methodNames(self, className: str) -> List[str]:

        methodNames: List[str] = []
        try:
            methodNames = self.visitor.classMethods[className]
        except KeyError:
            pass                # A class with no methods ??

        return methodNames

    def _classNames(self) -> List[str]:
        return self.visitor.classNames

    def _layoutUmlClasses(self, umlFrame: UmlClassDiagramsFrame):
        """
        Organize by vertical descending sizes

        Args:
            umlFrame:
        """
        oglClasses: List[OglClass] = list(self._oglClasses.values())
        # Sort by descending height
        sortedOglClasses = sorted(oglClasses, key=lambda oglClassToSort: oglClassToSort._height, reverse=True)

        x: int = 20
        y: int = 20

        incY: int = 0
        for oglClass in sortedOglClasses:
            incX, sy = oglClass.GetSize()
            incX += 20
            sy += 20
            incY = max(incY, int(sy))
            # find good coordinates
            if x + incX >= umlFrame.maxWidth:
                x = 20
                y += incY
                incY = int(sy)
            oglClass.SetPosition(x, y)
            x += incX

    def _parseFieldToPyut(self, fieldData: str) -> PyutField:

        self.logger.debug(f'fieldData: {fieldData}')

        if ReverseEngineerPython2.PYTHON_TYPE_DELIMITER in fieldData and ReverseEngineerPython2.PYTHON_ASSIGNMENT in fieldData:
            pyutField: PyutField = self.__complexParseFieldToPyut(fieldData)
        else:
            pyutField: PyutField = self.__simpleParseFieldToPyut(fieldData)

        return pyutField

    def __simpleParseFieldToPyut(self, fieldData: str) -> PyutField:

        pyutField: PyutField = PyutField()

        noCommentFieldData: str = self.__stripEndOfLineComment(fieldData)
        fieldAndValue: List[str] = noCommentFieldData.split(ReverseEngineerPython2.PYTHON_ASSIGNMENT)

        pyutField.name         = fieldAndValue[0].strip()
        pyutField.defaultValue = fieldAndValue[1].strip()

        return pyutField

    def __complexParseFieldToPyut(self, fieldData: str) -> PyutField:
        """
          Can look like this:

           fieldData: x:int=0

        Args:
            fieldData:

        Returns:
        """
        noCommentFieldData: str = self.__stripEndOfLineComment(fieldData)

        fieldAndType: List[str] = noCommentFieldData.split(ReverseEngineerPython2.PYTHON_TYPE_DELIMITER)
        fieldName:    str       = fieldAndType[0]

        vis: PyutVisibilityEnum = self.__determineFieldVisibility(fieldName)

        fieldName = self.__appropriatelyCleanupName(vis=vis, fieldName=fieldName)

        pyutField: PyutField = PyutField(name=fieldName, visibility=vis)

        if len(fieldAndType) > 1:
            typeAndDefaultValue: List[str] = fieldAndType[1].split(ReverseEngineerPython2.PYTHON_ASSIGNMENT)

            pyutType: PyutType = PyutType(value=typeAndDefaultValue[0].strip())
            pyutField.setType(theType=pyutType)
            if len(typeAndDefaultValue) > 1:
                pyutField.setDefaultValue(typeAndDefaultValue[1].strip())

        return pyutField

    def __determineFieldVisibility(self, name: str) -> PyutVisibilityEnum:

        vis: PyutVisibilityEnum = PyutVisibilityEnum.PUBLIC
        if len(name) > 1:
            if name[-2:] != "__":
                if name[0:2] == "__":
                    vis: PyutVisibilityEnum = PyutVisibilityEnum.PRIVATE
                elif name[0] == "_":
                    vis: PyutVisibilityEnum = PyutVisibilityEnum.PROTECTED
        return vis

    def __appropriatelyCleanupName(self, vis: PyutVisibilityEnum, fieldName: str) -> str:

        if vis == PyutVisibilityEnum.PUBLIC:
            return fieldName
        elif vis == PyutVisibilityEnum.PRIVATE:
            fieldName = fieldName[2:]
            return fieldName
        else:
            fieldName = fieldName[1:]
            return fieldName

    def __stripEndOfLineComment(self, fieldData: str) -> str:

        fieldAndComment: List[str] = fieldData.split(ReverseEngineerPython2.PYTHON_EOL_COMMENT)

        return fieldAndComment[0]

    def __createInheritanceLink(self, child: OglClass, parent: OglClass, umlFrame: UmlClassDiagramsFrame):
        """
        Add a paternity link between child and father.

        Args:
            child:  A child
            parent: The daddy!!

        Returns: an OgLink

        """
        cmdGroup: CommandGroup         = CommandGroup('Creating an inheritance link')
        # inheritance points back to parent
        cmd: CreateOglLinkCommand = CreateOglLinkCommand(src=child, dst=parent)
        cmdGroup.addCommand(cmd)

        historyManager: HistoryManager = umlFrame.getHistory()
        historyManager.addCommandGroup(cmdGroup)

        cmd.execute()