cmdline/tool/src/org/netbeans/modules/jackpot30/cmdline/Main.java
author Jan Lahoda <jlahoda@netbeans.org>
Thu, 19 Dec 2013 15:43:00 +0100
branchrelease74
changeset 985 fb9da7d66c9f
parent 976 f2cfa4472600
child 996 5c2ff9a779a8
permissions -rw-r--r--
Suppress the loggers as soon as possible
jlahoda@480
     1
/*
jlahoda@480
     2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
jlahoda@480
     3
 *
jlahoda@717
     4
 * Copyright 2010-2011 Sun Microsystems, Inc. All rights reserved.
jlahoda@480
     5
 *
jlahoda@480
     6
 * The contents of this file are subject to the terms of either the GNU
jlahoda@480
     7
 * General Public License Version 2 only ("GPL") or the Common
jlahoda@480
     8
 * Development and Distribution License("CDDL") (collectively, the
jlahoda@480
     9
 * "License"). You may not use this file except in compliance with the
jlahoda@480
    10
 * License. You can obtain a copy of the License at
jlahoda@480
    11
 * http://www.netbeans.org/cddl-gplv2.html
jlahoda@480
    12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
jlahoda@480
    13
 * specific language governing permissions and limitations under the
jlahoda@480
    14
 * License.  When distributing the software, include this License Header
jlahoda@480
    15
 * Notice in each file and include the License file at
jlahoda@480
    16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
jlahoda@480
    17
 * particular file as subject to the "Classpath" exception as provided
jlahoda@480
    18
 * by Sun in the GPL Version 2 section of the License file that
jlahoda@480
    19
 * accompanied this code. If applicable, add the following below the
jlahoda@480
    20
 * License Header, with the fields enclosed by brackets [] replaced by
jlahoda@480
    21
 * your own identifying information:
jlahoda@480
    22
 * "Portions Copyrighted [year] [name of copyright owner]"
jlahoda@480
    23
 *
jlahoda@480
    24
 * If you wish your version of this file to be governed by only the CDDL
jlahoda@480
    25
 * or only the GPL Version 2, indicate your decision by adding
jlahoda@480
    26
 * "[Contributor] elects to include this software in this distribution
jlahoda@480
    27
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
jlahoda@480
    28
 * single choice of license, a recipient has the option to distribute
jlahoda@480
    29
 * your version of this file under either the CDDL, the GPL Version 2 or
jlahoda@480
    30
 * to extend the choice of license to its licensees as provided above.
jlahoda@480
    31
 * However, if you add GPL Version 2 code and therefore, elected the GPL
jlahoda@480
    32
 * Version 2 license, then the option applies only if the new code is
jlahoda@480
    33
 * made subject to such option by the copyright holder.
jlahoda@480
    34
 *
jlahoda@480
    35
 * Contributor(s):
jlahoda@480
    36
 *
jlahoda@717
    37
 * Portions Copyrighted 2010-2011 Sun Microsystems, Inc.
jlahoda@480
    38
 */
jlahoda@480
    39
jlahoda@480
    40
package org.netbeans.modules.jackpot30.cmdline;
jlahoda@480
    41
jlahoda@800
    42
import java.awt.BorderLayout;
jlahoda@800
    43
import java.awt.event.ActionEvent;
jlahoda@800
    44
import java.awt.event.ActionListener;
jlahoda@543
    45
import java.io.BufferedWriter;
jlahoda@480
    46
import java.io.File;
jlahoda@493
    47
import java.io.FileOutputStream;
jlahoda@480
    48
import java.io.IOException;
jlahoda@543
    49
import java.io.OutputStreamWriter;
jlahoda@494
    50
import java.io.PrintStream;
jlahoda@543
    51
import java.io.Writer;
jlahoda@800
    52
import java.lang.reflect.InvocationTargetException;
jlahoda@480
    53
import java.net.URL;
jlahoda@480
    54
import java.util.ArrayList;
jlahoda@740
    55
import java.util.Arrays;
jlahoda@480
    56
import java.util.Collection;
jlahoda@709
    57
import java.util.HashMap;
jlahoda@505
    58
import java.util.Iterator;
jlahoda@480
    59
import java.util.LinkedList;
jlahoda@480
    60
import java.util.List;
jlahoda@709
    61
import java.util.Map;
jlahoda@480
    62
import java.util.Map.Entry;
jlahoda@480
    63
import java.util.Set;
jlahoda@480
    64
import java.util.TreeSet;
jlahoda@480
    65
import java.util.concurrent.atomic.AtomicBoolean;
jlahoda@493
    66
import java.util.logging.Level;
jlahoda@493
    67
import java.util.logging.Logger;
jlahoda@800
    68
import java.util.prefs.BackingStoreException;
jlahoda@505
    69
import java.util.prefs.Preferences;
jlahoda@480
    70
import java.util.regex.Pattern;
jlahoda@800
    71
import javax.swing.JCheckBox;
jlahoda@800
    72
import javax.swing.JDialog;
jlahoda@800
    73
import javax.swing.JOptionPane;
jlahoda@800
    74
import javax.swing.JPanel;
jlahoda@800
    75
import javax.swing.SwingUtilities;
jlahoda@480
    76
import javax.swing.event.ChangeListener;
jlahoda@480
    77
import joptsimple.ArgumentAcceptingOptionSpec;
jlahoda@480
    78
import joptsimple.OptionException;
jlahoda@480
    79
import joptsimple.OptionParser;
jlahoda@480
    80
import joptsimple.OptionSet;
jlahoda@480
    81
import org.netbeans.api.java.classpath.ClassPath;
jlahoda@922
    82
import org.netbeans.api.java.classpath.GlobalPathRegistry;
jlahoda@503
    83
import org.netbeans.api.java.source.CompilationController;
jlahoda@480
    84
import org.netbeans.api.java.source.ModificationResult;
jlahoda@480
    85
import org.netbeans.core.startup.MainLookup;
jlahoda@795
    86
import org.netbeans.modules.jackpot30.ui.settings.XMLHintPreferences;
jlahoda@889
    87
import org.netbeans.modules.java.hints.jackpot.spi.PatternConvertor;
jlahoda@740
    88
import org.netbeans.modules.java.hints.providers.spi.HintDescription;
jlahoda@740
    89
import org.netbeans.modules.java.hints.providers.spi.HintMetadata;
jlahoda@740
    90
import org.netbeans.modules.java.hints.spiimpl.MessageImpl;
jlahoda@740
    91
import org.netbeans.modules.java.hints.spiimpl.RulesManager;
jlahoda@740
    92
import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch;
jlahoda@740
    93
import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.BatchResult;
jlahoda@740
    94
import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.Folder;
jlahoda@740
    95
import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.Resource;
jlahoda@740
    96
import org.netbeans.modules.java.hints.spiimpl.batch.BatchSearch.VerifiedSpansCallBack;
jlahoda@740
    97
import org.netbeans.modules.java.hints.spiimpl.batch.BatchUtilities;
jlahoda@740
    98
import org.netbeans.modules.java.hints.spiimpl.batch.ProgressHandleWrapper;
jlahoda@740
    99
import org.netbeans.modules.java.hints.spiimpl.batch.ProgressHandleWrapper.ProgressHandleAbstraction;
jlahoda@740
   100
import org.netbeans.modules.java.hints.spiimpl.batch.Scopes;
jlahoda@800
   101
import org.netbeans.modules.java.hints.spiimpl.options.HintsPanel;
jlahoda@740
   102
import org.netbeans.modules.java.hints.spiimpl.options.HintsSettings;
jlahoda@800
   103
import org.netbeans.modules.java.hints.spiimpl.refactoring.Utilities.ClassPathBasedHintWrapper;
jlahoda@480
   104
import org.netbeans.modules.java.source.parsing.JavaPathRecognizer;
jlahoda@480
   105
import org.netbeans.modules.parsing.impl.indexing.CacheFolder;
jlahoda@480
   106
import org.netbeans.modules.parsing.impl.indexing.RepositoryUpdater;
jlahoda@503
   107
import org.netbeans.spi.editor.hints.ErrorDescription;
jlahoda@480
   108
import org.netbeans.spi.java.classpath.ClassPathProvider;
jlahoda@480
   109
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
jlahoda@740
   110
import org.netbeans.spi.java.hints.Hint.Kind;
jlahoda@480
   111
import org.netbeans.spi.java.queries.SourceLevelQueryImplementation2;
jlahoda@480
   112
import org.openide.filesystems.FileObject;
jlahoda@480
   113
import org.openide.filesystems.FileStateInvalidException;
jlahoda@480
   114
import org.openide.filesystems.FileUtil;
jlahoda@800
   115
import org.openide.util.Exceptions;
jlahoda@480
   116
import org.openide.util.Lookup;
jlahoda@795
   117
import org.openide.util.NbPreferences;
jlahoda@480
   118
import org.openide.util.RequestProcessor;
jlahoda@480
   119
import org.openide.util.lookup.Lookups;
jlahoda@480
   120
import org.openide.util.lookup.ProxyLookup;
jlahoda@480
   121
import org.openide.util.lookup.ServiceProvider;
jlahoda@480
   122
jlahoda@480
   123
/**
jlahoda@480
   124
 *
jlahoda@480
   125
 * @author lahvac
jlahoda@480
   126
 */
jlahoda@480
   127
public class Main {
jlahoda@480
   128
jlahoda@795
   129
    private static final String OPTION_APPLY = "apply";
jlahoda@503
   130
    private static final String OPTION_NO_APPLY = "no-apply";
jlahoda@766
   131
    private static final String SOURCE_LEVEL_DEFAULT = "1.7";
jlahoda@766
   132
    private static final String ACCEPTABLE_SOURCE_LEVEL_PATTERN = "(1\\.)?[2-9][0-9]*";
jlahoda@503
   133
    
jlahoda@480
   134
    public static void main(String... args) throws IOException, ClassNotFoundException {
jlahoda@480
   135
        System.exit(compile(args));
jlahoda@480
   136
    }
jlahoda@480
   137
jlahoda@480
   138
    public static int compile(String... args) throws IOException, ClassNotFoundException {
jlahoda@480
   139
        System.setProperty("netbeans.user", "/tmp/tmp-foo");
jlahoda@922
   140
jlahoda@480
   141
        OptionParser parser = new OptionParser();
jlahoda@480
   142
//        ArgumentAcceptingOptionSpec<File> projects = parser.accepts("project", "project(s) to refactor").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class);
jlahoda@480
   143
        ArgumentAcceptingOptionSpec<File> classpath = parser.accepts("classpath", "classpath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class);
jlahoda@480
   144
        ArgumentAcceptingOptionSpec<File> bootclasspath = parser.accepts("bootclasspath", "bootclasspath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class);
jlahoda@489
   145
        ArgumentAcceptingOptionSpec<File> sourcepath = parser.accepts("sourcepath", "sourcepath").withRequiredArg().withValuesSeparatedBy(File.pathSeparatorChar).ofType(File.class);
jlahoda@707
   146
        ArgumentAcceptingOptionSpec<File> cache = parser.accepts("cache", "a cache directory to store working data").withRequiredArg().ofType(File.class);
jlahoda@493
   147
        ArgumentAcceptingOptionSpec<File> out = parser.accepts("out", "output diff").withRequiredArg().ofType(File.class);
jlahoda@795
   148
        ArgumentAcceptingOptionSpec<File> configFile = parser.accepts("config-file", "configuration file").withRequiredArg().ofType(File.class);
jlahoda@480
   149
        ArgumentAcceptingOptionSpec<String> hint = parser.accepts("hint", "hint name").withRequiredArg().ofType(String.class);
jlahoda@505
   150
        ArgumentAcceptingOptionSpec<String> config = parser.accepts("config", "configurations").withRequiredArg().ofType(String.class);
jlahoda@766
   151
        ArgumentAcceptingOptionSpec<String> source = parser.accepts("source", "source level").withRequiredArg().ofType(String.class).defaultsTo(SOURCE_LEVEL_DEFAULT);
jlahoda@889
   152
        ArgumentAcceptingOptionSpec<File> hintFile = parser.accepts("hint-file", "file with rules that should be performed").withRequiredArg().ofType(File.class);
jlahoda@480
   153
jlahoda@480
   154
        parser.accepts("list", "list all known hints");
jlahoda@494
   155
        parser.accepts("progress", "show progress");
jlahoda@494
   156
        parser.accepts("debug", "enable debugging loggers");
jlahoda@480
   157
        parser.accepts("help", "prints this help");
jlahoda@503
   158
        parser.accepts(OPTION_NO_APPLY, "do not apply changes - only print locations were the hint would be applied");
jlahoda@795
   159
        parser.accepts(OPTION_APPLY, "apply changes");
jlahoda@800
   160
        parser.accepts("show-gui", "show configuration dialog");
jlahoda@480
   161
jlahoda@480
   162
        OptionSet parsed;
jlahoda@480
   163
jlahoda@480
   164
        try {
jlahoda@480
   165
            parsed = parser.parse(args);
jlahoda@480
   166
        } catch (OptionException ex) {
jlahoda@480
   167
            System.err.println(ex.getLocalizedMessage());
jlahoda@480
   168
            parser.printHelpOn(System.out);
jlahoda@480
   169
            return 1;
jlahoda@480
   170
        }
jlahoda@480
   171
jlahoda@985
   172
        if (!parsed.has("debug")) {
jlahoda@985
   173
            prepareLoggers();
jlahoda@985
   174
        }
jlahoda@985
   175
jlahoda@480
   176
        if (parsed.has("help")) {
jlahoda@480
   177
            parser.printHelpOn(System.out);
jlahoda@480
   178
            return 0;
jlahoda@480
   179
        }
jlahoda@480
   180
jlahoda@922
   181
        List<FileObject> roots = new ArrayList<FileObject>();
jlahoda@922
   182
        List<Folder> rootFolders = new ArrayList<Folder>();
jlahoda@922
   183
jlahoda@922
   184
        for (String sr : parsed.nonOptionArguments()) {
jlahoda@922
   185
            File r = new File(sr);
jlahoda@922
   186
            FileObject root = FileUtil.toFileObject(r);
jlahoda@922
   187
jlahoda@922
   188
            if (root != null) {
jlahoda@922
   189
                roots.add(root);
jlahoda@922
   190
                rootFolders.add(new Folder(root));
jlahoda@922
   191
            }
jlahoda@922
   192
        }
jlahoda@922
   193
jlahoda@922
   194
        ClassPath bootCP = createClassPath(parsed.has(bootclasspath) ? parsed.valuesOf(bootclasspath) : null, createDefaultBootClassPath());
jlahoda@922
   195
        ClassPath compileCP = createClassPath(parsed.has(classpath) ? parsed.valuesOf(classpath) : null, ClassPath.EMPTY);
jlahoda@922
   196
        final ClassPath sourceCP = createClassPath(parsed.has(sourcepath) ? parsed.valuesOf(sourcepath) : null, ClassPathSupport.createClassPath(roots.toArray(new FileObject[0])));
jlahoda@922
   197
        final ClassPath binaryCP = ClassPathSupport.createProxyClassPath(bootCP, compileCP);
jlahoda@922
   198
jlahoda@800
   199
        if (parsed.has("show-gui")) {
jlahoda@800
   200
            if (parsed.has(configFile)) {
jlahoda@800
   201
                final File settingsFile = parsed.valueOf(configFile);
jlahoda@800
   202
                try {
jlahoda@800
   203
                    SwingUtilities.invokeAndWait(new Runnable() {
jlahoda@800
   204
                        @Override public void run() {
jlahoda@800
   205
                            try {
jlahoda@922
   206
                                showGUICustomizer(settingsFile, binaryCP, sourceCP);
jlahoda@800
   207
                            } catch (IOException ex) {
jlahoda@800
   208
                                Exceptions.printStackTrace(ex);
jlahoda@800
   209
                            } catch (BackingStoreException ex) {
jlahoda@800
   210
                                Exceptions.printStackTrace(ex);
jlahoda@800
   211
                            }
jlahoda@800
   212
                        }
jlahoda@800
   213
                    });
jlahoda@800
   214
                } catch (InterruptedException ex) {
jlahoda@800
   215
                    Exceptions.printStackTrace(ex);
jlahoda@800
   216
                } catch (InvocationTargetException ex) {
jlahoda@800
   217
                    Exceptions.printStackTrace(ex);
jlahoda@800
   218
                }
jlahoda@800
   219
jlahoda@800
   220
                return 0;
jlahoda@800
   221
            } else {
jlahoda@800
   222
                System.err.println("show-gui requires config-file");
jlahoda@800
   223
                return 1;
jlahoda@800
   224
            }
jlahoda@800
   225
        }
jlahoda@800
   226
jlahoda@480
   227
        File cacheDir = parsed.valueOf(cache);
jlahoda@480
   228
        boolean deleteCacheDir = false;
jlahoda@480
   229
jlahoda@480
   230
        try {
jlahoda@480
   231
            if (cacheDir == null) {
jlahoda@480
   232
                cacheDir = File.createTempFile("jackpot", "cache");
jlahoda@480
   233
                cacheDir.delete();
jlahoda@480
   234
                if (!(deleteCacheDir = cacheDir.mkdirs())) {
jlahoda@480
   235
                    System.err.println("cannot create temporary cache");
jlahoda@480
   236
                    return 1;
jlahoda@480
   237
                }
jlahoda@480
   238
            }
jlahoda@480
   239
jlahoda@480
   240
            if (cacheDir.isFile()) {
jlahoda@480
   241
                System.err.println("cache directory exists and is a file");
jlahoda@480
   242
                return 1;
jlahoda@480
   243
            }
jlahoda@480
   244
jlahoda@480
   245
            String[] cacheDirContent = cacheDir.list();
jlahoda@480
   246
jlahoda@480
   247
            if (cacheDirContent != null && cacheDirContent.length > 0 && !new File(cacheDir, "segments").exists()) {
jlahoda@480
   248
                System.err.println("cache directory is not empty, but was not created by this tool");
jlahoda@480
   249
                return 1;
jlahoda@480
   250
            }
jlahoda@480
   251
jlahoda@480
   252
            cacheDir.mkdirs();
jlahoda@480
   253
jlahoda@480
   254
            CacheFolder.setCacheFolder(FileUtil.toFileObject(FileUtil.normalizeFile(cacheDir)));
jlahoda@480
   255
jlahoda@480
   256
            org.netbeans.api.project.ui.OpenProjects.getDefault().getOpenProjects();
jlahoda@480
   257
            RepositoryUpdater.getDefault().start(false);
jlahoda@480
   258
jlahoda@709
   259
            if (roots.isEmpty() && !parsed.has("list")) {
jlahoda@480
   260
                System.err.println("no source roots to work on");
jlahoda@480
   261
                return 1;
jlahoda@480
   262
            }
jlahoda@480
   263
jlahoda@707
   264
            Iterable<? extends HintDescription> hints;
jlahoda@707
   265
            
jlahoda@709
   266
            if (parsed.has("list")) {
jlahoda@737
   267
                printHints(sourceCP, binaryCP);
jlahoda@709
   268
                return 0;
jlahoda@709
   269
            }
jlahoda@709
   270
jlahoda@795
   271
            Preferences settingsFromConfigFile;
jlahoda@952
   272
            Preferences hintSettingsPreferences;
jlahoda@952
   273
            HintsSettings hintSettings;
jlahoda@795
   274
            boolean apply;
jlahoda@795
   275
jlahoda@795
   276
            if (parsed.has(configFile)) {
jlahoda@795
   277
                settingsFromConfigFile = XMLHintPreferences.from(parsed.valueOf(configFile));
jlahoda@952
   278
                hintSettings = HintsSettings.createPreferencesBasedHintsSettings(hintSettingsPreferences = settingsFromConfigFile.node("settings"), false, null);
jlahoda@795
   279
                apply = settingsFromConfigFile.getBoolean("apply", false);
jlahoda@795
   280
            } else {
jlahoda@795
   281
                settingsFromConfigFile = null;
jlahoda@952
   282
                hintSettings = HintsSettings.createPreferencesBasedHintsSettings(hintSettingsPreferences = NbPreferences.root().node("tempSettings"), false, null);
jlahoda@795
   283
                apply = false;
jlahoda@795
   284
            }
jlahoda@795
   285
jlahoda@707
   286
            if (parsed.has(hint)) {
jlahoda@795
   287
                if (settingsFromConfigFile != null) {
jlahoda@889
   288
                    System.err.println("cannot specify --hint and --config-file together");
jlahoda@795
   289
                    return 1;
jlahoda@795
   290
                }
jlahoda@795
   291
                hints = findHints(sourceCP, binaryCP, parsed.valueOf(hint), hintSettings);
jlahoda@889
   292
            } else if (parsed.has(hintFile)) {
jlahoda@889
   293
                if (settingsFromConfigFile != null) {
jlahoda@889
   294
                    System.err.println("cannot specify --hint-file and --config-file together");
jlahoda@889
   295
                    return 1;
jlahoda@889
   296
                }
jlahoda@889
   297
                FileObject hintFileFO = FileUtil.toFileObject(parsed.valueOf(hintFile));
jlahoda@889
   298
                assert hintFileFO != null;
jlahoda@889
   299
                hints = PatternConvertor.create(hintFileFO.asText());
jlahoda@889
   300
                for (HintDescription hd : hints) {
jlahoda@952
   301
                    hintSettings.setEnabled(hd.getMetadata(), true);
jlahoda@889
   302
                }
jlahoda@707
   303
            } else {
jlahoda@952
   304
                hints = readHints(sourceCP, binaryCP, hintSettings, hintSettingsPreferences, settingsFromConfigFile != null ? settingsFromConfigFile.getBoolean("runDeclarative", true) : true);
jlahoda@480
   305
            }
jlahoda@480
   306
jlahoda@707
   307
            if (parsed.has(config) && !parsed.has(hint)) {
jlahoda@707
   308
                System.err.println("--config cannot specified when no hint is specified");
jlahoda@707
   309
                return 1;
jlahoda@707
   310
            }
jlahoda@707
   311
jlahoda@505
   312
            if (parsed.has(config)) {
jlahoda@505
   313
                Iterator<? extends HintDescription> hit = hints.iterator();
jlahoda@505
   314
                HintDescription hd = hit.next();
jlahoda@505
   315
jlahoda@505
   316
                if (hit.hasNext()) {
jlahoda@505
   317
                    System.err.println("--config cannot specified when more than one hint is specified");
jlahoda@505
   318
jlahoda@505
   319
                    return 1;
jlahoda@505
   320
                }
jlahoda@505
   321
jlahoda@952
   322
                Preferences prefs = hintSettings.getHintPreferences(hd.getMetadata());
jlahoda@505
   323
jlahoda@505
   324
                boolean stop = false;
jlahoda@505
   325
jlahoda@505
   326
                for (String c : parsed.valuesOf(config)) {
jlahoda@505
   327
                    int assign = c.indexOf('=');
jlahoda@505
   328
jlahoda@505
   329
                    if (assign == (-1)) {
jlahoda@505
   330
                        System.err.println("configuration option is missing '=' (" + c + ")");
jlahoda@505
   331
                        stop = true;
jlahoda@505
   332
                        continue;
jlahoda@505
   333
                    }
jlahoda@505
   334
jlahoda@505
   335
                    prefs.put(c.substring(0, assign), c.substring(assign + 1));
jlahoda@505
   336
                }
jlahoda@505
   337
jlahoda@505
   338
                if (stop) {
jlahoda@505
   339
                    return 1;
jlahoda@505
   340
                }
jlahoda@505
   341
            }
jlahoda@505
   342
jlahoda@766
   343
            String sourceLevel = parsed.valueOf(source);
jlahoda@766
   344
jlahoda@766
   345
            if (!Pattern.compile(ACCEPTABLE_SOURCE_LEVEL_PATTERN).matcher(sourceLevel).matches()) {
jlahoda@766
   346
                System.err.println("unrecognized source level specification: " + sourceLevel);
jlahoda@766
   347
                return 1;
jlahoda@766
   348
            }
jlahoda@795
   349
jlahoda@795
   350
            if (parsed.has(OPTION_NO_APPLY)) {
jlahoda@795
   351
                apply = false;
jlahoda@795
   352
            } else if (parsed.has(OPTION_APPLY)) {
jlahoda@795
   353
                apply = true;
jlahoda@795
   354
            }
jlahoda@766
   355
            
jlahoda@876
   356
            if (apply && !hints.iterator().hasNext()) {
jlahoda@876
   357
                System.err.println("no hints specified");
jlahoda@876
   358
                return 1;
jlahoda@876
   359
            }
jlahoda@876
   360
jlahoda@877
   361
            Object[] register2Lookup = new Object[] {
jlahoda@877
   362
                new ClassPathProviderImpl(bootCP, compileCP, sourceCP),
jlahoda@877
   363
                new JavaPathRecognizer(),
jlahoda@877
   364
                new SourceLevelQueryImpl(sourceCP, sourceLevel)
jlahoda@877
   365
            };
jlahoda@877
   366
jlahoda@480
   367
            try {
jlahoda@877
   368
                for (Object toRegister : register2Lookup) {
jlahoda@877
   369
                    MainLookup.register(toRegister);
jlahoda@877
   370
                }
jlahoda@795
   371
jlahoda@503
   372
                ProgressHandleWrapper progress = parsed.has("progress") ? new ProgressHandleWrapper(new ConsoleProgressHandleAbstraction(), 1) : new ProgressHandleWrapper(1);
jlahoda@503
   373
jlahoda@795
   374
                if (apply) {
jlahoda@952
   375
                    apply(hints, rootFolders.toArray(new Folder[0]), progress, hintSettings, parsed.valueOf(out));
jlahoda@795
   376
                } else {
jlahoda@952
   377
                    findOccurrences(hints, rootFolders.toArray(new Folder[0]), progress, hintSettings, parsed.valueOf(out));
jlahoda@503
   378
                }
jlahoda@480
   379
            } catch (Throwable e) {
jlahoda@480
   380
                e.printStackTrace();
jlahoda@877
   381
            } finally {
jlahoda@877
   382
                for (Object toUnRegister : register2Lookup) {
jlahoda@877
   383
                    MainLookup.unregister(toUnRegister);
jlahoda@877
   384
                }
jlahoda@480
   385
            }
jlahoda@480
   386
        } finally {
jlahoda@480
   387
            if (deleteCacheDir) {
jlahoda@480
   388
                FileObject cacheDirFO = FileUtil.toFileObject(cacheDir);
jlahoda@480
   389
jlahoda@480
   390
                if (cacheDirFO != null) {
jlahoda@480
   391
                    //TODO: would be better to do j.i.File.delete():
jlahoda@480
   392
                    cacheDirFO.delete();
jlahoda@480
   393
                }
jlahoda@480
   394
            }
jlahoda@480
   395
        }
jlahoda@480
   396
jlahoda@480
   397
        return 0;
jlahoda@480
   398
    }
jlahoda@480
   399
jlahoda@737
   400
    private static Map<HintMetadata, Collection<? extends HintDescription>> listHints(ClassPath sourceFrom, ClassPath binaryFrom) {
jlahoda@709
   401
        Map<HintMetadata, Collection<? extends HintDescription>> result = new HashMap<HintMetadata, Collection<? extends HintDescription>>();
jlahoda@709
   402
jlahoda@740
   403
        for (Entry<HintMetadata, ? extends Collection<? extends HintDescription>> entry: RulesManager.getInstance().readHints(null, Arrays.asList(sourceFrom, binaryFrom), null).entrySet()) {
jlahoda@709
   404
            result.put(entry.getKey(), entry.getValue());
jlahoda@709
   405
        }
jlahoda@709
   406
jlahoda@709
   407
        return result;
jlahoda@709
   408
    }
jlahoda@709
   409
    
jlahoda@952
   410
    private static Iterable<? extends HintDescription> findHints(ClassPath sourceFrom, ClassPath binaryFrom, String name, HintsSettings toEnableIn) {
jlahoda@480
   411
        List<HintDescription> descs = new LinkedList<HintDescription>();
jlahoda@480
   412
jlahoda@737
   413
        for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) {
jlahoda@480
   414
            if (e.getKey().displayName.equals(name)) {
jlahoda@480
   415
                descs.addAll(e.getValue());
jlahoda@952
   416
                toEnableIn.setEnabled(e.getKey(), true);
jlahoda@480
   417
            }
jlahoda@480
   418
        }
jlahoda@480
   419
jlahoda@480
   420
        return descs;
jlahoda@480
   421
    }
jlahoda@480
   422
jlahoda@952
   423
    private static Iterable<? extends HintDescription> allHints(ClassPath sourceFrom, ClassPath binaryFrom, HintsSettings toEnableIn) {
jlahoda@707
   424
        List<HintDescription> descs = new LinkedList<HintDescription>();
jlahoda@707
   425
jlahoda@737
   426
        for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) {
jlahoda@740
   427
            if (e.getKey().kind != Kind.INSPECTION) continue;
jlahoda@707
   428
            if (!e.getKey().enabled) continue;
jlahoda@707
   429
            descs.addAll(e.getValue());
jlahoda@952
   430
            toEnableIn.setEnabled(e.getKey(), true);
jlahoda@795
   431
        }
jlahoda@795
   432
jlahoda@795
   433
        return descs;
jlahoda@795
   434
    }
jlahoda@795
   435
jlahoda@952
   436
    private static Iterable<? extends HintDescription> readHints(ClassPath sourceFrom, ClassPath binaryFrom, HintsSettings toEnableIn, Preferences toEnableInPreferencesHack, boolean declarativeEnabledByDefault) {
jlahoda@795
   437
        Map<HintMetadata, ? extends Collection<? extends HintDescription>> hardcoded = RulesManager.getInstance().readHints(null, Arrays.<ClassPath>asList(), null);
jlahoda@922
   438
        Map<HintMetadata, ? extends Collection<? extends HintDescription>> all = RulesManager.getInstance().readHints(null, Arrays.asList(sourceFrom, binaryFrom), null);
jlahoda@795
   439
        List<HintDescription> descs = new LinkedList<HintDescription>();
jlahoda@795
   440
jlahoda@795
   441
        for (Entry<HintMetadata, ? extends Collection<? extends HintDescription>> entry: all.entrySet()) {
jlahoda@795
   442
            if (hardcoded.containsKey(entry.getKey())) {
jlahoda@952
   443
                if (toEnableIn.isEnabled(entry.getKey())) {
jlahoda@795
   444
                    descs.addAll(entry.getValue());
jlahoda@795
   445
                }
jlahoda@795
   446
            } else {
jlahoda@952
   447
                if (/*XXX: hack*/toEnableInPreferencesHack.node(entry.getKey().id).getBoolean("enabled", declarativeEnabledByDefault)) {
jlahoda@922
   448
                    descs.addAll(entry.getValue());
jlahoda@922
   449
                }
jlahoda@795
   450
            }
jlahoda@707
   451
        }
jlahoda@707
   452
jlahoda@707
   453
        return descs;
jlahoda@707
   454
    }
jlahoda@707
   455
jlahoda@494
   456
    private static final Logger TOP_LOGGER = Logger.getLogger("");
jlahoda@494
   457
jlahoda@494
   458
    private static void prepareLoggers() {
jlahoda@494
   459
        TOP_LOGGER.setLevel(Level.OFF);
jlahoda@767
   460
        System.setProperty("RepositoryUpdate.increasedLogLevel", "OFF");
jlahoda@494
   461
    }
jlahoda@494
   462
    
jlahoda@952
   463
    private static void findOccurrences(Iterable<? extends HintDescription> descs, Folder[] sourceRoot, ProgressHandleWrapper progress, HintsSettings settings, File out) throws IOException {
jlahoda@875
   464
        final Map<String, String> id2DisplayName = new HashMap<String, String>();
jlahoda@875
   465
jlahoda@875
   466
        for (HintDescription hd : descs) {
jlahoda@875
   467
            if (hd.getMetadata() != null) {
jlahoda@875
   468
                id2DisplayName.put(hd.getMetadata().id, hd.getMetadata().displayName);
jlahoda@875
   469
            }
jlahoda@875
   470
        }
jlahoda@875
   471
jlahoda@503
   472
        ProgressHandleWrapper w = progress.startNextPartWithEmbedding(1, 1);
jlahoda@952
   473
        BatchResult occurrences = BatchSearch.findOccurrences(descs, Scopes.specifiedFoldersScope(sourceRoot), w, settings);
jlahoda@503
   474
jlahoda@503
   475
        List<MessageImpl> problems = new LinkedList<MessageImpl>();
jlahoda@503
   476
        BatchSearch.getVerifiedSpans(occurrences, progress, new VerifiedSpansCallBack() {
jlahoda@503
   477
            @Override public void groupStarted() {}
jlahoda@503
   478
            @Override public boolean spansVerified(CompilationController wc, Resource r, Collection<? extends ErrorDescription> hints) throws Exception {
jlahoda@503
   479
                for (ErrorDescription ed : hints) {
jlahoda@875
   480
                    print(ed, id2DisplayName);
jlahoda@503
   481
                }
jlahoda@503
   482
                return true;
jlahoda@503
   483
            }
jlahoda@503
   484
            @Override public void groupFinished() {}
jlahoda@503
   485
            @Override public void cannotVerifySpan(Resource r) {
jlahoda@503
   486
                //TODO: ignored - what to do?
jlahoda@503
   487
            }
jlahoda@712
   488
        }, problems, new AtomicBoolean());
jlahoda@503
   489
    }
jlahoda@503
   490
jlahoda@875
   491
    private static void print(ErrorDescription error, Map<String, String> id2DisplayName) throws IOException {
jlahoda@503
   492
        int lineNumber = error.getRange().getBegin().getLine();
jlahoda@503
   493
        String line = error.getFile().asLines().get(lineNumber);
jlahoda@503
   494
        int column = error.getRange().getBegin().getColumn();
jlahoda@503
   495
        StringBuilder b = new StringBuilder();
jlahoda@503
   496
jlahoda@503
   497
        for (int i = 0; i < column; i++) {
jlahoda@503
   498
            if (Character.isWhitespace(line.charAt(i))) {
jlahoda@503
   499
                b.append(line.charAt(i));
jlahoda@503
   500
            } else {
jlahoda@503
   501
                b.append(' ');
jlahoda@503
   502
            }
jlahoda@503
   503
        }
jlahoda@503
   504
jlahoda@503
   505
        b.append('^');
jlahoda@503
   506
jlahoda@875
   507
        String id = error.getId();
jlahoda@875
   508
jlahoda@875
   509
        if (id != null && id.startsWith("text/x-java:")) {
jlahoda@875
   510
            id = id.substring("text/x-java:".length());
jlahoda@875
   511
        }
jlahoda@875
   512
jlahoda@875
   513
        String idDisplayName = id2DisplayName.get(id);
jlahoda@875
   514
jlahoda@875
   515
        if (idDisplayName == null) {
jlahoda@875
   516
            idDisplayName = "unknown";
jlahoda@875
   517
        }
jlahoda@875
   518
jlahoda@882
   519
        for (Entry<String, String> remap : toIdRemap.entrySet()) {
jlahoda@883
   520
            idDisplayName = idDisplayName.replace(remap.getKey(), remap.getValue());
jlahoda@882
   521
        }
jlahoda@882
   522
jlahoda@883
   523
        idDisplayName = idDisplayName.replaceAll("[^A-Za-z0-9]", "_").replaceAll("_+", "_");
jlahoda@883
   524
jlahoda@883
   525
        idDisplayName = "[" + idDisplayName + "] ";
jlahoda@875
   526
jlahoda@875
   527
        System.out.println(FileUtil.getFileDisplayName(error.getFile()) + ":" + (lineNumber + 1) + ": warning: " + idDisplayName + error.getDescription());
jlahoda@503
   528
        System.out.println(line);
jlahoda@503
   529
        System.out.println(b);
jlahoda@503
   530
    }
jlahoda@503
   531
jlahoda@882
   532
    private static final Map<String, String> toIdRemap = new HashMap<String, String>() {{
jlahoda@882
   533
        put("==", "equals");
jlahoda@882
   534
        put("!=", "not_equals");
jlahoda@882
   535
    }};
jlahoda@882
   536
jlahoda@952
   537
    private static void apply(Iterable<? extends HintDescription> descs, Folder[] sourceRoot, ProgressHandleWrapper progress, HintsSettings settings, File out) throws IOException {
jlahoda@503
   538
        ProgressHandleWrapper w = progress.startNextPartWithEmbedding(1, 1);
jlahoda@952
   539
        BatchResult occurrences = BatchSearch.findOccurrences(descs, Scopes.specifiedFoldersScope(sourceRoot), w, settings);
jlahoda@480
   540
jlahoda@480
   541
        List<MessageImpl> problems = new LinkedList<MessageImpl>();
jlahoda@480
   542
        Collection<ModificationResult> diffs = BatchUtilities.applyFixes(occurrences, w, new AtomicBoolean(), problems);
jlahoda@480
   543
jlahoda@493
   544
        if (out != null) {
jlahoda@543
   545
            Writer outS = null;
jlahoda@493
   546
jlahoda@493
   547
            try {
jlahoda@543
   548
                outS = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(out)));
jlahoda@493
   549
jlahoda@493
   550
                for (ModificationResult mr : diffs) {
jlahoda@737
   551
                    org.netbeans.modules.jackpot30.indexing.batch.BatchUtilities.exportDiff(mr, null, outS);
jlahoda@493
   552
                }
jlahoda@493
   553
            } finally {
jlahoda@493
   554
                try {
jlahoda@493
   555
                    outS.close();
jlahoda@493
   556
                } catch (IOException ex) {
jlahoda@493
   557
                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
jlahoda@493
   558
                }
jlahoda@493
   559
            }
jlahoda@493
   560
        } else {
jlahoda@493
   561
            for (ModificationResult mr : diffs) {
jlahoda@493
   562
                mr.commit();
jlahoda@493
   563
            }
jlahoda@480
   564
        }
jlahoda@480
   565
    }
jlahoda@480
   566
jlahoda@737
   567
    private static void printHints(ClassPath sourceFrom, ClassPath binaryFrom) throws IOException {
jlahoda@480
   568
        Set<String> hints = new TreeSet<String>();
jlahoda@480
   569
jlahoda@737
   570
        for (Entry<HintMetadata, Collection<? extends HintDescription>> e : listHints(sourceFrom, binaryFrom).entrySet()) {
jlahoda@480
   571
            hints.add(e.getKey().displayName);
jlahoda@480
   572
        }
jlahoda@480
   573
jlahoda@480
   574
        for (String h : hints) {
jlahoda@480
   575
            System.out.println(h);
jlahoda@480
   576
        }
jlahoda@480
   577
    }
jlahoda@480
   578
jlahoda@480
   579
    private static ClassPath createDefaultBootClassPath() throws IOException {
jlahoda@480
   580
        try {
jlahoda@480
   581
            String cp = System.getProperty("sun.boot.class.path");
jlahoda@480
   582
            List<URL> urls = new ArrayList<URL>();
jlahoda@480
   583
            String[] paths = cp.split(Pattern.quote(System.getProperty("path.separator")));
jlahoda@480
   584
jlahoda@480
   585
            for (String path : paths) {
jlahoda@480
   586
                File f = new File(path);
jlahoda@480
   587
jlahoda@480
   588
                if (!f.canRead())
jlahoda@480
   589
                    continue;
jlahoda@480
   590
jlahoda@480
   591
                FileObject fo = FileUtil.toFileObject(FileUtil.normalizeFile(f));
jlahoda@480
   592
jlahoda@480
   593
                if (FileUtil.isArchiveFile(fo)) {
jlahoda@480
   594
                    fo = FileUtil.getArchiveRoot(fo);
jlahoda@480
   595
                }
jlahoda@480
   596
jlahoda@480
   597
                if (fo != null) {
jlahoda@480
   598
                    urls.add(fo.getURL());
jlahoda@480
   599
                }
jlahoda@480
   600
            }
jlahoda@480
   601
jlahoda@480
   602
            return ClassPathSupport.createClassPath(urls.toArray(new URL[0]));
jlahoda@480
   603
        } catch (FileStateInvalidException e) {
jlahoda@480
   604
            throw e;
jlahoda@480
   605
        }
jlahoda@480
   606
    }
jlahoda@480
   607
jlahoda@480
   608
    private static ClassPath createClassPath(Iterable<? extends File> roots, ClassPath def) {
jlahoda@480
   609
        if (roots == null) return def;
jlahoda@480
   610
jlahoda@480
   611
        List<URL> rootURLs = new ArrayList<URL>();
jlahoda@480
   612
jlahoda@480
   613
        for (File r : roots) {
jlahoda@480
   614
            rootURLs.add(FileUtil.urlForArchiveOrDir(r));
jlahoda@480
   615
        }
jlahoda@480
   616
jlahoda@480
   617
        return ClassPathSupport.createClassPath(rootURLs.toArray(new URL[0]));
jlahoda@480
   618
    }
jlahoda@480
   619
jlahoda@922
   620
    private static void showGUICustomizer(File settingsFile, ClassPath binaryCP, ClassPath sourceCP) throws IOException, BackingStoreException {
jlahoda@922
   621
        GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, new ClassPath[] {binaryCP});
jlahoda@922
   622
        GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {sourceCP});
jlahoda@922
   623
        ClassPathBasedHintWrapper hints = new ClassPathBasedHintWrapper();
jlahoda@922
   624
        final Preferences p = XMLHintPreferences.from(settingsFile);
jlahoda@976
   625
        JPanel hintPanel = new HintsPanel(p.node("settings"), hints, true);
jlahoda@922
   626
        final JCheckBox runDeclarativeHints = new JCheckBox("Always Run Declarative Rules");
jlahoda@800
   627
jlahoda@922
   628
        runDeclarativeHints.setToolTipText("Always run the declarative rules found on classpath? (Only those selected above will be run when unchecked.)");
jlahoda@800
   629
        runDeclarativeHints.setSelected(p.getBoolean("runDeclarative", true));
jlahoda@800
   630
        runDeclarativeHints.addActionListener(new ActionListener() {
jlahoda@800
   631
            @Override public void actionPerformed(ActionEvent e) {
jlahoda@800
   632
                p.putBoolean("runDeclarative", runDeclarativeHints.isSelected());
jlahoda@800
   633
            }
jlahoda@800
   634
        });
jlahoda@800
   635
jlahoda@800
   636
        JPanel customizer = new JPanel(new BorderLayout());
jlahoda@800
   637
jlahoda@800
   638
        customizer.add(hintPanel, BorderLayout.CENTER);
jlahoda@800
   639
        customizer.add(runDeclarativeHints, BorderLayout.SOUTH);
jlahoda@800
   640
        JOptionPane jop = new JOptionPane(customizer, JOptionPane.PLAIN_MESSAGE);
jlahoda@800
   641
        JDialog dialog = jop.createDialog("Select Hints");
jlahoda@800
   642
jlahoda@800
   643
        jop.selectInitialValue();
jlahoda@800
   644
        dialog.setVisible(true);
jlahoda@800
   645
        dialog.dispose();
jlahoda@800
   646
jlahoda@800
   647
        Object result = jop.getValue();
jlahoda@800
   648
jlahoda@800
   649
        if (result.equals(JOptionPane.OK_OPTION)) {
jlahoda@800
   650
            p.flush();
jlahoda@800
   651
        }
jlahoda@800
   652
    }
jlahoda@800
   653
jlahoda@480
   654
    @ServiceProvider(service=Lookup.class)
jlahoda@480
   655
    public static final class LookupProviderImpl extends ProxyLookup {
jlahoda@480
   656
jlahoda@480
   657
        public LookupProviderImpl() {
jlahoda@480
   658
            super(Lookups.forPath("Services/AntBasedProjectTypes"));
jlahoda@480
   659
        }
jlahoda@480
   660
    }
jlahoda@480
   661
jlahoda@480
   662
    public static final class ClassPathProviderImpl implements ClassPathProvider {
jlahoda@480
   663
        private final ClassPath boot;
jlahoda@480
   664
        private final ClassPath compile;
jlahoda@480
   665
        private final ClassPath source;
jlahoda@480
   666
jlahoda@480
   667
        public ClassPathProviderImpl(ClassPath boot, ClassPath compile, ClassPath source) {
jlahoda@480
   668
            this.boot = boot;
jlahoda@480
   669
            this.compile = compile;
jlahoda@480
   670
            this.source = source;
jlahoda@480
   671
        }
jlahoda@480
   672
jlahoda@480
   673
        @Override
jlahoda@480
   674
        public ClassPath findClassPath(FileObject file, String type) {
jlahoda@480
   675
            if (source.findOwnerRoot(file) != null) {
jlahoda@480
   676
                if (ClassPath.BOOT.equals(type)) {
jlahoda@480
   677
                    return boot;
jlahoda@480
   678
                } else if (ClassPath.COMPILE.equals(type)) {
jlahoda@480
   679
                    return compile;
jlahoda@480
   680
                } else  if (ClassPath.SOURCE.equals(type)) {
jlahoda@480
   681
                    return source;
jlahoda@480
   682
                }
jlahoda@480
   683
            }
jlahoda@480
   684
jlahoda@480
   685
            return null;
jlahoda@480
   686
        }
jlahoda@480
   687
    }
jlahoda@480
   688
jlahoda@480
   689
    public static final class SourceLevelQueryImpl implements SourceLevelQueryImplementation2 {
jlahoda@480
   690
        private final ClassPath sourceCP;
jlahoda@480
   691
        private final Result sourceLevel;
jlahoda@480
   692
jlahoda@480
   693
        public SourceLevelQueryImpl(ClassPath sourceCP, final String sourceLevel) {
jlahoda@480
   694
            this.sourceCP = sourceCP;
jlahoda@480
   695
            this.sourceLevel = new Result() {
jlahoda@480
   696
                @Override public String getSourceLevel() {
jlahoda@480
   697
                    return sourceLevel;
jlahoda@480
   698
                }
jlahoda@480
   699
                @Override public void addChangeListener(ChangeListener listener) {}
jlahoda@480
   700
                @Override public void removeChangeListener(ChangeListener listener) {}
jlahoda@480
   701
            };
jlahoda@480
   702
        }
jlahoda@480
   703
jlahoda@480
   704
        @Override
jlahoda@480
   705
        public Result getSourceLevel(FileObject javaFile) {
jlahoda@480
   706
            if (sourceCP.findOwnerRoot(javaFile) != null) {
jlahoda@480
   707
                return sourceLevel;
jlahoda@480
   708
            } else {
jlahoda@480
   709
                return null;
jlahoda@480
   710
            }
jlahoda@480
   711
        }
jlahoda@480
   712
jlahoda@480
   713
    }
jlahoda@480
   714
jlahoda@480
   715
    private static final class ConsoleProgressHandleAbstraction implements ProgressHandleAbstraction {
jlahoda@480
   716
jlahoda@480
   717
        private final int width = 80;
jlahoda@480
   718
jlahoda@480
   719
        private int total = -1;
jlahoda@480
   720
        private int current = 0;
jlahoda@480
   721
jlahoda@494
   722
        public ConsoleProgressHandleAbstraction() {
jlahoda@480
   723
        }
jlahoda@480
   724
jlahoda@480
   725
        @Override
jlahoda@480
   726
        public synchronized void start(int totalWork) {
jlahoda@480
   727
            if (total != (-1)) throw new UnsupportedOperationException();
jlahoda@480
   728
            total = totalWork;
jlahoda@480
   729
            update();
jlahoda@480
   730
        }
jlahoda@480
   731
jlahoda@480
   732
        @Override
jlahoda@480
   733
        public synchronized void progress(int currentWorkDone) {
jlahoda@480
   734
            current = currentWorkDone;
jlahoda@480
   735
            update();
jlahoda@480
   736
        }
jlahoda@480
   737
jlahoda@480
   738
        @Override
jlahoda@480
   739
        public void progress(String message) {
jlahoda@480
   740
        }
jlahoda@480
   741
jlahoda@480
   742
        @Override
jlahoda@480
   743
        public void finish() {
jlahoda@480
   744
        }
jlahoda@480
   745
jlahoda@480
   746
        private void update() {
jlahoda@480
   747
            RequestProcessor.getDefault().post(new Runnable() {
jlahoda@480
   748
jlahoda@480
   749
                @Override
jlahoda@480
   750
                public void run() {
jlahoda@480
   751
                    doUpdate();
jlahoda@480
   752
                }
jlahoda@480
   753
            });
jlahoda@480
   754
        }
jlahoda@480
   755
jlahoda@480
   756
        private int currentShownDone = -1;
jlahoda@480
   757
jlahoda@480
   758
        private void doUpdate() {
jlahoda@480
   759
            int done;
jlahoda@480
   760
jlahoda@480
   761
            synchronized(this) {
jlahoda@480
   762
                done = (int) ((((double) width - 2) / total) * current);
jlahoda@480
   763
jlahoda@480
   764
                if (done == currentShownDone) {
jlahoda@480
   765
                    return;
jlahoda@480
   766
                }
jlahoda@480
   767
jlahoda@480
   768
                currentShownDone = done;
jlahoda@480
   769
            }
jlahoda@480
   770
            
jlahoda@494
   771
            int todo = width - done;
jlahoda@494
   772
            PrintStream pw = System.out;
jlahoda@480
   773
jlahoda@480
   774
            pw.print("[");
jlahoda@480
   775
jlahoda@480
   776
jlahoda@480
   777
            while (done-- > 0) {
jlahoda@480
   778
                pw.print("=");
jlahoda@480
   779
            }
jlahoda@480
   780
jlahoda@480
   781
            while (todo-- > 0) {
jlahoda@480
   782
                pw.print(" ");
jlahoda@480
   783
            }
jlahoda@480
   784
jlahoda@480
   785
            pw.print("]\r");
jlahoda@480
   786
        }
jlahoda@480
   787
jlahoda@480
   788
    }
jlahoda@480
   789
jlahoda@480
   790
}