Ticket #13318: patch-fix-13318-4.patch

File patch-fix-13318-4.patch, 71.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 01d0378..b19aea3 100644
    a b import java.util.concurrent.ExecutionException; 
    3434import java.util.concurrent.ExecutorService;
    3535import java.util.concurrent.Executors;
    3636import java.util.concurrent.Future;
    37 import java.util.logging.Handler;
    38 import java.util.logging.Level;
    39 import java.util.logging.LogRecord;
    40 import java.util.logging.Logger;
    4137
    4238import javax.swing.Action;
    4339import javax.swing.InputMap;
    import org.openstreetmap.josm.data.projection.Projection; 
    7369import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
    7470import org.openstreetmap.josm.data.validation.OsmValidator;
    7571import org.openstreetmap.josm.gui.GettingStarted;
    76 import org.openstreetmap.josm.gui.MainApplication.Option;
    7772import org.openstreetmap.josm.gui.MainFrame;
    7873import org.openstreetmap.josm.gui.MainMenu;
    7974import org.openstreetmap.josm.gui.MainPanel;
    8075import org.openstreetmap.josm.gui.MapFrame;
    8176import org.openstreetmap.josm.gui.MapFrameListener;
     77import org.openstreetmap.josm.gui.ProgramArguments;
     78import org.openstreetmap.josm.gui.ProgramArguments.Option;
    8279import org.openstreetmap.josm.gui.io.SaveLayersDialog;
    8380import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
    8481import org.openstreetmap.josm.gui.layer.Layer;
    import org.openstreetmap.josm.plugins.PluginHandler; 
    103100import org.openstreetmap.josm.tools.CheckParameterUtil;
    104101import org.openstreetmap.josm.tools.I18n;
    105102import org.openstreetmap.josm.tools.ImageProvider;
     103import org.openstreetmap.josm.tools.Logging;
    106104import org.openstreetmap.josm.tools.OpenBrowser;
    107105import org.openstreetmap.josm.tools.OsmUrlToBounds;
    108106import org.openstreetmap.josm.tools.PlatformHook;
    public abstract class Main { 
    218216
    219217    protected static final Map<String, Throwable> NETWORK_ERRORS = new HashMap<>();
    220218
    221     // First lines of last 5 error and warning messages, used for bug reports
    222     private static final List<String> ERRORS_AND_WARNINGS = Collections.<String>synchronizedList(new ArrayList<String>());
    223 
    224219    private static final Set<OnlineResource> OFFLINE_RESOURCES = EnumSet.noneOf(OnlineResource.class);
    225220
    226221    /**
    227222     * Logging level (5 = trace, 4 = debug, 3 = info, 2 = warn, 1 = error, 0 = none).
    228223     * @since 6248
     224     * @deprecated Use {@link Logging} class.
    229225     */
     226    @Deprecated
    230227    public static int logLevel = 3;
    231228
    232229    /**
    public abstract class Main { 
    235232     */
    236233    protected static final MainPanel mainPanel = new MainPanel(getLayerManager());
    237234
    238     private static void rememberWarnErrorMsg(String msg) {
    239         // Only remember first line of message
    240         int idx = msg.indexOf('\n');
    241         if (idx > 0) {
    242             ERRORS_AND_WARNINGS.add(msg.substring(0, idx));
    243         } else {
    244             ERRORS_AND_WARNINGS.add(msg);
    245         }
    246         // Only keep 10 lines to avoid memory leak
    247         while (ERRORS_AND_WARNINGS.size() > 10) {
    248             ERRORS_AND_WARNINGS.remove(0);
    249         }
    250     }
    251 
    252235    /**
    253236     * Replies the first lines of last 5 error and warning messages, used for bug reports
    254237     * @return the first lines of last 5 error and warning messages
    255238     * @since 7420
    256239     */
    257240    public static final Collection<String> getLastErrorAndWarnings() {
    258         return Collections.unmodifiableList(ERRORS_AND_WARNINGS);
     241        return Logging.getLastErrorAndWarnings();
    259242    }
    260243
    261244    /**
    public abstract class Main { 
    263246     * @since 8959
    264247     */
    265248    public static void clearLastErrorAndWarnings() {
    266         ERRORS_AND_WARNINGS.clear();
     249        Logging.clearLastErrorAndWarnings();
    267250    }
    268251
    269252    /**
    public abstract class Main { 
    272255     * @since 6248
    273256     */
    274257    public static void error(String msg) {
    275         if (logLevel < 1)
    276             return;
    277         if (msg != null && !msg.isEmpty()) {
    278             System.err.println(tr("ERROR: {0}", msg));
    279             rememberWarnErrorMsg("E: "+msg);
    280         }
     258        Logging.error(msg);
    281259    }
    282260
    283261    /**
    public abstract class Main { 
    285263     * @param msg The message to print.
    286264     */
    287265    public static void warn(String msg) {
    288         if (logLevel < 2)
    289             return;
    290         if (msg != null && !msg.isEmpty()) {
    291             System.err.println(tr("WARNING: {0}", msg));
    292             rememberWarnErrorMsg("W: "+msg);
    293         }
     266        Logging.warn(msg);
    294267    }
    295268
    296269    /**
    public abstract class Main { 
    298271     * @param msg The message to print.
    299272     */
    300273    public static void info(String msg) {
    301         if (logLevel < 3)
    302             return;
    303         if (msg != null && !msg.isEmpty()) {
    304             System.out.println(tr("INFO: {0}", msg));
    305         }
     274        Logging.info(msg);
    306275    }
    307276
    308277    /**
    public abstract class Main { 
    310279     * @param msg The message to print.
    311280     */
    312281    public static void debug(String msg) {
    313         if (logLevel < 4)
    314             return;
    315         if (msg != null && !msg.isEmpty()) {
    316             System.out.println(tr("DEBUG: {0}", msg));
    317         }
     282        Logging.debug(msg);
    318283    }
    319284
    320285    /**
    public abstract class Main { 
    322287     * @param msg The message to print.
    323288     */
    324289    public static void trace(String msg) {
    325         if (logLevel < 5)
    326             return;
    327         if (msg != null && !msg.isEmpty()) {
    328             System.out.print("TRACE: ");
    329             System.out.println(msg);
    330         }
     290        Logging.trace(msg);
    331291    }
    332292
    333293    /**
    public abstract class Main { 
    337297     * @since 6852
    338298     */
    339299    public static boolean isDebugEnabled() {
    340         return logLevel >= 4;
     300        return Logging.isLoggingEnabled(Logging.LEVEL_DEBUG);
    341301    }
    342302
    343303    /**
    public abstract class Main { 
    347307     * @since 6852
    348308     */
    349309    public static boolean isTraceEnabled() {
    350         return logLevel >= 5;
     310        return Logging.isLoggingEnabled(Logging.LEVEL_TRACE);
    351311    }
    352312
    353313    /**
    public abstract class Main { 
    358318     * @since 6248
    359319     */
    360320    public static void error(String msg, Object... objects) {
    361         error(MessageFormat.format(msg, objects));
     321        Logging.error(msg, objects);
    362322    }
    363323
    364324    /**
    public abstract class Main { 
    368328     * @param objects The objects to insert into format string.
    369329     */
    370330    public static void warn(String msg, Object... objects) {
    371         warn(MessageFormat.format(msg, objects));
     331        Logging.warn(msg, objects);
    372332    }
    373333
    374334    /**
    public abstract class Main { 
    378338     * @param objects The objects to insert into format string.
    379339     */
    380340    public static void info(String msg, Object... objects) {
    381         info(MessageFormat.format(msg, objects));
     341        Logging.info(msg, objects);
    382342    }
    383343
    384344    /**
    public abstract class Main { 
    388348     * @param objects The objects to insert into format string.
    389349     */
    390350    public static void debug(String msg, Object... objects) {
    391         debug(MessageFormat.format(msg, objects));
     351        Logging.debug(msg, objects);
    392352    }
    393353
    394354    /**
    public abstract class Main { 
    398358     * @param objects The objects to insert into format string.
    399359     */
    400360    public static void trace(String msg, Object... objects) {
    401         trace(MessageFormat.format(msg, objects));
     361        Logging.trace(msg, objects);
    402362    }
    403363
    404364    /**
    public abstract class Main { 
    407367     * @since 6248
    408368     */
    409369    public static void error(Throwable t) {
    410         error(t, true);
     370        Logging.logWithStackTrace(Logging.LEVEL_ERROR, t);
    411371    }
    412372
    413373    /**
    public abstract class Main { 
    416376     * @since 6248
    417377     */
    418378    public static void warn(Throwable t) {
    419         warn(t, true);
     379        Logging.logWithStackTrace(Logging.LEVEL_WARN, t);
    420380    }
    421381
    422382    /**
    public abstract class Main { 
    425385     * @since 10420
    426386     */
    427387    public static void debug(Throwable t) {
    428         debug(getErrorMessage(t));
     388        Logging.log(Logging.LEVEL_DEBUG, t);
    429389    }
    430390
    431391    /**
    public abstract class Main { 
    434394     * @since 10420
    435395     */
    436396    public static void trace(Throwable t) {
    437         trace(getErrorMessage(t));
     397        Logging.log(Logging.LEVEL_TRACE, t);
    438398    }
    439399
    440400    /**
    public abstract class Main { 
    444404     * @since 6642
    445405     */
    446406    public static void error(Throwable t, boolean stackTrace) {
    447         error(getErrorMessage(t));
    448407        if (stackTrace) {
    449             t.printStackTrace();
     408            Logging.log(Logging.LEVEL_ERROR, t);
     409        } else {
     410            Logging.logWithStackTrace(Logging.LEVEL_ERROR, t);
    450411        }
    451412    }
    452413
    public abstract class Main { 
    457418     * @since 10420
    458419     */
    459420    public static void error(Throwable t, String message) {
    460         warn(message + ' ' + getErrorMessage(t));
     421        Logging.log(Logging.LEVEL_ERROR, message, t);
    461422    }
    462423
    463424    /**
    public abstract class Main { 
    467428     * @since 6642
    468429     */
    469430    public static void warn(Throwable t, boolean stackTrace) {
    470         warn(getErrorMessage(t));
    471431        if (stackTrace) {
    472             t.printStackTrace();
     432            Logging.log(Logging.LEVEL_WARN, t);
     433        } else {
     434            Logging.logWithStackTrace(Logging.LEVEL_WARN, t);
    473435        }
    474436    }
    475437
    public abstract class Main { 
    480442     * @since 10420
    481443     */
    482444    public static void warn(Throwable t, String message) {
    483         warn(message + ' ' + getErrorMessage(t));
     445        Logging.log(Logging.LEVEL_WARN, message, t);
    484446    }
    485447
    486448    /**
    public abstract class Main { 
    492454    public static String getErrorMessage(Throwable t) {
    493455        if (t == null) {
    494456            return null;
     457        } else {
     458            return Logging.getErrorMessage(t);
    495459        }
    496         StringBuilder sb = new StringBuilder(t.getClass().getName());
    497         String msg = t.getMessage();
    498         if (msg != null) {
    499             sb.append(": ").append(msg.trim());
    500         }
    501         Throwable cause = t.getCause();
    502         if (cause != null && !cause.equals(t)) {
    503             sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause));
    504         }
    505         return sb.toString();
    506460    }
    507461
    508462    /**
    public abstract class Main { 
    570524    public void initialize() {
    571525        fileWatcher.start();
    572526
    573         new InitializationTask(tr("Executing platform startup hook")) {
    574             @Override
    575             public void initialize() {
    576                 platform.startupHook();
    577             }
    578         }.call();
    579 
    580         new InitializationTask(tr("Building main menu")) {
     527        new InitializationTask(tr("Executing platform startup hook"), platform::startupHook).call();
    581528
    582             @Override
    583             public void initialize() {
    584                 initializeMainWindow();
    585             }
    586         }.call();
     529        new InitializationTask(tr("Building main menu"), this::initializeMainWindow).call();
    587530
    588531        undoRedo.addCommandQueueListener(redoUndoListener);
    589532
    public abstract class Main { 
    596539        // contains several initialization tasks to be executed (in parallel) by a ExecutorService
    597540        List<Callable<Void>> tasks = new ArrayList<>();
    598541
    599         tasks.add(new InitializationTask(tr("Initializing OSM API")) {
    600 
    601             @Override
    602             public void initialize() {
     542        tasks.add(new InitializationTask(tr("Initializing OSM API"), () -> {
    603543                // We try to establish an API connection early, so that any API
    604544                // capabilities are already known to the editor instance. However
    605545                // if it goes wrong that's not critical at this stage.
    public abstract class Main { 
    608548                } catch (OsmTransferCanceledException | OsmApiInitializationException e) {
    609549                    Main.warn(getErrorMessage(Utils.getRootCause(e)));
    610550                }
    611             }
    612         });
     551            }));
    613552
    614         tasks.add(new InitializationTask(tr("Initializing validator")) {
     553        tasks.add(new InitializationTask(tr("Initializing validator"), OsmValidator::initialize));
    615554
    616             @Override
    617             public void initialize() {
    618                 OsmValidator.initialize();
    619             }
    620         });
    621 
    622         tasks.add(new InitializationTask(tr("Initializing presets")) {
    623 
    624             @Override
    625             public void initialize() {
    626                 TaggingPresets.initialize();
    627             }
    628         });
     555        tasks.add(new InitializationTask(tr("Initializing presets"), TaggingPresets::initialize));
    629556
    630         tasks.add(new InitializationTask(tr("Initializing map styles")) {
     557        tasks.add(new InitializationTask(tr("Initializing map styles"), MapPaintPreference::initialize));
    631558
    632             @Override
    633             public void initialize() {
    634                 MapPaintPreference.initialize();
    635             }
    636         });
    637 
    638         tasks.add(new InitializationTask(tr("Loading imagery preferences")) {
    639 
    640             @Override
    641             public void initialize() {
    642                 ImageryPreference.initialize();
    643             }
    644         });
     559        tasks.add(new InitializationTask(tr("Loading imagery preferences"), ImageryPreference::initialize));
    645560
    646561        try {
    647             final ExecutorService service = Executors.newFixedThreadPool(
     562            ExecutorService service = Executors.newFixedThreadPool(
    648563                    Runtime.getRuntime().availableProcessors(), Utils.newThreadFactory("main-init-%d", Thread.NORM_PRIORITY));
    649564            for (Future<Void> i : service.invokeAll(tasks)) {
    650565                i.get();
    public abstract class Main { 
    657572        // hooks for the jmapviewer component
    658573        FeatureAdapter.registerBrowserAdapter(OpenBrowser::displayUrl);
    659574        FeatureAdapter.registerTranslationAdapter(I18n.getTranslationAdapter());
    660         FeatureAdapter.registerLoggingAdapter(name -> {
    661                 Logger logger = Logger.getAnonymousLogger();
    662                 logger.setUseParentHandlers(false);
    663                 logger.setLevel(Level.ALL);
    664                 if (logger.getHandlers().length == 0) {
    665                     logger.addHandler(new Handler() {
    666                         @Override
    667                         public void publish(LogRecord record) {
    668                             String msg = MessageFormat.format(record.getMessage(), record.getParameters());
    669                             if (record.getLevel().intValue() >= Level.SEVERE.intValue()) {
    670                                 Main.error(msg);
    671                             } else if (record.getLevel().intValue() >= Level.WARNING.intValue()) {
    672                                 Main.warn(msg);
    673                             } else if (record.getLevel().intValue() >= Level.INFO.intValue()) {
    674                                 Main.info(msg);
    675                             } else if (record.getLevel().intValue() >= Level.FINE.intValue()) {
    676                                 Main.debug(msg);
    677                             } else {
    678                                 Main.trace(msg);
    679                             }
    680                         }
    681 
    682                         @Override
    683                         public void flush() {
    684                             // Do nothing
    685                         }
     575        FeatureAdapter.registerLoggingAdapter(name -> Logging.getLogger());
    686576
    687                         @Override
    688                         public void close() {
    689                             // Do nothing
    690                         }
    691                     });
    692                 }
    693                 return logger;
    694             });
    695 
    696         new InitializationTask(tr("Updating user interface")) {
    697 
    698             @Override
    699             public void initialize() {
    700                 toolbar.refreshToolbarControl();
    701                 toolbar.control.updateUI();
    702                 contentPanePrivate.updateUI();
    703             }
    704         }.call();
     577        new InitializationTask(tr("Updating user interface"), () -> {
     578            toolbar.refreshToolbarControl();
     579            toolbar.control.updateUI();
     580            contentPanePrivate.updateUI();
     581        }).call();
    705582    }
    706583
    707584    /**
    public abstract class Main { 
    712589        // can be implementd by subclasses
    713590    }
    714591
    715     private abstract static class InitializationTask implements Callable<Void> {
     592    private static class InitializationTask implements Callable<Void> {
    716593
    717594        private final String name;
     595        private Runnable task;
    718596
    719         protected InitializationTask(String name) {
     597        protected InitializationTask(String name, Runnable task) {
    720598            this.name = name;
     599            this.task = task;
    721600        }
    722601
    723         public abstract void initialize();
    724 
    725602        @Override
    726603        public Void call() {
    727604            Object status = null;
    728605            if (initListener != null) {
    729606                status = initListener.updateStatus(name);
    730607            }
    731             initialize();
     608            task.run();
    732609            if (initListener != null) {
    733610                initListener.finish(status);
    734611            }
    public abstract class Main { 
    950827     * Should be called before the main constructor to setup some parameter stuff
    951828     * @param args The parsed argument list.
    952829     */
    953     public static void preConstructorInit(Map<Option, Collection<String>> args) {
     830    public static void preConstructorInit(ProgramArguments args) {
    954831        ProjectionPreference.setProjection();
    955832
    956833        String defaultlaf = platform.getDefaultStyle();
    public abstract class Main { 
    1017894        }
    1018895    }
    1019896
    1020     protected static void postConstructorProcessCmdLine(Map<Option, Collection<String>> args) {
    1021         if (args.containsKey(Option.DOWNLOAD)) {
    1022             List<File> fileList = new ArrayList<>();
    1023             for (String s : args.get(Option.DOWNLOAD)) {
    1024                 DownloadParamType.paramType(s).download(s, fileList);
    1025             }
    1026             if (!fileList.isEmpty()) {
    1027                 OpenFileAction.openFiles(fileList, true);
    1028             }
     897    protected static void postConstructorProcessCmdLine(ProgramArguments args) {
     898        List<File> fileList = new ArrayList<>();
     899        for (String s : args.get(Option.DOWNLOAD)) {
     900            DownloadParamType.paramType(s).download(s, fileList);
    1029901        }
    1030         if (args.containsKey(Option.DOWNLOADGPS)) {
    1031             for (String s : args.get(Option.DOWNLOADGPS)) {
    1032                 DownloadParamType.paramType(s).downloadGps(s);
    1033             }
     902        if (!fileList.isEmpty()) {
     903            OpenFileAction.openFiles(fileList, true);
    1034904        }
    1035         if (args.containsKey(Option.SELECTION)) {
    1036             for (String s : args.get(Option.SELECTION)) {
    1037                 SearchAction.search(s, SearchAction.SearchMode.add);
    1038             }
     905        for (String s : args.get(Option.DOWNLOADGPS)) {
     906            DownloadParamType.paramType(s).downloadGps(s);
     907        }
     908        for (String s : args.get(Option.SELECTION)) {
     909            SearchAction.search(s, SearchAction.SearchMode.add);
    1039910        }
    1040911    }
    1041912
    public abstract class Main { 
    14881359        public static void setup() {
    14891360            if (!windowSwitchListeners.isEmpty()) {
    14901361                for (Window w : Window.getWindows()) {
    1491                     if (w.isShowing()) {
    1492                         if (!Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
    1493                             w.addWindowListener(getInstance());
    1494                         }
     1362                    if (w.isShowing() && !Arrays.asList(w.getWindowListeners()).contains(getInstance())) {
     1363                        w.addWindowListener(getInstance());
    14951364                    }
    14961365                }
    14971366            }
  • 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 e42444a..4395c0a 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 { 
    299176        I18n.init();
    300177
    301178        // construct argument table
    302         Map<Option, Collection<String>> args = null;
     179        ProgramArguments args = null;
    303180        try {
    304             args = buildCommandLineArgumentMap(argArray);
     181            args = new ProgramArguments(argArray);
    305182        } catch (IllegalArgumentException e) {
    306183            System.exit(1);
    307184            return;
    308185        }
    309186
    310         final boolean languageGiven = args.containsKey(Option.LANGUAGE);
    311 
    312         if (languageGiven) {
    313             I18n.set(args.get(Option.LANGUAGE).iterator().next());
    314         }
     187        Level logLevel = args.getLogLevel();
     188        Logging.setLogLevel(logLevel);
     189        Main.info(tr("Log level is at ", logLevel));
    315190
    316         if (args.containsKey(Option.TRACE)) {
    317             // Enable JOSM debug level
    318             logLevel = 5;
    319             // Enable debug in OAuth signpost via system preference, but only at trace level
    320             Utils.updateSystemProperty("debug", "true");
    321             Main.info(tr("Enabled detailed debug level (trace)"));
    322         } else if (args.containsKey(Option.DEBUG)) {
    323             // Enable JOSM debug level
    324             logLevel = 4;
    325             Main.info(tr("Printing debugging messages to console"));
    326         }
     191        Optional<String> language = args.getSingle(Option.LANGUAGE);
     192        I18n.set(language.orElse(null));
    327193
    328194        Policy.setPolicy(new Policy() {
    329195            // Permissions for plug-ins loaded when josm is started via webstart
    public class MainApplication extends Main { 
    349215
    350216        Main.COMMAND_LINE_ARGS.addAll(Arrays.asList(argArray));
    351217
    352         if (args.containsKey(Option.VERSION)) {
     218        if (args.showVersion()) {
    353219            System.out.println(Version.getInstance().getAgentString());
    354220            System.exit(0);
     221        } else if (args.showHelp()) {
     222            showHelp();
     223            System.exit(0);
    355224        }
    356225
    357         boolean skipLoadingPlugins = false;
    358         if (args.containsKey(Option.SKIP_PLUGINS)) {
    359             skipLoadingPlugins = true;
     226        boolean skipLoadingPlugins = args.hasOption(Option.SKIP_PLUGINS);
     227        if (skipLoadingPlugins) {
    360228            Main.info(tr("Plugin loading skipped"));
    361229        }
    362230
    363         Main.pref.init(args.containsKey(Option.RESET_PREFERENCES));
    364 
    365         if (args.containsKey(Option.SET)) {
    366             for (String i : args.get(Option.SET)) {
    367                 String[] kv = i.split("=", 2);
    368                 Main.pref.put(kv[0], "null".equals(kv[1]) ? null : kv[1]);
    369             }
     231        if (Logging.isLoggingEnabled(Logging.LEVEL_TRACE)) {
     232            // Enable debug in OAuth signpost via system preference, but only at trace level
     233            Utils.updateSystemProperty("debug", "true");
     234            Main.info(tr("Enabled detailed debug level (trace)"));
    370235        }
    371236
    372         if (!languageGiven) {
     237        Main.pref.init(args.hasOption(Option.RESET_PREFERENCES));
     238
     239        args.getPreferencesToSet().forEach(Main.pref::put);
     240
     241        if (!language.isPresent()) {
    373242            I18n.set(Main.pref.get("language", null));
    374243        }
    375244        Main.pref.updateSystemProperties();
    public class MainApplication extends Main { 
    377246        checkIPv6();
    378247
    379248        // asking for help? show help and exit
    380         if (args.containsKey(Option.HELP)) {
     249        if (args.hasOption(Option.HELP)) {
    381250            showHelp();
    382251            System.exit(0);
    383252        }
    public class MainApplication extends Main { 
    391260        I18n.setupLanguageFonts();
    392261
    393262        WindowGeometry geometry = WindowGeometry.mainWindow("gui.geometry",
    394                 args.containsKey(Option.GEOMETRY) ? args.get(Option.GEOMETRY).iterator().next() : null,
    395                 !args.containsKey(Option.NO_MAXIMIZE) && Main.pref.getBoolean("gui.maximized", false));
     263                args.getSingle(Option.GEOMETRY).orElse(null),
     264                !args.hasOption(Option.NO_MAXIMIZE) && Main.pref.getBoolean("gui.maximized", false));
    396265        final MainFrame mainFrame = new MainFrame(contentPanePrivate, mainPanel, geometry);
    397266        Main.parent = mainFrame;
    398267
    399         if (args.containsKey(Option.LOAD_PREFERENCES)) {
     268        if (args.hasOption(Option.LOAD_PREFERENCES)) {
    400269            CustomConfigurator.XMLCommandProcessor config = new CustomConfigurator.XMLCommandProcessor(Main.pref);
    401270            for (String i : args.get(Option.LOAD_PREFERENCES)) {
    402271                info("Reading preferences from " + i);
    403272                try (InputStream is = HttpClient.create(new URL(i)).connect().getContent()) {
    404273                    config.openAndReadXML(is);
    405274                } catch (IOException ex) {
    406                     throw new RuntimeException(ex);
     275                    throw BugReport.intercept(ex).put("file", i);
    407276                }
    408277            }
    409278        }
    public class MainApplication extends Main { 
    466335        Main.MasterWindowListener.setup();
    467336
    468337        boolean maximized = Main.pref.getBoolean("gui.maximized", false);
    469         if ((!args.containsKey(Option.NO_MAXIMIZE) && maximized) || args.containsKey(Option.MAXIMIZE)) {
     338        if ((!args.hasOption(Option.NO_MAXIMIZE) && maximized) || args.hasOption(Option.MAXIMIZE)) {
    470339            mainFrame.setMaximized(true);
    471340        }
    472341        if (main.menu.fullscreenToggleAction != null) {
    public class MainApplication extends Main { 
    524393        toolbar.refreshToolbarControl();
    525394    }
    526395
    527     private static void processOffline(Map<Option, Collection<String>> args) {
    528         if (args.containsKey(Option.OFFLINE)) {
    529             for (String s : args.get(Option.OFFLINE).iterator().next().split(",")) {
     396    private static void processOffline(ProgramArguments args) {
     397        for (String offlineNames : args.get(Option.OFFLINE)) {
     398            for (String s : offlineNames.split(",")) {
    530399                try {
    531400                    Main.setOffline(OnlineResource.valueOf(s.toUpperCase(Locale.ENGLISH)));
    532401                } catch (IllegalArgumentException e) {
    public class MainApplication extends Main { 
    536405                    return;
    537406                }
    538407            }
    539             Set<OnlineResource> offline = Main.getOfflineResources();
    540             if (!offline.isEmpty()) {
    541                 Main.warn(trn("JOSM is running in offline mode. This resource will not be available: {0}",
    542                         "JOSM is running in offline mode. These resources will not be available: {0}",
    543                         offline.size(), offline.size() == 1 ? offline.iterator().next() : Arrays.toString(offline.toArray())));
    544             }
     408        }
     409        Set<OnlineResource> offline = Main.getOfflineResources();
     410        if (!offline.isEmpty()) {
     411            Main.warn(trn("JOSM is running in offline mode. This resource will not be available: {0}",
     412                    "JOSM is running in offline mode. These resources will not be available: {0}",
     413                    offline.size(), offline.size() == 1 ? offline.iterator().next() : Arrays.toString(offline.toArray())));
    545414        }
    546415    }
    547416
    public class MainApplication extends Main { 
    596465
    597466    private static class GuiFinalizationWorker implements Runnable {
    598467
    599         private final Map<Option, Collection<String>> args;
     468        private final ProgramArguments args;
    600469        private final DefaultProxySelector proxySelector;
    601470
    602         GuiFinalizationWorker(Map<Option, Collection<String>> args, DefaultProxySelector proxySelector) {
     471        GuiFinalizationWorker(ProgramArguments args, DefaultProxySelector proxySelector) {
    603472            this.args = args;
    604473            this.proxySelector = proxySelector;
    605474        }
  • 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..7c7728e
    - +  
     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            // this may cause infinite loops in the unlikely case that there is a loop in the causes.
     282            sb.append(". ").append(tr("Cause: ")).append(getErrorMessage(cause));
     283        }
     284        return sb.toString();
     285    }
     286
     287    /**
     288     * Clear the list of last warnings
     289     */
     290    public static void clearLastErrorAndWarnings() {
     291        WARNINGS.clear();
     292    }
     293
     294    /**
     295     * Get the last error and warning messages in the order in which they were received.
     296     * @return The last errors and warnings.
     297     */
     298    public static List<String> getLastErrorAndWarnings() {
     299        return WARNINGS.getMessages();
     300    }
     301
     302    /**
     303     * Provides direct access to the logger used. Use of methods like {@link #warn(String)} is prefered.
     304     * @return The logger
     305     */
     306    public static Logger getLogger() {
     307        return LOGGER;
     308    }
     309
     310    private static class RememberWarningHandler extends Handler {
     311        private final String[] log = new String[10];
     312        private int messagesLogged;
     313
     314        RememberWarningHandler() {
     315            setLevel(LEVEL_WARN);
     316        }
     317
     318        synchronized void clear() {
     319            messagesLogged = 0;
     320            Arrays.fill(log, null);
     321        }
     322
     323        @Override
     324        public synchronized void publish(LogRecord record) {
     325            if (!isLoggable(record)) {
     326                return;
     327            }
     328
     329            String msg = getPrefix(record) + record.getMessage();
     330
     331            // Only remember first line of message
     332            int idx = msg.indexOf('\n');
     333            if (idx > 0) {
     334                msg = msg.substring(0, idx);
     335            }
     336            log[messagesLogged % log.length] = msg;
     337            messagesLogged++;
     338        }
     339
     340        private static String getPrefix(LogRecord record) {
     341            if (record.getLevel().equals(LEVEL_WARN)) {
     342                return "W: ";
     343            } else {
     344                // worse than warn
     345                return "E: ";
     346            }
     347        }
     348
     349        synchronized List<String> getMessages() {
     350            List<String> logged = Arrays.asList(log);
     351            ArrayList<String> res = new ArrayList<>();
     352            int logOffset = messagesLogged % log.length;
     353            if (messagesLogged > logOffset) {
     354                res.addAll(logged.subList(logOffset, log.length));
     355            }
     356            res.addAll(logged.subList(0, logOffset));
     357            return res;
     358        }
     359
     360        @Override
     361        public synchronized void flush() {
     362            // nothing to do
     363        }
     364
     365        @Override
     366        public void close() {
     367            // nothing to do
     368        }
     369    }
     370}
  • 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 2aa38e1..c2be905 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 { 
    184185     * @return The method name.
    185186     */
    186187    public static String getCallingMethod(int offset) {
    187         StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
    188188        String className = BugReport.class.getName();
     189        String methodName = "getCallingMethod";
     190        StackTraceElement found = getCallingMethod(offset, className, methodName::equals);
     191        if (found != null) {
     192            return found.getClassName().replaceFirst(".*\\.", "") + '#' + found.getMethodName();
     193        } else {
     194            return "?";
     195        }
     196    }
     197
     198    /**
     199     * Find the method that called the given method on the current stack trace.
     200     * @param offset
     201     *           How many methods to look back in the stack trace. 1 gives the method calling this method, 0 gives you getCallingMethod().
     202     * @param className The name of the class to search for
     203     * @param methodName The name of the method to search for
     204     * @return The class and method name or null if it is unknown.
     205     */
     206    public static StackTraceElement getCallingMethod(int offset, String className, Predicate<String> methodName) {
     207        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
    189208        for (int i = 0; i < stackTrace.length - offset; i++) {
    190209            StackTraceElement element = stackTrace[i];
    191             if (className.equals(element.getClassName()) && "getCallingMethod".equals(element.getMethodName())) {
     210            if (className.equals(element.getClassName()) && methodName.test(element.getMethodName())) {
    192211                StackTraceElement toReturn = stackTrace[i + offset];
    193                 return toReturn.getClassName().replaceFirst(".*\\.", "") + '#' + toReturn.getMethodName();
     212                return toReturn;
    194213            }
    195214        }
    196         return "?";
     215        return null;
    197216    }
    198217
    199218    /**
  • 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 f9bcc23..800af7c 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 { 
    9899        // call the really early hook before we anything else
    99100        Main.platform.preStartupHook();
    100101
    101         Main.logLevel = 3;
     102        Logging.setLogLevel(Logging.LEVEL_INFO);
    102103        Main.pref.init(false);
    103104        String url = Main.pref.get("osm-server.url");
    104105        if (url == null || url.isEmpty() || isProductionApiUrl(url)) {
  • test/unit/org/openstreetmap/josm/MainTest.java

    diff --git a/test/unit/org/openstreetmap/josm/MainTest.java b/test/unit/org/openstreetmap/josm/MainTest.java
    index 2b4bcb3..e9336bd 100644
    a b  
    22package org.openstreetmap.josm;
    33
    44import static org.junit.Assert.assertEquals;
    5 import static org.junit.Assert.assertFalse;
    65import static org.junit.Assert.assertNull;
    76import static org.junit.Assert.assertTrue;
    87
    import java.util.Collection; 
    1110import org.junit.BeforeClass;
    1211import org.junit.Test;
    1312import org.openstreetmap.josm.Main.DownloadParamType;
    14 import org.openstreetmap.josm.gui.MainApplication;
    1513
    1614import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    1715
    public class MainTest { 
    4240    }
    4341
    4442    /**
    45      * Unit test of {@code Main#preConstructorInit}.
    46      */
    47     @Test
    48     public void testPreConstructorInit() {
    49         Main.preConstructorInit(MainApplication.buildCommandLineArgumentMap(new String[0]));
    50         Main.preConstructorInit(MainApplication.buildCommandLineArgumentMap(new String[]{"--geometry=400x300+10+5", "--no-maximize"}));
    51         //assertEquals(new WindowGeometry(new Point(10, 5), new Dimension(400, 300)), Main.geometry); // FIXME see #12927
    52     }
    53 
    54     /**
    5543     * Unit tests on log messages.
    5644     */
    5745    @Test
    public class MainTest { 
    7866        assertTrue(warnings.contains("W: Warning message on one line"));
    7967        assertTrue(warnings.contains("W: First line of warning message on several lines"));
    8068
    81         int defaultLevel = Main.logLevel;
    82 
    83         // Check levels
    84         Main.logLevel = 5;
    85         assertTrue(Main.isTraceEnabled());
    86         assertTrue(Main.isDebugEnabled());
    87 
    88         Main.logLevel = 4;
    89         assertFalse(Main.isTraceEnabled());
    90         assertTrue(Main.isDebugEnabled());
    91 
    92         Main.logLevel = 3;
    93         assertFalse(Main.isTraceEnabled());
    94         assertFalse(Main.isDebugEnabled());
    95 
    96         Main.logLevel = defaultLevel;
    9769    }
    9870}
  • 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 49b6536..3d7558a 100644
    a b import org.openstreetmap.josm.io.OsmApi; 
    1919import org.openstreetmap.josm.io.OsmApiInitializationException;
    2020import org.openstreetmap.josm.io.OsmTransferCanceledException;
    2121import org.openstreetmap.josm.tools.I18n;
     22import org.openstreetmap.josm.tools.Logging;
    2223import org.openstreetmap.josm.tools.MemoryManagerTest;
    2324import org.openstreetmap.josm.tools.date.DateUtils;
    2425
    public class JOSMTestRules implements TestRule { 
    181182        // All tests use the same timezone.
    182183        TimeZone.setDefault(DateUtils.UTC);
    183184        // Set log level to info
    184         Main.logLevel = 3;
     185        Logging.setLogLevel(Logging.LEVEL_INFO);
    185186
    186187        // Set up i18n
    187188        if (i18n != null) {
  • new file test/unit/org/openstreetmap/josm/tools/LoggingTest.java

    diff --git a/test/unit/org/openstreetmap/josm/tools/LoggingTest.java b/test/unit/org/openstreetmap/josm/tools/LoggingTest.java
    new file mode 100644
    index 0000000..58bd31c
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.tools;
     3
     4import static org.junit.Assert.assertArrayEquals;
     5import static org.junit.Assert.assertEquals;
     6import static org.junit.Assert.assertFalse;
     7import static org.junit.Assert.assertNull;
     8import static org.junit.Assert.assertTrue;
     9
     10import java.io.IOException;
     11import java.util.function.Consumer;
     12import java.util.logging.Handler;
     13import java.util.logging.Level;
     14import java.util.logging.LogRecord;
     15
     16import org.junit.After;
     17import org.junit.Before;
     18import org.junit.Test;
     19
     20/**
     21 * @author michael
     22 *
     23 */
     24public class LoggingTest {
     25
     26    private LogRecord captured;
     27    private final Handler handler = new Handler() {
     28
     29        @Override
     30        public void publish(LogRecord record) {
     31            captured = record;
     32        }
     33
     34        @Override
     35        public void flush() {
     36        }
     37
     38        @Override
     39        public void close() throws SecurityException {
     40        }
     41    };
     42
     43    /**
     44     * @throws java.lang.Exception
     45     */
     46    @Before
     47    public void setUp() throws Exception {
     48        captured = null;
     49        Logging.getLogger().addHandler(handler);
     50    }
     51
     52    /**
     53     * @throws java.lang.Exception
     54     */
     55    @After
     56    public void tearDown() throws Exception {
     57        Logging.getLogger().removeHandler(handler);
     58    }
     59
     60    /**
     61     * Test method for {@link org.openstreetmap.josm.tools.Logging#setLogLevel(java.util.logging.Level)}.
     62     */
     63    @Test
     64    public void testSetLogLevel() {
     65        Logging.setLogLevel(Logging.LEVEL_DEBUG);
     66        assertEquals(Logging.LEVEL_DEBUG, Logging.getLogger().getLevel());
     67        Logging.setLogLevel(Logging.LEVEL_WARN);
     68        assertEquals(Logging.LEVEL_WARN, Logging.getLogger().getLevel());
     69    }
     70
     71    private void testLogCaptured(Level level, String expected, Runnable printMessage) {
     72        testLogCaptured(level, result -> assertEquals(expected, result), printMessage);
     73    }
     74
     75    private void testLogCaptured(Level level, Consumer<String> expectedTester, Runnable printMessage) {
     76        Logging.setLogLevel(level);
     77        captured = null;
     78        printMessage.run();
     79
     80        expectedTester.accept(captured.getMessage());
     81        assertEquals(level, captured.getLevel());
     82
     83        captured = null;
     84        Logging.setLogLevel(Level.OFF);
     85        printMessage.run();
     86        assertNull(captured);
     87    }
     88
     89    /**
     90     * Test method for {@link org.openstreetmap.josm.tools.Logging#error(java.lang.String)}.
     91     */
     92    @Test
     93    public void testErrorString() {
     94        testLogCaptured(Logging.LEVEL_ERROR, "test", () -> Logging.error("test"));
     95    }
     96
     97    /**
     98     * Test method for {@link org.openstreetmap.josm.tools.Logging#error(java.lang.String, java.lang.Object[])}.
     99     */
     100    @Test
     101    public void testErrorStringObjectArray() {
     102        testLogCaptured(Logging.LEVEL_ERROR, "test x 1", () -> Logging.error("test {0} {1}", "x", 1));
     103    }
     104
     105    /**
     106     * Test method for {@link org.openstreetmap.josm.tools.Logging#warn(java.lang.String)}.
     107     */
     108    @Test
     109    public void testWarnString() {
     110        testLogCaptured(Logging.LEVEL_WARN, "test", () -> Logging.warn("test"));
     111    }
     112
     113    /**
     114     * Test method for {@link org.openstreetmap.josm.tools.Logging#warn(java.lang.String, java.lang.Object[])}.
     115     */
     116    @Test
     117    public void testWarnStringObjectArray() {
     118        testLogCaptured(Logging.LEVEL_WARN, "test x 1", () -> Logging.warn("test {0} {1}", "x", 1));
     119    }
     120
     121    /**
     122     * Test method for {@link org.openstreetmap.josm.tools.Logging#info(java.lang.String)}.
     123     */
     124    @Test
     125    public void testInfoString() {
     126        testLogCaptured(Logging.LEVEL_INFO, "test", () -> Logging.info("test"));
     127    }
     128
     129    /**
     130     * Test method for {@link org.openstreetmap.josm.tools.Logging#info(java.lang.String, java.lang.Object[])}.
     131     */
     132    @Test
     133    public void testInfoStringObjectArray() {
     134        testLogCaptured(Logging.LEVEL_INFO, "test x 1", () -> Logging.info("test {0} {1}", "x", 1));
     135    }
     136
     137    /**
     138     * Test method for {@link org.openstreetmap.josm.tools.Logging#debug(java.lang.String)}.
     139     */
     140    @Test
     141    public void testDebugString() {
     142        testLogCaptured(Logging.LEVEL_DEBUG, "test", () -> Logging.debug("test"));
     143    }
     144
     145    /**
     146     * Test method for {@link org.openstreetmap.josm.tools.Logging#debug(java.lang.String, java.lang.Object[])}.
     147     */
     148    @Test
     149    public void testDebugStringObjectArray() {
     150        testLogCaptured(Logging.LEVEL_DEBUG, "test x 1", () -> Logging.debug("test {0} {1}", "x", 1));
     151    }
     152
     153    /**
     154     * Test method for {@link org.openstreetmap.josm.tools.Logging#trace(java.lang.String)}.
     155     */
     156    @Test
     157    public void testTraceString() {
     158        testLogCaptured(Logging.LEVEL_TRACE, "test", () -> Logging.trace("test"));
     159    }
     160
     161    /**
     162     * Test method for {@link org.openstreetmap.josm.tools.Logging#trace(java.lang.String, java.lang.Object[])}.
     163     */
     164    @Test
     165    public void testTraceStringObjectArray() {
     166        testLogCaptured(Logging.LEVEL_TRACE, "test x 1", () -> Logging.trace("test {0} {1}", "x", 1));
     167    }
     168
     169    /**
     170     * Test method for {@link org.openstreetmap.josm.tools.Logging#log(java.util.logging.Level, java.lang.Throwable)}.
     171     */
     172    @Test
     173    public void testLogLevelThrowable() {
     174        testLogCaptured(Logging.LEVEL_ERROR, "java.io.IOException: x", () -> Logging.log(Logging.LEVEL_ERROR, new IOException("x")));
     175
     176        testLogCaptured(Logging.LEVEL_TRACE, "java.io.IOException: x", () -> Logging.log(Logging.LEVEL_TRACE, new IOException("x")));
     177    }
     178
     179    /**
     180     * Test method for {@link org.openstreetmap.josm.tools.Logging#log(java.util.logging.Level, java.lang.String, java.lang.Throwable)}.
     181     */
     182    @Test
     183    public void testLogLevelStringThrowable() {
     184        testLogCaptured(Logging.LEVEL_ERROR, "y: java.io.IOException: x", () -> Logging.log(Logging.LEVEL_ERROR, "y", new IOException("x")));
     185
     186        testLogCaptured(Logging.LEVEL_TRACE, "y: java.io.IOException: x", () -> Logging.log(Logging.LEVEL_TRACE, "y", new IOException("x")));
     187    }
     188
     189    /**
     190     * Test method for {@link org.openstreetmap.josm.tools.Logging#logWithStackTrace(java.util.logging.Level, java.lang.Throwable)}.
     191     */
     192    @Test
     193    public void testLogWithStackTraceLevelThrowable() {
     194        Consumer<String> test = string -> {
     195            assertTrue(string.startsWith("java.io.IOException: x"));
     196            assertTrue(string.indexOf("testLogWithStackTraceLevelThrowable") >= 0);
     197        };
     198        testLogCaptured(Logging.LEVEL_ERROR, test, () -> Logging.logWithStackTrace(Logging.LEVEL_ERROR, new IOException("x")));
     199        testLogCaptured(Logging.LEVEL_TRACE, test, () -> Logging.logWithStackTrace(Logging.LEVEL_TRACE, new IOException("x")));
     200
     201        testLogCaptured(Logging.LEVEL_TRACE, string -> assertTrue(string.startsWith("java.io.IOException\n")),
     202                () -> Logging.logWithStackTrace(Logging.LEVEL_TRACE, new IOException()));
     203
     204        testLogCaptured(Logging.LEVEL_TRACE, string -> assertTrue(string.indexOf("Cause:") >= 0),
     205                () -> Logging.logWithStackTrace(Logging.LEVEL_TRACE, new IOException(new IOException())));
     206
     207    }
     208
     209    /**
     210     * Test method for {@link org.openstreetmap.josm.tools.Logging#logWithStackTrace(java.util.logging.Level, java.lang.String, java.lang.Throwable)}.
     211     */
     212    @Test
     213    public void testLogWithStackTraceLevelStringThrowable() {
     214        Consumer<String> test = string -> {
     215            assertTrue(string.startsWith("y: java.io.IOException: x"));
     216            assertTrue(string.indexOf("testLogWithStackTraceLevelStringThrowable") > 0);
     217        };
     218        testLogCaptured(Logging.LEVEL_ERROR, test, () -> Logging.logWithStackTrace(Logging.LEVEL_ERROR, "y", new IOException("x")));
     219        testLogCaptured(Logging.LEVEL_TRACE, test, () -> Logging.logWithStackTrace(Logging.LEVEL_TRACE, "y", new IOException("x")));
     220    }
     221
     222    /**
     223     * Test method for {@link org.openstreetmap.josm.tools.Logging#isLoggingEnabled(java.util.logging.Level)}.
     224     */
     225    @Test
     226    public void testIsLoggingEnabled() {
     227        Logging.setLogLevel(Logging.LEVEL_ERROR);
     228        assertTrue(Logging.isLoggingEnabled(Logging.LEVEL_ERROR));
     229        assertFalse(Logging.isLoggingEnabled(Logging.LEVEL_INFO));
     230        assertFalse(Logging.isLoggingEnabled(Logging.LEVEL_TRACE));
     231        Logging.setLogLevel(Logging.LEVEL_INFO);
     232        assertTrue(Logging.isLoggingEnabled(Logging.LEVEL_ERROR));
     233        assertTrue(Logging.isLoggingEnabled(Logging.LEVEL_INFO));
     234        assertFalse(Logging.isLoggingEnabled(Logging.LEVEL_TRACE));
     235        Logging.setLogLevel(Logging.LEVEL_TRACE);
     236        assertTrue(Logging.isLoggingEnabled(Logging.LEVEL_ERROR));
     237        assertTrue(Logging.isLoggingEnabled(Logging.LEVEL_INFO));
     238        assertTrue(Logging.isLoggingEnabled(Logging.LEVEL_TRACE));
     239    }
     240
     241    /**
     242     * Test method for {@link org.openstreetmap.josm.tools.Logging#clearLastErrorAndWarnings()}.
     243     */
     244    @Test
     245    public void testClearLastErrorAndWarnings() {
     246        Logging.setLogLevel(Logging.LEVEL_WARN);
     247        Logging.clearLastErrorAndWarnings();
     248        Logging.error("x");
     249        assertFalse(Logging.getLastErrorAndWarnings().isEmpty());
     250        assertFalse(Logging.getLastErrorAndWarnings().isEmpty());
     251        Logging.clearLastErrorAndWarnings();
     252        assertTrue(Logging.getLastErrorAndWarnings().isEmpty());
     253    }
     254
     255    /**
     256     * Test method for {@link org.openstreetmap.josm.tools.Logging#getLastErrorAndWarnings()}.
     257     */
     258    @Test
     259    public void testGetLastErrorAndWarnings() {
     260        Logging.setLogLevel(Logging.LEVEL_WARN);
     261        Logging.clearLastErrorAndWarnings();
     262        Logging.warn("x");
     263
     264        assertEquals(1, Logging.getLastErrorAndWarnings().size());
     265        assertEquals("W: x", Logging.getLastErrorAndWarnings().get(0));
     266
     267        Logging.setLogLevel(Logging.LEVEL_ERROR);
     268        Logging.warn("x");
     269
     270        assertEquals(1, Logging.getLastErrorAndWarnings().size());
     271
     272        Logging.error("y\nz");
     273
     274        assertEquals(2, Logging.getLastErrorAndWarnings().size());
     275        assertArrayEquals(new Object[] {"W: x", "E: y"}, Logging.getLastErrorAndWarnings().toArray());
     276
     277        // limit somewhere reasonable
     278        for (int i = 3; i < 6; i++) {
     279            Logging.error("x");
     280            assertEquals(i, Logging.getLastErrorAndWarnings().size());
     281        }
     282        for (int i = 2; i < 100; i++) {
     283            Logging.error("x");
     284        }
     285        assertTrue(Logging.getLastErrorAndWarnings().size() < 101);
     286    }
     287
     288}