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 class DecompilationContext implements UserDataHolder
{
    /**
     * The console context to use for reporting.
     */
    private final ConsoleContext consoleContext;

    /**
     * The command to execute.
     */
    private final String command;

    /**
     * The directory available for placing temporary files.
     */
    private final File targetDirectory;

    /**
     * The project this decompiation is occurring in.
     */
    private final Project project;

    /**
     * User data.
     */
    private final Map<Key, Object> userData = Maps.newConcurrentMap();

    private final DecompilationEngine engine;

    /**
     * Initialises a new instance of this class.
     *
     * @param project the project
     * @param consoleContext the reporting console context
     */
    public DecompilationContext(@NotNull Project project,
                                @NotNull ConsoleContext consoleContext,
                                @NotNull DecompilationEngine engine
                                )
    {
        this.project = Preconditions.checkNotNull(project);
        this.consoleContext = Preconditions.checkNotNull(consoleContext);
        this.engine = Preconditions.checkNotNull(engine);
        command = Preconditions.checkNotNull(engine.prepareCommand(project));
        this.targetDirectory = prepareTargetDir(project);
        targetDirectory.mkdir();
        targetDirectory.deleteOnExit();

    }

    private File prepareTargetDir(@NotNull Project project) {
        Config config = PluginUtil.getConfig(project);
        File tempDir;
        if (config.isUseCustomTempDir()) {
            tempDir = new File(config.getCustomTempDirPath());
            if (!tempDir.exists()) {
                Preconditions.checkState(tempDir.mkdirs(), "Could not mkdirs: "+tempDir.getAbsolutePath());
            }
        } else {
            tempDir = OsUtil.tempDir();
        }
        return new File(tempDir, "ij" + System.currentTimeMillis());
    }

    // javadoc unnecessary
    public ConsoleContext getConsoleContext()
    {
        return consoleContext;
    }

    // javadoc unnecessary
    public String getCommand()
    {
        return command;
    }

    // javadoc unnecessary
    public File getTargetDirectory()
    {
        return targetDirectory;
    }

    // javadoc unnecessary
    public Project getProject()
    {
        return project;
    }

    public DecompilationEngine getEngine() {
        return engine;
    }

    // javadoc unnecessary
    public Config getConfig()
    {
        return PluginUtil.getConfig(project);
    }

    @Override
    public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
        userData.put(key, value);
    }

    // javadoc unnecessary
    public <T> T getUserData(@NotNull Key<T> key)
    {
        return (T)userData.get(key);
    }

    // javadoc unnecessary
    public <T> T removeUserData(@NotNull Key<T> key)
    {
        return (T)userData.remove(key);
    }

    // javadoc unnecessary
    public <T> boolean containsUserData(Key<T> key)
    {
        return userData.containsKey(key);
    }
} 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 interface DecompilationEngine {
    public final Selector selector = new Selector();

    String prepareCommand(@NotNull Project project);
    String waterMark();

    public static final class Selector {
        private Selector() {}

        public DecompilationEngine get(@NotNull Project project) {
            return new JadEngine();
        }
    }
} 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 interface Decompiler {
    /**
     * Decompiles the class detailed in the decompilation descriptor.
     *
     * @param descriptor a description of the class to decompile
     * @param context the context of the decompilation
     * @return a file representing the decompiled class
     * @throws DecompilationException if something prevents the operation
     */
    @Nullable
    VirtualFile decompile(DecompilationDescriptor descriptor,
                          DecompilationContext context) throws DecompilationException;

    /**
     * Gets the virtual file that would be createdif the decompilation occurs.
     *
     * @param descriptor a description of the class to decompile
     * @param context the context of the decompilation
     * @return the virtual file
     */
    @Nullable
    VirtualFile getVirtualFile(DecompilationDescriptor descriptor,
                               DecompilationContext context);
} public class FileSystemDecompiler extends DecompilerBase {
    private final Logger LOG = Logger.getInstance(getClass());

    private static final Key<Boolean> CANNOT_STORE = new Key<Boolean>("FileSystemDecompiler.cannot-store-in-fs");

    private static final Key<VirtualFile> LOCAL_FS_FILE = new Key<VirtualFile>("FileSystemDecompiler.local-fs-file");

    private final AppInvoker appInvoker;

    public FileSystemDecompiler(AppInvoker appInvoker) {
        this.appInvoker = appInvoker;
    }

    /**
     * {@inheritDoc}
     */
    protected OperationStatus setup(DecompilationDescriptor descriptor,
                                    DecompilationContext context) throws DecompilationException {
        boolean debug = LOG.isDebugEnabled();

        OperationStatus status = super.setup(descriptor, context);

        Map<String, Object[]> messages = new HashMap<String, Object[]>();

        if (status == OperationStatus.CONTINUE) {
            Config config = context.getConfig();
            String outputDirPath = config.getOutputDirectory();
            if (!StringUtil.isEmptyOrSpaces(outputDirPath)) {
                File outputDirectory = new File(outputDirPath);
                boolean outputDirExists = outputDirectory.exists();
                if (!outputDirExists && config.isCreateOutputDirectory()) {
                    if (!outputDirectory.mkdirs()) {
                        messages.put("error.could-not-create-output-directory", new String[]{config.getOutputDirectory()});
                        if (debug) {
                            LOG.debug("Output directory could not be created");
                        }
                    }
                } else if (!outputDirExists) {
                    messages.put("error.non-existant-output-directory", new String[]{config.getOutputDirectory()});
                    if (debug) {
                        LOG.debug("Output directory does not exist");
                    }
                }
            } else {
                messages.put("error.output-directory-not-set", new String[]{config.getOutputDirectory()});
                if (debug) {
                    LOG.debug("Directory not set");
                }
            }
        }

        boolean cannotStore = !messages.isEmpty();
        CANNOT_STORE.set(context, cannotStore);
        if (cannotStore) {
            status = OperationStatus.ABORT;
            LOG.error("Result cannot be stored");
        }

        ConsoleContext consoleContext = context.getConsoleContext();
        for (Map.Entry<String, Object[]> entry : messages.entrySet()) {
            String errorMessage = consoleContext.addMessage(ConsoleEntryType.DECOMPILATION_OPERATION, entry.getKey(), entry.getValue());
            LOG.error(errorMessage);
        }

        if (cannotStore) {
            consoleContext.addMessage(ConsoleEntryType.DECOMPILATION_OPERATION,
                    "error.storing-class-in-fs",
                    descriptor.getClassName());
            consoleContext.setWorthDisplaying(true);
        }

        return status;
    }

    /* {@inheritDoc} */
    protected Optional<VirtualFile> insertIntoFileSystem(@NotNull DecompilationDescriptor descriptor,
                                                         @NotNull final DecompilationContext context,
                                                         @NotNull MemoryVF file) {
        final boolean debug = LOG.isDebugEnabled();

        if (debug) {
            LOG.debug("Inserting into local file system");
        }

        final LocalFileSystem localFs = getLocalFileSystem();
        Config config = context.getConfig();
        File localPath = new File(config.getOutputDirectory() + File.separator + descriptor.getPackageNameAsPath());

        if (debug) {
            LOG.debug("Insert into " + localPath.getAbsolutePath());
        }

        Optional<VirtualFile> insertFile;
        boolean exists = localPath.exists();
        boolean canWrite = localPath.canWrite();
        boolean mkDirs = true;
        if (!exists) {
            mkDirs = localPath.mkdirs();
        }
        boolean cannotStore = false;
        if ((exists & canWrite) || mkDirs) {
            String fileName = descriptor.getClassName() + IntelliJadConstants.DOT_JAVA_EXTENSION;
            final File localFile = new File(localPath, fileName);
            if (localFile.exists() && !localFile.setWritable(true)) {
                LOG.warn("Could not set "+localFile.getAbsolutePath()+" as writable");
            }
            Closer closer = Closer.create();            
            try {                
                if (debug) {
                    LOG.debug("Insert into local file " + localFile.getAbsolutePath());
                }
                OutputStream output = closer.register(new BufferedOutputStream(new FileOutputStream(localFile)));
                if (debug) {
                    LOG.debug("Writing...");
                }
                InputStream input = closer.register(new BufferedInputStream(file.getInputStream()));
                StreamUtil.copyStreamContent(input, output);
                if (debug) {
                    LOG.debug("Written");
                }
                output.close();
                if (debug) {
                    LOG.debug("Closed");
                }                
            } catch (IOException e) {
                LOG.error("Could not save file", e);
                cannotStore = true;                
            } finally {
                try {
                    closer.close();
                } catch (IOException e) {
                    LOG.error("Could not close files", e);
                }
            }
            if (cannotStore) {
                insertFile = Optional.absent();
            } else {
                final AtomicReference<VirtualFile> foundFile = new AtomicReference<VirtualFile>();
                appInvoker.runWriteActionAndWait(new Runnable() {
                    public void run() {
                        if (debug) {
                            LOG.debug("Looking for file: " + localFile.getAbsolutePath());
                        }
                        foundFile.set(localFs.refreshAndFindFileByIoFile(localFile));
                        if (debug) {
                            LOG.debug("Found " + String.valueOf(foundFile.get()));
                        }
                    }
                });
    
                insertFile = Optional.of(foundFile.get());
                LOCAL_FS_FILE.set(context, insertFile.get());
            }
        } else {
            LOG.warn("Path: " + localPath.getAbsolutePath() + ", exists=" + exists + ", canWrite=" + canWrite + ", mkDirs=" + mkDirs);
            cannotStore = true;
            insertFile = Optional.absent();
        }
        CANNOT_STORE.set(context, cannotStore);

        return insertFile;
    }

    /* {@inheritDoc} */
    protected void attachSourceToLibraries(@NotNull DecompilationDescriptor descriptor,
                                           @NotNull DecompilationContext context,
                                           @NotNull List<Library> libraries) {
        if (CANNOT_STORE.get(context, false)) {
            // something has occurred to make storing the file on disk a problem            
            LOG.error("Cannot attach source: " + descriptor.getClassName());
        } else {
            attachSource(descriptor, context, LOCAL_FS_FILE.get(context));
        }
    }

    private VirtualFile attachSource(@NotNull final DecompilationDescriptor descriptor,
                                     @NotNull final DecompilationContext context,
                                     @NotNull final VirtualFile file) {
        final Project project = context.getProject();
        LocalFileSystem vfs = getLocalFileSystem();
        Config config = context.getConfig();
        File td = new File(config.getOutputDirectory());
        final VirtualFile targetDirectory = Preconditions.checkNotNull(vfs.findFileByIoFile(td), "VF not found for "+td.getAbsolutePath());

        appInvoker.runWriteActionAndWait(new Runnable() {
            public void run() {
                final List<Library> libraries = LibraryUtil.findLibrariesByClass(descriptor.getFullyQualifiedName(),
                        project);
                if (!libraries.isEmpty()) {
                    ConsoleContext consoleContext = context.getConsoleContext();
                    for (Library library : libraries) {
                        String[] urls = library.getUrls(OrderRootType.SOURCES);
                        boolean found = false;
                        for (int i = 0; !found && i < urls.length; i++) {
                            found = targetDirectory.getUrl().equals(urls[i]);
                        }
                        if (!found) {
                            Library.ModifiableModel model = library.getModifiableModel();
                            model.addRoot(targetDirectory, OrderRootType.SOURCES);
                            model.commit();
                        }

                        IntelliJadConstants.GENERATED_SOURCE_LIBRARIES.get(project).add(library);
                        consoleContext.addMessage(ConsoleEntryType.LIBRARY_OPERATION,
                                "message.associating-source-with-library",
                                descriptor.getClassName(),
                                library.getName() == null ? IntelliJadResourceBundle.message("message.unnamed-library") : library.getName());
                    }
                } else {
                    // there are two reasons we could be in here - the class file was opened from an arbitrary location
                    // outside of the project, or the class is found in the SDK.  In the first instance, there is no
                    // library
                    context.getConsoleContext().addMessage(ConsoleEntryType.LIBRARY_OPERATION,
                            "message.library-not-found-for-class",
                            descriptor.getClassName());
                }
            }
        });


        return file;
    }

    /**
     * Locks the file against source changes
     *
     * @param context
     * @param file
     */
    protected void lockFile(@NotNull DecompilationContext context,
                            @NotNull MemoryVF file) {
        if (CANNOT_STORE.get(context, false)) {
            LOG.error("Cannot lock file: " + file);
        } else {
            if (LOG.isDebugEnabled()) {
                LOG.debug("About to lock file: " + file);
            }
            final VirtualFile fsFile = LOCAL_FS_FILE.get(context);
            appInvoker.runWriteActionAndWait(new Runnable() {
                @Override
                public void run() {
                try {
                    fsFile.setWritable(false);
                } catch (IOException e) {
                    LOG.error("Could not lock " + fsFile, e);
                }
                }
            });
            
            Config config = context.getConfig();
            if (config.isReadOnly()) {                
                File ioFile = VfsUtilCore.virtualToIoFile(fsFile);
                boolean roSet = ioFile.setReadOnly();
                
                if (!roSet) {
                    LOG.info("Could not set " + ioFile.getAbsolutePath() + " as read-only");
                }
            }
        }
    }

    private LocalFileSystem getLocalFileSystem() {
        return (LocalFileSystem) VirtualFileManager.getInstance().getFileSystem(LocalFileSystem.PROTOCOL);
    }

    /**
     * {@inheritDoc}
     */
    @Nullable
    public VirtualFile getVirtualFile(@NotNull DecompilationDescriptor descriptor,
                                      @NotNull DecompilationContext context) {
        VirtualFile file;
        if (context.containsUserData(CANNOT_STORE) && context.getUserData(CANNOT_STORE)) {
            file = null;
        } else if (context.containsUserData(LOCAL_FS_FILE)) {
            file = context.getUserData(LOCAL_FS_FILE);
        } else {
            file = null;
        }
        return file;
    }
} public class EnvironmentValidator {
    private static final Logger LOG = Logger.getInstance(EnvironmentValidator.class);
    
    /**
     * Validates the environment prior to decompilation.
     *
     * @param config the configuration
     * @param envContext the environment configuration
     * @param consoleContext the console's logging context for this operation
     * @return true if the decompilation should continue
     */
    public static ValidationResult validateEnvironment(@NotNull Config config,
                                                       @NotNull EnvironmentContext envContext,
                                                       @NotNull ConsoleContext consoleContext)
    {
        String message = null;
        Object[] params = {};
        String jadPath = config.getJadPath();
        if (StringUtil.isEmptyOrSpaces(jadPath)) {
            message = "error.unspecified-jad-path";
        } else {
            File f = new File(jadPath);
            if (!f.exists())
            {
                message = "error.non-existant-jad-path";
                params = new String[]{jadPath};
            }
            else if (!f.isFile())
            {
                message = "error.invalid-jad-path";
                params = new String[]{jadPath};
            }
        }

        ValidationResult result;
        if (message != null) {
            String preparedMessage = IntelliJadResourceBundle.message(message, params);
            if (ApplicationManager.getApplication().isUnitTestMode()) {
                result = new ValidationResult(false, true);
                LOG.error("Validation failed: "+preparedMessage);
            } else {
                result = showErrorDialog(config,
                                         envContext,
                                         consoleContext, preparedMessage);
                consoleContext.addSectionMessage(ConsoleEntryType.ERROR,
                                                 message,
                                                 params);
            }
        } else {
            result = new ValidationResult(true, false);
        }
        return result;
    }

    /**
     * Shows the error dialog, allowing the user to cancel the decompilation or open the config.
     *
     * @param config the configuration
     * @param envContext the environment context
     * @param consoleContext the console's logging context
     * @param message the error message
     * @return the result of the dialog-based operation
     */
    private static ValidationResult showErrorDialog(@NotNull Config config,
                                                    @NotNull EnvironmentContext envContext,
                                                    @NotNull ConsoleContext consoleContext,
                                                    @NotNull String message)
    {
        DialogBuilder builder = new DialogBuilder(envContext.getProject());
        builder.setTitle(IntelliJadResourceBundle.message("plugin.IntelliJad.name"));
        builder.addOkAction().setText(IntelliJadResourceBundle.message("option.open-config"));
        builder.addCancelAction().setText(IntelliJadResourceBundle.message("option.cancel-decompilation"));
        JLabel label = new JLabel(message);
        label.setUI(new MultiLineLabelUI());
        builder.setCenterPanel(label);
        builder.setOkActionEnabled(true);

        ValidationResult result;
        switch (builder.show())
        {
            case DialogWrapper.OK_EXIT_CODE:
                // this will cause recursive correction unless cancel is selected
                Project project = envContext.getProject();
                ConfigAccessor configAccessor = config.isUseProjectSpecificSettings() ? new ProjectConfigComponent(project) : new ApplicationConfigComponent();
                // design point - if this dialog is cancelled, should we assume the decompilation is cancelled?
                ShowSettingsUtil.getInstance().editConfigurable(project,
                                                                configAccessor);
                config.copyFrom(configAccessor.getConfig());
                result = validateEnvironment(config,
                                             envContext,
                                             consoleContext);
                break;
            case DialogWrapper.CANCEL_EXIT_CODE:
            default:
                result = new ValidationResult(false,
                                              true);
                break;
        }

        return result;
    }
} public class ValidationResult
{
    /**
     * The validity state of the configuration.
     */
    private final boolean valid;

    /**
     * Indicates if the user cancelled the whole operation.
     */
    private final boolean cancelled;

    /**
     * Initialises a new instance of this class.
     *
     * @param valid true if the config is valid
     * @param cancelled true if the operation should be cancelled
     */
    ValidationResult(boolean valid,
                     boolean cancelled)
    {
        this.valid = valid;
        this.cancelled = cancelled;
    }

    /**
     * Gets the validity state of the configuration.  True implies a valid config.
     *
     * @return true if the configuration is valid
     */
    public boolean isValid()
    {
        return valid;
    }

    /**
     * Returns if the operation was cancelled or not.  This takes priority over anything else in
     * this result, i.e. even if the config is valid (which theoretically can't occur in the current
     * GUI flow) the decompilation should be considered cancelled if this method returns true.
     *
     * @return true if the operation is cancelled
     */
    public boolean isCancelled()
    {
        return cancelled;
    }
} 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;
    }
}

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import java.io.File;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import net.stevechaloner.intellijad.config.Config;
import net.stevechaloner.intellijad.console.ConsoleContext;
import net.stevechaloner.intellijad.console.ConsoleEntryType;
import net.stevechaloner.intellijad.console.ConsoleManager;
import net.stevechaloner.intellijad.console.IntelliJadConsole;
import net.stevechaloner.intellijad.decompilers.DecompilationContext;
import net.stevechaloner.intellijad.decompilers.DecompilationDescriptor;
import net.stevechaloner.intellijad.decompilers.DecompilationEngine;
import net.stevechaloner.intellijad.decompilers.DecompilationException;
import net.stevechaloner.intellijad.decompilers.DecompilationResult;
import net.stevechaloner.intellijad.decompilers.Decompiler;
import net.stevechaloner.intellijad.decompilers.FileSystemDecompiler;
import net.stevechaloner.intellijad.environment.EnvironmentContext;
import net.stevechaloner.intellijad.environment.EnvironmentValidator;
import net.stevechaloner.intellijad.environment.ValidationResult;
import net.stevechaloner.intellijad.util.AppInvoker;
import net.stevechaloner.intellijad.util.FileSystemUtil;
import net.stevechaloner.intellijad.util.PluginUtil;
import org.jetbrains.annotations.NotNull;
package net.stevechaloner.intellijad;


/**
 * Created by Lukasz on 25.02.14.
 */
public class DecompilationTask extends Task.Modal implements Callable<DecompilationResult> {
    private final Logger LOG = Logger.getInstance(getClass());

    private final IntelliJad intelliJad;
    private final EnvironmentContext envContext;
    private final DecompilationDescriptor descriptor;
    private final AppInvoker appInvoker;
    private final ConsoleManager consoleManager;

    private FutureTask<DecompilationResult> result;

    private ProgressIndicator indicator;

    public DecompilationTask(IntelliJad intelliJad, EnvironmentContext envContext, DecompilationDescriptor descriptor) {
        super(envContext.getProject(), IntelliJadResourceBundle.message("message.decompile.text.busyText"), false);
        this.intelliJad = intelliJad;
        this.envContext = envContext;
        this.descriptor = descriptor;
        appInvoker = intelliJad.getAppInvoker();
        consoleManager = intelliJad.getConsoleManager();

        result = new FutureTask<DecompilationResult>(this);
    }

    public Future<DecompilationResult> result() {
        return result;
    }

    private void reopenEditor(final FileEditorManager editorManager, final VirtualFile file) {
        appInvoker.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                editorManager.closeFile(descriptor.getClassFile());
                editorManager.openFile(file, true);
            }
        });
    }

    private SdkHandler sdkHandler() {
        return SdkHandler.create(ApplicationManager.getApplication());
    }
    
    @Override
    public DecompilationResult call() throws Exception {
        indicator.setFraction(0.0);
        indicator.setIndeterminate(true);

        final boolean debug = LOG.isDebugEnabled();

        long startTime = System.currentTimeMillis();
        DecompilationResult result = new DecompilationResult();
        Project project = envContext.getProject();

        // this allows recovery from a canProjectClose method vetoed by another manager
        if (!intelliJad.isPrimed(project)) {
            intelliJad.primeProject(project);
        }


        IntelliJadConsole console = consoleManager.getConsole(project);
        ConsoleContext consoleContext = console.createConsoleContext("message.class",
                descriptor.getClassName());
        Config config = PluginUtil.getConfig(project);