1 /*
2  * File    : DisplayFormatters.java
3  * Created : 07-Oct-2003
4  * By      : gardnerpar
5  *
6  * Azureus - a Java Bittorrent client
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details ( see the LICENSE file ).
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22 
23 package org.gudy.azureus2.core3.util;
24 
25 /**
26  * @author gardnerpar
27  *
28  */
29 
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.Calendar;
33 import java.util.Date;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.math.BigDecimal;
39 import java.text.DecimalFormat;
40 import java.text.DecimalFormatSymbols;
41 import java.text.SimpleDateFormat;
42 import java.text.NumberFormat;
43 
44 import org.gudy.azureus2.core3.download.*;
45 import org.gudy.azureus2.core3.config.*;
46 import org.gudy.azureus2.core3.torrent.TOTorrent;
47 import org.gudy.azureus2.core3.disk.*;
48 import org.gudy.azureus2.core3.internat.*;
49 
50 
51 
52 public class
53 DisplayFormatters
54 {
55 	final private static boolean ROUND_NO = true;
56 	//final private static boolean ROUND_YES = false;
57 	final private static boolean TRUNCZEROS_NO = false;
58 	final private static boolean TRUNCZEROS_YES = true;
59 
60 	final public static int UNIT_B  = 0;
61 	final public static int UNIT_KB = 1;
62 	final public static int UNIT_MB = 2;
63 	final public static int UNIT_GB = 3;
64 	final public static int UNIT_TB = 4;
65 
66 	final private static int UNITS_PRECISION[] =	 {	 0, // B
67 	                                                     1, //KB
68 	                                                     2, //MB
69 	                                                     2, //GB
70 	                                                     3 //TB
71 	                                                  };
72 
73 	final private static NumberFormat[]	cached_number_formats = new NumberFormat[20];
74 
75 	private static NumberFormat	percentage_format;
76 
77 	private static final String[]	all_units = new String[5];
78 
79 	private static String[] units;
80 	private static String[] units_bits;
81 	private static String[] units_rate;
82 	private static int unitsStopAt = UNIT_TB;
83 
84 	private static String[] units_base10;
85 
86 	private static String		per_sec;
87 
88 	private static boolean use_si_units;
89 	private static boolean force_si_values;
90 	private static boolean use_units_rate_bits;
91 	private static boolean not_use_GB_TB;
92 
93     private static int message_text_state = 0;
94 
95     private static boolean	separate_prot_data_stats;
96     private static boolean	data_stats_only;
97 	private static char decimalSeparator;
98 
99 	private static volatile Map<String,Formatter>	format_map = new HashMap<String, Formatter>();
100 
101 	static{
COConfigurationManager.addAndFireParameterListeners( new String[]{ R, R, R, R, R, }, new ParameterListener() { public void parameterChanged( String x ) { use_si_units = COConfigurationManager.getBooleanParameter(R); force_si_values = COConfigurationManager.getBooleanParameter(R); use_units_rate_bits = COConfigurationManager.getBooleanParameter(R); not_use_GB_TB = COConfigurationManager.getBooleanParameter(R); unitsStopAt = (not_use_GB_TB) ? UNIT_MB : UNIT_TB; setUnits(); updateFormatOverrides( COConfigurationManager.getStringParameter( R, R )); } })102 		COConfigurationManager.addAndFireParameterListeners(
103 				new String[]{
104 					"config.style.useSIUnits",
105 					"config.style.forceSIValues",
106 					"config.style.useUnitsRateBits",
107 					"config.style.doNotUseGB",
108 					"config.style.formatOverrides",
109 				},
110 				new ParameterListener()
111 				{
112 					public void
113 					parameterChanged(
114 						String	x )
115 					{
116 						use_si_units 			= COConfigurationManager.getBooleanParameter("config.style.useSIUnits");
117 						force_si_values 		= COConfigurationManager.getBooleanParameter("config.style.forceSIValues");
118 						use_units_rate_bits 	= COConfigurationManager.getBooleanParameter("config.style.useUnitsRateBits");
119 			            not_use_GB_TB 			= COConfigurationManager.getBooleanParameter("config.style.doNotUseGB");
120 
121 			            unitsStopAt = (not_use_GB_TB) ? UNIT_MB : UNIT_TB;
122 
123 						setUnits();
124 
125 						updateFormatOverrides( COConfigurationManager.getStringParameter( "config.style.formatOverrides", "" ));
126 					}
127 				});
128 
COConfigurationManager.addListener( new COConfigurationListener() { public void configurationSaved() { setUnits(); loadMessages(); } })129     	COConfigurationManager.addListener(
130     		new COConfigurationListener()
131     		{
132     			public void
133     			configurationSaved()
134     			{
135     				setUnits();
136     				loadMessages();
137 
138     			}
139 
140     		});
141 
COConfigurationManager.addAndFireParameterListeners( new String[]{ R, R, }, new ParameterListener() { public void parameterChanged( String x ) { separate_prot_data_stats = COConfigurationManager.getBooleanParameter(R); data_stats_only = COConfigurationManager.getBooleanParameter(R); } })142 		COConfigurationManager.addAndFireParameterListeners(
143 				new String[]{
144 						"config.style.dataStatsOnly",
145 						"config.style.separateProtDataStats",
146 				},
147 				new ParameterListener()
148 				{
149 					public void
150 					parameterChanged(
151 						String	x )
152 					{
153 						separate_prot_data_stats = COConfigurationManager.getBooleanParameter("config.style.separateProtDataStats");
154 						data_stats_only			 = COConfigurationManager.getBooleanParameter("config.style.dataStatsOnly");
155 					}
156 				});
157 
setUnits()158 		setUnits();
159 
loadMessages()160 		loadMessages();
161 	}
162 
163   public static void
setUnits()164   setUnits()
165   {
166       // (1) http://physics.nist.gov/cuu/Units/binary.html
167       // (2) http://www.isi.edu/isd/LOOM/documentation/unit-definitions.text
168 
169 	units 		= new String[unitsStopAt + 1];
170 	units_bits 	= new String[unitsStopAt + 1];
171     units_rate 	= new String[unitsStopAt + 1];
172 
173     if ( use_si_units ){
174       all_units[UNIT_TB] = getUnit("TiB");
175       all_units[UNIT_GB] = getUnit("GiB");
176       all_units[UNIT_MB] = getUnit("MiB");
177       all_units[UNIT_KB] = getUnit("KiB");
178       all_units[UNIT_B]  = getUnit("B");
179 
180       	// fall through intentional
181 
182       switch (unitsStopAt) {
183         case UNIT_TB:
184          units[UNIT_TB] = all_units[UNIT_TB];
185          units_bits[UNIT_TB] = getUnit("Tibit");
186          units_rate[UNIT_TB] = (use_units_rate_bits) ? getUnit("Tibit")  : getUnit("TiB");
187         case UNIT_GB:
188           units[UNIT_GB]= all_units[UNIT_GB];
189           units_bits[UNIT_GB]= getUnit("Gibit");
190           units_rate[UNIT_GB] = (use_units_rate_bits) ? getUnit("Gibit")  : getUnit("GiB");
191         case UNIT_MB:
192           units[UNIT_MB] = all_units[UNIT_MB];
193           units_bits[UNIT_MB] = getUnit("Mibit");
194           units_rate[UNIT_MB] = (use_units_rate_bits) ? getUnit("Mibit")  : getUnit("MiB");
195         case UNIT_KB:
196           // can be upper or lower case k
197           units[UNIT_KB] = all_units[UNIT_KB];
198           units_bits[UNIT_KB] = getUnit("Kibit");
199 
200           // can be upper or lower case k, upper more consistent
201           units_rate[UNIT_KB] = (use_units_rate_bits) ? getUnit("Kibit")  : getUnit("KiB");
202         case UNIT_B:
203           units[UNIT_B] =all_units[UNIT_B];
204           units_bits[UNIT_B] = getUnit("bit");
205           units_rate[UNIT_B] = (use_units_rate_bits)  ?   getUnit("bit")  :   getUnit("B");
206       }
207     }else{
208       all_units[UNIT_TB] = getUnit("TB");
209       all_units[UNIT_GB] = getUnit("GB");
210       all_units[UNIT_MB] = getUnit("MB");
211       all_units[UNIT_KB] = getUnit("kB");
212       all_units[UNIT_B]  = getUnit("B");
213 
214       switch (unitsStopAt) {
215         case UNIT_TB:
216           units[UNIT_TB] = all_units[UNIT_TB];
217           units_bits[UNIT_TB] = getUnit("Tbit");
218           units_rate[UNIT_TB] = (use_units_rate_bits) ? getUnit("Tbit")  : getUnit("TB");
219         case UNIT_GB:
220           units[UNIT_GB]= all_units[UNIT_GB];
221           units_bits[UNIT_GB]= getUnit("Gbit");
222           units_rate[UNIT_GB] = (use_units_rate_bits) ? getUnit("Gbit")  : getUnit("GB");
223         case UNIT_MB:
224           units[UNIT_MB] = all_units[UNIT_MB];
225           units_bits[UNIT_MB] = getUnit("Mbit");
226           units_rate[UNIT_MB] = (use_units_rate_bits) ? getUnit("Mbit")  : getUnit("MB");
227         case UNIT_KB:
228           // yes, the k should be lower case
229           units[UNIT_KB] = all_units[UNIT_KB];
230           units_bits[UNIT_KB] = getUnit("kbit");
231           units_rate[UNIT_KB] = (use_units_rate_bits) ? getUnit("kbit")  : getUnit("kB");
232         case UNIT_B:
233           units[UNIT_B] = all_units[UNIT_B];
234           units_bits[UNIT_B] = getUnit("bit");
235           units_rate[UNIT_B] = (use_units_rate_bits)  ?  getUnit("bit")  :  getUnit("B");
236       }
237     }
238 
239 
240     per_sec = getResourceString( "Formats.units.persec", "/s" );
241 
242     units_base10 =
243     	new String[]{
244     		getUnit( use_units_rate_bits?"bit":"B"),
245     		getUnit( use_units_rate_bits?"kbit":"KB"),
246     		getUnit( use_units_rate_bits?"Mbit":"MB" ),
247     		getUnit( use_units_rate_bits?"Gbit":"GB"),
248     		getUnit( use_units_rate_bits?"Tbit":"TB" )};
249 
250     for (int i = 0; i <= unitsStopAt; i++) {
251       units[i] 		= units[i];
252       units_rate[i] = units_rate[i] + per_sec;
253     }
254 
255 	Arrays.fill( cached_number_formats, null );
256 
257 	percentage_format = NumberFormat.getPercentInstance();
258 	percentage_format.setMinimumFractionDigits(1);
259 	percentage_format.setMaximumFractionDigits(1);
260 
261 		 decimalSeparator = new DecimalFormatSymbols().getDecimalSeparator();
262    }
263 
264 	private static String
getUnit( String key )265 	getUnit(
266 		String	key )
267 	{
268 		String res = " " + getResourceString( "Formats.units." + key, key );
269 
270 		return( res );
271 	}
272 
273 	private static String	PeerManager_status_finished;
274 	private static String	PeerManager_status_finishedin;
275 	private static String	Formats_units_alot;
276 	private static String	discarded;
277 	private static String	ManagerItem_waiting;
278 	private static String	ManagerItem_initializing;
279 	private static String	ManagerItem_allocating;
280 	private static String	ManagerItem_checking;
281 	private static String	ManagerItem_finishing;
282 	private static String	ManagerItem_ready;
283 	private static String	ManagerItem_downloading;
284 	private static String	ManagerItem_swarmMerge;
285 	private static String	ManagerItem_seeding;
286 	private static String	ManagerItem_superseeding;
287 	private static String	ManagerItem_stopping;
288 	private static String	ManagerItem_stopped;
289 	private static String	ManagerItem_paused;
290 	private static String	ManagerItem_queued;
291 	private static String	ManagerItem_error;
292 	private static String	ManagerItem_forced;
293 	private static String	ManagerItem_moving;
294 
295 	private static String	yes;
296 	private static String	no;
297 
298 	public static void
loadMessages()299 	loadMessages()
300 	{
301 		PeerManager_status_finished 	= getResourceString( "PeerManager.status.finished", "Finished" );
302 		PeerManager_status_finishedin	= getResourceString( "PeerManager.status.finishedin", "Finished in" );
303 		Formats_units_alot				= getResourceString( "Formats.units.alot", "A lot" );
304 		discarded						= getResourceString( "discarded", "discarded" );
305 		ManagerItem_waiting				= getResourceString( "ManagerItem.waiting", "waiting" );
306 		ManagerItem_initializing		= getResourceString( "ManagerItem.initializing", "initializing" );
307 		ManagerItem_allocating			= getResourceString( "ManagerItem.allocating", "allocating" );
308 		ManagerItem_checking			= getResourceString( "ManagerItem.checking", "checking" );
309 		ManagerItem_finishing			= getResourceString( "ManagerItem.finishing", "finishing" );
310 		ManagerItem_ready				= getResourceString( "ManagerItem.ready", "ready" );
311 		ManagerItem_downloading			= getResourceString( "ManagerItem.downloading", "downloading" );
312 		ManagerItem_swarmMerge			= getResourceString( "TableColumn.header.mergeddata", "swarm merge" );
313 		ManagerItem_seeding				= getResourceString( "ManagerItem.seeding", "seeding" );
314 		ManagerItem_superseeding		= getResourceString( "ManagerItem.superseeding", "superseeding" );
315 		ManagerItem_stopping			= getResourceString( "ManagerItem.stopping", "stopping" );
316 		ManagerItem_stopped				= getResourceString( "ManagerItem.stopped", "stopped" );
317 		ManagerItem_paused				= getResourceString( "ManagerItem.paused", "paused" );
318 		ManagerItem_queued				= getResourceString( "ManagerItem.queued", "queued" );
319 		ManagerItem_error				= getResourceString( "ManagerItem.error", "error" );
320 		ManagerItem_forced				= getResourceString( "ManagerItem.forced", "forced" );
321 		ManagerItem_moving				= getResourceString( "ManagerItem.moving", "moving" );
322 		yes								= getResourceString( "GeneralView.yes", "Yes" );
323 		no								= getResourceString( "GeneralView.no", "No" );
324 	}
325 
326 	private static String
getResourceString( String key, String def )327 	getResourceString(
328 		String	key,
329 		String	def )
330 	{
331 		if ( message_text_state == 0 ){
332 
333 				// this fooling around is to permit the use of this class in the absence of the (large) overhead
334 				// of resource bundles
335 
336 			try{
337 				MessageText.class.getName();
338 
339 				message_text_state	= 1;
340 
341 			}catch( Throwable e ){
342 
343 				message_text_state	= 2;
344 			}
345 		}
346 
347 		if ( message_text_state == 1 ){
348 
349 			return( MessageText.getString( key ));
350 
351 		}else{
352 
353 			return( def );
354 		}
355 	}
356 
357 	public static String
getYesNo( boolean b )358 	getYesNo(
359 		boolean	b )
360 	{
361 		return( b?yes:no );
362 	}
363 
364 	public static String
getRateUnit( int unit_size )365 	getRateUnit(
366 		int		unit_size )
367 	{
368 		return( units_rate[unit_size].substring(1, units_rate[unit_size].length()) );
369 	}
370 	public static String
getUnit( int unit_size )371 	getUnit(
372 		int		unit_size )
373 	{
374 		return( units[unit_size].substring(1, units[unit_size].length()) );
375 	}
376 
377 	public static String
getRateUnitBase10(int unit_size)378 	getRateUnitBase10(int unit_size) {
379 		return units_base10[unit_size] + per_sec;
380 	}
381 
382 	public static String
getUnitBase10(int unit_size)383 	getUnitBase10(int unit_size) {
384 		return units_base10[unit_size];
385 	}
386 
387 	public static boolean
isRateUsingBits()388 	isRateUsingBits()
389 	{
390 		return( use_units_rate_bits );
391 	}
392 
393 	public static String
formatByteCountToKiBEtc(int n)394 	formatByteCountToKiBEtc(int n)
395 	{
396 		return( formatByteCountToKiBEtc((long)n));
397 	}
398 
399 	public static String
formatByteCountToKiBEtc( long n )400 	formatByteCountToKiBEtc(
401 		long n )
402 	{
403 		return( formatByteCountToKiBEtc( n, false, TRUNCZEROS_NO));
404 	}
405 
406 	public static
formatByteCountToKiBEtc( long n, boolean bTruncateZeros )407 	String formatByteCountToKiBEtc(
408 		long n, boolean bTruncateZeros )
409 	{
410 		return( formatByteCountToKiBEtc( n, false, bTruncateZeros ));
411 	}
412 
413 	public static
formatByteCountToKiBEtc( long n, boolean rate, boolean bTruncateZeros)414 	String formatByteCountToKiBEtc(
415 		long	n,
416 		boolean	rate,
417 		boolean bTruncateZeros)
418 	{
419 		return formatByteCountToKiBEtc(n, rate, bTruncateZeros, -1);
420 	}
421 
422 	public static int
getKinB()423 	getKinB()
424 	{
425 		return( force_si_values?1024:(use_si_units?1024:1000));
426 	}
427 
428 	public static
formatByteCountToKiBEtc( long n, boolean rate, boolean bTruncateZeros, int precision)429 	String formatByteCountToKiBEtc(
430 		long	n,
431 		boolean	rate,
432 		boolean bTruncateZeros,
433 		int precision)
434 	{
435 		double dbl = (rate && use_units_rate_bits) ? n * 8 : n;
436 
437 	  	int unitIndex = UNIT_B;
438 
439         long	div = force_si_values?1024:(use_si_units?1024:1000);
440 
441 	  	while (dbl >= div && unitIndex < unitsStopAt){
442 
443 		  dbl /= div;
444 		  unitIndex++;
445 		}
446 
447 	  if (precision < 0) {
448 	  	precision = UNITS_PRECISION[unitIndex];
449 	  }
450 
451 	  // round for rating, because when the user enters something like 7.3kbps
452 		// they don't want it truncated and displayed as 7.2
453 		// (7.3*1024 = 7475.2; 7475/1024.0 = 7.2998;  trunc(7.2998, 1 prec.) == 7.2
454 	  //
455 		// Truncate for rest, otherwise we get complaints like:
456 		// "I have a 1.0GB torrent and it says I've downloaded 1.0GB.. why isn't
457 		//  it complete? waaah"
458 
459 		return formatDecimal(dbl, precision, bTruncateZeros, rate)
460 				+ (rate ? units_rate[unitIndex] : units[unitIndex]);
461 	}
462 
463 	public static
formatByteCountToKiBEtc( long n, boolean rate, boolean bTruncateZeros, int precision, int minUnit )464 	String formatByteCountToKiBEtc(
465 			long	n,
466 			boolean	rate,
467 			boolean bTruncateZeros,
468 			int precision,
469 			int	minUnit )
470 	{
471 		double dbl = (rate && use_units_rate_bits) ? n * 8 : n;
472 
473 		int unitIndex = UNIT_B;
474 
475 		long	div = force_si_values?1024:(use_si_units?1024:1000);
476 
477 		while (dbl >= div && unitIndex < unitsStopAt){
478 
479 			dbl /= div;
480 			unitIndex++;
481 		}
482 
483 		while( unitIndex < minUnit ){
484 			dbl /= div;
485 			unitIndex++;
486 		}
487 		if (precision < 0) {
488 			precision = UNITS_PRECISION[unitIndex];
489 		}
490 
491 		// round for rating, because when the user enters something like 7.3kbps
492 		// they don't want it truncated and displayed as 7.2
493 		// (7.3*1024 = 7475.2; 7475/1024.0 = 7.2998;  trunc(7.2998, 1 prec.) == 7.2
494 		//
495 		// Truncate for rest, otherwise we get complaints like:
496 		// "I have a 1.0GB torrent and it says I've downloaded 1.0GB.. why isn't
497 		//  it complete? waaah"
498 
499 		return formatDecimal(dbl, precision, bTruncateZeros, rate)
500 		+ (rate ? units_rate[unitIndex] : units[unitIndex]);
501 	}
502 
503 	public static boolean
isDataProtSeparate()504 	isDataProtSeparate()
505 	{
506 		return( separate_prot_data_stats );
507 	}
508 
509 	public static String
formatDataProtByteCountToKiBEtc( long data, long prot )510 	formatDataProtByteCountToKiBEtc(
511 		long	data,
512 		long	prot )
513 	{
514 		if ( separate_prot_data_stats ){
515 			if ( data == 0 && prot == 0 ){
516 				return( formatByteCountToKiBEtc(0));
517 			}else if ( data == 0 ){
518 				return( "(" + formatByteCountToKiBEtc( prot) + ")");
519 			}else if ( prot == 0 ){
520 				return( formatByteCountToKiBEtc( data ));
521 			}else{
522 				return(formatByteCountToKiBEtc(data)+" ("+ formatByteCountToKiBEtc(prot)+")");
523 			}
524 		}else if ( data_stats_only ){
525 			return( formatByteCountToKiBEtc( data ));
526 		}else{
527 			return( formatByteCountToKiBEtc( prot + data ));
528 		}
529 	}
530 
531 	public static String
formatDataProtByteCountToKiBEtcPerSec( long data, long prot )532 	formatDataProtByteCountToKiBEtcPerSec(
533 		long	data,
534 		long	prot )
535 	{
536 		if ( separate_prot_data_stats ){
537 			if ( data == 0 && prot == 0 ){
538 				return(formatByteCountToKiBEtcPerSec(0));
539 			}else if ( data == 0 ){
540 				return( "(" + formatByteCountToKiBEtcPerSec( prot) + ")");
541 			}else if ( prot == 0 ){
542 				return( formatByteCountToKiBEtcPerSec( data ));
543 			}else{
544 				return(formatByteCountToKiBEtcPerSec(data)+" ("+ formatByteCountToKiBEtcPerSec(prot)+")");
545 			}
546 		}else if ( data_stats_only ){
547 			return( formatByteCountToKiBEtcPerSec( data ));
548 		}else{
549 			return( formatByteCountToKiBEtcPerSec( prot + data ));
550 		}
551 	}
552 
553 	public static String
formatByteCountToKiBEtcPerSec( long n )554 	formatByteCountToKiBEtcPerSec(
555 		long		n )
556 	{
557 		return( formatByteCountToKiBEtc(n,true,TRUNCZEROS_NO));
558 	}
559 
560 
561     public static String
formatByteCountToKiBEtcPerSec( long n, boolean bTruncateZeros)562 	formatByteCountToKiBEtcPerSec(
563 		long		n,
564 		boolean bTruncateZeros)
565 	{
566 		return( formatByteCountToKiBEtc(n,true, bTruncateZeros));
567 	}
568 
569 		// base 10 ones
570 
571 	public static String
formatByteCountToBase10KBEtc( long n)572 	formatByteCountToBase10KBEtc(
573 			long n)
574 	{
575 		if ( use_units_rate_bits ){
576 			n *= 8;
577 		}
578 
579 		if (n < 1000){
580 
581 			return n + units_base10[UNIT_B];
582 
583 		}else if (n < 1000 * 1000){
584 
585 			return 	(n / 1000) + "." +
586 					((n % 1000) / 100) +
587 					units_base10[UNIT_KB];
588 
589 		}else if ( n < 1000L * 1000L * 1000L  || not_use_GB_TB ){
590 
591 			return 	(n / (1000L * 1000L)) + "." +
592 					((n % (1000L * 1000L)) / (1000L * 100L)) +
593 					units_base10[UNIT_MB];
594 
595 		}else if (n < 1000L * 1000L * 1000L * 1000L){
596 
597 			return (n / (1000L * 1000L * 1000L)) + "." +
598 					((n % (1000L * 1000L * 1000L)) / (1000L * 1000L * 100L))+
599 					units_base10[UNIT_GB];
600 
601 		}else if (n < 1000L * 1000L * 1000L * 1000L* 1000L){
602 
603 			return (n / (1000L * 1000L * 1000L* 1000L)) + "." +
604 					((n % (1000L * 1000L * 1000L* 1000L)) / (1000L * 1000L * 1000L* 100L))+
605 					units_base10[UNIT_TB];
606 		}else{
607 
608 			return Formats_units_alot;
609 		}
610 	}
611 
612 	public static String
formatByteCountToBase10KBEtcPerSec( long n )613 	formatByteCountToBase10KBEtcPerSec(
614 			long		n )
615 	{
616 		return( formatByteCountToBase10KBEtc(n) + per_sec );
617 	}
618 
619 
620     /**
621      * Print the BITS/second in an international format.
622      * @param n - always formatted using SI (i.e. decimal) prefixes
623      * @return String in an internationalized format.
624      */
625     public static String
formatByteCountToBitsPerSec( long n)626     formatByteCountToBitsPerSec(
627         long n)
628     {
629         double dbl = n * 8;
630 
631         int unitIndex = UNIT_B;
632 
633         long	div = 1000;
634 
635         while (dbl >= div && unitIndex < unitsStopAt){
636 
637           dbl /= div;
638           unitIndex++;
639         }
640 
641         int  precision = UNITS_PRECISION[unitIndex];
642 
643         return( formatDecimal(dbl, precision, true, true) + units_bits[unitIndex] + per_sec );
644     }
645 
646     public static String
formatETA(long eta)647     formatETA(long eta)
648     {
649     	return( formatETA( eta, false ));
650     }
651 
652     private static final SimpleDateFormat abs_df = new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss" );
653 
654     public static String
formatETA(long eta,boolean abs )655     formatETA(long eta,boolean abs )
656     {
657     	if (eta == 0) return PeerManager_status_finished;
658     	if (eta == -1) return "";
659     	if (eta > 0){
660     		if ( abs && !(eta == Constants.CRAPPY_INFINITY_AS_INT || eta >= Constants.CRAPPY_INFINITE_AS_LONG )){
661 
662     			long now 	= SystemTime.getCurrentTime();
663     			long then 	= now + eta*1000;
664 
665     			if ( eta > 5*60 ){
666 
667     				then = (then/(60*1000))*(60*1000);
668     			}
669 
670      			String	str1;
671       			String	str2;
672 
673       			synchronized( abs_df ){
674       				str1 = abs_df.format(new Date( now ));
675       				str2 = abs_df.format(new Date( then ));
676       			}
677 
678       			int	len = Math.min(str1.length(), str2.length())-2;
679 
680       			int	diff_at = len;
681 
682       			for ( int i=0; i<len; i++){
683 
684       				char	c1 = str1.charAt( i );
685 
686       				if ( c1 != str2.charAt(i)){
687 
688       					diff_at = i;
689 
690       					break;
691       				}
692       			}
693 
694       			String	res;
695 
696       			if ( diff_at >= 11 ){
697 
698       				res = str2.substring( 11 );
699 
700       			}else if ( diff_at >= 5 ){
701 
702       				res = str2.substring( 5 );
703 
704       			}else{
705 
706       				res = str2;
707       			}
708 
709       			return( res  );
710 
711     		}else{
712     			return TimeFormatter.format(eta);
713     		}
714     	}
715 
716     	return PeerManager_status_finishedin + " " + TimeFormatter.format(eta * -1);
717     }
718 
719 
720 	public static String
formatDownloaded( DownloadManagerStats stats )721 	formatDownloaded(
722 		DownloadManagerStats	stats )
723 	{
724 		long	total_discarded = stats.getDiscarded();
725 		long	total_received 	= stats.getTotalGoodDataBytesReceived();
726 
727 		if(total_discarded == 0){
728 
729 			return formatByteCountToKiBEtc(total_received);
730 
731 		}else{
732 
733 			return formatByteCountToKiBEtc(total_received) + " ( " +
734 					DisplayFormatters.formatByteCountToKiBEtc(total_discarded) + " " +
735 					discarded + " )";
736 		}
737 	}
738 
739 	public static String
formatHashFails( DownloadManager download_manager )740 	formatHashFails(
741 		DownloadManager		download_manager )
742 	{
743 		TOTorrent	torrent = download_manager.getTorrent();
744 
745 		if ( torrent != null ){
746 
747 			long bad = download_manager.getStats().getHashFailBytes();
748 
749 					// size can exceed int so ensure longs used in multiplication
750 
751 			long count = bad / (long)torrent.getPieceLength();
752 
753 			String result = count + " ( " + formatByteCountToKiBEtc(bad) + " )";
754 
755 			return result;
756 	  	}
757 
758   		return "";
759 	}
760 
761 	public static String
formatDownloadStatus( DownloadManager manager )762 	formatDownloadStatus(
763 		DownloadManager		manager )
764 	{
765 		if ( manager == null ){
766 
767 			return( ManagerItem_error + ": Download is null" );
768 		}
769 
770 		int state = manager.getState();
771 
772 		String	tmp = "";
773 
774 		switch (state) {
775 			case DownloadManager.STATE_QUEUED:
776 				tmp = ManagerItem_queued;
777 				break;
778 
779 			case DownloadManager.STATE_DOWNLOADING:
780 				tmp = ManagerItem_downloading;
781 				if ( manager.isSwarmMerging() != null ){
782 					tmp += " + " + ManagerItem_swarmMerge;
783 				}
784 				break;
785 
786 			case DownloadManager.STATE_SEEDING:{
787 
788 				DiskManager diskManager = manager.getDiskManager();
789 
790 				if ( diskManager != null ){
791 
792 					int	mp = diskManager.getMoveProgress();
793 
794 					if ( mp != -1 ){
795 
796 						tmp = ManagerItem_moving + ": "	+ formatPercentFromThousands( mp );
797 
798 					}else{
799 						int done = diskManager.getCompleteRecheckStatus();
800 
801 						if ( done != -1 ){
802 
803 							tmp = ManagerItem_seeding + " + " + ManagerItem_checking + ": "	+ formatPercentFromThousands(done);
804 						}
805 					}
806 				}
807 
808 				if ( tmp == "" ){
809 
810 					if (manager.getPeerManager() != null && manager.getPeerManager().isSuperSeedMode()) {
811 
812 						tmp = ManagerItem_superseeding;
813 
814 					}else{
815 
816 						tmp = ManagerItem_seeding;
817 					}
818 				}
819 
820 				break;
821 			}
822 			case DownloadManager.STATE_STOPPED:
823 				tmp = manager.isPaused() ? ManagerItem_paused : ManagerItem_stopped;
824 				break;
825 
826 			case DownloadManager.STATE_ERROR:
827 				tmp = ManagerItem_error + ": " + manager.getErrorDetails();
828 				break;
829 
830 			case DownloadManager.STATE_WAITING:
831 				tmp = ManagerItem_waiting;
832 				break;
833 
834 			case DownloadManager.STATE_INITIALIZING:
835 				tmp = ManagerItem_initializing;
836 				break;
837 
838 			case DownloadManager.STATE_INITIALIZED:
839 				tmp = ManagerItem_initializing;
840 				break;
841 
842 			case DownloadManager.STATE_ALLOCATING:{
843 				tmp = ManagerItem_allocating;
844 				DiskManager diskManager = manager.getDiskManager();
845 				if (diskManager != null){
846 					tmp += ": " + formatPercentFromThousands( diskManager.getPercentDone());
847 				}
848 				break;
849 			}
850 			case DownloadManager.STATE_CHECKING:
851 				tmp = ManagerItem_checking + ": "
852 						+ formatPercentFromThousands(manager.getStats().getCompleted());
853 				break;
854 
855 			case DownloadManager.STATE_FINISHING:
856 				tmp = ManagerItem_finishing;
857 				break;
858 
859 			case DownloadManager.STATE_READY:
860 				tmp = ManagerItem_ready;
861 				break;
862 
863 			case DownloadManager.STATE_STOPPING:
864 				tmp = ManagerItem_stopping;
865 				break;
866 
867 			default:
868 				tmp = String.valueOf(state);
869 		}
870 
871 		if (manager.isForceStart() &&
872 		    (state == DownloadManager.STATE_SEEDING ||
873 		     state == DownloadManager.STATE_DOWNLOADING))
874 			tmp = ManagerItem_forced + " " + tmp;
875 		return( tmp );
876 	}
877 
878 	public static String
formatDownloadStatusDefaultLocale( DownloadManager manager )879 	formatDownloadStatusDefaultLocale(
880 		DownloadManager		manager )
881 	{
882 		int state = manager.getState();
883 
884 		String	tmp = "";
885 
886 		DiskManager	dm = manager.getDiskManager();
887 
888 		switch (state) {
889 		  case DownloadManager.STATE_WAITING :
890 			tmp = MessageText.getDefaultLocaleString("ManagerItem.waiting");
891 			break;
892 		  case DownloadManager.STATE_INITIALIZING :
893 			  tmp = MessageText.getDefaultLocaleString("ManagerItem.initializing");
894 			  break;
895 		  case DownloadManager.STATE_INITIALIZED :
896 			  tmp = MessageText.getDefaultLocaleString("ManagerItem.initializing");
897 			  break;
898 		  case DownloadManager.STATE_ALLOCATING :
899 			tmp = MessageText.getDefaultLocaleString("ManagerItem.allocating");
900 			break;
901 		  case DownloadManager.STATE_CHECKING :
902 			tmp = MessageText.getDefaultLocaleString("ManagerItem.checking");
903 			break;
904 		  case DownloadManager.STATE_FINISHING :
905 		    tmp = MessageText.getDefaultLocaleString("ManagerItem.finishing");
906 		    break;
907          case DownloadManager.STATE_READY :
908 			tmp = MessageText.getDefaultLocaleString("ManagerItem.ready");
909 			break;
910 		  case DownloadManager.STATE_DOWNLOADING :
911 			tmp = MessageText.getDefaultLocaleString("ManagerItem.downloading");
912 			if ( manager.isSwarmMerging() != null ){
913 				tmp += " + " + MessageText.getDefaultLocaleString( "TableColumn.header.mergeddata" );
914 			}
915 			break;
916 		  case DownloadManager.STATE_SEEDING :
917 		  	if (dm != null && dm.getCompleteRecheckStatus() != -1 ) {
918 		  		int	done = dm.getCompleteRecheckStatus();
919 
920 				if ( done == -1 ){
921 				  done = 1000;
922 				}
923 
924 		  		tmp = MessageText.getDefaultLocaleString("ManagerItem.seeding") + " + " +
925 		  				MessageText.getDefaultLocaleString("ManagerItem.checking") +
926 		  				": " + formatPercentFromThousands( done );
927 		  	}
928 		  	else if(manager.getPeerManager()!= null && manager.getPeerManager().isSuperSeedMode()){
929 
930 		  		tmp = MessageText.getDefaultLocaleString("ManagerItem.superseeding");
931 		  	}
932 		  	else {
933 		  		tmp = MessageText.getDefaultLocaleString("ManagerItem.seeding");
934 		  	}
935 		  	break;
936 		  case DownloadManager.STATE_STOPPING :
937 		  	tmp = MessageText.getDefaultLocaleString("ManagerItem.stopping");
938 		  	break;
939 		  case DownloadManager.STATE_STOPPED :
940 			tmp = MessageText.getDefaultLocaleString(manager.isPaused()?"ManagerItem.paused":"ManagerItem.stopped");
941 			break;
942 		  case DownloadManager.STATE_QUEUED :
943 			tmp = MessageText.getDefaultLocaleString("ManagerItem.queued");
944 			break;
945 		  case DownloadManager.STATE_ERROR :
946 			tmp = MessageText.getDefaultLocaleString("ManagerItem.error").concat(": ").concat(manager.getErrorDetails()); //$NON-NLS-1$ //$NON-NLS-2$
947 			break;
948 			default :
949 			tmp = String.valueOf(state);
950 		}
951 
952 		return( tmp );
953 	}
954 
955 	public static String
trimDigits( String str, int num_digits )956 	trimDigits(
957 		String		str,
958 		int			num_digits )
959 	{
960 		char[] 	chars 	= str.toCharArray();
961 		String 	res 	= "";
962 		int		digits 	= 0;
963 
964 		for (int i=0;i<chars.length;i++){
965 			char c = chars[i];
966 			if ( Character.isDigit(c)){
967 				digits++;
968 				if ( digits <= num_digits ){
969 					res += c;
970 				}
971 			}else if ( c == '.' && digits >= 3 ){
972 
973 			}else{
974 				res += c;
975 			}
976 		}
977 
978 		return( res );
979 	}
980 
formatPercentFromThousands(int thousands)981   public static String formatPercentFromThousands(int thousands) {
982 
983     return percentage_format.format(thousands / 1000.0);
984   }
985 
formatTimeStamp(long time)986   public static String formatTimeStamp(long time) {
987     StringBuffer sb = new StringBuffer();
988     Calendar calendar = Calendar.getInstance();
989     calendar.setTimeInMillis(time);
990     sb.append('[');
991     sb.append(formatIntToTwoDigits(calendar.get(Calendar.DAY_OF_MONTH)));
992     sb.append('.');
993     sb.append(formatIntToTwoDigits(calendar.get(Calendar.MONTH)+1));	// 0 based
994     sb.append('.');
995     sb.append(calendar.get(Calendar.YEAR));
996     sb.append(' ');
997     sb.append(formatIntToTwoDigits(calendar.get(Calendar.HOUR_OF_DAY)));
998     sb.append(':');
999     sb.append(formatIntToTwoDigits(calendar.get(Calendar.MINUTE)));
1000     sb.append(':');
1001     sb.append(formatIntToTwoDigits(calendar.get(Calendar.SECOND)));
1002     sb.append(']');
1003     return sb.toString();
1004   }
1005 
formatIntToTwoDigits(int n)1006   public static String formatIntToTwoDigits(int n) {
1007     return n < 10 ? "0".concat(String.valueOf(n)) : String.valueOf(n);
1008   }
1009 
formatDate(long date, String format)1010   private static String formatDate(long date, String format) {
1011 	  if (date == 0) {return "";}
1012 	  SimpleDateFormat temp = new SimpleDateFormat(format);
1013 	  return temp.format(new Date(date));
1014   }
1015 
formatDate(long date)1016   public static String formatDate(long date) {
1017   	return formatDate(date, "dd-MMM-yyyy HH:mm:ss");
1018   }
1019 
formatDateShort(long date)1020   public static String formatDateShort(long date) {
1021 	  return formatDate(date, "MMM dd, HH:mm");
1022     }
1023 
formatDateNum(long date)1024   public static String formatDateNum(long date) {
1025 	  return formatDate(date, "yyyy-MM-dd HH:mm:ss");
1026   }
1027 
1028   //
1029   // These methods will be exposed in the plugin API.
1030   //
1031 
formatCustomDateOnly(long date)1032   public static String formatCustomDateOnly(long date) {
1033 	  if (date == 0) {return "";}
1034 	  return formatDate(date, "dd-MMM-yyyy");
1035   }
1036 
formatCustomTimeOnly(long date)1037   public static String formatCustomTimeOnly(long date) {
1038 	  return formatCustomTimeOnly(date, true);
1039   }
1040 
formatCustomTimeOnly(long date, boolean with_secs)1041   public static String formatCustomTimeOnly(long date, boolean with_secs) {
1042 	  if (date == 0) {return "";}
1043 	  return formatDate(date, (with_secs) ? "HH:mm:ss" : "HH:mm");
1044   }
1045 
formatCustomDateTime(long date)1046   public static String formatCustomDateTime(long date) {
1047 	  if (date == 0) {return "";}
1048 	  return formatDate(date);
1049   }
1050 
1051   //
1052   // End methods
1053   //
1054 
1055   public static String
formatTime( long time )1056   formatTime(
1057     long    time )
1058   {
1059     return( TimeFormatter.formatColon( time / 1000 ));
1060   }
1061 
1062   /**
1063    * Format a real number to the precision specified.  Does not round the number
1064    * or truncate trailing zeros.
1065    *
1066    * @param value real number to format
1067    * @param precision # of digits after the decimal place
1068    * @return formatted string
1069    */
1070   public static String
formatDecimal( double value, int precision)1071   formatDecimal(
1072   	double value,
1073   	int		precision)
1074   {
1075   	return formatDecimal(value, precision, TRUNCZEROS_NO, ROUND_NO);
1076   }
1077 
1078 
1079   /**
1080    * Format a real number
1081    *
1082    * @param value real number to format
1083    * @param precision max # of digits after the decimal place
1084    * @param bTruncateZeros remove any trailing zeros after decimal place
1085    * @param bRound Whether the number will be rounded to the precision, or
1086    *                truncated off.
1087    * @return formatted string
1088    */
1089 	public static String
formatDecimal( double value, int precision, boolean bTruncateZeros, boolean bRound)1090 	formatDecimal(
1091 			double value,
1092 			int precision,
1093 			boolean bTruncateZeros,
1094 			boolean bRound)
1095 	{
1096 		if (Double.isNaN(value) || Double.isInfinite(value)) {
1097 			return Constants.INFINITY_STRING;
1098 		}
1099 
1100 		double tValue;
1101 		if (bRound) {
1102 			tValue = value;
1103 		} else {
1104 			// NumberFormat rounds, so truncate at precision
1105 			if (precision == 0) {
1106 				tValue = (long) value;
1107 			} else {
1108 				double shift = Math.pow(10, precision);
1109 				tValue = ((long) (value * shift)) / shift;
1110 			}
1111 		}
1112 
1113 		int cache_index = (precision << 2) + ((bTruncateZeros ? 1 : 0) << 1)
1114 				+ (bRound ? 1 : 0);
1115 
1116 		NumberFormat nf = null;
1117 
1118 		if (cache_index < cached_number_formats.length) {
1119 			nf = cached_number_formats[cache_index];
1120 		}
1121 
1122 		if (nf == null) {
1123 			nf = NumberFormat.getNumberInstance();
1124 			nf.setGroupingUsed(false); // no commas
1125 			if (!bTruncateZeros) {
1126 				nf.setMinimumFractionDigits(precision);
1127 			}
1128 			if (bRound) {
1129 				nf.setMaximumFractionDigits(precision);
1130 			}
1131 
1132 			if (cache_index < cached_number_formats.length) {
1133 				cached_number_formats[cache_index] = nf;
1134 			}
1135 		}
1136 
1137 		return nf.format(tValue);
1138 	}
1139 
1140   		/**
1141   		 * Attempts vaguely smart string truncation by searching for largest token and truncating that
1142   		 * @param str
1143   		 * @param width
1144   		 * @return
1145   		 */
1146 
1147   	public static String
truncateString( String str, int width )1148 	truncateString(
1149 		String	str,
1150 		int		width )
1151   	{
1152   		int	excess = str.length() - width;
1153 
1154   		if ( excess <= 0 ){
1155 
1156   			return( str );
1157   		}
1158 
1159   		excess += 3;	// for ...
1160 
1161   		int	token_start = -1;
1162   		int	max_len		= 0;
1163   		int	max_start	= 0;
1164 
1165   		for (int i=0;i<str.length();i++){
1166 
1167   			char	c = str.charAt(i);
1168 
1169   			if ( Character.isLetterOrDigit( c ) || c == '-' || c == '~' ){
1170 
1171   				if ( token_start == -1 ){
1172 
1173   					token_start	= i;
1174 
1175   				}else{
1176 
1177   					int	len = i - token_start;
1178 
1179   					if ( len > max_len ){
1180 
1181   						max_len		= len;
1182   						max_start	= token_start;
1183   					}
1184   				}
1185   			}else{
1186 
1187   				token_start = -1;
1188   			}
1189   		}
1190 
1191   		if ( max_len >= excess ){
1192 
1193   			int	trim_point = max_start + max_len;
1194 
1195   			return( str.substring( 0, trim_point - excess ) + "..." + str.substring( trim_point ));
1196   		}else{
1197 
1198   			return( str.substring( 0, width-3 ) + "..." );
1199   		}
1200   	}
1201 
1202 
getDecimalSeparator()1203 	public static char getDecimalSeparator() {
1204 		return decimalSeparator;
1205 	}
1206 
1207 	private static void
updateFormatOverrides( String formats )1208 	updateFormatOverrides(
1209 		String	formats )
1210 	{
1211 		Map<String,Formatter> map = new HashMap<String, Formatter>();
1212 
1213 		String[] lines = formats.split( "\n" );
1214 
1215 		List<String>	errors = new ArrayList<String>();
1216 
1217 		for ( String line: lines ){
1218 
1219 			String error = null;
1220 
1221 			line = line.trim();
1222 
1223 			if ( line.length() == 0 ){
1224 
1225 				continue;
1226 			}
1227 
1228 			String[] key_value = line.split( ":", 2 );
1229 
1230 			if ( key_value.length != 2 ){
1231 
1232 				error = "is missing ':'";
1233 
1234 			}else{
1235 
1236 				String	key 	= key_value[0].trim();
1237 				String	value 	= key_value[1].trim();
1238 
1239 				Formatter formatter = new Formatter();
1240 
1241 				error = formatter.parse( value );
1242 
1243 				if ( error == null ){
1244 
1245 					map.put( key, formatter );
1246 				}
1247 			}
1248 
1249 			if ( error != null ){
1250 
1251 				errors.add( "'" + line + "' " + error );
1252 			}
1253 		}
1254 
1255 		String status_msg;
1256 
1257 		if ( errors.size() > 0 ){
1258 
1259 			status_msg = "Format parsing failed: " + errors;
1260 
1261 		}else{
1262 
1263 			status_msg = "";
1264 		}
1265 
1266 		COConfigurationManager.setParameter( "config.style.formatOverrides.status", status_msg );
1267 
1268 		format_map = map;
1269 	}
1270 
1271 	public static String
formatCustomRate( String key, long value )1272 	formatCustomRate(
1273 		String		key,
1274 		long		value )
1275 	{
1276 		Formatter formatter = format_map.get( key );
1277 
1278 		if ( formatter != null ){
1279 
1280 			return( formatter.format( value, true ));
1281 		}
1282 
1283 		return( null );
1284 	}
1285 
1286 	public static String
formatCustomSize( String key, long value )1287 	formatCustomSize(
1288 		String		key,
1289 		long		value )
1290 	{
1291 		Formatter formatter = format_map.get( key );
1292 
1293 		if ( formatter != null ){
1294 
1295 			return( formatter.format( value, false ));
1296 		}
1297 
1298 		return( null );
1299 	}
1300 
1301 	private static class
1302 	Formatter
1303 	{
1304 		private final static int FORMAT_UNIT_B	= 0x0001;
1305 		private final static int FORMAT_UNIT_K	= 0x0002;
1306 		private final static int FORMAT_UNIT_M	= 0x0004;
1307 		private final static int FORMAT_UNIT_G	= 0x0008;
1308 		private final static int FORMAT_UNIT_T	= 0x0010;
1309 
1310 		private final static int FORMAT_UNIT_NONE	= 0x0000;
1311 		private final static int FORMAT_UNIT_ALL	= 0xffff;
1312 
1313 		private final static int[] tens = { 1, 10, 100, 1000, 10000, 100000, 1000000 };
1314 
1315 		private int 	unit_formats 	= FORMAT_UNIT_ALL;
1316 		private boolean	hide_units		= false;
1317 		private boolean	short_units		= false;
1318 		private Boolean	rate_units		= null;
1319 
1320 		private NumberFormat 	number_format 		= null;
1321 		private long			number_format_fact	= 1;
1322 
1323 		private int rounding = BigDecimal.ROUND_HALF_EVEN;	// decimal format default
1324 
1325 		private String
parse( String str )1326 		parse(
1327 			String	str )
1328 		{
1329 			try{
1330 				String[] args = str.split( "," );
1331 
1332 				for ( String arg: args ){
1333 
1334 					arg = arg.trim();
1335 
1336 					if ( arg.length() == 0 ){
1337 
1338 						continue;
1339 					}
1340 
1341 					String[] sub_args = arg.split( ";" );
1342 
1343 					if ( sub_args.length == 0 ){
1344 
1345 						return( "invalid argument '" + arg + "'" );
1346 					}
1347 
1348 					String main_arg = null;
1349 
1350 					for ( String sub_arg: sub_args ){
1351 
1352 						sub_arg = sub_arg.trim();
1353 
1354 						String[] bits = sub_arg.split( "=" );
1355 
1356 						if ( bits.length != 2 ){
1357 
1358 							return( "invalid argument '" + arg + "'" );
1359 						}
1360 
1361 						String arg_name 	= bits[0].trim().toLowerCase( Locale.US );
1362 						String arg_value	= bits[1].trim();
1363 
1364 						if ( main_arg == null ){
1365 
1366 							main_arg = arg_name;
1367 
1368 							if ( main_arg.equals( "units" )){
1369 
1370 								int	mask = arg_value.contains( "-" )?FORMAT_UNIT_ALL:FORMAT_UNIT_NONE;
1371 
1372 								String[] units = arg_value.toLowerCase( Locale.US ).split( "&" );
1373 
1374 								for ( String unit: units ){
1375 
1376 									boolean	remove;
1377 
1378 									if ( unit.startsWith( "-" )){
1379 
1380 										unit = unit.substring(1);
1381 
1382 										remove = true;
1383 
1384 									}else{
1385 
1386 										remove = false;
1387 									}
1388 
1389 									char c = unit.charAt(0);
1390 
1391 									int	m;
1392 
1393 									if ( c == 'b' ){
1394 
1395 										m = FORMAT_UNIT_B;
1396 
1397 									}else if ( c == 'k' ){
1398 
1399 										m = FORMAT_UNIT_K;
1400 
1401 									}else if ( c == 'm' ){
1402 
1403 										m = FORMAT_UNIT_M;
1404 
1405 									}else if ( c == 'g' ){
1406 
1407 										m = FORMAT_UNIT_G;
1408 
1409 									}else if ( c == 't' ){
1410 
1411 										m = FORMAT_UNIT_T;
1412 
1413 									}else{
1414 
1415 										return( "Invalid unit: " + unit );
1416 									}
1417 
1418 									if ( remove ){
1419 
1420 										mask = mask & ~m;
1421 
1422 									}else{
1423 
1424 										mask = mask | m;
1425 									}
1426 								}
1427 
1428 								unit_formats = mask;
1429 
1430 							}else if ( main_arg.equals( "format" )){
1431 
1432 								number_format = NumberFormat.getInstance();
1433 
1434 								if ( number_format instanceof DecimalFormat ){
1435 
1436 									((DecimalFormat)number_format).applyPattern( arg_value );
1437 
1438 								}else{
1439 
1440 									Debug.out( "Number pattern isn't a DecimalFormat: " + number_format );
1441 								}
1442 
1443 								int max_fd = number_format.getMaximumFractionDigits();
1444 
1445 								if ( max_fd < tens.length ){
1446 
1447 									number_format_fact = tens[max_fd];
1448 
1449 								}else{
1450 
1451 									number_format_fact = 1;
1452 
1453 									for (int i=0;i<max_fd;i++){
1454 
1455 										number_format_fact *= 10;
1456 									}
1457 								}
1458 							}else{
1459 
1460 								Debug.out( "TODO: " + main_arg );
1461 							}
1462 						}else{
1463 
1464 							if ( main_arg.equals( "units" )){
1465 
1466 								if ( arg_name.equals( "hide" )){
1467 
1468 									hide_units = arg_value.toLowerCase( Locale.US ).startsWith( "y" );
1469 
1470 								}else if ( arg_name.equals( "short" )){
1471 
1472 									short_units = arg_value.toLowerCase( Locale.US ).startsWith( "y" );
1473 
1474 								}else if ( arg_name.equals( "rate" )){
1475 
1476 									rate_units = arg_value.toLowerCase( Locale.US ).startsWith( "y" );
1477 
1478 								}else{
1479 
1480 									Debug.out( "TODO: " + arg_name );
1481 								}
1482 							}else if ( main_arg.equals( "format" )){
1483 
1484 								if ( arg_name.equals( "round" )){
1485 
1486 									String r = arg_value.toLowerCase( Locale.US );
1487 
1488 									if ( r.equals( "up" )){
1489 
1490 										rounding = BigDecimal.ROUND_UP;
1491 
1492 									}else if ( r.equals( "down" )){
1493 
1494 										rounding = BigDecimal.ROUND_DOWN;
1495 
1496 									}else if ( r.equals( "halfup" )){
1497 
1498 										rounding = BigDecimal.ROUND_HALF_UP;
1499 
1500 									}else if ( r.equals( "halfdown" )){
1501 
1502 										rounding = BigDecimal.ROUND_HALF_DOWN;
1503 
1504 									}else{
1505 
1506 										return( "Invald round mode: " + r );
1507 									}
1508 								}
1509 							}else{
1510 
1511 								Debug.out( "TODO: " + arg_name );
1512 							}
1513 						}
1514 					}
1515 				}
1516 
1517 				return( null );
1518 
1519 			}catch( Throwable e ){
1520 
1521 				return( Debug.getNestedExceptionMessage( e ));
1522 			}
1523 		}
1524 
1525 		private String
format( long _value, boolean is_rate )1526 		format(
1527 			long		_value,
1528 			boolean		is_rate )
1529 		{
1530 			try{
1531 				double value = _value;
1532 
1533 				String	unit_str = "";
1534 
1535 				if ( unit_formats == FORMAT_UNIT_K ){
1536 
1537 					value = value/1024;
1538 
1539 					unit_str = all_units[ UNIT_KB ];
1540 
1541 				}else if ( unit_formats == FORMAT_UNIT_M ){
1542 
1543 					value = value/(1024*1024);
1544 
1545 					unit_str = all_units[ UNIT_MB ];
1546 
1547 				}else if ( unit_formats == FORMAT_UNIT_G ){
1548 
1549 					value = value/(1024*1024*1024L);
1550 
1551 					unit_str = all_units[ UNIT_GB ];
1552 
1553 				}else if ( unit_formats == FORMAT_UNIT_T ){
1554 
1555 					value = value/(1024*1024*1024*1024L);
1556 
1557 					unit_str = all_units[ UNIT_TB ];
1558 				}
1559 
1560 				String result;
1561 
1562 				if ( number_format != null ){
1563 
1564 					if ( rounding != BigDecimal.ROUND_HALF_EVEN ){
1565 
1566 							// meh, DecimalFormat doesn't support rounding modes so we have to pre-calculate and hope
1567 
1568 						double	l_value = (long)value;
1569 
1570 						double	fraction = value - l_value;
1571 
1572 						fraction *= number_format_fact;
1573 
1574 						double l_fraction = (long)fraction;
1575 
1576 						double rem = fraction - l_fraction;
1577 
1578 						if ( rounding == BigDecimal.ROUND_DOWN ){
1579 
1580 						}else if ( rounding == BigDecimal.ROUND_UP ){
1581 
1582 							if ( rem > 0 ){
1583 
1584 								l_fraction++;
1585 							}
1586 						}else if ( rounding == BigDecimal.ROUND_HALF_UP ){
1587 
1588 							if ( rem >= 0.5 ){
1589 
1590 								l_fraction++;
1591 							}
1592 						}else if ( rounding == BigDecimal.ROUND_HALF_DOWN ){
1593 
1594 							if ( rem > 0.5 ){
1595 
1596 								l_fraction++;
1597 							}
1598 						}
1599 
1600 						l_fraction /= number_format_fact;
1601 
1602 						//System.out.println( value + " -> " + l_value + "/" + l_fraction + "/" + rem );
1603 
1604 						value = l_value + l_fraction;
1605 					}
1606 
1607 					synchronized( number_format ){
1608 
1609 						result = number_format.format( value );
1610 					}
1611 
1612 				}else{
1613 
1614 					result = String.valueOf( value );
1615 				}
1616 
1617 				if ( hide_units ){
1618 
1619 					return( result );
1620 				}
1621 
1622 				if ( unit_str.length() > 0 ){
1623 
1624 					if ( short_units ){
1625 
1626 						result += " " + unit_str.charAt(1);
1627 
1628 					}else{
1629 
1630 						result += unit_str;
1631 					}
1632 				}
1633 
1634 				if ( is_rate && ( rate_units == null || rate_units )){
1635 
1636 					result += per_sec;
1637 				}
1638 
1639 				return( result );
1640 
1641 			}catch( Throwable e ){
1642 
1643 				Debug.out( e );
1644 
1645 				return( String.valueOf( _value ));
1646 			}
1647 		}
1648 	}
1649 
1650 
1651 
1652  	// Used to test fractions and displayformatter.
1653   	// Keep until everything works okay.
1654   	public static void
main(String[] args)1655   	main(String[] args)
1656   	{
1657   		// set decimal display to ","
1658   		//Locale.setDefault(Locale.GERMAN);
1659 
1660   		double d = 0.000003991630774821635;
1661   		NumberFormat nf =  NumberFormat.getNumberInstance();
1662   		nf.setMaximumFractionDigits(6);
1663   		nf.setMinimumFractionDigits(6);
1664   		String s = nf.format(d);
1665 
1666   		System.out.println("Actual: " + d);  // Displays 3.991630774821635E-6
1667   		System.out.println("NF/6:   " + s);  // Displays 0.000004
1668   		// should display 0.000003
1669 			System.out.println("DF:     " + DisplayFormatters.formatDecimal(d , 6));
1670   		// should display 0
1671 			System.out.println("DF 0:   " + DisplayFormatters.formatDecimal(d , 0));
1672   		// should display 0.000000
1673 			System.out.println("0.000000:" + DisplayFormatters.formatDecimal(0 , 6));
1674   		// should display 0.001
1675 			System.out.println("0.001:" + DisplayFormatters.formatDecimal(0.001, 6, TRUNCZEROS_YES, ROUND_NO));
1676   		// should display 0
1677 			System.out.println("0:" + DisplayFormatters.formatDecimal(0 , 0));
1678   		// should display 123456
1679 			System.out.println("123456:" + DisplayFormatters.formatDecimal(123456, 0));
1680   		// should display 123456
1681 			System.out.println("123456:" + DisplayFormatters.formatDecimal(123456.999, 0));
1682 			System.out.println(DisplayFormatters.formatDecimal(0.0/0, 3));
1683 	}
1684 }