View Javadoc

1   package com.sharkysoft.printf.engine;
2   
3   import com.sharkysoft.string.StringToolbox;
4   import com.sharkysoft.util.UnreachableCodeException;
5   
6   /***
7    * Justifies text in field.
8    *
9    * <p><b>Details:</b> A <code>StringFormatter</code> formats arbitrary length
10   * strings into a fixed-<wbr>length field according to specifiable formatting
11   * criteria.</p>
12   *
13   * <p>To justify a string in a field, create an instance of
14   * <code>StringFormatter</code>, configure the formatting properties, and then
15   * process the string through the justifier using
16   * {@link #format(String) format}.</p>
17   *
18   * <p><b>Example:</b> Output the contents of <code>vasNames</code>, an array of
19   * strings, with one element per line, right-<wbr>justified in a
20   * 50-<wbr>character field.  For strings shorter than 50 characters, fill in the
21   * open space on the left with an underscore character.</p>
22   *
23    *<blockquote><pre>
24      *String[] vasNames = {"apple", "banana", "cherry"};
25      *StringFormatter vpJustifier = new StringFormatter();
26      *vpJustifier.setFieldWidth(50);
27      *vpJustifier.setAlignment(AlignmentMode.gpRight);
28      *vpJustifier.setPadChar('_');
29      *for (int vnI = 0; vnI &lt; vasNames.length; ++ i)
30      *  System.out.println(vpJustifier.format(vasNames[i]));
31    *</pre></blockquote>
32   *
33   * <p>The output of this code is:</p>
34   *
35      *<blockquote><pre>
36       *_____________________________________________apple
37       *____________________________________________banana
38       *____________________________________________cherry
39     *</pre></blockquote>
40   *
41   * @author Sharky
42   */
43  public class StringFormatter
44  {
45  
46    //////////////////
47    //              //
48    //  FieldWidth  //
49    //              //
50    //////////////////
51  
52    /***
53     * Field width.
54     *
55     * <p><b>Details:</p> Property <code>FieldWidth</code> is the width of the
56     * field into which text will be formatted.</p>
57     *
58     * <p><b>Default value:</b> <code>0</code>.</p>
59     *
60     * @see #getFieldWidth()
61     * @see #setFieldWidth(int)
62     */
63    protected int mnFieldWidth = 0;
64  
65    /***
66     * Retrieves FieldWidth property.
67     *
68     * @return current value
69     *
70     * @see #mnFieldWidth
71     */
72    public int getFieldWidth() {return mnFieldWidth;}
73  
74    /***
75     * Updates FieldWidth property.
76     *
77     * @param inFieldWidth new value
78     *
79     * @see #mnFieldWidth
80     */
81    public void setFieldWidth(final int inFieldWidth)
82    {
83      if (inFieldWidth < 0)
84        throw new IllegalArgumentException("inFieldWidth=" + inFieldWidth);
85      mnFieldWidth = inFieldWidth;
86    }
87  
88    /////////////////
89    //             //
90    //  Alignment  //
91    //             //
92    /////////////////
93  
94    /***
95     * Alignment mode.
96     *
97     * <p><b>Details:</b> Property <code>Alignment</code> controls the positioning
98     * of text within the field.  Available alignment options include left
99     * justification, right justification, full justification, and centering.</p>
100    *
101    * <p><b>Default value:</b>
102    * {@link AlignmentMode#gpLeft AlignmentMode.gpLeft}.</p>
103    *
104    * @see #getAlignment()
105    * @see #setAlignment(AlignmentMode)
106    */
107   protected int mnAlignment = AlignmentMode.LEFT;
108 
109   /***
110    * Retrieves Alignment property.
111    *
112    * @return current value
113    *
114    * @see #mnAlignment
115    */
116   public AlignmentMode getAlignment()
117   {
118       return AlignmentMode.forInt(mnAlignment);
119   }
120 
121   /***
122    * Updates Alignment property.
123    *
124    * @param ipAlignment new value
125    *
126    * @see #mnAlignment
127    */
128   public void setAlignment(final AlignmentMode ipAlignment)
129   {
130     mnAlignment = ipAlignment.toInt();
131   }
132 
133   ///////////////
134   //           //
135   //  PadChar  //
136   //           //
137   ///////////////
138 
139   /***
140    * Padding character.
141    *
142    * <p><b>Details:</b> Property <code>PadChar</code> is the character used to
143    * fill the unused portion of the field when the text is shorter than the
144    * field.</p>
145    *
146    * <p><b>Default value:</b> '&nbsp;' (space character).</p>
147    *
148    * @see #getPadChar()
149    * @see #setPadChar(char)
150    */
151   protected char mcPadChar = ' ';
152 
153   /***
154    * Retrieves PadChar property.
155    *
156    * @return current value
157    *
158    * @see #mcPadChar
159    */
160   public char getPadChar() {return mcPadChar;}
161 
162   /***
163    * Updates PadChar property.
164    *
165    * @param icPadChar new value
166    *
167    * @see #mcPadChar
168    */
169   public void setPadChar(final char icPadChar) {mcPadChar = icPadChar;}
170 
171   ////////////////
172   //            //
173   //  Cropping  //
174   //            //
175   ////////////////
176 
177   /***
178    * Cropping mode.
179    *
180    * <p><b>Details:</b> Property <code>Cropping</code> controls whether and how
181    * text is compressed if it is larger than the field into which it is
182    * formatted.  The head, tail, or center portion of the text may be cropped to
183    * ensure that the formatted text fits within the field.  Or, cropping may be
184    * prohibited, so that long text actually overflows the field.</p>
185    *
186    * <p><b>Default value:</b>
187    * {@link CroppingMode#gpNone CroppingMode.gpNone}.</p>
188    *
189    * @see #getCropping()
190    * @see #setCropping(CroppingMode)
191    */
192   protected int mnCropping = CroppingMode.NONE;
193 
194   /***
195    * Retrieves Cropping property.
196    *
197    * @return current value
198    *
199    * @see #mnCropping
200    */
201   public CroppingMode getCropping()
202   {
203       return CroppingMode.forInt(mnCropping);
204   }
205 
206   /***
207    * Updates Cropping property.
208    *
209    * @param ipCropping new value
210    *
211    * @see #mnCropping
212    */
213   public void setCropping(final CroppingMode ipCropping)
214   {
215     mnCropping = ipCropping.toInt();
216   }
217 
218   /***
219    * Default constructor.
220    */
221   public StringFormatter() {}
222 
223   /***
224    * Crops text.
225    *
226    * <p><b>Details:</b> <code>crop</code> crops the supplied string according to
227    * the currently configured properties.  <code>isText</code> must not be
228    * <code>null</code>.</p>
229    *
230    * @param isText the string to crop
231    * @return the cropped string
232    */
233   private final String crop(final String isText)
234   {
235     final int vnPadSize = mnFieldWidth - isText.length();
236     if (vnPadSize <= 0)
237       switch (mnCropping)
238       {
239       case CroppingMode.NONE:
240         return isText;
241       case CroppingMode.LEFT:
242         return isText.substring(- vnPadSize);
243       case CroppingMode.RIGHT:
244         return isText.substring(0, mnFieldWidth);
245       case CroppingMode.MIDDLE:
246         return isText.substring(- vnPadSize / 2, - vnPadSize / 2 + mnFieldWidth);
247       default:
248         throw new UnreachableCodeException();
249       }
250     return isText;
251   }
252 
253   /***
254    * Justifies text.
255    *
256    * <p><b>Details:</b> <code>format</code> formats the supplied string
257    * according to currently configured properties.</p>
258    *
259    * <p>If <code>Alignment</code> is set to <code>FULL</code>, then the
260    * split-<wbr>string format method ({@link #format(String, String)}) should be
261    * used instead.  This is because the format method with only one string
262    * parameter does not know where the filler characters should be inserted.
263    * Eventually, this method may be upgraded to support automatic expansion of
264    * whitespace characters within the string for full justification.</p>
265    *
266    * @param isText the text to justify
267    * @return the formatted string
268    */
269   public final String format(final String isText)
270   {
271   	return format(isText, mcPadChar);
272   }
273 
274 	final String format(final String isText, final char icPad)
275 	{
276 		int vnPadSize = mnFieldWidth - isText.length();
277 		if (vnPadSize <= 0)
278 			return crop(isText);
279 		final String vsPadding = StringToolbox.repeat(icPad, vnPadSize);
280 		switch (mnAlignment)
281 		{
282 		case AlignmentMode.RIGHT:
283 			return vsPadding + isText;
284 		case AlignmentMode.LEFT:
285 			return isText + vsPadding;
286 		case AlignmentMode.CENTER:
287 			vnPadSize /= 2;
288 			return vsPadding.substring(0, vnPadSize) + isText + vsPadding.substring(vnPadSize);
289 		case AlignmentMode.FULL:
290 			throw new UnsupportedOperationException("full justification");
291 		default:
292 			throw new UnreachableCodeException();
293 		}
294 	}
295 
296   /***
297    * Justifies text.
298    *
299    * <p><b>Details:</b> <code>format</code> justifies a split string according
300    * to the currently configured properties.  If <code>Alignment</code> is set
301    * to <code>FULL</code>, then the prefix is left-<wbr>aligned and the suffix
302    * is right-<wbr>aligned, and the portion between them is filled with the
303    * padding character.  Otherwise, the strings are simply concatenated together
304    * and formatted as in {@link #format(String)}.</p>
305    *
306    * @param isPrefix the left portion of the string
307    * @param isSuffix the right portion of the string
308    * @return the formatted string
309    */
310   public final String format(final String isPrefix, final String isSuffix)
311   {
312   	return format(isPrefix, isSuffix, mcPadChar);
313   }
314 
315 	final String format(final String isPrefix, final String isSuffix, final char icPad)
316 	{
317 		if (mnAlignment != AlignmentMode.FULL)
318 			return format(isPrefix + isSuffix, icPad);
319 		final int vnPadSize = mnFieldWidth - (isPrefix.length() + isSuffix.length());
320 		if (vnPadSize <= 0)
321 			return crop(isPrefix + isSuffix);
322 		final String vsPadding = StringToolbox.repeat(icPad, vnPadSize);
323 		return isPrefix + vsPadding + isSuffix;
324 	}
325 
326   /***
327    * Generates debug output.
328    *
329    * <p><b>Details:</b> <code>toString</code> generates a string representation
330    * of this instance and its properties.  This output is useful only for
331    * debugging.</p>
332    *
333    * @return string representation
334    */
335   public String toString()
336   {
337     return "StringFormatter {"
338         + "FieldWidth: " + mnFieldWidth
339         + ", Alignment: " + AlignmentMode.toString(mnAlignment)
340         + ", PadChar: (char) " + (int) mcPadChar
341         + ", Cropping:" + CroppingMode.toString(mnCropping)
342         + "}";
343   }
344 
345 }
346