Ticket #13318: patch-main-extract-argument-handling.patch

File patch-main-extract-argument-handling.patch, 58.6 KB (added by michael2402, 5 years ago)
  • src/org/openstreetmap/josm/Main.java

    diff --git a/src/org/openstreetmap/josm/Main.java b/src/org/openstreetmap/josm/Main.java
    index c216556..b8a16dc 100644
    a b import java.util.concurrent.ExecutionException; 
    3636import java.util.concurrent.ExecutorService;
    3737import java.util.concurrent.Executors;
    3838import java.util.concurrent.Future;
    39 import java.util.logging.Handler;
    40 import java.util.logging.Level;
    41 import java.util.logging.LogRecord;
    42 import java.util.logging.Logger;
    4339
    4440import javax.swing.Action;
    4541import javax.swing.InputMap;
    import org.openstreetmap.josm.data.projection.Projection; 
    7672import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
    7773import org.openstreetmap.josm.data.validation.OsmValidator;
    7874import org.openstreetmap.josm.gui.GettingStarted;
    79 import org.openstreetmap.josm.gui.MainApplication.Option;
    8075import org.openstreetmap.josm.gui.MainFrame;
    8176import org.openstreetmap.josm.gui.MainMenu;
    8277import org.openstreetmap.josm.gui.MainPanel;
    8378import org.openstreetmap.josm.gui.MapFrame;
    8479import org.openstreetmap.josm.gui.MapFrameListener;
     80import org.openstreetmap.josm.gui.ProgramArguments;
     81import org.openstreetmap.josm.gui.ProgramArguments.Option;
    8582import org.openstreetmap.josm.gui.help.HelpUtil;
    8683import org.openstreetmap.josm.gui.io.SaveLayersDialog;
    8784import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
    import org.openstreetmap.josm.plugins.PluginHandler; 
    108105import org.openstreetmap.josm.tools.CheckParameterUtil;
    109106import org.openstreetmap.josm.tools.I18n;
    110107import org.openstreetmap.josm.tools.ImageProvider;
     108import org.openstreetmap.josm.tools.Logging;
    111109import org.openstreetmap.josm.tools.OpenBrowser;
    112110import org.openstreetmap.josm.tools.OsmUrlToBounds;
    113111import org.openstreetmap.josm.tools.PlatformHook;
    public abstract class Main { 
    223221
    224222    protected static final Map<String, Throwable> NETWORK_ERRORS = new HashMap<>();
    225223
    226     // First lines of last 5 error and warning messages, used for bug reports
    227     private static final List<String> ERRORS_AND_WARNINGS = Collections.<String>synchronizedList(new ArrayList<String>());
    228 
    229224    private static final Set<OnlineResource> OFFLINE_RESOURCES = EnumSet.noneOf(OnlineResource.class);
    230225
    231226    /**
    232227     * Logging level (5 = trace, 4 = debug, 3 = info, 2 = warn, 1 = error, 0 = none).
    233228     * @since 6248
     229     * @deprecated Use {@link Logging} class.
    234230     */
     231    @Deprecated
    235232    public static int logLevel = 3;
    236233
    237234    /**
    public abstract class Main { 
    240237     */
    241238    protected static final MainPanel mainPanel = new MainPanel(getLayerManager());
    242239
    243     private static void rememberWarnErrorMsg(String msg) {
    244         // Only remember first line of message
    245         int idx = msg.indexOf('\n');
    246         if (idx > 0) {
    247             ERRORS_AND_WARNINGS.add(msg.substring(0, idx));
    248         } else {
    249             ERRORS_AND_WARNINGS.add(msg);
    250         }
    251         // Only keep 10 lines to avoid memory leak
    252         while (ERRORS_AND_WARNINGS.size() > 10) {
    253             ERRORS_AND_WARNINGS.remove(0);
    254         }
    255     }
    256 
    257240    /**
    258241     * Replies the first lines of last 5 error and warning messages, used for bug reports
    259242     * @return the first lines of last 5 error and warning messages
    260243     * @since 7420
    261244     */
    262245    public static final Collection<String> getLastErrorAndWarnings() {
    263         return Collections.unmodifiableList(ERRORS_AND_WARNINGS);
     246        return Logging.getLastErrorAndWarnings();
    264247    }
    265248
    266249    /**
    public abstract class Main { 
    268251     * @since 8959
    269252     */
    270253    public static void clearLastErrorAndWarnings() {
    271         ERRORS_AND_WARNINGS.clear();
     254        Logging.clearLastErrorAndWarnings();
    272255    }
    273256
    274257    /**
    public abstract class Main { 
    277260     * @since 6248
    278261     */
    279262    public static void error(String msg) {
    280         if (logLevel < 1)
    281             return;
    282         if (msg != null && !msg.isEmpty()) {
    283             System.err.println(tr("ERROR: {0}", msg));
    284             rememberWarnErrorMsg("E: "+msg);
    285         }
     263        Logging.error(msg);
    286264    }
    287265
    288266    /**
    public abstract class Main { 
    290268     * @param msg The message to print.
    291269     */
    292270    public static void warn(String msg) {
    293         if (logLevel < 2)
    294             return;
    295         if (msg != null && !msg.isEmpty()) {
    296             System.err.println(tr("WARNING: {0}", msg));
    297             rememberWarnErrorMsg("W: "+msg);
    298         }
     271        Logging.warn(msg);
    299272    }
    300273
    301274    /**
    public abstract class Main { 
    303276     * @param msg The message to print.
    304277     */
    305278    public static void info(String msg) {
    306         if (logLevel < 3)
    307             return;
    308         if (msg != null && !msg.isEmpty()) {
    309             System.out.println(tr("INFO: {0}", msg));
    310         }
     279        Logging.info(msg);
    311280    }
    312281
    313282    /**
    public abstract class Main { 
    315284     * @param msg The message to print.
    316285     */
    317286    public static void debug(String msg) {
    318         if (logLevel < 4)
    319             return;
    320         if (msg != null && !msg.isEmpty()) {
    321             System.out.println(tr("DEBUG: {0}", msg));
    322         }
     287        Logging.debug(msg);
    323288    }
    324289
    325290    /**
    public abstract class Main { 
    327292     * @param msg The message to print.
    328293     */
    329294    public static void trace(String msg) {
    330         if (logLevel < 5)
    331             return;
    332         if (msg != null && !msg.isEmpty()) {
    333             System.out.print("TRACE: ");
    334             System.out.println(msg);
    335         }
     295        Logging.trace(msg);
    336296    }
    337297
    338298    /**
    public abstract class Main { 
    342302     * @since 6852
    343303     */
    344304    public static boolean isDebugEnabled() {
    345         return logLevel >= 4;
     305        return Logging.isLoggingEnabled(Logging.LEVEL_DEBUG);
    346306    }
    347307
    348308    /**
    public abstract class Main { 
    352312     * @since 6852
    353313     */
    354314    public static boolean isTraceEnabled() {
    355         return logLevel >= 5;
     315        return Logging.isLoggingEnabled(Logging.LEVEL_TRACE);
    356316    }
    357317
    358318    /**
    public abstract class Main { 
    363323     * @since 6248
    364324     */
    365325    public static void error(String msg, Object... objects) {
    366         error(MessageFormat.format(msg, objects));
     326        Logging.error(msg, objects);
    367327    }
    368328
    369329    /**
    public abstract class Main { 
    373333     * @param objects The objects to insert into format string.
    374334     */
    375335    public static void warn(String msg, Object... objects) {
    376         warn(MessageFormat.format(msg, objects));
     336        Logging.warn(msg, objects);
    377337    }
    378338
    379339    /**
    public abstract class Main { 
    383343     * @param objects The objects to insert into format string.
    384344     */
    385345    public static void info(String msg, Object... objects) {
    386         info(MessageFormat.format(msg, objects));
     346        Logging.info(msg, objects);
    387347    }
    388348
    389349    /**
    public abstract class Main { 
    393353     * @param objects The objects to insert into format string.
    394354     */
    395355    public static void debug(String msg, Object... objects) {
    396         debug(MessageFormat.format(msg, objects));
     356        Logging.debug(msg, objects);
    397357    }
    398358
    399359    /**
    public abstract class Main { 
    403363     * @param objects The objects to insert into format string.
    404364     */
    405365    public static void trace(String msg, Object... objects) {
    406         trace(MessageFormat.format(msg, objects));
     366        Logging.trace(msg, objects);
    407367    }
    408368
    409369    /**
    public abstract class Main { 
    412372     * @since 6248
    413373     */
    414374    public static void error(Throwable t) {
    415         error(t, true);
     375        Logging.logWithStackTrace(Logging.LEVEL_ERROR, t);
    416376    }
    417377
    418378    /**
    public abstract class Main { 
    421381     * @since 6248
    422382     */
    423383    public static void warn(Throwable t) {
    424         warn(t, true);
     384        Logging.logWithStackTrace(Logging.LEVEL_WARN, t);
    425385    }
    426386
    427387    /**
    public abstract class Main { 
    430390     * @since 10420
    431391     */
    432392    public static void debug(Throwable t) {
    433         debug(getErrorMessage(t));
     393        Logging.log(Logging.LEVEL_DEBUG, t);
    434394    }
    435395
    436396    /**
    public abstract class Main { 
    439399     * @since 10420
    440400     */
    441401    public static void trace(Throwable t) {
    442         trace(getErrorMessage(t));
     402        Logging.log(Logging.LEVEL_TRACE, t);
    443403    }
    444404
    445405    /**
    public abstract class Main { 
    449409     * @since 6642
    450410     */
    451411    public static void error(Throwable t, boolean stackTrace) {
    452         error(getErrorMessage(t));
    453412        if (stackTrace) {
    454             t.printStackTrace();
     413            Logging.log(Logging.LEVEL_ERROR, t);
     414        } else {
     415            Logging.logWithStackTrace(Logging.LEVEL_ERROR, t);
    455416        }
    456417    }
    457418
    public abstract class Main { 
    462423     * @since 10420
    463424     */
    464425    public static void error(Throwable t, String message) {
    465         warn(message + ' ' + getErrorMessage(t));
     426        Logging.log(Logging.LEVEL_ERROR, message, t);
    466427    }
    467428
    468429    /**
    public abstract class Main { 
    472433     * @since 6642
    473434     */
    474435    public static void warn(Throwable t, boolean stackTrace) {
    475         warn(getErrorMessage(t));
    476436        if (stackTrace) {
    477             t.printStackTrace();
     437            Logging.log(Logging.LEVEL_WARN, t);
     438        } else {
     439            Logging.logWithStackTrace(Logging.LEVEL_WARN, t);
    478440        }
    479441    }
    480442
    public abstract class Main { 
    485447     * @since 10420
    486448     */
    487449    public static void warn(Throwable t, String message) {
    488         warn(message + ' ' + getErrorMessage(t));
     450        Logging.log(Logging.LEVEL_WARN, message, t);
    489451    }
    490452
    491453    /**
    public abstract class Main { 
    495457     * @since 6642
    496458     */
    497459    public static String getErrorMessage(Throwable t) {
    498         if (t == null) {
    499             return null;
    500         }
    501         StringBuilder sb = new StringBuilder(t.getClass().getName());
    502         String msg = t.getMessage();
    503         if (msg != null) {
    504             sb.append(": ").append(msg.trim());
    505         }
    506         Throwable cause = t.getCause();
    507         if (cause != null && !cause.equals(t)) {
    508             sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause));
    509         }
    510         return sb.toString();
     460        return Logging.getErrorMessage(t);
    511461    }
    512462
    513463    /**
    public abstract class Main { 
    590540        isOpenjdk = System.getProperty("java.vm.name").toUpperCase(Locale.ENGLISH).indexOf("OPENJDK") != -1;
    591541        fileWatcher.start();
    592542
    593         new InitializationTask(tr("Executing platform startup hook")) {
    594             @Override
    595             public void initialize() {
    596                 platform.startupHook();
    597             }
    598         }.call();
     543        new InitializationTask(tr("Executing platform startup hook"), platform::startupHook).call();
    599544
    600         new InitializationTask(tr("Building main menu")) {
    601 
    602             @Override
    603             public void initialize() {
    604                 initializeMainWindow();
    605             }
    606         }.call();
     545        new InitializationTask(tr("Building main menu"), this::initializeMainWindow).call();
    607546
    608547        undoRedo.addCommandQueueListener(redoUndoListener);
    609548
    public abstract class Main { 
    616555        // contains several initialization tasks to be executed (in parallel) by a ExecutorService
    617556        List<Callable<Void>> tasks = new ArrayList<>();
    618557
    619         tasks.add(new InitializationTask(tr("Initializing OSM API")) {
    620 
    621             @Override
    622             public void initialize() {
     558        tasks.add(new InitializationTask(tr("Initializing OSM API"), () -> {
    623559                // We try to establish an API connection early, so that any API
    624560                // capabilities are already known to the editor instance. However
    625561                // if it goes wrong that's not critical at this stage.
    public abstract class Main { 
    628564                } catch (OsmTransferCanceledException | OsmApiInitializationException e) {
    629565                    Main.warn(getErrorMessage(Utils.getRootCause(e)));
    630566                }
    631             }
    632         });
    633 
    634         tasks.add(new InitializationTask(tr("Initializing validator")) {
     567            }));
    635568
    636             @Override
    637             public void initialize() {
    638                 OsmValidator.initialize();
    639             }
    640         });
     569        tasks.add(new InitializationTask(tr("Initializing validator"), OsmValidator::initialize));
    641570
    642         tasks.add(new InitializationTask(tr("Initializing presets")) {
     571        tasks.add(new InitializationTask(tr("Initializing presets"), TaggingPresets::initialize));
    643572
    644             @Override
    645             public void initialize() {
    646                 TaggingPresets.initialize();
    647             }
    648         });
     573        tasks.add(new InitializationTask(tr("Initializing map styles"), MapPaintPreference::initialize));
    649574
    650         tasks.add(new InitializationTask(tr("Initializing map styles")) {
    651 
    652             @Override
    653             public void initialize() {
    654                 MapPaintPreference.initialize();
    655             }
    656         });
    657 
    658         tasks.add(new InitializationTask(tr("Loading imagery preferences")) {
    659 
    660             @Override
    661             public void initialize() {
    662                 ImageryPreference.initialize();
    663             }
    664         });
     575        tasks.add(new InitializationTask(tr("Loading imagery preferences"), ImageryPreference::initialize));
    665576
    666577        try {
    667             final ExecutorService service = Executors.newFixedThreadPool(
     578            ExecutorService service = Executors.newFixedThreadPool(
    668579                    Runtime.getRuntime().availableProcessors(), Utils.newThreadFactory("main-init-%d", Thread.NORM_PRIORITY));
    669580            for (Future<Void> i : service.invokeAll(tasks)) {
    670581                i.get();
    public abstract class Main { 
    677588        // hooks for the jmapviewer component
    678589        FeatureAdapter.registerBrowserAdapter(OpenBrowser::displayUrl);
    679590        FeatureAdapter.registerTranslationAdapter(I18n.getTranslationAdapter());
    680         FeatureAdapter.registerLoggingAdapter(name -> {
    681                 Logger logger = Logger.getAnonymousLogger();
    682                 logger.setUseParentHandlers(false);
    683                 logger.setLevel(Level.ALL);
    684                 if (logger.getHandlers().length == 0) {
    685                     logger.addHandler(new Handler() {
    686                         @Override
    687                         public void publish(LogRecord record) {
    688                             String msg = MessageFormat.format(record.getMessage(), record.getParameters());
    689                             if (record.getLevel().intValue() >= Level.SEVERE.intValue()) {
    690                                 Main.error(msg);
    691                             } else if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
    692                                 Main.warn(msg);
    693                             } else if (record.getLevel().intValue() >= Level.INFO.intValue()) {
    694                                 Main.info(msg);
    695                             } else if (record.getLevel().intValue() >= Level.FINE.intValue()) {
    696                                 Main.debug(msg);
    697                             } else {
    698                                 Main.trace(msg);
    699                             }
    700                         }
    701 
    702                         @Override
    703                         public void flush() {
    704                             // Do nothing
    705                         }
    706 
    707                         @Override
    708                         public void close() {
    709                             // Do nothing
    710                         }
    711                     });
    712                 }
    713                 return logger;
    714             });
     591        FeatureAdapter.registerLoggingAdapter(name -> Logging.getLogger());
    715592
    716         new InitializationTask(tr("Updating user interface")) {
    717 
    718             @Override
    719             public void initialize() {
    720                 toolbar.refreshToolbarControl();
    721                 toolbar.control.updateUI();
    722                 contentPanePrivate.updateUI();
    723             }
    724         }.call();
     593        new InitializationTask(tr("Updating user interface"), () -> {
     594            toolbar.refreshToolbarControl();
     595            toolbar.control.updateUI();
     596            contentPanePrivate.updateUI();
     597        }).call();
    725598    }
    726599
    727600    /**
    public abstract class Main { 
    732605        // can be implementd by subclasses
    733606    }
    734607
    735     private abstract static class InitializationTask implements Callable<Void> {
     608    private static class InitializationTask implements Callable<Void> {
    736609
    737610        private final String name;
     611        private Runnable task;
    738612
    739         protected InitializationTask(String name) {
     613        protected InitializationTask(String name, Runnable task) {
    740614            this.name = name;
     615            this.task = task;
    741616        }
    742617
    743         public abstract void initialize();
    744 
    745618        @Override
    746619        public Void call() {
    747620            Object status = null;
    748621            if (initListener != null) {
    749622                status = initListener.updateStatus(name);
    750623            }
    751             initialize();
     624            task.run();
    752625            if (initListener != null) {
    753626                initListener.finish(status);
    754627            }
    public abstract class Main { 
    970843     * Should be called before the main constructor to setup some parameter stuff
    971844     * @param args The parsed argument list.
    972845     */
    973     public static void preConstructorInit(Map<Option, Collection<String>> args) {
     846    public static void preConstructorInit(ProgramArguments args) {
    974847        ProjectionPreference.setProjection();
    975848
    976849        String defaultlaf = platform.getDefaultStyle();
    public abstract class Main { 
    1037910        }
    1038911    }
    1039912
    1040     protected static void postConstructorProcessCmdLine(Map<Option, Collection<String>> args) {
    1041         if (args.containsKey(Option.DOWNLOAD)) {
    1042             List<File> fileList = new ArrayList<>();
    1043             for (String s : args.get(Option.DOWNLOAD)) {
    1044                 DownloadParamType.paramType(s).download(s, fileList);
    1045             }
    1046             if (!fileList.isEmpty()) {
    1047                 OpenFileAction.openFiles(fileList, true);
    1048             }
     913    protected static void postConstructorProcessCmdLine(ProgramArguments args) {
     914        List<File> fileList = new ArrayList<>();
     915        for (String s : args.get(Option.DOWNLOAD)) {
     916            DownloadParamType.paramType(s).download(s, fileList);
    1049917        }
    1050         if (args.containsKey(Option.DOWNLOADGPS)) {
    1051             for (String s : args.get(Option.DOWNLOADGPS)) {
    1052                 DownloadParamType.paramType(s).downloadGps(s);
    1053             }
     918        if (!fileList.isEmpty()) {
     919            OpenFileAction.openFiles(fileList, true);
    1054920        }
    1055         if (args.containsKey(Option.SELECTION)) {
    1056             for (String s : args.get(Option.SELECTION)) {
    1057                 SearchAction.search(s, SearchAction.SearchMode.add);
    1058             }
     921        for (String s : args.get(Option.DOWNLOADGPS)) {
     922            DownloadParamType.paramType(s).downloadGps(s);
     923        }
     924        for (String s : args.get(Option.SELECTION)) {
     925            SearchAction.search(s, SearchAction.SearchMode.add);
    1059926        }
    1060927    }
    1061928
    public abstract class Main { 
    15471414        public static void setup() {
    15481415            if (!windowSwitchListeners.isEmpty()) {
    15491416                for (Window w : Window.getWindows()) {
    1550                     if (w.isShowing()) {
    1551                         if (!Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
    1552                             w.addWindowListener(getInstance());
    1553                         }
     1417                    if (w.isShowing() && !Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
     1418                        w.addWindowListener(getInstance());
    15541419                    }
    15551420                }
    15561421            }
  • src/org/openstreetmap/josm/gui/MainApplication.java

    diff --git a/src/org/openstreetmap/josm/gui/MainApplication.java b/src/org/openstreetmap/josm/gui/MainApplication.java
    index f58a661..d3fb110 100644
    a b import java.security.PermissionCollection; 
    2424import java.security.Permissions;
    2525import java.security.Policy;
    2626import java.security.cert.CertificateException;
    27 import java.util.ArrayList;
    2827import java.util.Arrays;
    2928import java.util.Collection;
    30 import java.util.EnumMap;
    3129import java.util.List;
    3230import java.util.Locale;
    33 import java.util.Map;
     31import java.util.Optional;
    3432import java.util.Set;
    3533import java.util.TreeSet;
     34import java.util.logging.Level;
    3635
    3736import javax.swing.JOptionPane;
    3837import javax.swing.RepaintManager;
    import org.openstreetmap.josm.actions.RestartAction; 
    4544import org.openstreetmap.josm.data.AutosaveTask;
    4645import org.openstreetmap.josm.data.CustomConfigurator;
    4746import org.openstreetmap.josm.data.Version;
     47import org.openstreetmap.josm.gui.ProgramArguments.Option;
    4848import org.openstreetmap.josm.gui.SplashScreen.SplashProgressMonitor;
    4949import org.openstreetmap.josm.gui.download.DownloadDialog;
    5050import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
    import org.openstreetmap.josm.plugins.PluginInformation; 
    6262import org.openstreetmap.josm.tools.FontsManager;
    6363import org.openstreetmap.josm.tools.HttpClient;
    6464import org.openstreetmap.josm.tools.I18n;
     65import org.openstreetmap.josm.tools.Logging;
    6566import org.openstreetmap.josm.tools.OsmUrlToBounds;
    6667import org.openstreetmap.josm.tools.PlatformHookWindows;
    6768import org.openstreetmap.josm.tools.Utils;
    6869import org.openstreetmap.josm.tools.WindowGeometry;
     70import org.openstreetmap.josm.tools.bugreport.BugReport;
    6971import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
    7072
    71 import gnu.getopt.Getopt;
    72 import gnu.getopt.LongOpt;
    73 
    7473/**
    7574 * Main window class application.
    7675 *
    public class MainApplication extends Main { 
    170169    }
    171170
    172171    /**
    173      * JOSM command line options.
    174      * @see <a href="https://josm.openstreetmap.de/wiki/Help/CommandLineOptions">Help/CommandLineOptions</a>
    175      * @since 5279
    176      */
    177     public enum Option {
    178         /** --help|-h                                  Show this help */
    179         HELP(false),
    180         /** --version                                  Displays the JOSM version and exits */
    181         VERSION(false),
    182         /** --debug                                    Print debugging messages to console */
    183         DEBUG(false),
    184         /** --trace                                    Print detailed debugging messages to console */
    185         TRACE(false),
    186         /** --language=&lt;language&gt;                Set the language */
    187         LANGUAGE(true),
    188         /** --reset-preferences                        Reset the preferences to default */
    189         RESET_PREFERENCES(false),
    190         /** --load-preferences=&lt;url-to-xml&gt;      Changes preferences according to the XML file */
    191         LOAD_PREFERENCES(true),
    192         /** --set=&lt;key&gt;=&lt;value&gt;            Set preference key to value */
    193         SET(true),
    194         /** --geometry=widthxheight(+|-)x(+|-)y        Standard unix geometry argument */
    195         GEOMETRY(true),
    196         /** --no-maximize                              Do not launch in maximized mode */
    197         NO_MAXIMIZE(false),
    198         /** --maximize                                 Launch in maximized mode */
    199         MAXIMIZE(false),
    200         /** --download=minlat,minlon,maxlat,maxlon     Download the bounding box <br>
    201          *  --download=&lt;URL&gt;                     Download the location at the URL (with lat=x&amp;lon=y&amp;zoom=z) <br>
    202          *  --download=&lt;filename&gt;                Open a file (any file type that can be opened with File/Open) */
    203         DOWNLOAD(true),
    204         /** --downloadgps=minlat,minlon,maxlat,maxlon  Download the bounding box as raw GPS <br>
    205          *  --downloadgps=&lt;URL&gt;                  Download the location at the URL (with lat=x&amp;lon=y&amp;zoom=z) as raw GPS */
    206         DOWNLOADGPS(true),
    207         /** --selection=&lt;searchstring&gt;           Select with the given search */
    208         SELECTION(true),
    209         /** --offline=&lt;osm_api|josm_website|all&gt; Disable access to the given resource(s), delimited by comma */
    210         OFFLINE(true),
    211         /** --skip-plugins */
    212         SKIP_PLUGINS(false);
    213 
    214         private final String name;
    215         private final boolean requiresArg;
    216 
    217         Option(boolean requiresArgument) {
    218             this.name = name().toLowerCase(Locale.ENGLISH).replace('_', '-');
    219             this.requiresArg = requiresArgument;
    220         }
    221 
    222         /**
    223          * Replies the option name
    224          * @return The option name, in lowercase
    225          */
    226         public String getName() {
    227             return name;
    228         }
    229 
    230         /**
    231          * Determines if this option requires an argument.
    232          * @return {@code true} if this option requires an argument, {@code false} otherwise
    233          */
    234         public boolean requiresArgument() {
    235             return requiresArg;
    236         }
    237     }
    238 
    239     /**
    240      * Builds the command-line argument map.
    241      * @param args command-line arguments array
    242      * @return command-line argument map
    243      */
    244     public static Map<Option, Collection<String>> buildCommandLineArgumentMap(String ... args) {
    245 
    246         List<LongOpt> los = new ArrayList<>();
    247         for (Option o : Option.values()) {
    248             los.add(new LongOpt(o.getName(), o.requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, 0));
    249         }
    250 
    251         Getopt g = new Getopt("JOSM", args, "hv", los.toArray(new LongOpt[los.size()]));
    252 
    253         Map<Option, Collection<String>> argMap = new EnumMap<>(Option.class);
    254 
    255         int c;
    256         while ((c = g.getopt()) != -1) {
    257             Option opt;
    258             switch (c) {
    259                 case 'h':
    260                     opt = Option.HELP;
    261                     break;
    262                 case 'v':
    263                     opt = Option.VERSION;
    264                     break;
    265                 case 0:
    266                     opt = Option.values()[g.getLongind()];
    267                     break;
    268                 default:
    269                     opt = null;
    270             }
    271             if (opt != null) {
    272                 Collection<String> values = argMap.get(opt);
    273                 if (values == null) {
    274                     values = new ArrayList<>();
    275                     argMap.put(opt, values);
    276                 }
    277                 values.add(g.getOptarg());
    278             } else
    279                 throw new IllegalArgumentException("Invalid option: "+c);
    280         }
    281         // positional arguments are a shortcut for the --download ... option
    282         for (int i = g.getOptind(); i < args.length; ++i) {
    283             Collection<String> values = argMap.get(Option.DOWNLOAD);
    284             if (values == null) {
    285                 values = new ArrayList<>();
    286                 argMap.put(Option.DOWNLOAD, values);
    287             }
    288             values.add(args[i]);
    289         }
    290 
    291         return argMap;
    292     }
    293 
    294     /**
    295172     * Main application Startup
    296173     * @param argArray Command-line arguments
    297174     */
    public class MainApplication extends Main { 
    300177        Main.checkJavaVersion();
    301178
    302179        // construct argument table
    303         Map<Option, Collection<String>> args = null;
     180        ProgramArguments args = null;
    304181        try {
    305             args = buildCommandLineArgumentMap(argArray);
     182            args = new ProgramArguments(argArray);
    306183        } catch (IllegalArgumentException e) {
    307184            System.exit(1);
    308185            return;
    309186        }
    310187
    311         final boolean languageGiven = args.containsKey(Option.LANGUAGE);
     188        Level logLevel = args.getLogLevel();
     189        Logging.setLogLevel(logLevel);
     190        Main.info(tr("Log level is at ", logLevel));
    312191
    313         if (languageGiven) {
    314             I18n.set(args.get(Option.LANGUAGE).iterator().next());
    315         }
     192        Optional<String> language = args.getSingle(Option.LANGUAGE);
     193        I18n.set(language.orElse(null));
    316194
    317195        initApplicationPreferences();
    318196
    public class MainApplication extends Main { 
    340218
    341219        Main.COMMAND_LINE_ARGS.addAll(Arrays.asList(argArray));
    342220
    343         if (args.containsKey(Option.VERSION)) {
     221        if (args.showVersion()) {
    344222            System.out.println(Version.getInstance().getAgentString());
    345223            System.exit(0);
    346         }
    347 
    348         if (args.containsKey(Option.DEBUG) || args.containsKey(Option.TRACE)) {
    349             // Enable JOSM debug level
    350             logLevel = 4;
    351             Main.info(tr("Printing debugging messages to console"));
     224        } else if (args.showHelp()) {
     225            showHelp();
     226            System.exit(0);
    352227        }
    353228
    354229        boolean skipLoadingPlugins = false;
    355         if (args.containsKey(Option.SKIP_PLUGINS)) {
     230        if (args.hasOption(Option.SKIP_PLUGINS)) {
    356231            skipLoadingPlugins = true;
    357232            Main.info(tr("Plugin loading skipped"));
    358233        }
    359234
    360         if (args.containsKey(Option.TRACE)) {
    361             // Enable JOSM debug level
    362             logLevel = 5;
     235        if (Logging.isLoggingEnabled(Logging.LEVEL_TRACE)) {
    363236            // Enable debug in OAuth signpost via system preference, but only at trace level
    364237            Utils.updateSystemProperty("debug", "true");
    365238            Main.info(tr("Enabled detailed debug level (trace)"));
    366239        }
    367240
    368         Main.pref.init(args.containsKey(Option.RESET_PREFERENCES));
     241        Main.pref.init(args.hasOption(Option.RESET_PREFERENCES));
    369242
    370         if (args.containsKey(Option.SET)) {
    371             for (String i : args.get(Option.SET)) {
    372                 String[] kv = i.split("=", 2);
    373                 Main.pref.put(kv[0], "null".equals(kv[1]) ? null : kv[1]);
    374             }
    375         }
     243        args.getPreferencesToSet().forEach(Main.pref::put);
    376244
    377         if (!languageGiven) {
     245        if (!language.isPresent()) {
    378246            I18n.set(Main.pref.get("language", null));
    379247        }
    380248        Main.pref.updateSystemProperties();
    public class MainApplication extends Main { 
    382250        checkIPv6();
    383251
    384252        // asking for help? show help and exit
    385         if (args.containsKey(Option.HELP)) {
     253        if (args.hasOption(Option.HELP)) {
    386254            showHelp();
    387255            System.exit(0);
    388256        }
    public class MainApplication extends Main { 
    396264        I18n.setupLanguageFonts();
    397265
    398266        WindowGeometry geometry = WindowGeometry.mainWindow("gui.geometry",
    399                 args.containsKey(Option.GEOMETRY) ? args.get(Option.GEOMETRY).iterator().next() : null,
    400                 !args.containsKey(Option.NO_MAXIMIZE) && Main.pref.getBoolean("gui.maximized", false));
     267                args.getSingle(Option.GEOMETRY).orElse(null),
     268                !args.hasOption(Option.NO_MAXIMIZE) && Main.pref.getBoolean("gui.maximized", false));
    401269        final MainFrame mainFrame = new MainFrame(contentPanePrivate, mainPanel, geometry);
    402270        Main.parent = mainFrame;
    403271
    404         if (args.containsKey(Option.LOAD_PREFERENCES)) {
     272        if (args.hasOption(Option.LOAD_PREFERENCES)) {
    405273            CustomConfigurator.XMLCommandProcessor config = new CustomConfigurator.XMLCommandProcessor(Main.pref);
    406274            for (String i : args.get(Option.LOAD_PREFERENCES)) {
    407275                info("Reading preferences from " + i);
    408276                try (InputStream is = HttpClient.create(new URL(i)).connect().getContent()) {
    409277                    config.openAndReadXML(is);
    410278                } catch (IOException ex) {
    411                     throw new RuntimeException(ex);
     279                    throw BugReport.intercept(ex).put("file", i);
    412280                }
    413281            }
    414282        }
    public class MainApplication extends Main { 
    471339        Main.MasterWindowListener.setup();
    472340
    473341        boolean maximized = Main.pref.getBoolean("gui.maximized", false);
    474         if ((!args.containsKey(Option.NO_MAXIMIZE) && maximized) || args.containsKey(Option.MAXIMIZE)) {
     342        if ((!args.hasOption(Option.NO_MAXIMIZE) && maximized) || args.hasOption(Option.MAXIMIZE)) {
    475343            mainFrame.setMaximized(true);
    476344        }
    477345        if (main.menu.fullscreenToggleAction != null) {
    public class MainApplication extends Main { 
    529397        toolbar.refreshToolbarControl();
    530398    }
    531399
    532     private static void processOffline(Map<Option, Collection<String>> args) {
    533         if (args.containsKey(Option.OFFLINE)) {
    534             for (String s : args.get(Option.OFFLINE).iterator().next().split(",")) {
     400    private static void processOffline(ProgramArguments args) {
     401        for (String offlineNames : args.get(Option.OFFLINE)) {
     402            for (String s : offlineNames.split(",")) {
    535403                try {
    536404                    Main.setOffline(OnlineResource.valueOf(s.toUpperCase(Locale.ENGLISH)));
    537405                } catch (IllegalArgumentException e) {
    public class MainApplication extends Main { 
    541409                    return;
    542410                }
    543411            }
    544             Set<OnlineResource> offline = Main.getOfflineResources();
    545             if (!offline.isEmpty()) {
    546                 Main.warn(trn("JOSM is running in offline mode. This resource will not be available: {0}",
    547                         "JOSM is running in offline mode. These resources will not be available: {0}",
    548                         offline.size(), offline.size() == 1 ? offline.iterator().next() : Arrays.toString(offline.toArray())));
    549             }
     412        }
     413        Set<OnlineResource> offline = Main.getOfflineResources();
     414        if (!offline.isEmpty()) {
     415            Main.warn(trn("JOSM is running in offline mode. This resource will not be available: {0}",
     416                    "JOSM is running in offline mode. These resources will not be available: {0}",
     417                    offline.size(), offline.size() == 1 ? offline.iterator().next() : Arrays.toString(offline.toArray())));
    550418        }
    551419    }
    552420
    public class MainApplication extends Main { 
    601469
    602470    private static class GuiFinalizationWorker implements Runnable {
    603471
    604         private final Map<Option, Collection<String>> args;
     472        private final ProgramArguments args;
    605473        private final DefaultProxySelector proxySelector;
    606474
    607         GuiFinalizationWorker(Map<Option, Collection<String>> args, DefaultProxySelector proxySelector) {
     475        GuiFinalizationWorker(ProgramArguments args, DefaultProxySelector proxySelector) {
    608476            this.args = args;
    609477            this.proxySelector = proxySelector;
    610478        }
  • new file src/org/openstreetmap/josm/gui/ProgramArguments.java

    diff --git a/src/org/openstreetmap/josm/gui/ProgramArguments.java b/src/org/openstreetmap/josm/gui/ProgramArguments.java
    new file mode 100644
    index 0000000..cf446f2
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui;
     3
     4import java.util.ArrayList;
     5import java.util.Collection;
     6import java.util.Collections;
     7import java.util.EnumMap;
     8import java.util.HashMap;
     9import java.util.List;
     10import java.util.Locale;
     11import java.util.Map;
     12import java.util.Optional;
     13import java.util.logging.Level;
     14import java.util.stream.Stream;
     15
     16import org.openstreetmap.josm.tools.Logging;
     17
     18import gnu.getopt.Getopt;
     19import gnu.getopt.LongOpt;
     20
     21/**
     22 * This class holds the arguments passed on to Main.
     23 * @author Michael Zangl
     24 * @since xxx
     25 */
     26public class ProgramArguments {
     27
     28    /**
     29     * JOSM command line options.
     30     * @see <a href="https://josm.openstreetmap.de/wiki/Help/CommandLineOptions">Help/CommandLineOptions</a>
     31     * @since xxx
     32     */
     33    public enum Option {
     34        /** --help|-h                                  Show this help */
     35        HELP(false),
     36        /** --version                                  Displays the JOSM version and exits */
     37        VERSION(false),
     38        /** --debug                                    Print debugging messages to console */
     39        DEBUG(false),
     40        /** --trace                                    Print detailed debugging messages to console */
     41        TRACE(false),
     42        /** --language=&lt;language&gt;                Set the language */
     43        LANGUAGE(true),
     44        /** --reset-preferences                        Reset the preferences to default */
     45        RESET_PREFERENCES(false),
     46        /** --load-preferences=&lt;url-to-xml&gt;      Changes preferences according to the XML file */
     47        LOAD_PREFERENCES(true),
     48        /** --set=&lt;key&gt;=&lt;value&gt;            Set preference key to value */
     49        SET(true),
     50        /** --geometry=widthxheight(+|-)x(+|-)y        Standard unix geometry argument */
     51        GEOMETRY(true),
     52        /** --no-maximize                              Do not launch in maximized mode */
     53        NO_MAXIMIZE(false),
     54        /** --maximize                                 Launch in maximized mode */
     55        MAXIMIZE(false),
     56        /** --download=minlat,minlon,maxlat,maxlon     Download the bounding box <br>
     57         *  --download=&lt;URL&gt;                     Download the location at the URL (with lat=x&amp;lon=y&amp;zoom=z) <br>
     58         *  --download=&lt;filename&gt;                Open a file (any file type that can be opened with File/Open) */
     59        DOWNLOAD(true),
     60        /** --downloadgps=minlat,minlon,maxlat,maxlon  Download the bounding box as raw GPS <br>
     61         *  --downloadgps=&lt;URL&gt;                  Download the location at the URL (with lat=x&amp;lon=y&amp;zoom=z) as raw GPS */
     62        DOWNLOADGPS(true),
     63        /** --selection=&lt;searchstring&gt;           Select with the given search */
     64        SELECTION(true),
     65        /** --offline=&lt;osm_api|josm_website|all&gt; Disable access to the given resource(s), delimited by comma */
     66        OFFLINE(true),
     67        /** --skip-plugins */
     68        SKIP_PLUGINS(false);
     69
     70        private final String name;
     71        private final boolean requiresArg;
     72
     73        Option(boolean requiresArgument) {
     74            this.name = name().toLowerCase(Locale.ENGLISH).replace('_', '-');
     75            this.requiresArg = requiresArgument;
     76        }
     77
     78        /**
     79         * Replies the option name
     80         * @return The option name, in lowercase
     81         */
     82        public String getName() {
     83            return name;
     84        }
     85
     86        /**
     87         * Determines if this option requires an argument.
     88         * @return {@code true} if this option requires an argument, {@code false} otherwise
     89         */
     90        public boolean requiresArgument() {
     91            return requiresArg;
     92        }
     93
     94        LongOpt toLongOpt() {
     95            return new LongOpt(getName(), requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, 0);
     96        }
     97    }
     98
     99    private final Map<Option, List<String>> argMap = new EnumMap<>(Option.class);
     100
     101    /**
     102     * Construct the program arguments object
     103     * @param args The args passed to main.
     104     */
     105    public ProgramArguments(String[] args) {
     106        Stream.of(Option.values()).forEach(o -> argMap.put(o, new ArrayList<>()));
     107
     108        buildCommandLineArgumentMap(args);
     109    }
     110
     111    /**
     112     * Builds the command-line argument map.
     113     * @param args command-line arguments array
     114     */
     115    private void buildCommandLineArgumentMap(String ... args) {
     116        LongOpt[] los = Stream.of(Option.values()).map(Option::toLongOpt).toArray(i -> new LongOpt[i]);
     117
     118        Getopt g = new Getopt("JOSM", args, "hv", los);
     119
     120        int c;
     121        while ((c = g.getopt()) != -1) {
     122            Option opt;
     123            switch (c) {
     124            case 'h':
     125                opt = Option.HELP;
     126                break;
     127            case 'v':
     128                opt = Option.VERSION;
     129                break;
     130            case 0:
     131                opt = Option.values()[g.getLongind()];
     132                break;
     133            default:
     134                opt = null;
     135            }
     136            if (opt != null) {
     137                addOption(opt, g.getOptarg());
     138            } else
     139                throw new IllegalArgumentException("Invalid option: "+c);
     140        }
     141        // positional arguments are a shortcut for the --download ... option
     142        for (int i = g.getOptind(); i < args.length; ++i) {
     143            addOption(Option.DOWNLOAD, args[i]);
     144        }
     145    }
     146
     147    private void addOption(Option opt, String optarg) {
     148        argMap.get(opt).add(optarg);
     149    }
     150
     151    /**
     152     * Gets a single argument (the first) that was given for the given option.
     153     * @param option The option to search
     154     * @return The argument as optional value.
     155     */
     156    public Optional<String> getSingle(Option option) {
     157        return get(option).stream().findFirst();
     158    }
     159
     160    /**
     161     * Gets all values that are given for a given option
     162     * @param option The option
     163     * @return The values that were given. May be empty.
     164     */
     165    public Collection<String> get(Option option) {
     166        return Collections.unmodifiableList(argMap.get(option));
     167    }
     168
     169    /**
     170     * Test if a given option was used by the user.
     171     * @param option The option to test for
     172     * @return <code>true</code> if the user used it.
     173     */
     174    public boolean hasOption(Option option) {
     175        return !get(option).isEmpty();
     176    }
     177
     178    /**
     179     * Helper method to indicate if version should be displayed.
     180     * @return <code>true</code> to display version
     181     */
     182    public boolean showVersion() {
     183        return hasOption(Option.VERSION);
     184    }
     185
     186    /**
     187     * Helper method to indicate if help should be displayed.
     188     * @return <code>true</code> to display version
     189     */
     190    public boolean showHelp() {
     191        return !get(Option.HELP).isEmpty();
     192    }
     193
     194    /**
     195     * Get the log level the user wants us to use.
     196     * @return The log level.
     197     */
     198    public Level getLogLevel() {
     199        if (hasOption(Option.TRACE)) {
     200            return Logging.LEVEL_TRACE;
     201        } else if (hasOption(Option.DEBUG)) {
     202            return Logging.LEVEL_DEBUG;
     203        } else {
     204            return Logging.LEVEL_INFO;
     205        }
     206    }
     207
     208    /**
     209     * Gets a map of all preferences the user wants to set.
     210     * @return The preferences to set. It contains null values for preferences to unset
     211     */
     212    public Map<String, String> getPreferencesToSet() {
     213        HashMap<String, String> map = new HashMap<>();
     214        get(Option.SET).stream().map(i -> i.split("=", 2)).forEach(kv -> map.put(kv[0], getValue(kv)));
     215        return map;
     216    }
     217
     218    private static String getValue(String[] kv) {
     219        if (kv.length < 2) {
     220            return "";
     221        } else if ("null".equals(kv[1])) {
     222            return null;
     223        } else {
     224            return kv[1];
     225        }
     226    }
     227}
  • new file src/org/openstreetmap/josm/tools/Logging.java

    diff --git a/src/org/openstreetmap/josm/tools/Logging.java b/src/org/openstreetmap/josm/tools/Logging.java
    new file mode 100644
    index 0000000..db6855f
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.io.OutputStream;
     7import java.io.PrintWriter;
     8import java.io.StringWriter;
     9import java.text.MessageFormat;
     10import java.util.ArrayList;
     11import java.util.Arrays;
     12import java.util.List;
     13import java.util.function.Supplier;
     14import java.util.logging.ConsoleHandler;
     15import java.util.logging.Handler;
     16import java.util.logging.Level;
     17import java.util.logging.LogRecord;
     18import java.util.logging.Logger;
     19
     20import org.openstreetmap.josm.tools.bugreport.BugReport;
     21
     22/**
     23 * This class contains utility methods to log errors and warnings.
     24 * <p>
     25 * There are multiple log levels supported.
     26 * @author Michael Zangl
     27 * @since xxx
     28 */
     29public final class Logging {
     30    /**
     31     * The josm internal log level indicating a severe error in the application that usually leads to a crash.
     32     */
     33    public static final Level LEVEL_ERROR = Level.SEVERE;
     34    /**
     35     * The josm internal log level to use when something that may lead to a crash or wrong behaviour has happened.
     36     */
     37    public static final Level LEVEL_WARN = Level.WARNING;
     38    /**
     39     * The josm internal log level to use for important events that will be useful when debugging problems
     40     */
     41    public static final Level LEVEL_INFO = Level.INFO;
     42    /**
     43     * The josm internal log level to print debug output
     44     */
     45    public static final Level LEVEL_DEBUG = Level.FINE;
     46    /**
     47     * The finest log level josm supports. This lets josm print a lot of debug output.
     48     */
     49    public static final Level LEVEL_TRACE = Level.FINEST;
     50    private static final Logger LOGGER = Logger.getAnonymousLogger();
     51    private static final RememberWarningHandler WARNINGS = new RememberWarningHandler();
     52
     53    static {
     54        LOGGER.setLevel(Level.ALL);
     55        LOGGER.setUseParentHandlers(false);
     56
     57        ConsoleHandler stderr = new ConsoleHandler();
     58        LOGGER.addHandler(stderr);
     59        stderr.setLevel(LEVEL_WARN);
     60
     61        ConsoleHandler stdout = new ConsoleHandler() {
     62            @Override
     63            protected synchronized void setOutputStream(OutputStream out) {
     64                // overwrite output stream.
     65                super.setOutputStream(System.out);
     66            }
     67
     68            @Override
     69            public void publish(LogRecord record) {
     70                if (!stderr.isLoggable(record)) {
     71                    super.publish(record);
     72                }
     73            }
     74        };
     75        LOGGER.addHandler(stdout);
     76        stdout.setLevel(Level.ALL);
     77
     78        LOGGER.addHandler(WARNINGS);
     79    }
     80
     81    private Logging() {
     82        // hide
     83    }
     84
     85    /**
     86     * Set the global log level.
     87     * @param level The log level to use
     88     */
     89    public static void setLogLevel(Level level) {
     90        LOGGER.setLevel(level);
     91    }
     92
     93    /**
     94     * Prints an error message if logging is on.
     95     * @param message The message to print.
     96     */
     97    public static void error(String message) {
     98        logPrivate(LEVEL_ERROR, message);
     99    }
     100
     101    /**
     102     * Prints a formatted error message if logging is on. Calls {@link MessageFormat#format}
     103     * function to format text.
     104     * @param pattern The formatted message to print.
     105     * @param args The objects to insert into format string.
     106     */
     107    public static void error(String pattern, Object... args) {
     108        logPrivate(LEVEL_ERROR, pattern, args);
     109    }
     110
     111    /**
     112     * Prints a warning message if logging is on.
     113     * @param message The message to print.
     114     */
     115    public static void warn(String message) {
     116        logPrivate(LEVEL_WARN, message);
     117    }
     118
     119    /**
     120     * Prints a formatted warning message if logging is on. Calls {@link MessageFormat#format}
     121     * function to format text.
     122     * @param pattern The formatted message to print.
     123     * @param args The objects to insert into format string.
     124     */
     125    public static void warn(String pattern, Object... args) {
     126        logPrivate(LEVEL_WARN, pattern, args);
     127    }
     128
     129    /**
     130     * Prints a info message if logging is on.
     131     * @param message The message to print.
     132     */
     133    public static void info(String message) {
     134        logPrivate(LEVEL_INFO, message);
     135    }
     136
     137    /**
     138     * Prints a formatted info message if logging is on. Calls {@link MessageFormat#format}
     139     * function to format text.
     140     * @param pattern The formatted message to print.
     141     * @param args The objects to insert into format string.
     142     */
     143    public static void info(String pattern, Object... args) {
     144        logPrivate(LEVEL_INFO, pattern, args);
     145    }
     146
     147    /**
     148     * Prints a debug message if logging is on.
     149     * @param message The message to print.
     150     */
     151    public static void debug(String message) {
     152        logPrivate(LEVEL_DEBUG, message);
     153    }
     154
     155    /**
     156     * Prints a formatted debug message if logging is on. Calls {@link MessageFormat#format}
     157     * function to format text.
     158     * @param pattern The formatted message to print.
     159     * @param args The objects to insert into format string.
     160     */
     161    public static void debug(String pattern, Object... args) {
     162        logPrivate(LEVEL_DEBUG, pattern, args);
     163    }
     164
     165    /**
     166     * Prints a trace message if logging is on.
     167     * @param message The message to print.
     168     */
     169    public static void trace(String message) {
     170        logPrivate(LEVEL_TRACE, message);
     171    }
     172
     173    /**
     174     * Prints a formatted trace message if logging is on. Calls {@link MessageFormat#format}
     175     * function to format text.
     176     * @param pattern The formatted message to print.
     177     * @param args The objects to insert into format string.
     178     */
     179    public static void trace(String pattern, Object... args) {
     180        logPrivate(LEVEL_TRACE, pattern, args);
     181    }
     182
     183    /**
     184     * Logs a throwable that happened.
     185     * @param level The level.
     186     * @param t The throwable that should be logged.
     187     */
     188    public static void log(Level level, Throwable t) {
     189        logPrivate(level, () -> getErrorLog(null, t));
     190    }
     191
     192    /**
     193     * Logs a throwable that happened.
     194     * @param level The level.
     195     * @param message An additional error message
     196     * @param t The throwable that caused the message
     197     */
     198    public static void log(Level level, String message, Throwable t) {
     199        logPrivate(level, () -> getErrorLog(message, t));
     200    }
     201
     202    /**
     203     * Logs a throwable that happened. Adds the stack trace to the log.
     204     * @param level The level.
     205     * @param t The throwable that should be logged.
     206     */
     207    public static void logWithStackTrace(Level level, Throwable t) {
     208        logPrivate(level, () -> getErrorLogWithStack(null, t));
     209    }
     210
     211    /**
     212     * Logs a throwable that happened. Adds the stack trace to the log.
     213     * @param level The level.
     214     * @param message An additional error message
     215     * @param t The throwable that should be logged.
     216     */
     217    public static void logWithStackTrace(Level level, String message, Throwable t) {
     218        logPrivate(level, () -> getErrorLogWithStack(message, t));
     219    }
     220
     221    private static void logPrivate(Level level, String pattern, Object... args) {
     222        logPrivate(level, () -> MessageFormat.format(pattern, args));
     223    }
     224
     225    private static void logPrivate(Level level, String message) {
     226        logPrivate(level, () -> message);
     227    }
     228
     229    private static void logPrivate(Level level, Supplier<String> supplier) {
     230        // all log methods immeadiately call one of the logPrivate methods.
     231        if (LOGGER.isLoggable(level)) {
     232            StackTraceElement callingMethod = BugReport.getCallingMethod(1, Logging.class.getName(), name -> !"logPrivate".equals(name));
     233            LOGGER.logp(level, callingMethod.getClassName(), callingMethod.getMethodName(), supplier);
     234        }
     235    }
     236
     237    /**
     238     * Tests if a given log level is enabled. This can be used to avoid constructing debug data if required.
     239     *
     240     * For formatting text, you should use the {@link #debug(String, Object...)} message
     241     * @param level A lvele constant. You can e.g. use {@link Logging#LEVEL_ERROR}
     242     * @return <code>true</code> if debug is enabled.
     243     */
     244    public static boolean isLoggingEnabled(Level level) {
     245        return LOGGER.isLoggable(level);
     246    }
     247
     248    private static String getErrorLog(String message, Throwable t) {
     249        StringBuilder sb = new StringBuilder();
     250        if (message != null) {
     251            sb.append(message).append(": ");
     252        }
     253        sb.append(getErrorMessage(t));
     254        return sb.toString();
     255    }
     256
     257    private static String getErrorLogWithStack(String message, Throwable t) {
     258        StringWriter sb = new StringWriter();
     259        sb.append(getErrorLog(message, t));
     260        sb.append('\n');
     261        t.printStackTrace(new PrintWriter(sb));
     262        return sb.toString();
     263    }
     264
     265    /**
     266     * Returns a human-readable message of error, also usable for developers.
     267     * @param t The error
     268     * @return The human-readable error message
     269     */
     270    public static String getErrorMessage(Throwable t) {
     271        if (t == null) {
     272            return "(no error)";
     273        }
     274        StringBuilder sb = new StringBuilder(t.getClass().getName());
     275        String msg = t.getMessage();
     276        if (msg != null) {
     277            sb.append(": ").append(msg.trim());
     278        }
     279        Throwable cause = t.getCause();
     280        if (cause != null && !cause.equals(t)) {
     281            sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause));
     282        }
     283        return sb.toString();
     284    }
     285
     286    /**
     287     * Clear the list of last warnings
     288     */
     289    public static void clearLastErrorAndWarnings() {
     290        WARNINGS.clear();
     291    }
     292
     293    /**
     294     * Get the last error and warning messages in the order in which they were received.
     295     * @return The last errors and warnings.
     296     */
     297    public static List<String> getLastErrorAndWarnings() {
     298        return WARNINGS.getMessages();
     299    }
     300
     301    /**
     302     * Provides direct access to the logger used. Use of methods like {@link #warn(String)} is prefered.
     303     * @return The logger
     304     */
     305    public static Logger getLogger() {
     306        return LOGGER;
     307    }
     308
     309    private static class RememberWarningHandler extends Handler {
     310        private final String[] log = new String[10];
     311        private int messagesLogged;
     312
     313        RememberWarningHandler() {
     314            setLevel(LEVEL_WARN);
     315        }
     316
     317        synchronized void clear() {
     318            messagesLogged = 0;
     319            Arrays.fill(log, null);
     320        }
     321
     322        @Override
     323        public synchronized void publish(LogRecord record) {
     324            if (!isLoggable(record)) {
     325                return;
     326            }
     327
     328            String msg = getPrefix(record) + record.getMessage();
     329
     330            // Only remember first line of message
     331            int idx = msg.indexOf('\n');
     332            if (idx > 0) {
     333                msg = msg.substring(0, idx);
     334            }
     335            log[messagesLogged % log.length] = msg;
     336            messagesLogged++;
     337        }
     338
     339        private static String getPrefix(LogRecord record) {
     340            if (record.getLevel().equals(LEVEL_WARN)) {
     341                return "W: ";
     342            } else {
     343                // worse than warn
     344                return "E: ";
     345            }
     346        }
     347
     348        synchronized List<String> getMessages() {
     349            List<String> logged = Arrays.asList(log);
     350            ArrayList<String> res = new ArrayList<>();
     351            int logOffset = messagesLogged % log.length;
     352            if (messagesLogged > logOffset) {
     353                res.addAll(logged.subList(logOffset, log.length));
     354            }
     355            res.addAll(logged.subList(0, logOffset));
     356            return res;
     357        }
     358
     359        @Override
     360        public synchronized void flush() {
     361            // nothing to do
     362        }
     363
     364        @Override
     365        public void close() {
     366            // nothing to do
     367        }
     368    }
     369}
  • src/org/openstreetmap/josm/tools/bugreport/BugReport.java

    diff --git a/src/org/openstreetmap/josm/tools/bugreport/BugReport.java b/src/org/openstreetmap/josm/tools/bugreport/BugReport.java
    index d47586f..426cb59 100644
    a b import java.io.PrintWriter; 
    55import java.io.Serializable;
    66import java.io.StringWriter;
    77import java.util.concurrent.CopyOnWriteArrayList;
     8import java.util.function.Predicate;
    89
    910import org.openstreetmap.josm.actions.ShowStatusReportAction;
    1011
    public final class BugReport implements Serializable { 
    180181     * @return The method name.
    181182     */
    182183    public static String getCallingMethod(int offset) {
    183         StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
    184184        String className = BugReport.class.getName();
     185        String methodName = "getCallingMethod";
     186        StackTraceElement found = getCallingMethod(offset, className, methodName::equals);
     187        if (found != null) {
     188            return found.getClassName().replaceFirst(".*\\.", "") + '#' + found.getMethodName();
     189        } else {
     190            return "?";
     191        }
     192    }
     193
     194    /**
     195     * Find the method that called the given method on the current stack trace.
     196     * @param offset
     197     *           How many methods to look back in the stack trace. 1 gives the method calling this method, 0 gives you getCallingMethod().
     198     * @param className The name of the class to search for
     199     * @param methodName The name of the method to search for
     200     * @return The class and method name or null if it is unknown.
     201     */
     202    public static StackTraceElement getCallingMethod(int offset, String className, Predicate<String> methodName) {
     203        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
    185204        for (int i = 0; i < stackTrace.length - offset; i++) {
    186205            StackTraceElement element = stackTrace[i];
    187             if (className.equals(element.getClassName()) && "getCallingMethod".equals(element.getMethodName())) {
     206            if (className.equals(element.getClassName()) && methodName.test(element.getMethodName())) {
    188207                StackTraceElement toReturn = stackTrace[i + offset];
    189                 return toReturn.getClassName().replaceFirst(".*\\.", "") + '#' + toReturn.getMethodName();
     208                return toReturn;
    190209            }
    191210        }
    192         return "?";
     211        return null;
    193212    }
    194213
    195214    /**
  • test/unit/org/openstreetmap/josm/JOSMFixture.java

    diff --git a/test/unit/org/openstreetmap/josm/JOSMFixture.java b/test/unit/org/openstreetmap/josm/JOSMFixture.java
    index 5e31aa7..7596606 100644
    a b import org.openstreetmap.josm.gui.util.GuiHelper; 
    2121import org.openstreetmap.josm.io.CertificateAmendment;
    2222import org.openstreetmap.josm.io.OsmApi;
    2323import org.openstreetmap.josm.tools.I18n;
     24import org.openstreetmap.josm.tools.Logging;
    2425
    2526/**
    2627 * Fixture to define a proper and safe environment before running tests.
    public class JOSMFixture { 
    99100        Main.platform.preStartupHook();
    100101
    101102        Main.logLevel = 3;
     103        Logging.setLogLevel(Logging.LEVEL_INFO);
    102104        Main.pref.init(false);
    103105        Main.pref.put("osm-server.url", "http://api06.dev.openstreetmap.org/api");
    104106        I18n.set(Main.pref.get("language", "en"));
  • test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java

    diff --git a/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java b/test/unit/org/openstreetmap/josm/testutils/JOSMTestRules.java
    index 634b625..d46b251 100644
    a b import java.io.File; 
    55import java.io.IOException;
    66import java.text.MessageFormat;
    77import java.util.TimeZone;
     8import java.util.logging.Level;
    89
    910import org.junit.rules.TemporaryFolder;
    1011import org.junit.rules.TestRule;
    import org.openstreetmap.josm.io.OsmApi; 
    1920import org.openstreetmap.josm.io.OsmApiInitializationException;
    2021import org.openstreetmap.josm.io.OsmTransferCanceledException;
    2122import org.openstreetmap.josm.tools.I18n;
     23import org.openstreetmap.josm.tools.Logging;
    2224import org.openstreetmap.josm.tools.MemoryManagerTest;
    2325import org.openstreetmap.josm.tools.date.DateUtils;
    2426
    public class JOSMTestRules implements TestRule { 
    182184        TimeZone.setDefault(DateUtils.UTC);
    183185        // Set log level to info
    184186        Main.logLevel = 3;
     187        Logging.setLogLevel(Level.INFO);
    185188
    186189        // Set up i18n
    187190        if (i18n != null) {