public class NavigationListener implements FileEditorManagerListener {
    /**
     * Handler classes for the result of navigation actions.
     */
    private final Map<NavigationTriggeredDecompile, NavigationOption> navigationOptions = new HashMap<NavigationTriggeredDecompile, NavigationOption>() {
        {
            put(NavigationTriggeredDecompile.ALWAYS,
                    new NavigationOption() {
                        public void execute(@NotNull Config config,
                                            @NotNull DecompilationDescriptor descriptor) {
                            boolean excluded = new Exclusion(config).isExcluded(descriptor);
                            if (!excluded) {
                                decompilationListener.decompile(new EnvironmentContext(project),
                                        descriptor);
                            }
                        }
                    });
            put(NavigationTriggeredDecompile.ON_DEMAND,
                    new NavigationOption() {
                        public void execute(@NotNull Config config,
                                            @NotNull DecompilationDescriptor descriptor) {
                            // no-op
                        }
                    });
            put(NavigationTriggeredDecompile.NEVER,
                    new NavigationOption() {
                        public void execute(@NotNull Config config,
                                            @NotNull DecompilationDescriptor descriptor) {
                            // no-op
                        }
                    });
        }
    };

    /**
     * The decompilation listener.
     */
    @NotNull
    private final DecompilationChoiceListener decompilationListener;

    /**
     * The project this listener is interested in.
     */
    @NotNull
    private final Project project;

    /**
     * Initialises a new instance of this class.
     *
     * @param project               the project this object is listening for
     * @param decompilationListener the decompilation listener
     */
    public NavigationListener(@NotNull Project project,
                              @NotNull DecompilationChoiceListener decompilationListener) {
        this.project = project;
        this.decompilationListener = decompilationListener;
    }

    private NavigationTriggeredDecompile getDecompileMode() {
        Config config = PluginUtil.getConfig(project);
        return NavigationTriggeredDecompile.getByName(config.getDecompileOnNavigation());
    }

    /**
     * {@inheritDoc}
     */
    public void fileOpened(FileEditorManager fileEditorManager,
                           VirtualFile file) {
        if (file != null && "class".equals(file.getExtension())) {
            Config config = PluginUtil.getConfig(project);
            DecompilationDescriptor dd = DecompilationDescriptorFactory.getFactoryForFile(file).create(file);
            NavigationOption navigationOption = navigationOptions.get(getDecompileMode());
            navigationOption.execute(config, dd);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void fileClosed(FileEditorManager fileEditorManager,
                           VirtualFile virtualFile) {        
    }

    /**
     * {@inheritDoc}
     */
    public void selectionChanged(FileEditorManagerEvent fileEditorManagerEvent) {
        // no-op
    }

    /**
     * Handles the result of a navigation-based action decision.
     */
    private interface NavigationOption {
        /**
         * Handle the choice.
         *
         * @param config     the configutation
         * @param descriptor the descriptor of the class to decompile
         */
        void execute(@NotNull Config config,
                     @NotNull DecompilationDescriptor descriptor);
    }
} public class Config implements DOMable {
    private static final PropertyDescriptor<String> DECOMPILE_ON_NAVIGATION = new ImmutablePropertyDescriptor<String>("decompile-on-navigation",
            NavigationTriggeredDecompile.ALWAYS.getName());
    private static final PropertyDescriptor<Boolean> CREATE_OUTPUT_DIRECTORY = new ImmutablePropertyDescriptor<Boolean>("create-output-directory");
    private static final PropertyDescriptor<Boolean> ALWAYS_EXCLUDE_RECURSIVELY = new ImmutablePropertyDescriptor<Boolean>("always-exclude-recursively");
    private static final PropertyDescriptor<Boolean> CLEAR_AND_CLOSE_CONSOLE_ON_SUCCESS = new ImmutablePropertyDescriptor<Boolean>("clear-and-close-console-on-success");        
    private static final PropertyDescriptor<ExclusionTableModel> EXCLUSION_TABLE_MODEL = new ImmutablePropertyDescriptor<ExclusionTableModel>("exclusion-table-model");
    private static final PropertyDescriptor<String> JAD_PATH = new ImmutablePropertyDescriptor<String>("jad-path");
    private static final PropertyDescriptor<Integer> LIMIT_INDENTATION = new ImmutablePropertyDescriptor<Integer>("indentation", 4);
    private static final PropertyDescriptor<Boolean> READ_ONLY = new ImmutablePropertyDescriptor<Boolean>("read-only");
    private static final PropertyDescriptor<Boolean> SORT = new ImmutablePropertyDescriptor<Boolean>("sort");
    private static final PropertyDescriptor<Boolean> USE_PROJECT_SPECIFIC_SETTINGS = new ImmutablePropertyDescriptor<Boolean>("use-project-specific-settings");
    private static final PropertyDescriptor<String> REFORMAT_STYLE = new ImmutablePropertyDescriptor<String>("reformat-style",
            CodeStyle.PREFERRED_STYLE.getName());
    private static final PropertyDescriptor<Boolean> CLEANUP_SOURCE_ROOTS = new ImmutablePropertyDescriptor<Boolean>("cleanup-source-roots",
            Boolean.TRUE);
    private static final PropertyDescriptor<Boolean> FORCED_DECOMPILE_TO_MEMORY = new ImmutablePropertyDescriptor<Boolean>("forced-decompile-to-memory", Boolean.FALSE);
    private static final PropertyDescriptor<Boolean> CUSTOM_TEMP_DIR = new ImmutablePropertyDescriptor<Boolean>("use-custom-temp-dir", Boolean.FALSE);
    private static final PropertyDescriptor<String> CUSTOM_TEMP_DIR_PATH = new ImmutablePropertyDescriptor<String>("custom-temp-dir-path");
    //keep deprecated properties to support users coming from older plugin version
    @Deprecated
    private static final PropertyDescriptor<Boolean> DECOMPILE_TO_MEMORY = new ImmutablePropertyDescriptor<Boolean>("decompile-to-memory", Boolean.FALSE);
    @Deprecated
    private static final PropertyDescriptor<Boolean> KEEP_DECOMPILED_TO_MEMORY = new ImmutablePropertyDescriptor<Boolean>("keep-decompiled-to-memory", Boolean.FALSE);
    
    /**
     * The persistence model.
     */
    private final DOMable domable;

    /**
     * The properties.
     */
    private final PropertyContainer<PropertyDescriptor, DOMable> propertyContainer;

    private final List<CommandLinePropertyDescriptor> commandLinePropertyDescriptors = new ArrayList<CommandLinePropertyDescriptor>();

    private final RuleContext ruleContext;

    public Config(RuleContext ruleContext) {
        this.ruleContext = ruleContext;
        ruleContext.setConfig(this);

        DOMablePropertyContainer dpc = new DOMablePropertyContainer(new ImmutablePropertyDescriptor("config"));
        registerBooleanProperty(JadOptions.ANNOTATE, dpc);
        registerBooleanProperty(JadOptions.ANNOTATE_FULLY, dpc);
        registerBooleanProperty(JadOptions.CLEAR_PREFIXES, dpc);
        registerBooleanProperty(CREATE_OUTPUT_DIRECTORY, dpc);
        registerStringProperty(DECOMPILE_ON_NAVIGATION, dpc);
        registerBooleanProperty(CLEAR_AND_CLOSE_CONSOLE_ON_SUCCESS, dpc);
        registerBooleanProperty(ALWAYS_EXCLUDE_RECURSIVELY, dpc);
        registerBooleanProperty(JadOptions.DEAD, dpc);
        registerBooleanProperty(DECOMPILE_TO_MEMORY, dpc);
        registerBooleanProperty(KEEP_DECOMPILED_TO_MEMORY, dpc);
        registerBooleanProperty(JadOptions.DEFAULT_INITIALIZERS, dpc);
        registerBooleanProperty(JadOptions.DISASSEMBLER_ONLY, dpc);
        registerStringProperty(JadOptions.FILE_EXTENSION, dpc);
        registerBooleanProperty(JadOptions.FIELDS_FIRST, dpc);
        registerBooleanProperty(JadOptions.FULLY_QUALIFIED_NAMES, dpc);
        registerIntegerProperty(LIMIT_INDENTATION, dpc);
        registerIntegerProperty(JadOptions.LIMIT_INT_RADIX, dpc);
        registerIntegerProperty(JadOptions.LIMIT_LONG_RADIX, dpc);
        registerIntegerProperty(JadOptions.LIMIT_MAX_STRING_LENGTH, dpc);
        registerIntegerProperty(JadOptions.LIMIT_PACK_FIELDS, dpc);
        registerBooleanProperty(JadOptions.LINE_NUMBERS_AS_COMMENTS, dpc);
        registerBooleanProperty(JadOptions.NOCONV, dpc);
        registerBooleanProperty(JadOptions.NOCAST, dpc);
        registerBooleanProperty(JadOptions.NOCLASS, dpc);
        registerBooleanProperty(JadOptions.NOCODE, dpc);
        registerBooleanProperty(JadOptions.NODOS, dpc);
        registerBooleanProperty(JadOptions.NOCTOR, dpc);
        registerBooleanProperty(JadOptions.NOFD, dpc);
        registerBooleanProperty(JadOptions.NOINNER, dpc);
        registerBooleanProperty(JadOptions.NOLVT, dpc);
        registerBooleanProperty(JadOptions.NONLB, dpc);
        registerStringProperty(JadOptions.OUTPUT_DIRECTORY, dpc);
        registerBooleanProperty(JadOptions.OVERWRITE, dpc);
        registerStringProperty(JadOptions.PREFIX_NUMERICAL_CLASSES, dpc);
        registerStringProperty(JadOptions.PREFIX_NUMERICAL_FIELDS, dpc);
        registerStringProperty(JadOptions.PREFIX_NUMERICAL_LOCALS, dpc);
        registerStringProperty(JadOptions.PREFIX_NUMERICAL_METHODS, dpc);
        registerStringProperty(JadOptions.PREFIX_NUMERICAL_PARAMETERS, dpc);
        registerStringProperty(JadOptions.PREFIX_PACKAGES, dpc);
        registerStringProperty(JadOptions.PREFIX_UNUSED_EXCEPTIONS, dpc);
        registerBooleanProperty(READ_ONLY, dpc);
        registerBooleanProperty(JadOptions.REDUNDANT_BRACES, dpc);
        registerBooleanProperty(JadOptions.RESTORE_PACKAGES, dpc);
        registerBooleanProperty(JadOptions.SAFE, dpc);
        registerBooleanProperty(SORT, dpc);
        registerBooleanProperty(JadOptions.SPACE_AFTER_KEYWORD, dpc);
        registerBooleanProperty(JadOptions.SPLIT_STRINGS_AT_NEWLINE, dpc);
        registerBooleanProperty(JadOptions.STATISTICS, dpc);
        registerBooleanProperty(JadOptions.USE_TABS, dpc);
        registerBooleanProperty(JadOptions.VERBOSE, dpc);
        registerStringProperty(JAD_PATH, dpc);
        registerBooleanProperty(USE_PROJECT_SPECIFIC_SETTINGS, dpc);
        registerStringProperty(REFORMAT_STYLE, dpc);
        registerBooleanProperty(CLEANUP_SOURCE_ROOTS, dpc);
        registerBooleanProperty(FORCED_DECOMPILE_TO_MEMORY, dpc);
        registerBooleanProperty(CUSTOM_TEMP_DIR, dpc);
        registerStringProperty(CUSTOM_TEMP_DIR_PATH, dpc);

        dpc.put(EXCLUSION_TABLE_MODEL,
                new DOMableTableModel(EXCLUSION_TABLE_MODEL,
                        new ExclusionTableModel()));

        this.domable = dpc;
        this.propertyContainer = dpc;
    }

    /**
     * Register the property with the container.
     *
     * @param pd  the property's descriptor
     * @param dpc the property container
     */
    private void registerBooleanProperty(PropertyDescriptor<Boolean> pd,
                                         DOMablePropertyContainer dpc) {
        dpc.put(pd,
                new DOMableGeneric<Boolean>(pd,
                        ConverterFactory.getBooleanConverter(),
                        DOMableCollectionContentType.BOOLEAN));
    }

    /**
     * Register the property with the container, the rule context and
     * the command line property manager.
     *
     * @param clpd the command line property's descriptor
     * @param dpc  the property container
     */
    private void registerBooleanProperty(CommandLinePropertyDescriptor<Boolean> clpd,
                                         DOMablePropertyContainer dpc) {
        dpc.put(clpd,
                new DOMableGeneric<Boolean>(clpd,
                        ConverterFactory.getBooleanConverter(),
                        DOMableCollectionContentType.BOOLEAN));
        ruleContext.addProperty(clpd);
        commandLinePropertyDescriptors.add(clpd);
    }

    /**
     * Register the property with the container.
     *
     * @param pd  the property's descriptor
     * @param dpc the property container
     */
    private void registerIntegerProperty(PropertyDescriptor<Integer> pd,
                                         DOMablePropertyContainer dpc) {
        dpc.put(pd,
                new DOMableGeneric<Integer>(pd,
                        ConverterFactory.getIntegerConverter(),
                        DOMableCollectionContentType.INTEGER));
    }

    /**
     * Register the property with the container, the rule context and
     * the command line property manager.
     *
     * @param clpd the command line property's descriptor
     * @param dpc  the property container
     */
    private void registerIntegerProperty(CommandLinePropertyDescriptor<Integer> clpd,
                                         DOMablePropertyContainer dpc) {
        dpc.put(clpd,
                new DOMableGeneric<Integer>(clpd,
                        ConverterFactory.getIntegerConverter(),
                        DOMableCollectionContentType.INTEGER));
        ruleContext.addProperty(clpd);
        commandLinePropertyDescriptors.add(clpd);
    }

    /**
     * Register the property with the container.
     *
     * @param pd  the property's descriptor
     * @param dpc the property container
     */
    private void registerStringProperty(PropertyDescriptor<String> pd,
                                        DOMablePropertyContainer dpc) {
        dpc.put(pd,
                new DOMableGeneric<String>(pd,
                        ConverterFactory.getStringConverter(),
                        DOMableCollectionContentType.STRING));
    }

    /**
     * Register the property with the container, the rule context and
     * the command line property manager.
     *
     * @param clpd the command line property's descriptor
     * @param dpc  the property container
     */
    private void registerStringProperty(CommandLinePropertyDescriptor<String> clpd,
                                        DOMablePropertyContainer dpc) {
        dpc.put(clpd,
                new DOMableGeneric<String>(clpd,
                        ConverterFactory.getStringConverter(),
                        DOMableCollectionContentType.STRING));
        ruleContext.addProperty(clpd);
        commandLinePropertyDescriptors.add(clpd);
    }

    public boolean isNocast() {
        return JadOptions.NOCAST.getValue(propertyContainer.get(JadOptions.NOCAST));
    }

    public void setNocast(boolean nocast) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NOCAST);
        value.setValue(nocast);
    }

    public boolean isNoclass() {
        return JadOptions.NOCLASS.getValue(propertyContainer.get(JadOptions.NOCLASS));
    }

    public void setNoclass(boolean noclass) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NOCLASS);
        value.setValue(noclass);
    }

    public String getDecompileOnNavigation() {
        String value = DECOMPILE_ON_NAVIGATION.getValue(propertyContainer.get(DECOMPILE_ON_NAVIGATION));
        if ("Ask".equals(value)) {
            return NavigationTriggeredDecompile.ON_DEMAND.getName();
        }
        return value;
    }

    public void setDecompileOnNavigation(String confirmNavigationTriggeredDecompile) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(DECOMPILE_ON_NAVIGATION);
        value.setValue(confirmNavigationTriggeredDecompile);
    }

    public boolean isReadOnly() {
        return READ_ONLY.getValue(propertyContainer.get(READ_ONLY));
    }

    public void setReadOnly(boolean readOnly) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(READ_ONLY);
        value.setValue(readOnly);
    }

    public boolean isAnnotate() {
        return JadOptions.ANNOTATE.getValue(propertyContainer.get(JadOptions.ANNOTATE));
    }

    public void setAnnotate(boolean annotate) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.ANNOTATE);
        value.setValue(annotate);
    }

    public boolean isAnnotateFully() {
        return JadOptions.ANNOTATE_FULLY.getValue(propertyContainer.get(JadOptions.ANNOTATE_FULLY));
    }

    public void setAnnotateFully(boolean annotateFully) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.ANNOTATE_FULLY);
        value.setValue(annotateFully);
    }

    public boolean isRedundantBraces() {
        return JadOptions.REDUNDANT_BRACES.getValue(propertyContainer.get(JadOptions.REDUNDANT_BRACES));
    }

    public void setRedundantBraces(boolean redundantBraces) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.REDUNDANT_BRACES);
        value.setValue(redundantBraces);
    }

    public boolean isClearPrefixes() {
        return JadOptions.CLEAR_PREFIXES.getValue(propertyContainer.get(JadOptions.CLEAR_PREFIXES));
    }

    public void setClearPrefixes(boolean clearPrefixes) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.CLEAR_PREFIXES);
        value.setValue(clearPrefixes);
    }

    public String getOutputDirectory() {
        return JadOptions.OUTPUT_DIRECTORY.getValue(propertyContainer.get(JadOptions.OUTPUT_DIRECTORY));
    }

    public void setOutputDirectory(String outputDirectory) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(JadOptions.OUTPUT_DIRECTORY);
        value.setValue(outputDirectory);
    }

    public boolean isDead() {
        return JadOptions.DEAD.getValue(propertyContainer.get(JadOptions.DEAD));
    }

    public void setDead(boolean dead) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.DEAD);
        value.setValue(dead);
    }

    public boolean isDissassemblerOnly() {
        return JadOptions.DISASSEMBLER_ONLY.getValue(propertyContainer.get(JadOptions.DISASSEMBLER_ONLY));
    }

    public void setDissassemblerOnly(boolean dissassemblerOnly) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.DISASSEMBLER_ONLY);
        value.setValue(dissassemblerOnly);
    }

    public boolean isFullyQualifiedNames() {
        return JadOptions.FULLY_QUALIFIED_NAMES.getValue(propertyContainer.get(JadOptions.FULLY_QUALIFIED_NAMES));
    }

    public void setFullyQualifiedNames(boolean fullyQualifiedNames) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.FULLY_QUALIFIED_NAMES);
        value.setValue(fullyQualifiedNames);
    }

    public boolean isFieldsFirst() {
        return JadOptions.FIELDS_FIRST.getValue(propertyContainer.get(JadOptions.FIELDS_FIRST));
    }

    public void setFieldsFirst(boolean fieldsFirst) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.FIELDS_FIRST);
        value.setValue(fieldsFirst);
    }

    public boolean isDefaultInitializers() {
        return JadOptions.DEFAULT_INITIALIZERS.getValue(propertyContainer.get(JadOptions.DEFAULT_INITIALIZERS));
    }

    public void setDefaultInitializers(boolean defaultInitializers) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.DEFAULT_INITIALIZERS);
        value.setValue(defaultInitializers);
    }

    public Integer getMaxStringLength() {
        return JadOptions.LIMIT_MAX_STRING_LENGTH.getValue(propertyContainer.get(JadOptions.LIMIT_MAX_STRING_LENGTH));
    }

    public void setMaxStringLength(Integer maxStringLength) {
        DOMableGeneric<Integer> value = (DOMableGeneric<Integer>) propertyContainer.get(JadOptions.LIMIT_MAX_STRING_LENGTH);
        value.setValue(maxStringLength);
    }

    public boolean isLineNumbersAsComments() {
        return JadOptions.LINE_NUMBERS_AS_COMMENTS.getValue(propertyContainer.get(JadOptions.LINE_NUMBERS_AS_COMMENTS));
    }

    public void setLineNumbersAsComments(boolean lineNumbersAsComments) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.LINE_NUMBERS_AS_COMMENTS);
        value.setValue(lineNumbersAsComments);
    }

    public Integer getLongRadix() {
        return JadOptions.LIMIT_LONG_RADIX.getValue(propertyContainer.get(JadOptions.LIMIT_LONG_RADIX));
    }

    public void setLongRadix(Integer longRadix) {
        DOMableGeneric<Integer> value = (DOMableGeneric<Integer>) propertyContainer.get(JadOptions.LIMIT_LONG_RADIX);
        value.setValue(longRadix);
    }

    public boolean isSplitStringsAtNewline() {
        return JadOptions.SPLIT_STRINGS_AT_NEWLINE.getValue(propertyContainer.get(JadOptions.SPLIT_STRINGS_AT_NEWLINE));
    }

    public void setSplitStringsAtNewline(boolean splitStringsAtNewline) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.SPLIT_STRINGS_AT_NEWLINE);
        value.setValue(splitStringsAtNewline);
    }

    public boolean isNoconv() {
        return JadOptions.NOCONV.getValue(propertyContainer.get(JadOptions.NOCONV));
    }

    public void setNoconv(boolean noconv) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NOCONV);
        value.setValue(noconv);
    }

    public boolean isNocode() {
        return JadOptions.NOCODE.getValue(propertyContainer.get(JadOptions.NOCODE));
    }

    public void setNocode(boolean nocode) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NOCODE);
        value.setValue(nocode);
    }

    public boolean isNoctor() {
        return JadOptions.NOCTOR.getValue(propertyContainer.get(JadOptions.NOCTOR));
    }

    public void setNoctor(boolean noctor) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NOCTOR);
        value.setValue(noctor);
    }

    public boolean isNodos() {
        return JadOptions.NODOS.getValue(propertyContainer.get(JadOptions.NODOS));
    }

    public void setNodos(boolean nodos) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NODOS);
        value.setValue(nodos);
    }

    public boolean isNofd() {
        return JadOptions.NOFD.getValue(propertyContainer.get(JadOptions.NOFD));
    }

    public void setNofd(boolean nofd) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NOFD);
        value.setValue(nofd);
    }

    public boolean isNoinner() {
        return JadOptions.NOINNER.getValue(propertyContainer.get(JadOptions.NOINNER));
    }

    public void setNoinner(boolean noinner) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NOINNER);
        value.setValue(noinner);
    }

    public boolean isNolvt() {
        return JadOptions.NOLVT.getValue(propertyContainer.get(JadOptions.NOLVT));
    }

    public void setNolvt(boolean nolvt) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NOLVT);
        value.setValue(nolvt);
    }

    public boolean isNonlb() {
        return JadOptions.NONLB.getValue(propertyContainer.get(JadOptions.NONLB));
    }

    public void setNonlb(boolean nonlb) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.NONLB);
        value.setValue(nonlb);

    }

    public Integer getIntRadix() {
        return JadOptions.LIMIT_INT_RADIX.getValue(propertyContainer.get(JadOptions.LIMIT_INT_RADIX));
    }

    public void setIntRadix(Integer intRadix) {
        DOMableGeneric<Integer> value = (DOMableGeneric<Integer>) propertyContainer.get(JadOptions.LIMIT_INT_RADIX);
        value.setValue(intRadix);
    }

    public String getFileExtension() {
        return JadOptions.FILE_EXTENSION.getValue(propertyContainer.get(JadOptions.FILE_EXTENSION));
    }

    public boolean isSafe() {
        return JadOptions.SAFE.getValue(propertyContainer.get(JadOptions.SAFE));
    }

    public void setSafe(boolean safe) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.SAFE);
        value.setValue(safe);
    }

    public boolean isSpaceAfterKeyword() {
        return JadOptions.SPACE_AFTER_KEYWORD.getValue(propertyContainer.get(JadOptions.SPACE_AFTER_KEYWORD));
    }

    public void setSpaceAfterKeyword(boolean spaceAfterKeyword) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.SPACE_AFTER_KEYWORD);
        value.setValue(spaceAfterKeyword);
    }

    public Integer getIndentation() {
        return LIMIT_INDENTATION.getValue(propertyContainer.get(LIMIT_INDENTATION));
    }

    public void setIndentation(Integer indentation) {
        DOMableGeneric<Integer> value = (DOMableGeneric<Integer>) propertyContainer.get(LIMIT_INDENTATION);
        value.setValue(indentation);
    }

    public boolean isUseTabs() {
        return JadOptions.USE_TABS.getValue(propertyContainer.get(JadOptions.USE_TABS));
    }

    public void setUseTabs(boolean useTabs) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.USE_TABS);
        value.setValue(useTabs);
    }

    public String getPrefixPackages() {
        return JadOptions.PREFIX_PACKAGES.getValue(propertyContainer.get(JadOptions.PREFIX_PACKAGES));
    }

    public void setPrefixPackages(String prefixPackages) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(JadOptions.PREFIX_PACKAGES);
        value.setValue(prefixPackages);
    }

    public String getPrefixNumericalClasses() {
        return JadOptions.PREFIX_NUMERICAL_CLASSES.getValue(propertyContainer.get(JadOptions.PREFIX_NUMERICAL_CLASSES));
    }

    public void setPrefixNumericalClasses(String prefixNumericalClasses) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(JadOptions.PREFIX_NUMERICAL_CLASSES);
        value.setValue(prefixNumericalClasses);
    }

    public String getPrefixUnusedExceptions() {
        return JadOptions.PREFIX_UNUSED_EXCEPTIONS.getValue(propertyContainer.get(JadOptions.PREFIX_UNUSED_EXCEPTIONS));
    }

    public void setPrefixUnusedExceptions(String prefixUnusedExceptions) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(JadOptions.PREFIX_UNUSED_EXCEPTIONS);
        value.setValue(prefixUnusedExceptions);
    }

    public String getPrefixNumericalFields() {
        return JadOptions.PREFIX_NUMERICAL_FIELDS.getValue(propertyContainer.get(JadOptions.PREFIX_NUMERICAL_FIELDS));
    }

    public void setPrefixNumericalFields(String prefixNumericalFields) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(JadOptions.PREFIX_NUMERICAL_FIELDS);
        value.setValue(prefixNumericalFields);
    }

    public String getPrefixNumericalLocals() {
        return JadOptions.PREFIX_NUMERICAL_LOCALS.getValue(propertyContainer.get(JadOptions.PREFIX_NUMERICAL_LOCALS));
    }

    public void setPrefixNumericalLocals(String prefixNumericalLocals) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(JadOptions.PREFIX_NUMERICAL_LOCALS);
        value.setValue(prefixNumericalLocals);
    }

    public String getPrefixNumericalMethods() {
        return JadOptions.PREFIX_NUMERICAL_METHODS.getValue(propertyContainer.get(JadOptions.PREFIX_NUMERICAL_METHODS));
    }

    public void setPrefixNumericalMethods(String prefixNumericalMethods) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(JadOptions.PREFIX_NUMERICAL_METHODS);
        value.setValue(prefixNumericalMethods);
    }

    public String getPrefixNumericalParameters() {
        return JadOptions.PREFIX_NUMERICAL_PARAMETERS.getValue(propertyContainer.get(JadOptions.PREFIX_NUMERICAL_PARAMETERS));
    }

    public void setPrefixNumericalParameters(String prefixNumericalParameters) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(JadOptions.PREFIX_NUMERICAL_PARAMETERS);
        value.setValue(prefixNumericalParameters);
    }

    public Integer getPackFields() {
        return JadOptions.LIMIT_PACK_FIELDS.getValue(propertyContainer.get(JadOptions.LIMIT_PACK_FIELDS));
    }

    public void setPackFields(Integer packFields) {
        DOMableGeneric<Integer> value = (DOMableGeneric<Integer>) propertyContainer.get(JadOptions.LIMIT_PACK_FIELDS);
        value.setValue(packFields);

    }

    public boolean isSort() {
        return SORT.getValue(propertyContainer.get(SORT));
    }

    public void setSort(boolean sort) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(SORT);
        value.setValue(sort);
    }

    public boolean isVerbose() {
        return JadOptions.VERBOSE.getValue(propertyContainer.get(JadOptions.VERBOSE));
    }

    public void setVerbose(boolean verbose) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.VERBOSE);
        value.setValue(verbose);
    }

    public boolean isOverwrite() {
        return JadOptions.OVERWRITE.getValue(propertyContainer.get(JadOptions.OVERWRITE));
    }

    public void setOverwrite(boolean overwrite) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.OVERWRITE);
        value.setValue(overwrite);
    }

    public boolean isStatistics() {
        return JadOptions.STATISTICS.getValue(propertyContainer.get(JadOptions.STATISTICS));
    }

    public void setStatistics(boolean statistics) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.STATISTICS);
        value.setValue(statistics);
    }

    public boolean isRestorePackages() {
        return JadOptions.RESTORE_PACKAGES.getValue(propertyContainer.get(JadOptions.RESTORE_PACKAGES));
    }

    public void setRestorePackages(boolean restorePackages) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(JadOptions.RESTORE_PACKAGES);
        value.setValue(restorePackages);
    }

    public String getJadPath() {
        return JAD_PATH.getValue(propertyContainer.get(JAD_PATH));
    }

    public void setJadPath(String jadPath) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(JAD_PATH);
        value.setValue(jadPath);
    }

    @Deprecated
    public boolean isDecompileToMemory() {
        return DECOMPILE_TO_MEMORY.getValue(propertyContainer.get(DECOMPILE_TO_MEMORY));
    }
    
    @Deprecated
    public void setDecompileToMemory(boolean decompileToMemory) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(DECOMPILE_TO_MEMORY);
        value.setValue(decompileToMemory);
    }
    
    @Deprecated
    public boolean isKeepDecompiledToMemory() {
        return KEEP_DECOMPILED_TO_MEMORY.getValue(propertyContainer.get(KEEP_DECOMPILED_TO_MEMORY));
    }
    
    @Deprecated
    public void setKeepDecompiledToMemory(boolean keep) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(KEEP_DECOMPILED_TO_MEMORY);
        value.setValue(keep);
    }

    public boolean isCreateOutputDirectory() {
        return CREATE_OUTPUT_DIRECTORY.getValue(propertyContainer.get(CREATE_OUTPUT_DIRECTORY));
    }

    public void setCreateOutputDirectory(boolean create) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(CREATE_OUTPUT_DIRECTORY);
        value.setValue(create);
    }

    public boolean isAlwaysExcludeRecursively() {
        return ALWAYS_EXCLUDE_RECURSIVELY.getValue(propertyContainer.get(ALWAYS_EXCLUDE_RECURSIVELY));
    }

    public void setAlwaysExcludeRecursively(boolean alwaysExcludeRecursively) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(ALWAYS_EXCLUDE_RECURSIVELY);
        value.setValue(alwaysExcludeRecursively);
    }

    public boolean isClearAndCloseConsoleOnSuccess() {
        return CLEAR_AND_CLOSE_CONSOLE_ON_SUCCESS.getValue(propertyContainer.get(CLEAR_AND_CLOSE_CONSOLE_ON_SUCCESS));
    }

    public void setClearAndCloseConsoleOnSuccess(boolean clearAndClose) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(CLEAR_AND_CLOSE_CONSOLE_ON_SUCCESS);
        value.setValue(clearAndClose);
    }

    public boolean isUseProjectSpecificSettings() {
        return USE_PROJECT_SPECIFIC_SETTINGS.getValue(propertyContainer.get(USE_PROJECT_SPECIFIC_SETTINGS));
    }

    public void setUseProjectSpecificSettings(boolean useProjectSpecificSettings) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(USE_PROJECT_SPECIFIC_SETTINGS);
        value.setValue(useProjectSpecificSettings);
    }

    public String getReformatStyle() {
        return REFORMAT_STYLE.getValue(propertyContainer.get(REFORMAT_STYLE));
    }

    public void setReformatStyle(String reformatStyle) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(REFORMAT_STYLE);
        value.setValue(reformatStyle);
    }

    public boolean isCleanupSourceRoots() {
        return CLEANUP_SOURCE_ROOTS.getValue(propertyContainer.get(CLEANUP_SOURCE_ROOTS));
    }

    public void setCleanupSourceRoots(boolean cleanupSourceRoots) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(CLEANUP_SOURCE_ROOTS);
        value.setValue(cleanupSourceRoots);
    }

    public boolean isForcedDecompileToMemory() {
        return FORCED_DECOMPILE_TO_MEMORY.getValue(propertyContainer.get(FORCED_DECOMPILE_TO_MEMORY));
    }

    public void setForcedDecompileToMemory(boolean forced) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(FORCED_DECOMPILE_TO_MEMORY);
        value.setValue(forced);
    }

    public void setUseCustomTempDir(boolean useCustomTemp) {
        DOMableGeneric<Boolean> value = (DOMableGeneric<Boolean>) propertyContainer.get(CUSTOM_TEMP_DIR);
        value.setValue(useCustomTemp);
    }

    public boolean isUseCustomTempDir() {
        return CUSTOM_TEMP_DIR.getValue(propertyContainer.get(CUSTOM_TEMP_DIR));
    }

    public void setCustomTempDirPath(String customTempDirPath) {
        DOMableGeneric<String> value = (DOMableGeneric<String>) propertyContainer.get(CUSTOM_TEMP_DIR_PATH);
        value.setValue(customTempDirPath);
    }

    public String getCustomTempDirPath() {
        return CUSTOM_TEMP_DIR_PATH.getValue(propertyContainer.get(CUSTOM_TEMP_DIR_PATH));
    }

    @NotNull
    public PropertyDescriptor getPropertyDescriptor() {
        return domable.getPropertyDescriptor();
    }

    @NotNull
    public Element write() {
        return domable.write();
    }

    /**
     * {@inheritDoc}
     */
    public void read(@NotNull Element element) {
        domable.read(element);
    }

    /**
     * {@inheritDoc}
     */
    public Object getValue() {
        return null;
    }

    public List<CommandLinePropertyDescriptor> getCommandLinePropertyDescriptors() {
        return commandLinePropertyDescriptors;
    }

    public String renderCommandLinePropertyDescriptors() {
        StringBuilder sb = new StringBuilder();

        for (CommandLinePropertyDescriptor pd : commandLinePropertyDescriptors) {
            sb.append(pd.getOption(ruleContext,
                    propertyContainer.get(pd)));
        }

        return sb.toString();
    }

    public ExclusionTableModel getExclusionTableModel() {
        return EXCLUSION_TABLE_MODEL.getValue(propertyContainer.get(EXCLUSION_TABLE_MODEL));
    }

    public List<String> getArguments() {
        List<String> arguments = new ArrayList<String>();
        for (CommandLinePropertyDescriptor pd : commandLinePropertyDescriptors) {
            String option = pd.getOption(ruleContext,
                    propertyContainer.get(pd));
            if (option != null) {
                arguments.add(option);
            }
        }
        return arguments;
    }

    public void copyFrom(Config config) {
        setNocast(config.isNocast());
        setNoclass(config.isNoclass());
        setDecompileOnNavigation(config.getDecompileOnNavigation());
        setReadOnly(config.isReadOnly());
        setAnnotate(config.isAnnotate());
        setAnnotateFully(config.isAnnotateFully());
        setRedundantBraces(config.isRedundantBraces());
        setClearPrefixes(config.isClearPrefixes());
        setOutputDirectory(config.getOutputDirectory());
        setDead(config.isDead());
        setDissassemblerOnly(config.isDissassemblerOnly());
        setFullyQualifiedNames(config.isFullyQualifiedNames());
        setFieldsFirst(config.isFieldsFirst());
        setDefaultInitializers(config.isDefaultInitializers());
        setMaxStringLength(config.getMaxStringLength());
        setLineNumbersAsComments(config.isLineNumbersAsComments());
        setLongRadix(config.getLongRadix());
        setSplitStringsAtNewline(config.isSplitStringsAtNewline());
        setNoconv(config.isNoconv());
        setNocode(config.isNocode());
        setNoctor(config.isNoctor());
        setNodos(config.isNodos());
        setNofd(config.isNofd());
        setNoinner(config.isNoinner());
        setNolvt(config.isNolvt());
        setNonlb(config.isNonlb());
        setIntRadix(config.getIntRadix());
        setSafe(config.isSafe());
        setSpaceAfterKeyword(config.isSpaceAfterKeyword());
        setIndentation(config.getIndentation());
        setUseTabs(config.isUseTabs());
        setPrefixPackages(config.getPrefixPackages());
        setPrefixNumericalClasses(config.getPrefixNumericalClasses());
        setPrefixUnusedExceptions(config.getPrefixUnusedExceptions());
        setPrefixNumericalFields(config.getPrefixNumericalFields());
        setPrefixNumericalLocals(config.getPrefixNumericalLocals());
        setPrefixNumericalMethods(config.getPrefixNumericalMethods());
        setPrefixNumericalParameters(config.getPrefixNumericalParameters());
        setPackFields(config.getPackFields());
        setSort(config.isSort());
        setVerbose(config.isVerbose());
        setOverwrite(config.isOverwrite());
        setStatistics(config.isStatistics());
        setRestorePackages(config.isRestorePackages());
        setJadPath(config.getJadPath());
        setDecompileToMemory(config.isDecompileToMemory());
        setCreateOutputDirectory(config.isCreateOutputDirectory());
        setAlwaysExcludeRecursively(config.isAlwaysExcludeRecursively());
        setClearAndCloseConsoleOnSuccess(config.isClearAndCloseConsoleOnSuccess());
        setUseProjectSpecificSettings(config.isUseProjectSpecificSettings());
        setReformatStyle(config.getReformatStyle());
        setCleanupSourceRoots(config.isCleanupSourceRoots());
        setForcedDecompileToMemory(config.isForcedDecompileToMemory());
        setUseCustomTempDir(config.isUseCustomTempDir());
        setCustomTempDirPath(config.getCustomTempDirPath());
    }

    public boolean isOutputDirectoryNotSet() {
        return !isDecompileToMemory() && StringUtil.isEmptyOrSpaces(getOutputDirectory());
    }
} public interface ConsoleContext
{
    /**
     * Checks if the console contains any information that makes it interesting
     * to the user, e.g. errors.
     *
     * @return true iff worth displaying
     */
    boolean isWorthDisplaying();

    /**
     * Sets the worthDisplaying flag.
     *
     * @param worthDisplaying true iff worth displaying
     */
    void setWorthDisplaying(boolean worthDisplaying);

    /**
     * Adds a message to a subsection  within this operation's log.
     *
     * @param entryType the entry type
     * @param message resource bundle key key
     * @param parameters resource bundle parameters
     */
    String addMessage(ConsoleEntryType entryType,
                    String message,
                    Object... parameters);

    /**
     * Adds a context-level message to this operation's log.
     *
     * @param entryType the entry type
     * @param message resource bundle key key
     * @param parameters resource bundle parameters
     */
    String addSectionMessage(ConsoleEntryType entryType,
                           String message,
                           Object... parameters);

    /**
     * Close the console view.
     */
    void close();

    @NotNull
    ConsoleTreeNode getContextNode();
} public class IntelliJadConsole implements NodeHandler
{
    /**
     * The tool window ID.
     */
    private static final String TOOL_WINDOW_ID = "IntelliJad Console";

    /**
     * An empty implementation of {@link Runnable} for use with tool windows.
     */
    private static final Runnable EMPTY_RUNNABLE = new Runnable()
    {
        public void run()
        {
            // no-op
        }
    };

    private final ConsoleTreeModel treeModel;
    private JPanel root;
    private JToggleButton clearAndCloseOnSuccess;
    private JButton clearButton;
    private JToolBar toolbar;
    private JButton helpButton;
    private JTree consoleTree;
    private JButton expandAll;
    private JButton collapseAll;
    private JScrollPane scrollPane;

    /**
     * Initialisation flag for JIT setup.
     */
    private boolean initialised = false;

    /**
     * The project this console reports for.
     */
    private final Project project;

    /**
     * Initialise a new instance of this class.
     *
     * @param project the project this console reports for
     */
    public IntelliJadConsole(@NotNull Project project)
    {
        this.treeModel = new ConsoleTreeModel(this);
        this.project = project;
    }

    private void jitInit()
    {
        if (!initialised)
        {
            consoleTree.setModel(treeModel);
            consoleTree.setCellRenderer(new ConsoleTreeCellRenderer());
            consoleTree.setScrollsOnExpand(true);

            toolbar.setFloatable(false);
            clearAndCloseOnSuccess.setSelected(PluginUtil.getConfig(project).isClearAndCloseConsoleOnSuccess());
            clearAndCloseOnSuccess.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent actionEvent)
                {
                    PluginUtil.getConfig(project).setClearAndCloseConsoleOnSuccess(clearAndCloseOnSuccess.isSelected());
                }
            });
            clearButton.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent actionEvent)
                {
                    clearConsoleContent();
                }
            });
            helpButton.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent actionEvent)
                {
                    HelpManager.getInstance().invokeHelp(IntelliJadConstants.CONFIGURATION_HELP_TOPIC);
                }
            });
            expandAll.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent actionEvent)
                {
                    expand(treeModel.getRootNode());
                }
            });
            collapseAll.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent actionEvent)
                {
                    collapse(treeModel.getRootNode());
                }
            });
            initialised = true;
        }
    }

    /**
     * Opens the console.
     */
    public void openConsole() {
        if (!ApplicationManager.getApplication().isUnitTestMode()) {
            AppInvoker.get().invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    jitInit();
                    ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);

                    ToolWindow window;
                    if (toolWindowManager != null)
                    {
                        window = toolWindowManager.getToolWindow(TOOL_WINDOW_ID);
                        if (window == null)
                        {
                            window = toolWindowManager.registerToolWindow(TOOL_WINDOW_ID,
                                    true,
                                    ToolWindowAnchor.BOTTOM);
                            ContentFactory contentFactory = SERVICE.getInstance();

                            Content content = contentFactory.createContent(getRoot(), TOOL_WINDOW_ID, false);
                            window.getContentManager().addContent(content);
                        }
                        window.setIcon(IntelliJadIcon.INTELLIJAD_LOGO_13X13.get());
                        window.show(EMPTY_RUNNABLE);
                    }
                }
            });
        }
    }

    /**
     * Closes the console.
     */
    public void closeConsole() {
        AppInvoker.get().invokeAndWait(new Runnable() {
            @Override
            public void run() {
                ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
                ToolWindow window;
                if (toolWindowManager != null)
                {
                    window = toolWindowManager.getToolWindow(TOOL_WINDOW_ID);
                    if (window != null)
                    {
                        window.hide(EMPTY_RUNNABLE);
                    }
                }
            }
        });
    }

    /**
     * Clears the console.
     */
    public void clearConsoleContent()
    {
        treeModel.clear();
    }

    /**
     * Creates a console context for an atomic decompilation.
     *
     * @param message the associated message
     * @param parameters any parameters used in the message
     * @return a new console context
     */
    public ConsoleContext createConsoleContext(final String message,
                                               final Object... parameters) {
        return AppInvoker.get().invokeAndWait(new Callable<ConsoleContext>() {
            @Override
            public ConsoleContext call() {
                return treeModel.createConsoleContext(IntelliJadResourceBundle.message(message, parameters));
            }
        });
    }

    /**
     * Get the root component of the view.
     *
     * @return the root component
     */
    public JComponent getRoot()
    {
        return root;
    }

    public void disposeConsole()
    {
        ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
        if (toolWindowManager != null)
        {
            try
            {
                toolWindowManager.unregisterToolWindow(TOOL_WINDOW_ID);
            }
            catch (IllegalArgumentException e)
            {
                // ignore - this can occur due to lazy initialization
            }
        }
    }


    public void expand(ConsoleTreeNode node)
    {
        if (node.isLeaf() && node.getParent() != null)
        {
            consoleTree.expandPath(new TreePath(((ConsoleTreeNode)node.getParent()).getPath()));
        }
        else
        {
            for (ConsoleTreeNode child : node.getChildren())
            {
                expand(child);
            }
        }
    }

    public void collapse(ConsoleTreeNode node)
    {
        if (node.isLeaf() && node.getParent() != null)
        {
            consoleTree.collapsePath(new TreePath(((ConsoleTreeNode)node.getParent()).getPath()));
        }
        else
        {
            for (ConsoleTreeNode child : node.getChildren())
            {
                collapse(child);
            }
        }
    }

    public void select(ConsoleTreeNode node)
    {
        TreePath path = new TreePath(node.getPath());
        consoleTree.expandPath(path);
        consoleTree.setSelectionPath(path);
        consoleTree.fireTreeExpanded(path);

        List<ConsoleTreeNode> children = node.getChildren();
        consoleTree.scrollPathToVisible(!children.isEmpty() ? path.pathByAddingChild(children.get(children.size() - 1)) : path);
    }
} public interface DecompilationChoiceListener
{
    /**
     * Decompile the class described in the decompilation descriptor.
     * 
     * @param environmentContext the environment context
     * @param decompilationDescriptor a description of the class to decompile
     *                                
     * @return result description
     */
    Future<DecompilationResult> decompile(EnvironmentContext environmentContext,
                   DecompilationDescriptor decompilationDescriptor);
} public abstract class DecompilationDescriptor
{
    /**
     * The type of path the class exists on, jar file or file system.
     */
    public enum ClassPathType
    {
        JAR, FS
    }

    /**
     * The class file to decompile.
     */
    @NotNull
    private final VirtualFile classFile;

    /**
     * The fully-qualified name of the class.
     */
    @Nullable
    private String fqName;

    /**
     * The fully-qualified name of the class represented as a path, including extension.
     */
    @Nullable
    private String fqNameAsPath;

    /**
     * The name of the class.
     */
    @Nullable
    private final String className;

    /**
     * The extension of the class (typically "class").
     */
    @Nullable
    private final String extension;

    /**
     * The name of the class's package.
     */
    @Nullable
    private String packageName;

    /**
     * The class's package as a path, i.e. with / instead of .
     */
    @Nullable
    private String packageNameAsPath;

    /**
     * The path to the class file.
     */
    @NotNull
    private String path;

    /**
     * Initialises a new instance of this class.
     *
     * @param classFile         the file pointing to the target class
     * @param fqName            the fully-qualified name of the class
     * @param fqNameAsPath      the fully-qualified name of the class as a path
     * @param packageName       the package (e.g. net.stevechaloner.intellijad)
     * @param packageNameAsPath the package as a path (e.g. net/stevechaloner/intellijad/)
     */
    DecompilationDescriptor(@NotNull VirtualFile classFile,
                            @NotNull String fqName,
                            @NotNull String fqNameAsPath,
                            @NotNull String packageName,
                            @NotNull String packageNameAsPath)
    {
        this.classFile = classFile;
        this.fqName = fqName;
        setFqNameAsPath(fqNameAsPath);
        this.className = classFile.getNameWithoutExtension();
        this.extension = classFile.getExtension();
        this.packageName = packageName;
        this.packageNameAsPath = packageNameAsPath;
        this.path = classFile.getPath();
    }

    /**
     * Initialises a new instance of this class.
     *
     * @param classFile the file pointing to the target class
     */
    DecompilationDescriptor(@NotNull VirtualFile classFile)
    {
        this.classFile = classFile;
        this.path = classFile.getPath();
        this.className = classFile.getNameWithoutExtension();
        this.extension = classFile.getExtension();
    }

    @Nullable
    public abstract ClassPathType getClassPathType();

    /**
     * Gets the path to the source file.
     *
     * @param availableDirectory a directory available for temporary extractions.
     * @return the source file
     */
    @NotNull
    public abstract File getSourceFile(@NotNull File availableDirectory);

    // javadoc unnecessary
    @NotNull
    public VirtualFile getClassFile()
    {
        return classFile;
    }

    // javadoc unnecessary
    @Nullable
    public String getFullyQualifiedName()
    {
        return fqName;
    }


    @Nullable
    public String getFullyQualifiedNameAsPath()
    {
        return fqNameAsPath;
    }

    public final void setFqNameAsPath(String fqNameAsPath)
    {
        if (!fqNameAsPath.endsWith(IntelliJadConstants.DOT_JAVA_EXTENSION))
        {
            fqNameAsPath += IntelliJadConstants.DOT_JAVA_EXTENSION;
        }
        this.fqNameAsPath = fqNameAsPath;

    }

    // javadoc unnecessary
    @Nullable
    public String getClassName()
    {
        return className;
    }

    // javadoc unnecessary
    @Nullable
    public String getExtension()
    {
        return extension;
    }

    // javadoc unnecessary
    @NotNull
    public String getPath()
    {
        return path;
    }

    // javadoc unnecessary
    @Nullable
    public String getPackageName()
    {
        return packageName;
    }

    // javadoc unnecessary
    @Nullable
    public String getPackageNameAsPath()
    {
        return packageNameAsPath;
    }

    // javadoc unnecessary
    public final void setFqName(String fqName)
    {
        this.fqName = fqName;
    }

    // javadoc unnecessary
    public final void setPackageName(String packageName)
    {
        this.packageName = packageName;
    }

    // javadoc unnecessary
    public final void setPackageNameAsPath(String packageNameAsPath)
    {
        this.packageNameAsPath = packageNameAsPath;
    }
} public class DecompilationResult {
    private final VirtualFile resultFile;

    public DecompilationResult() {
        this(null);
    }
    
    public DecompilationResult(VirtualFile resultFile) {
        this.resultFile = resultFile;
    }

    public boolean isSuccessful() {
        return resultFile != null;
    }
    
    public VirtualFile getResultFile() {
        return resultFile;
    }
} public class AppInvoker {
    
    private final Application application;
    
    private AppInvoker(Application _application) {
        application = _application;
    }
    
    public static AppInvoker create(Application application) {
        return new AppInvoker(application);
    }
    
    public static AppInvoker get() {
        return create(ApplicationManager.getApplication());
    }

    public <T> T invokeAndWait(Callable<T> action) {
        FutureTask<T> task = new FutureTask<T>(action);
        application.invokeAndWait(task, application.getAnyModalityState());
        try {
            return task.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    public void invokeAndWait(Runnable action) {
        application.invokeAndWait(action, application.getAnyModalityState());
    }

    public void runWriteActionAndWait(Runnable action) {
        runWriteActionAndWait(action, application.getAnyModalityState());
    }
    
    public void runWriteActionAndWait(Runnable action, ModalityState modality) {
        application.invokeAndWait(new WriteAction(application, action), modality);        
    }
    
    public void saveSettings() {
        runWriteActionAndWait(new Runnable() {
            @Override
            public void run() {
                application.saveSettings();
            }
        });        
    }
    
    private class WriteAction implements Runnable {
        private final Application application;
        private final Runnable action;

        private WriteAction(Application application, Runnable action) {
            this.application = application;
            this.action = action;
        }

        @Override
        public void run() {
            application.runWriteAction(action);
        }
    }
} public class FileSystemUtil {
    
    public static String generateTempDirName(Project project) {
        return project.getName();    
    }
    
    public static String generateTempOutputDir(@NotNull Project project) {
        return generateTempOutputDir(PluginUtil.getConfig(project)) + File.separator + generateTempDirName(project);
    } 

    public static String generateTempOutputDir(Config config) {
        String tempDirPath;
        if (config.isUseCustomTempDir()) {
            tempDirPath = config.getCustomTempDirPath();
        } else {
            tempDirPath = OsUtil.tempDirPath();
        }
        if (!tempDirPath.endsWith(File.separator)) {
            tempDirPath = tempDirPath + File.separator;
        }
        return tempDirPath + "intellijad";
    } 
    
    public static File createTargetDir(Config config) {
        String outputDirPath = config.getOutputDirectory();
        if (!StringUtil.isEmptyOrSpaces(outputDirPath)) {
            File outputDirectory = new File(outputDirPath);
            boolean outputDirExists = outputDirectory.exists();
            if (!outputDirExists && config.isCreateOutputDirectory()) {
                if (outputDirectory.mkdirs()) {
                    return outputDirectory;
                }
            }
        }
        return null;
    }
}
 public class PluginUtil {
    /**
     * Static access only.
     */
    private PluginUtil() {
    }

    /**
     * Gets the component from the application context.
     *
     * @param clazz the class of the component
     * @return the component
     */
    public static <C> C getComponent(Class<C> clazz) {
        return ApplicationManager.getApplication().getComponent(clazz);
    }

    /**
     * Gets the application-level configuration.
     *
     * @return the application-level configuration
     */
    public static Config getApplicationConfig() {
        return getComponent(ApplicationConfigComponent.class).getConfig();
    }

    /**
     * Gets the plugin configuration.
     *
     * @param project the project to get the config from
     * @return the project configuration
     */
    public static Config getConfig(@NotNull Project project) {
        ProjectConfigComponent projectComponent = project.getComponent(ProjectConfigComponent.class);
        Config config = null;
        if (projectComponent != null) {
            config = projectComponent.getConfig();
        }
        if (config == null || !config.isUseProjectSpecificSettings()) {
            config = getApplicationConfig();
        }
        return config;
    }

    public static Exclusion getExclusion(@NotNull Project project) {
        return new Exclusion(getConfig(project));
    }

    public static Balloon showBalloon(@NotNull JComponent component, @NotNull MessageType messageType, @NotNull String message) {
        BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(message, messageType, null);
        Balloon balloon = balloonBuilder.setFadeoutTime(TimeUnit.SECONDS.toMillis(1)).createBalloon();
        Dimension size = component.getSize();
        Balloon.Position position;
        int x;
        int y;
        if (size == null) {
            x = y = 0;
            position = Balloon.Position.above;
        } else {
            x = Math.min(10, size.width / 2);
            y = size.height;
            position = Balloon.Position.below;
        }
        balloon.show(new RelativePoint(component, new Point(x, y)), position);
        return balloon;
    }
}
 public class TempMemoryVFS implements MemoryVFS {
    private final Logger LOG = Logger.getInstance(getClass());
    
    private final TempFileSystem fs;
    private final VirtualFile root;
    
    private final Map<String, VirtualFile> files = new HashMap<String, VirtualFile>();
    
    private TempMemoryVFS(TempFileSystem fs, Project project) {
        this.fs = fs;
        root = initRoot(this.fs, project);
    }

    private VirtualFile initRoot(TempFileSystem fs, Project project) {
        VirtualFile fsRoot = Preconditions.checkNotNull(fs.getRoot(), "Null FS root");
        String rootName = FileSystemUtil.generateTempDirName(project);
        VirtualFile commonRoot = fsRoot.findChild("intellijad");
        
        try {
            if (commonRoot != null) {
                VirtualFile root = commonRoot.findChild(rootName);
                if (root != null) {
                    return root;
                } else {
                    return fs.createChildDirectory(null, commonRoot, rootName);    
                }
            } else {
                commonRoot = fs.createChildDirectory(null, fsRoot, "intellijad");
                return fs.createChildDirectory(null, commonRoot, rootName);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
            
    }
    
    public static MemoryVFS getInstance(Project project) {
        MemoryVFS memoryVFS = IntelliJadConstants.MEMORY_VFS.get(project);
        if (memoryVFS == null) {
            memoryVFS = new TempMemoryVFS(TempFileSystem.getInstance(), project);
            IntelliJadConstants.MEMORY_VFS.set(project, memoryVFS);
        }
        return memoryVFS;    
    }
    
    public static void dispose(Project project) {
        MemoryVFS vfs = IntelliJadConstants.MEMORY_VFS.get(project);
        if (vfs != null) {
            IntelliJadConstants.MEMORY_VFS.set(project, null);
            vfs.dispose();            
        }
    }
    
    @Override
    public void deleteFile(Object requestor, VirtualFile virtualFile) throws IOException {
        fs.deleteFile(requestor, virtualFile);
        files.remove(virtualFile.getPath());
    }

    @Override
    public MemoryVF getFileForPackage(String packageName) {
        StringTokenizer st = new StringTokenizer(packageName, ".");
        List<String> names = new ArrayList<String>();
        while (st.hasMoreTokens()) {
            names.add(st.nextToken());
        }
        return new TempMemoryVF(getFileForPackage(names, root), fs);
    }

    private VirtualFile getFileForPackage(@NotNull List<String> names,
                                                    @NotNull VirtualFile parent) {
        VirtualFile child = null;
        if (!names.isEmpty()) {
            String name = names.remove(0);
            String path = parent.getPath() + "/" + name;
            child = files.get(path);
            if (child == null) {
                try {
                    child = fs.createChildDirectory(null,
                            parent,
                            name);
                    files.put(child.getPath(), child);
                } catch (IOException e) {
                    Logger.getInstance(getClass().getName()).error(e);
                }
            }
        }

        if (child != null && !names.isEmpty()) {
            child = getFileForPackage(names, child);
        }
        return child;
    }
    
    @Override
    public void addFile(MemoryVF file) {}

    @Override
    public VirtualFileSystem asVirtualFileSystem() {
        return fs;
    }

    @Override
    public MemoryVF newMemoryFV(@NotNull String name, String content) {
        try {
            String path = root.getPath() + "/" + name;
            VirtualFile fileByPath = files.get(path);
            if (fileByPath != null) {
                deleteFile(null, fileByPath);
            }
            VirtualFile file = fs.createChildFile(null, root, name);
            files.put(file.getPath(), file);
            TempMemoryVF memoryVF = new TempMemoryVF(file, fs);
            memoryVF.setContent(content);
            return memoryVF;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void dispose() {
        files.clear();
        try {
            fs.deleteFile(null, root);
            LOG.info("Disposed "+root.getPath());
        } catch (IOException e) {
            LOG.error("Failed to dispose "+root.getPath(), e);
        }
    }
}
import com.intellij.openapi.application.Application;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerListener;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import net.stevechaloner.intellijad.actions.NavigationListener;
import net.stevechaloner.intellijad.config.Config;
import net.stevechaloner.intellijad.console.ConsoleContext;
import net.stevechaloner.intellijad.console.ConsoleManager;
import net.stevechaloner.intellijad.console.IntelliJadConsole;
import net.stevechaloner.intellijad.decompilers.DecompilationChoiceListener;
import net.stevechaloner.intellijad.decompilers.DecompilationDescriptor;
import net.stevechaloner.intellijad.decompilers.DecompilationResult;
import net.stevechaloner.intellijad.environment.EnvironmentContext;
import net.stevechaloner.intellijad.util.AppInvoker;
import net.stevechaloner.intellijad.util.FileSystemUtil;
import net.stevechaloner.intellijad.util.PluginUtil;
import net.stevechaloner.intellijad.vfs.TempMemoryVFS;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
        primeProject(project);
    }

    /**
     * Primes the project for any alterations and updates made by IntelliJad.
     *
     * @param project the project
     */
    public void primeProject(final Project project)
    {
        IntelliJadConstants.GENERATED_SOURCE_LIBRARIES.set(project, new ArrayList<Library>());
        IntelliJadConstants.DECOMPILATION_DISABLED.set(project, false);
        NavigationListener navigationListener = new NavigationListener(project, this);
        project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, navigationListener);
        IntelliJadConstants.DECOMPILE_LISTENER.set(project, navigationListener);

        ProjectClosingTasks.getInstance(project).addTask(new Runnable()
        {
            public void run()
            {
                List<Library> list = IntelliJadConstants.GENERATED_SOURCE_LIBRARIES.get(project);
                for (Library library : list) {
                    Library.ModifiableModel model = library.getModifiableModel();
                    VirtualFile[] files = model.getFiles(OrderRootType.SOURCES);
                    for (VirtualFile file : files) {
                        if (file.getParent() == null && IntelliJadConstants.ROOT_URI.equals(file.getUrl())) {
                            model.removeRoot(file.getUrl(), OrderRootType.SOURCES);
                        }
                    }
                    if (files.length > 0) {
                        model.commit();
                    }
                }
            }
        });
        IntelliJadConstants.INTELLIJAD_PRIMED.set(project, true);
    }

    /**
     * {@inheritDoc}
     */
    public boolean canCloseProject(Project project) {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public void projectClosed(Project project) {
        consoleManager.disposeConsole(project);
        IntelliJadConstants.DECOMPILE_LISTENER.set(project, null);
        TempMemoryVFS.dispose(project);        
        List<Library> libraries = IntelliJadConstants.GENERATED_SOURCE_LIBRARIES.get(project);
        if (libraries != null) {
            libraries.clear();
            IntelliJadConstants.GENERATED_SOURCE_LIBRARIES.set(project, null);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void projectClosing(final Project project) {
        IntelliJadConstants.INTELLIJAD_PRIMED.set(project, false);
    }

    public void onStartup() {
        Config config = PluginUtil.getApplicationConfig();        
        forceDecompilationToDirectory(config);            
    }
    
    public void onStartup(Project project) {
        Config config = PluginUtil.getConfig(project);
        //this will reconfigure project to decompile to file system
        forceDecompilationToDirectory(config, project);        
    }
    /**
     * {@inheritDoc}
     */
    public void initComponent() {
        ProjectManager.getInstance().addProjectManagerListener(this);
    }

    /**
     * {@inheritDoc}
     */
    public void disposeComponent() {
        ProjectManager.getInstance().removeProjectManagerListener(this);
    }

    @Nullable
    VirtualFile handleDisabledVirtualFs(LocalFileSystem lfs, Config config, Project project) {
        String outputDir = config.getOutputDirectory();
        if (StringUtil.isEmptyOrSpaces(outputDir)) {
            outputDir = setupTempOutputDir(config, project, true);
        }
        VirtualFile outDirFile = lfs.findFileByPath(outputDir);
        if (outDirFile == null) {
            File targetDir = FileSystemUtil.createTargetDir(config);
            if (targetDir != null) {
                outDirFile = lfs.refreshAndFindFileByIoFile(targetDir);
            } else {
                LOG.error("Output directory creation failed: "+outputDir);
                IntelliJadConstants.DECOMPILATION_DISABLED.set(project, true);
            }
        }
        if (!IntelliJadConstants.DECOMPILATION_DISABLED.get(project, false)) {
            SdkHandler.create(application).checkSDKRoot(project, outDirFile);
        }
        return outDirFile;
    } 
    
    public static boolean isPrimed(Project project) {
        Boolean isPrimed = IntelliJadConstants.INTELLIJAD_PRIMED.get(project);
        return isPrimed != null && isPrimed;
    }
    
    /**
     * {@inheritDoc}
     */
    public Future<DecompilationResult> decompile(EnvironmentContext envContext, DecompilationDescriptor descriptor) {