refactoring_custom/SmallSense__CustomCodeGeneratorOrRefactoring.st
author Jan Vrany <jan.vrany@fit.cvut.cz>
Mon, 02 Jul 2018 08:46:03 +0200
changeset 1073 c591c75fe5a8
parent 1072 a44c741ee5ef
permissions -rw-r--r--
Tagged Smalltalk/X 8.0.0

"
A custom code generation and refactoring support for Smalltalk/X
Copyright (C) 2013-2015 Jakub Nesveda
Copyright (C) 2015-2016 Jan Vrany

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
"
"{ Package: 'stx:goodies/smallsense/refactoring_custom' }"

"{ NameSpace: SmallSense }"

Object subclass:#CustomCodeGeneratorOrRefactoring
	instanceVariableNames:'compositeChangeCollector compositeChangeNesting userPreferences
		confirmChanges dialog changeManager model refactoryBuilder
		formatter resources'
	classVariableNames:''
	poolDictionaries:''
	category:'Interface-Refactoring-Custom'
!

!CustomCodeGeneratorOrRefactoring class methodsFor:'documentation'!

copyright
"
A custom code generation and refactoring support for Smalltalk/X
Copyright (C) 2013-2015 Jakub Nesveda
Copyright (C) 2015-2016 Jan Vrany

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
"
! !

!CustomCodeGeneratorOrRefactoring class methodsFor:'instance creation'!

new
    "return an initialized instance"

    ^ self basicNew initialize.
!

subGeneratorOrRefactoringOf:aCodeGeneratorOrRefactoring
    "Returns and initializes new instance of code generator or refactoring
    to be used inside another code generator or refactoring."
    | nestingCount |

    nestingCount := aCodeGeneratorOrRefactoring compositeChangeNesting.
    nestingCount isNil ifTrue:[ nestingCount := 0 ].

    ^ self new
        model:aCodeGeneratorOrRefactoring model;
        refactoryBuilder:aCodeGeneratorOrRefactoring refactoryBuilder;
        userPreferences:aCodeGeneratorOrRefactoring userPreferences;
        dialog:aCodeGeneratorOrRefactoring dialog;
        changeManager:aCodeGeneratorOrRefactoring changeManager;
        compositeChangeCollector:aCodeGeneratorOrRefactoring compositeChangeCollector;
        compositeChangeNesting:(1 + nestingCount);
        yourself

    "Created: / 19-04-2014 / 10:15:21 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 13-10-2014 / 20:32:02 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring class methodsFor:'accessing-presentation'!

description
    "Returns more detailed description of the receiver"

    ^ self subclassResponsibility

    "Created: / 01-12-2013 / 00:18:41 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

group
    "Returns a collection strings describing a group to which
     receiver belongs. A groups may be nested hence the array of
     strings. For example for subgroup 'Accessors' in group 'Generators'
     this method should return #('Generators' 'Accessors')."

    "/ By default return an empty array which means the item will appear
    "/ in top-level group.
    ^ #()

    "Created: / 01-12-2013 / 00:21:48 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 05-08-2014 / 13:23:11 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

label
    "Returns show label describing the receiver. This label
     is used in UI as menu item/tree item label."

    ^ self subclassResponsibility

    "Created: / 01-12-2013 / 00:18:05 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring class methodsFor:'enumerating'!

generatorsAndRefactoringsDo: aOneArgBlock
    "Evaluates a block through all generator or refactoring classes (actually all my subclasses)."

    self allSubclassesDo: aOneArgBlock

    "Created: / 28-12-2014 / 11:44:46 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring class methodsFor:'executing'!

executeInContext: aCustomContext
    ^ self new executeInContext: aCustomContext

    "Created: / 26-01-2014 / 13:42:32 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

executeInContextWithWaitCursor: aCustomContext
    ^ self new executeInContextWithWaitCursor: aCustomContext

    "Created: / 10-08-2014 / 09:34:17 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring class methodsFor:'private'!

canUseRefactoringSupport
    "check if refactory browser stuff is avaliable"

    ^ (Smalltalk at: #'stx_goodies_refactoryBrowser_changes') notNil 
        and:[ (Smalltalk at: #'stx_goodies_refactoryBrowser_browser') notNil
        and:[ UserPreferences current useRefactoringSupport ] ]

    "Modified: / 13-02-2016 / 15:06:47 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring class methodsFor:'queries'!

availableForProgrammingLanguages
    "Returns list of programming language instances for which this generator / refactoring works.
    (SmalltalkLanguage instance, JavaLanguage instance, GroovyLanguage instance, etc.)

     See also availableForProgrammingLanguagesInContext:withPerspective:"

    "We are assuming here that majority will be written for Smalltalk."
    ^ {SmalltalkLanguage instance}

    "Created: / 22-12-2014 / 20:12:22 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

availableForProgrammingLanguagesInContext: aCustomContext
    "Returns true if generator / refactoring works for programming languages
     of codebase elements (classes, methods, etc.) in CustomContext instance.

     Called by the UI to figure out what generators / refactorings
     are available at given point. See class CustomMenuBuilder for details."

    | languages perspective |

    perspective := aCustomContext perspective.
    perspective isNil ifTrue: [
        "Rather no quess if perspective is missing"
        ^ true
    ].

    languages := self availableForProgrammingLanguages.

    perspective isCodeViewPerspective ifTrue: [
        ^ aCustomContext selectedCodes ? #() anySatisfy: [ :codeSelection |
            | method |

            method := codeSelection selectedMethod.

            method notNil and: [ languages includes: method programmingLanguage ]
        ].
    ].

    perspective isMethodPerspective ifTrue: [
        aCustomContext selectedMethods isEmptyOrNil ifTrue: [ ^ true ].  

        ^ aCustomContext selectedMethods anySatisfy: [ :method | 
            method notNil and: [ languages includes: method programmingLanguage ]
        ].
    ].

    (perspective isClassPerspective 
        or: [ perspective isInstanceVariablePerspective ] 
        or: [ perspective isProtocolPerspective ]) ifTrue: [

        aCustomContext selectedClasses isEmptyOrNil ifTrue: [ ^ true ].

        ^ aCustomContext selectedClasses anySatisfy: [ :class | 
            class notNil and: [ languages includes: class programmingLanguage ]
        ].
    ].

    "For other perspectives (package, class category, namespace) no guess"
    ^ true

    "Created: / 22-12-2014 / 20:34:28 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 25-12-2014 / 09:31:32 / root"
    "Modified: / 24-01-2015 / 18:24:49 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

availableInContext: aCustomContext
    "Returns true if the generator/refactoring is available in given
     context, false otherwise.

     Called by the UI to figure out what generators / refactorings
     are available at given point. See class CustomContext for details."

    ^ self subclassResponsibility

    "Created: / 01-12-2013 / 00:13:28 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

availableInPerspective:aCustomPerspective
    "Returns true if the generator/refactoring is available in given
     perspective, false otherwise.

     Called by the UI to figure out what generators / refactorings
     to show"

    ^ self subclassResponsibility

    "Created: / 26-01-2014 / 13:03:25 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring class methodsFor:'testing'!

isAbstract
    ^ self == CustomCodeGeneratorOrRefactoring

    "Created: / 26-01-2014 / 21:38:30 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

isCustomCodeGenerator
    ^ false
!

isCustomRefactoring
    ^ false
! !

!CustomCodeGeneratorOrRefactoring methodsFor:'accessing'!

changeManager

    ^ changeManager

    "Created: / 31-05-2014 / 13:29:31 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

changeManager: aChangeManager

    changeManager := aChangeManager

    "Created: / 31-05-2014 / 13:30:02 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

compositeChangeCollector

    ^ compositeChangeCollector

    "Created: / 19-04-2014 / 10:18:06 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

compositeChangeCollector: aCompositeChangeCollector

    compositeChangeCollector := aCompositeChangeCollector

    "Created: / 19-04-2014 / 10:18:23 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

compositeChangeNesting

    ^ compositeChangeNesting

    "Created: / 11-05-2014 / 14:01:23 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

compositeChangeNesting: aNumber

    compositeChangeNesting := aNumber

    "Created: / 11-05-2014 / 14:01:56 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

dialog

    ^ dialog

    "Created: / 11-05-2014 / 00:27:21 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

dialog: aDialog

    dialog := aDialog

    "Created: / 11-05-2014 / 00:27:49 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

formatter

    ^ formatter

    "Created: / 19-09-2014 / 22:18:33 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

formatter: aSourceCodeFormatter

    formatter := aSourceCodeFormatter

    "Created: / 19-09-2014 / 22:18:50 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

model
    ^model

    "Created: / 23-08-2014 / 00:13:43 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

model: aModel

    model := aModel

    "Created: / 23-08-2014 / 00:13:26 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified (format): / 09-10-2014 / 10:17:59 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

refactoryBuilder

    ^ refactoryBuilder

    "Modified (format): / 23-08-2014 / 00:14:38 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

refactoryBuilder: aRefactoryBuilder

    refactoryBuilder := aRefactoryBuilder.

    "Modified (format): / 23-08-2014 / 00:14:33 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

sourceCodeGenerator

    ^ model sourceCodeGenerator

    "Created: / 19-09-2014 / 20:56:22 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 07-10-2014 / 22:47:34 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

userPreferences

    ^ userPreferences

    "Created: / 09-06-2014 / 21:49:33 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

userPreferences: aUserPreferences

    userPreferences := aUserPreferences

    "Created: / 09-06-2014 / 21:49:56 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring methodsFor:'bulk changes'!

executeCollectedChangesNamed:name
    "
    Same as CodeGeneratorTool >> executeCollectedChangesNamed:,
    but with custom RefactoryChangeManager and custom Dialog
    "

    compositeChangeCollector notNil ifTrue:[
        compositeChangeNesting := compositeChangeNesting - 1.
        compositeChangeNesting == 0 ifTrue:[
            compositeChangeCollector name:name.
            compositeChangeCollector changesSize == 0 ifTrue:[
                dialog information: (resources string: 'Nothing generated.').
            ] ifFalse:[
                changeManager performChange: compositeChangeCollector
            ].
            compositeChangeCollector := nil.
            self model changes: CompositeRefactoryChange new.
        ]
    ]

    "Created: / 31-05-2014 / 11:30:11 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 16-11-2014 / 10:42:32 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified (format): / 25-01-2015 / 14:31:46 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

startCollectChanges
    (self canUseRefactoringSupport) ifTrue:[
        compositeChangeCollector isNil ifTrue:[
            compositeChangeCollector := model changes.
            compositeChangeNesting := 0.
        ].
        compositeChangeNesting := compositeChangeNesting + 1.
    ]

    "Modified: / 16-11-2014 / 10:43:06 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring methodsFor:'code generation'!

addChange: aCodeChange

    aCodeChange notNil ifTrue: [
        compositeChangeCollector addChange: aCodeChange
    ]

    "Created: / 23-08-2014 / 15:40:17 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 17-09-2014 / 22:53:48 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring methodsFor:'compilation'!

compile:theCode forClass:aClass inCategory:cat
    "install some code for a class.
     If refactory browser stuff is avaliable the refactory tools are used to support undo"

    self
        compile:theCode forClass:aClass inCategory:cat
        skipIfSame:true
!

compile:theCode forClass:aClass inCategory:categoryOrNil skipIfSame:skipIfSame
    "Install some code for a class.
    If refactory browser stuff is avaliable the refactory tools are used to support undo
    (determined by aClass - can be RBClass/RBMetaclass instance or real class)"

    |compiler selector oldMethod isSame category|

    isSame := false.
    category := categoryOrNil ? (Compiler defaultMethodCategory).

    skipIfSame ifTrue:[
        compiler := aClass compilerClass new.
        compiler parseMethod:theCode in:aClass ignoreErrors:true ignoreWarnings:true.

        selector := compiler selector.
        selector notNil ifTrue:[
            oldMethod := aClass compiledMethodAt:selector.
            isSame := (oldMethod notNil and:[oldMethod source = theCode]).
            isSame ifTrue:[^ self ].
            oldMethod notNil ifTrue:[
                category := categoryOrNil ? (oldMethod category).
            ].
        ].
    ].

    aClass compile: theCode classified: category.

    "Modified: / 21-08-2006 / 18:39:06 / cg"
    "Modified (format): / 21-01-2012 / 10:40:59 / cg"
    "Modified (comment): / 08-02-2015 / 19:40:07 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring methodsFor:'executing'!

executeInContext: aCustomContext
    | context |

    context := aCustomContext copyWithModel: self model.

    self startCollectChanges.

    context isInteractiveContext ifTrue:[
        self configureInContext: context
    ].

    self validateInContext: context.
    self buildInContext: context.

    self executeCollectedChangesNamed: self class description.

    self updateInContext: context.

    "Created: / 19-03-2014 / 18:45:26 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 25-11-2014 / 21:07:32 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 29-08-2015 / 13:29:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

executeInContextWithWaitCursor: aCustomContext
    "Much like executeInContext: but with loading cursor animation"

    | wg executor |

    wg := WindowGroup activeGroup.
    wg isNil ifTrue:[
        executor := [:whatToDo | whatToDo value ]
    ] ifFalse:[
        executor := [:whatToDo | wg withWaitCursorDo: [ whatToDo value ] ]
    ].

    executor value:[
        self executeInContext: aCustomContext
    ]

    "Created: / 07-08-2014 / 23:17:17 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

executeSubGeneratorOrRefactoringClasses:aSubGeneratorOrRefactoringClasses inContext:aCustomContext
    "For each code generator or refactoring class initializes an instance
    and executes it."

    aSubGeneratorOrRefactoringClasses do:[ :class | 
        (class subGeneratorOrRefactoringOf:self)
            executeInContext:aCustomContext
    ]

    "Created: / 08-07-2014 / 18:31:10 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified (format): / 13-10-2014 / 20:25:13 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring methodsFor:'executing - private'!

buildInContext:aCustomContext
    "Should generate code or perform custom refactoring."

    ^ self subclassResponsibility

    "Created: / 16-09-2014 / 09:14:07 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 13-10-2014 / 17:21:42 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

configureInContext:aCustomContext
    "Perform neccessary configuration for given context, such as
     computing default values for parameters. This may interact with
     user by means of opening a dialog.

     This method is called only for interactive contexts. When using
     non interactively, a caller must do the configuration itself by means
     of accessors."

    "/ To be overridden by subclasses

    "Created: / 16-09-2014 / 07:24:10 / Jan Vrany <jan.vrany@fit.cvut.cz>"
    "Modified (comment): / 16-09-2014 / 11:00:17 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

updateInContext: aContext
    "Update the context so it points to generated class/methods. To be overriden by subclasses."

    "Created: / 29-08-2015 / 13:29:38 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!

validateInContext: aCustomContext

    "/ To be overridden by subclasses

    "Created: / 16-09-2014 / 09:45:10 / Jan Vrany <jan.vrany@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring methodsFor:'initialization'!

confirmChanges
    "if true, let user confirm complicated changes; if false, just do it"

    ^ confirmChanges ? true

    "Created: / 04-08-2011 / 17:31:45 / cg"
!

confirmChanges:aBoolean
    "if true, let user confirm complicated changes; if false, just do it"

    confirmChanges := aBoolean

    "Created: / 04-08-2011 / 17:26:47 / cg"
!

initialize

    userPreferences := UserPreferences current.
    "Translated dialogs have to be in part of browser, so use browser resources"
    resources := Tools::NewSystemBrowser classResources.

    self initializeFormatter;
        initializeChangeManager;
        initializeModel;
        initializeRefactoryBuilder;
        initializeDialog.

    "Created: / 17-03-2014 / 22:27:32 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 08-02-2015 / 20:17:43 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

initializeChangeManager
    changeManager := CustomBrowserChangeManager new.

    "Created: / 09-06-2014 / 22:56:56 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

initializeDialog
    dialog := CustomUserDialog new.

    "Created: / 09-06-2014 / 22:57:08 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

initializeFormatter
    formatter := CustomRBLocalSourceCodeFormatter new

    "Created: / 18-09-2014 / 23:12:42 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

initializeModel
    model := (CustomNamespace new)
            formatter:formatter;
            changeManager:changeManager;
            yourself

    "Created: / 09-06-2014 / 22:56:10 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 16-11-2014 / 10:41:34 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
!

initializeRefactoryBuilder
    refactoryBuilder := (CustomRefactoryBuilder new)
            formatter:formatter;
            changeManager:changeManager;
            model:model;
            yourself

    "Created: / 23-08-2014 / 00:05:52 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
    "Modified: / 16-11-2014 / 10:41:12 / Jakub Nesveda <nesvejak@fit.cvut.cz>"
! !

!CustomCodeGeneratorOrRefactoring methodsFor:'private'!

canUseRefactoringSupport
    "check if refactory browser stuff is avaliable"

     ^ self class canUseRefactoringSupport
! !

!CustomCodeGeneratorOrRefactoring methodsFor:'testing'!

isCustomCodeGenerator
    ^ false
!

isCustomRefactoring
    ^ false
! !

!CustomCodeGeneratorOrRefactoring class methodsFor:'documentation'!

version_HG

    ^ '$Changeset: <not expanded> $'
! !