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 < 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
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
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
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> ' ' (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
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