org.shaman.rpg.editor.objects.ui.properties.AdvancedLogicSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.shaman.rpg.editor.objects.ui.properties.AdvancedLogicSupport.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package org.shaman.rpg.editor.objects.ui.properties;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyEditor;
import java.io.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.apache.commons.io.FileUtils;
import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.project.Project;
import org.openide.*;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
import org.openide.windows.WindowManager;
import org.shaman.rpg.editor.objects.EditorLevelElement;
import org.shaman.rpg.editor.objects.LevelElementValidator;
import org.shaman.rpg.editor.objects.ObjectActionProvider;
import org.shaman.rpg.editor.objects.PropertyEditorProvider;
import org.shaman.rpg.editor.utilities.ClasspathAccess;
import org.shaman.rpg.engine.core.obj.BaseController;
import org.shaman.rpg.engine.core.obj.BaseModel;
import org.shaman.rpg.engine.core.obj.BaseView;
import org.shaman.rpg.engine.core.obj.controller.AdvancedLogicController;
import org.shaman.rpg.engine.core.obj.controller.LogicScript;
import org.shaman.rpg.engine.core.obj.model.ViewDelegateModel;

/**
 * Support for {@link AdvancedLogicController} in the editor.
 * It provides a validator (that the script class implements {@link LogicScript}),
 * a property editor (that let you choose the class or create a new script class)
 * and a action provider (that creates initialization code for the script)
 * @author Sebastian Weiss
 */
@ServiceProviders({ @ServiceProvider(service = PropertyEditorProvider.class, position = 9000),
        @ServiceProvider(service = ObjectActionProvider.Factory.class, position = 9000),
        @ServiceProvider(service = LevelElementValidator.Factory.class, position = 9000) })
public class AdvancedLogicSupport
        implements PropertyEditorProvider, ObjectActionProvider.Factory, LevelElementValidator.Factory {
    private static final Logger LOG = Logger.getLogger(AdvancedLogicSupport.class.getName());

    private static final String TEMPLATE_FILE = "Editor-Objects/TemplateLogicScript.java";

    private Validator validator = new Validator();

    private FileObject findFileObject(Project project, String className) {
        ClasspathAccess classpathAccess = project.getLookup().lookup(ClasspathAccess.class);
        if (classpathAccess == null) {
            LOG.info(
                    "failed to access classpath: no implementation of ClasspathAccess found in the project's lookup");
            return null;
        }
        FileObject fo = classpathAccess.getClassFile(className, ClasspathAccess.Language.JAVA, false);
        if (fo == null) {
            fo = classpathAccess.getClassFile(className, ClasspathAccess.Language.GROOVY, false);
        }
        if (fo == null) {
            return null;
        }
        return fo;
    }

    private boolean implementsLogicScript(JavaSource source) {
        final AtomicBoolean state = new AtomicBoolean(false);
        try {
            source.runWhenScanFinished(new Task<CompilationController>() {

                @Override
                public void run(CompilationController parameter) throws Exception {
                    parameter.toPhase(JavaSource.Phase.PARSED);
                    CompilationUnitTree tree = parameter.getCompilationUnit();
                    //                     System.out.println("compilation tree: "+tree);
                    for (Tree typeDecl : tree.getTypeDecls()) {
                        if (Tree.Kind.CLASS == typeDecl.getKind()) {
                            ClassTree clazz = (ClassTree) typeDecl;
                            //                           System.out.println("class tree: "+clazz);
                            for (Tree implTree : clazz.getImplementsClause()) {
                                if (implTree.toString().equals(LogicScript.class.getSimpleName())
                                        || implTree.toString().equals(LogicScript.class.getCanonicalName())) {
                                    state.set(true);
                                    return; //class implements LogicScript
                                }
                            }
                        }
                    }
                    state.set(false);
                }
            }, true).get();
        } catch (IOException | InterruptedException | ExecutionException ex) {
            Exceptions.printStackTrace(ex);
            return false;
        }
        return state.get();
    }

    private void createScriptFile(Project project, String packageClassName) {
        LOG.log(Level.INFO, "create new script file in project {0} under the class name {1}",
                new Object[] { project, packageClassName });

        ClasspathAccess ac = project.getLookup().lookup(ClasspathAccess.class);
        if (ac.getClassFile(packageClassName, ClasspathAccess.Language.JAVA, false) != null) {
            LOG.info("script file already exists");
            JOptionPane.showMessageDialog(null,
                    "unable to create script file " + packageClassName + ", file already exists",
                    "Unable to create script file", JOptionPane.ERROR_MESSAGE);
            return;
        }

        try {
            //create script file
            FileObject templateFile = FileUtil.getConfigFile(TEMPLATE_FILE);
            String template = templateFile.asText();
            String className = packageClassName.substring(packageClassName.lastIndexOf('.') + 1);
            String packageName = packageClassName.substring(0, packageClassName.lastIndexOf('.'));
            template = template.replace("${packageName}", packageName);
            template = template.replace("${className}", className);
            FileObject target = ac.getClassFile(packageClassName, ClasspathAccess.Language.JAVA, true);
            try (OutputStream out = target.getOutputStream()) {
                out.write(template.getBytes());
            }
            LOG.log(Level.INFO, "script file {0} created", packageClassName);
        } catch (IOException ex) {
            LOG.log(Level.SEVERE, "unable to create script file " + packageClassName, ex);
            JOptionPane.showMessageDialog(null, "Unable to create script file " + packageClassName,
                    "Unable to create script file", JOptionPane.ERROR_MESSAGE);
        }
    }

    private void updateScriptFile(Project project, String className, AdvancedLogicController controller) {
        //TODO
    }

    @Override
    public PropertyEditor createPropertyEditor(PropertyDescriptor propertyDescriptor,
            ContextDescriptor contextDescriptor) {
        if (contextDescriptor.object instanceof AdvancedLogicController
                && propertyDescriptor.getter.getName().equals("getScript")) {
            //An AdvancedLogicController and the script property
            ClasspathAccess classpathAccess = contextDescriptor.assetManager.getProject().getLookup()
                    .lookup(ClasspathAccess.class);
            if (classpathAccess == null) {
                LOG.info(
                        "failed to access classpath: no implementation of ClasspathAccess found in the project's lookup");
                return null;
            }
            return new PropEditor(contextDescriptor.assetManager.getProject(), classpathAccess);
        }
        return null;
    }

    @Override
    public <V extends BaseView, M extends BaseModel<? extends V>, C extends BaseController<? extends M>> ObjectActionProvider createActionProvider(
            EditorLevelElement<V, M, C> element, Object object, EditorLevelElement.ObjectType objectType) {
        if (object instanceof AdvancedLogicController) {
            return new ActionProvider(element, (AdvancedLogicController) object);
        } else {
            return null;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public <V extends BaseView, M extends BaseModel<? extends V>, C extends BaseController<? extends M>> LevelElementValidator<V, M, C> createValidator(
            EditorLevelElement<V, M, C> element) {
        return (LevelElementValidator<V, M, C>) validator;
    }

    private class Validator
            implements LevelElementValidator<BaseView, BaseModel<BaseView>, BaseController<BaseModel<BaseView>>> {

        @Override
        public String validate(
                EditorLevelElement<BaseView, BaseModel<BaseView>, BaseController<BaseModel<BaseView>>> element) {
            for (BaseController<?> c : element.getControllers()) {
                if (c instanceof AdvancedLogicController) {
                    String s = validate((AdvancedLogicController) c, element);
                    if (s != null) {
                        return s;
                    }
                }
            }
            return null;
        }

        private String validate(AdvancedLogicController c, EditorLevelElement<?, ?, ?> element) {
            String script = c.getScript();
            if (script == null) {
                return c.toString() + ": script is null";
            }
            if (script.isEmpty()) {
                return c.toString() + ": script name is empty";
            }
            FileObject fo = findFileObject(element.getLevel().getProject(), script);
            if (fo == null) {
                return c.toString() + ": script class not found";
            }
            JavaSource source = JavaSource.forFileObject(fo);
            if (source != null) {
                if (!implementsLogicScript(source)) {
                    return c.toString() + ": class does not implement LogicScript";
                }
            }
            return null;
        }
    }

    private class PropEditor implements PropertyEditor {
        private Project project;
        private ClasspathAccess classpathAccess;
        private String string;
        private final PropertyChangeSupport support;

        private PropEditor(Project project, ClasspathAccess classpathAccess) {
            this.project = project;
            this.classpathAccess = classpathAccess;
            support = new PropertyChangeSupport(this);
        }

        @Override
        public void setValue(Object value) {
            String old = string;
            if (value instanceof String) {
                string = (String) value;
            } else {
                string = null;
            }
            support.firePropertyChange("value", old, string); // NOI18N
        }

        @Override
        public String getValue() {
            return string;
        }

        @Override
        public boolean isPaintable() {
            return false;
        }

        @Override
        public void paintValue(Graphics gfx, Rectangle box) {
            throw new UnsupportedOperationException("Not supported.");
        }

        @Override
        public String getAsText() {
            if (string == null) {
                return "<null value>";
            } else {
                return string;
            }
        }

        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            if (text == null) {
                throw new IllegalArgumentException("null parameter"); // NOI18N
            }

            text = text.trim();

            if ("<null value>".equals(text)) { // NOI18N
                setValue(null);
                return;
            }

            setValue(text);
        }

        @Override
        public String getJavaInitializationString() {
            if (string == null) {
                return "null"; // NOI18N
            } else {
                return "\"" + string + "\""; // NOI18N
            }
        }

        @Override
        public String[] getTags() {
            return null;
        }

        @Override
        public Component getCustomEditor() {
            return new PropEditorDialog(this);
        }

        @Override
        public boolean supportsCustomEditor() {
            return true;
        }

        @Override
        public void addPropertyChangeListener(PropertyChangeListener listener) {
            support.addPropertyChangeListener(listener);
        }

        @Override
        public void removePropertyChangeListener(PropertyChangeListener listener) {
            support.addPropertyChangeListener(listener);
        }

    }

    private class PropEditorDialog extends JDialog {

        private final PropEditor editor;

        /**
         * A return status code - returned if Cancel button has been pressed
         */
        public static final int RET_CANCEL = 0;
        /**
         * A return status code - returned if OK button has been pressed
         */
        public static final int RET_OK = 1;

        private PropEditorDialog(final PropEditor editor) {
            super(WindowManager.getDefault().getMainWindow(), true);
            this.editor = editor;
            initComponents();
            setLocationRelativeTo(WindowManager.getDefault().getMainWindow());

            SwingWorker<Void, Void> w = new SwingWorker<Void, Void>() {
                private DefaultListModel<String> classes;

                @Override
                protected Void doInBackground() throws Exception {
                    ClasspathAccess ac = editor.classpathAccess;
                    ArrayList<String> c = new ArrayList<>();
                    for (Enumeration<String> e = ac.listClasses(true); e.hasMoreElements();) {
                        String className = e.nextElement();
                        FileObject fo = ac.getClassFile(className, ClasspathAccess.Language.JAVA, false);
                        if (fo != null) {
                            JavaSource source = JavaSource.forFileObject(fo);
                            if (source != null && implementsLogicScript(source)) {
                                c.add(className);
                            }
                        }
                    }
                    Collections.sort(c);
                    classes = new DefaultListModel<>();
                    for (String s : c) {
                        classes.addElement(s);
                    }
                    return null;
                }

                @Override
                protected void done() {
                    super.done();
                    classList.setModel(classes);
                    if (!classes.isEmpty()) {
                        okButton.setEnabled(true);
                    }
                }

            };
            w.execute();
        }

        /**
         * @return the return status of this dialog - one of RET_OK or
         * RET_CANCEL
         */
        public int getReturnStatus() {
            return returnStatus;
        }

        @SuppressWarnings("unchecked")
        private void initComponents() {

            okButton = new javax.swing.JButton();
            cancelButton = new javax.swing.JButton();
            classList = new JList<>(new String[] { org.openide.util.NbBundle.getMessage(AdvancedLogicSupport.class,
                    "AdvancedLogicSupportEditorDialog.wait") });
            classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            JScrollPane scrollPane = new JScrollPane(classList);
            scrollPane.setMinimumSize(new Dimension(300, 400));
            okButton.setEnabled(false);
            createNewButton = new JButton(org.openide.util.NbBundle.getMessage(AdvancedLogicSupport.class,
                    "AdvancedLogicSupportEditorDialog.createNew"));

            setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
            setTitle(org.openide.util.NbBundle.getMessage(AdvancedLogicSupport.class,
                    "AdvancedLogicSupportEditorDialog.title")); // NOI18N
            setResizable(true);
            addWindowListener(new java.awt.event.WindowAdapter() {
                public void windowClosing(java.awt.event.WindowEvent evt) {
                    closeDialog(evt);
                }
            });

            org.openide.awt.Mnemonics.setLocalizedText(okButton, org.openide.util.NbBundle
                    .getMessage(AdvancedLogicSupport.class, "AdvancedLogicSupportEditorDialog.okButton.text")); // NOI18N
            okButton.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    okButtonActionPerformed(evt);
                }
            });

            org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle
                    .getMessage(AdvancedLogicSupport.class, "AdvancedLogicSupportEditorDialog.cancelButton.text")); // NOI18N
            cancelButton.addActionListener(new java.awt.event.ActionListener() {
                public void actionPerformed(java.awt.event.ActionEvent evt) {
                    cancelButtonActionPerformed(evt);
                }
            });

            classList.addListSelectionListener(new ListSelectionListener() {
                @Override
                public void valueChanged(ListSelectionEvent e) {
                    listSelectionChangeEvent(e);
                }
            });

            createNewButton.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    createNewScriptEvent();
                }
            });

            javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
            getContentPane().setLayout(layout);
            layout.setHorizontalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup().addContainerGap().addGroup(layout
                            .createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING,
                                    layout.createSequentialGroup().addComponent(createNewButton)
                                            .addGap(0, 0, Short.MAX_VALUE)
                                            .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67,
                                                    javax.swing.GroupLayout.PREFERRED_SIZE)
                                            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                                            .addComponent(cancelButton))
                            .addGroup(layout.createSequentialGroup().addComponent(scrollPane))).addContainerGap()));

            layout.linkSize(javax.swing.SwingConstants.HORIZONTAL,
                    new java.awt.Component[] { cancelButton, okButton });

            layout.setVerticalGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING).addGroup(
                    javax.swing.GroupLayout.Alignment.TRAILING,
                    layout.createSequentialGroup().addContainerGap()
                            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                    .addComponent(scrollPane))
                            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                                    .addComponent(createNewButton).addComponent(cancelButton)
                                    .addComponent(okButton))
                            .addContainerGap()));

            getRootPane().setDefaultButton(okButton);

            pack();
        }

        private void listSelectionChangeEvent(ListSelectionEvent e) {
            okButton.setEnabled(true);
        }

        private void createNewScriptEvent() {
            String s = JOptionPane.showInputDialog(this, NbBundle.getMessage(AdvancedLogicSupport.class,
                    "AdvancedLogicSupportEditorDialog.scriptNameDialog"));
            if (s == null || s.isEmpty()) {
                return;
            }
            //create new script
            createScriptFile(editor.project, s);
            editor.setValue(s);
            doClose(RET_OK);
        }

        private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {
            String className = classList.getSelectedValue();
            editor.setValue(className);
            doClose(RET_OK);
        }

        private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {
            doClose(RET_CANCEL);
        }

        /**
         * Closes the dialog
         */
        private void closeDialog(java.awt.event.WindowEvent evt) {
            doClose(RET_CANCEL);
        }

        private void doClose(int retStatus) {
            returnStatus = retStatus;
            setVisible(false);
            dispose();
        }

        private javax.swing.JButton cancelButton;
        private javax.swing.JButton okButton;
        private JList<String> classList; //TODO: may change this to a tree
        private JButton createNewButton;
        private int returnStatus = RET_CANCEL;
    }

    public class ActionProvider implements ObjectActionProvider {
        private final EditorLevelElement<?, ?, ?> element;
        private final AdvancedLogicController controller;
        private final ClasspathAccess classpathAccess;

        private String oldClassName;
        private String[] oldModelNames;
        private Class<? extends BaseModel<?>>[] oldModelClasses;

        public ActionProvider(EditorLevelElement<?, ?, ?> element, AdvancedLogicController controller) {
            this.element = element;
            this.controller = controller;
            classpathAccess = element.getLevel().getProject().getLookup().lookup(ClasspathAccess.class);

            //read old properties
            oldClassName = controller.getScript();
            BaseModel[] models = controller.getModels().toArray(new BaseModel[0]);
            oldModelClasses = new Class[models.length];
            oldModelNames = new String[models.length];
            for (int i = 0; i < models.length; ++i) {
                if (models[i] == null) {
                    oldModelClasses[i] = null;
                    oldModelNames[i] = null;
                } else {
                    oldModelClasses[i] = (Class<? extends BaseModel<?>>) models[i].getClass();
                    oldModelNames[i] = controller.getSlotNames()[i];
                }
            }

            if (classpathAccess.getClassFile(oldClassName, ClasspathAccess.Language.JAVA, false) == null) {
                oldClassName = null; //class does not exists
            }
        }

        @Override
        public Action[] getContextActions() {
            return new Action[0]; //no actions
        }

        @Override
        public Action getPreferedAction() {
            return null;
        }

        //TODO: lokalize error dialogs
        @Override
        public void onApprove() {
            //read new properties
            String newClassName = controller.getScript();
            BaseModel[] models = controller.getModels().toArray(new BaseModel[0]);
            Class[] newModelClasses = new Class[models.length];
            String[] newModelNames = new String[models.length];
            for (int i = 0; i < models.length; ++i) {
                if (models[i] == null) {
                    newModelClasses[i] = null;
                    newModelNames[i] = null;
                } else {
                    newModelClasses[i] = (Class<? extends BaseModel<?>>) models[i].getClass();
                    newModelNames[i] = controller.getSlotNames()[i];
                }
            }

            FileObject scriptClass = classpathAccess.getClassFile(newClassName, ClasspathAccess.Language.JAVA,
                    false);
            if (scriptClass == null) {
                LOG.info("script does not exists, nothing to change");
                return; //script does not exists
            }

            //check if the script file must be modified
            if (Objects.equals(newClassName, oldClassName) && Arrays.equals(newModelClasses, oldModelClasses)
                    && Arrays.equals(newModelNames, oldModelNames)) {
                LOG.info("no changes in the controller, no need to change the controller");
                return; //no changes
            }

            //there are changes, change the script

            //read original file and find the code blocks to change
            String[] lines;
            try {
                lines = scriptClass.asLines().toArray(new String[0]);
            } catch (IOException ex) {
                LOG.log(Level.SEVERE, "unable to read script file", ex);
                JOptionPane.showMessageDialog(null,
                        "Unable to open script file,\n script file will not be modified.\n"
                                + ex.getLocalizedMessage(),
                        "Unable to open script file", JOptionPane.ERROR_MESSAGE);
                return;
            }
            String[] trimmedLines = new String[lines.length];
            for (int i = 0; i < lines.length; ++i) {
                trimmedLines[i] = lines[i].trim();
            }
            int startVars = -1;
            int endVars = -1;
            int startInit = -1;
            int endInit = -1;
            for (int i = 0; i < lines.length; ++i) {
                if (trimmedLines[i].startsWith("//GEN_START_VARS")) {
                    startVars = i;
                    for (int j = i + 1; j < lines.length; ++j) {
                        if (trimmedLines[j].startsWith("//GEN_END_VARS")) {
                            endVars = j;
                            break;
                        }
                    }
                    break;
                }
            }
            if (startVars == -1) {
                LOG.info("no GEN_START_VARS found");
                JOptionPane.showMessageDialog(null, "No GEN_START_VARS found, code generator cannot be executed",
                        "Invalid script file", JOptionPane.ERROR_MESSAGE);
                return;
            }
            if (endVars == -1) {
                LOG.info("no GEN_END_VARS found");
                JOptionPane.showMessageDialog(null, "No GEN_END_VARS found, code generator cannot be executed",
                        "Invalid script file", JOptionPane.ERROR_MESSAGE);
                return;
            }
            for (int i = 0; i < lines.length; ++i) {
                if (trimmedLines[i].startsWith("//GEN_START_INIT")) {
                    startInit = i;
                    for (int j = i + 1; j < lines.length; ++j) {
                        if (trimmedLines[j].startsWith("//GEN_END_INIT")) {
                            endInit = j;
                            break;
                        }
                    }
                    break;
                }
            }
            if (startInit == -1) {
                LOG.info("no GEN_START_INIT found");
                JOptionPane.showMessageDialog(null, "No GEN_START_INIT found, code generator cannot be executed",
                        "Invalid script file", JOptionPane.ERROR_MESSAGE);
                return;
            }
            if (endInit == -1) {
                LOG.info("no GEN_END_INIT found");
                JOptionPane.showMessageDialog(null, "No GEN_END_INIT found, code generator cannot be executed",
                        "Invalid script file", JOptionPane.ERROR_MESSAGE);
                return;
            }

            //generate code to insert
            String[] variableDeclarations = new String[newModelClasses.length];
            String[] variableAssignments = new String[newModelClasses.length];
            for (int i = 0; i < newModelClasses.length; ++i) {
                variableDeclarations[i] = getVariableDeclaration(models[i], newModelNames[i]);
                variableAssignments[i] = getVariableAssignement(models[i], newModelNames[i]);
            }

            //write file
            try (PrintWriter writer = new PrintWriter(scriptClass.getOutputStream())) {
                boolean declarationFirst = startVars < startInit;
                //write start of the file
                for (int i = 0; i < Math.min(startVars, startInit); ++i) {
                    writer.println(lines[i]);
                }
                if (declarationFirst) {
                    //write variable declaration
                    String prefix = lines[startVars].substring(0, lines[startVars].indexOf('/'));
                    writer.println(lines[startVars]);
                    for (int i = 0; i < variableDeclarations.length; ++i) {
                        writer.print(prefix);
                        writer.println(variableDeclarations[i]);
                    }
                    writer.println(lines[endVars]);
                    //write until assignement
                    for (int i = endVars; i < startInit; ++i) {
                        writer.println(lines[i]);
                    }
                    //write assignement
                    prefix = lines[startInit].substring(0, lines[startInit].indexOf('/'));
                    writer.println(lines[startInit]);
                    for (int i = 0; i < variableAssignments.length; ++i) {
                        writer.print(prefix);
                        writer.println(variableAssignments[i]);
                    }
                    writer.println(lines[endInit]);
                } else {
                    //write assignement
                    String prefix = lines[startInit].substring(0, lines[startInit].indexOf('/'));
                    writer.println(lines[startInit]);
                    for (int i = 0; i < variableAssignments.length; ++i) {
                        writer.print(prefix);
                        writer.println(variableAssignments[i]);
                    }
                    writer.println(lines[endInit]);
                    //write until assignement
                    for (int i = endVars; i < startInit; ++i) {
                        writer.println(lines[i]);
                    }
                    //write variable declaration
                    prefix = lines[startVars].substring(0, lines[startVars].indexOf('/'));
                    writer.println(lines[startVars]);
                    for (int i = 0; i < variableDeclarations.length; ++i) {
                        writer.print(prefix);
                        writer.println(variableDeclarations[i]);
                    }
                    writer.println(lines[endVars]);
                }
                //write until end of file
                for (int i = Math.max(endInit, endVars) + 1; i < lines.length; ++i) {
                    writer.println(lines[i]);
                }
            } catch (IOException ex) {
                LOG.log(Level.SEVERE, "unable to write script file", ex);
                JOptionPane.showMessageDialog(null, ex.getLocalizedMessage(), "Unable to write script file",
                        JOptionPane.ERROR_MESSAGE);
            }
            LOG.info("script file changed");
        }

        @Override
        public void onCancel() {
            //do nothing
        }

    }

    private static String toJavaIdentifier(String name) {
        //TODO: check if the name is a valid java variable name
        //If not, correct it
        return name;
    }

    private static String getVariableType(BaseModel model) {
        StringBuilder s = new StringBuilder();
        Class<? extends BaseModel> mc = model.getClass();
        s.append(mc.getCanonicalName());

        if (mc.equals(ViewDelegateModel.class)) {
            //special case: view delegate model, use class of the view
            BaseView v = ((ViewDelegateModel) model).getView();
            if (v != null) {
                Class<? extends BaseView> vc = v.getClass();
                s.append("<? extends ");
                s.append(vc.getCanonicalName());
                s.append(">");
                return s.toString();
            }
        }

        TypeVariable[] typeVars = mc.getTypeParameters();
        if (typeVars.length > 0) {
            s.append("<");
            for (int i = 0; i < typeVars.length; ++i) {
                if (i > 0)
                    s.append(", ");
                resolveType(s, typeVars[i]);
            }
            s.append(">");
        }
        return s.toString();
    }

    public static String getVariableDeclaration(BaseModel model, String variable) {
        StringBuilder s = new StringBuilder();
        s.append("private ");
        s.append(getVariableType(model));
        s.append(" ");
        s.append(toJavaIdentifier(variable));
        s.append(";");
        return s.toString();
    }

    public static String getVariableAssignement(BaseModel model, String variable) {
        StringBuilder s = new StringBuilder();
        s.append(toJavaIdentifier(variable));
        s.append(" = (");
        s.append(getVariableType(model));
        s.append(") models.get(\"");
        s.append(variable);
        s.append("\");");
        return s.toString();
    }

    private static void resolveType(StringBuilder s, Type t) {
        if (t instanceof TypeVariable) {
            Type[] bounds = ((TypeVariable) t).getBounds();
            s.append("? extends ");
            for (int j = 0; j < bounds.length; ++j) {
                if (j > 0)
                    s.append(", ");
                resolveType(s, bounds[j]);
            }
        } else if (t instanceof Class) {
            s.append(((Class) t).getCanonicalName());
        } else if (t instanceof ParameterizedType) {
            resolveType(s, ((ParameterizedType) t).getRawType());
            Type[] args = ((ParameterizedType) t).getActualTypeArguments();
            if (args.length > 0) {
                s.append("<");
                for (int j = 0; j < args.length; ++j) {
                    if (j > 0)
                        s.append(", ");
                    resolveType(s, args[j]);
                }
                s.append(">");
            }
        } else if (t instanceof WildcardType) {
            Type[] bounds = ((WildcardType) t).getUpperBounds();
            if (bounds.length > 0) {
                if (bounds.length == 1 && bounds[0].equals(Object.class)) {
                    return; //no upper bound, this should not happen
                }
                s.append("? extends ");
                for (int j = 0; j < bounds.length; ++j) {
                    if (j > 0)
                        s.append(", ");
                    resolveType(s, bounds[j]);
                }
            }
        } else {
            LOG.info("unknown type: " + t + " of class " + t.getClass());
        }
    }
}