001: // plasmaGrafics.java
002: // -----------------------
003: // part of YaCy
004: // (C) by Michael Peter Christen; mc@anomic.de
005: // first published on http://www.anomic.de
006: // Frankfurt, Germany, 2005
007: // Created 08.10.2005
008: //
009: // Contributions by Marc Nause [MN]
010: //
011: // $LastChangedDate: 2008-01-19 00:40:19 +0000 (Sa, 19 Jan 2008) $
012: // $LastChangedRevision: 4343 $
013: // $LastChangedBy: orbiter $
014: //
015: // This program is free software; you can redistribute it and/or modify
016: // it under the terms of the GNU General Public License as published by
017: // the Free Software Foundation; either version 2 of the License, or
018: // (at your option) any later version.
019: //
020: // This program is distributed in the hope that it will be useful,
021: // but WITHOUT ANY WARRANTY; without even the implied warranty of
022: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
023: // GNU General Public License for more details.
024: //
025: // You should have received a copy of the GNU General Public License
026: // along with this program; if not, write to the Free Software
027: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
028: //
029: // Using this software in any meaning (reading, learning, copying, compiling,
030: // running) means that you agree that the Author(s) is (are) not responsible
031: // for cost, loss of data or any harm that may be caused directly or indirectly
032: // by usage of this softare or this documentation. The usage of this software
033: // is on your own risk. The installation and usage (starting/running) of this
034: // software may allow other people or application to access your computer and
035: // any attached devices and is highly dependent on the configuration of the
036: // software which must be done by the user of the software; the author(s) is
037: // (are) also not responsible for proper configuration and usage of the
038: // software, even if provoked by documentation provided together with
039: // the software.
040: //
041: // Any changes to this file according to the GPL as documented in the file
042: // gpl.txt aside this file in the shipment you received can be done to the
043: // lines that follows this copyright notice here, but changes must not be
044: // done inside the copyright notive above. A re-distribution must contain
045: // the intact and unchanged copyright notice.
046: // Contributions and changes to the program code must be marked as such.
047:
048: package de.anomic.plasma;
049:
050: import java.awt.Color;
051: import java.awt.Graphics2D;
052: import java.awt.RenderingHints;
053: import java.awt.image.BufferedImage;
054: import java.util.Date;
055: import java.util.Iterator;
056:
057: import de.anomic.yacy.yacyCore;
058: import de.anomic.yacy.yacySearch;
059: import de.anomic.yacy.yacySeed;
060: import de.anomic.ymage.ymageMatrix;
061: import de.anomic.ymage.ymageToolPrint;
062:
063: public class plasmaGrafics {
064:
065: private static int shortestName = 10;
066: private static int longestName = 12;
067:
068: public static final String COL_BACKGROUND = "FFFFFF";
069: private static final String COL_DHTCIRCLE = "006020";
070: private static final String COL_HEADLINE = "FFFFFF";
071: private static final String COL_ACTIVE_DOT = "000044";
072: private static final String COL_ACTIVE_LINE = "335544";
073: private static final String COL_ACTIVE_TEXT = "66AA88";
074: private static final String COL_PASSIVE_DOT = "221111";
075: private static final String COL_PASSIVE_LINE = "443333";
076: private static final String COL_PASSIVE_TEXT = "663333";
077: private static final String COL_POTENTIAL_DOT = "002200";
078: private static final String COL_POTENTIAL_LINE = "224422";
079: private static final String COL_POTENTIAL_TEXT = "336633";
080: private static final String COL_WE_DOT = "FF0000";
081: private static final String COL_WE_LINE = "FFAAAA";
082: private static final String COL_WE_TEXT = "FFCCCC";
083:
084: private static final Color COL_BORDER = new Color(0, 0, 0);
085: private static final Color COL_NORMAL_TEXT = new Color(0, 0, 0);
086: private static final Color COL_LOAD_BG = new Color(247, 247, 247);
087:
088: public static class CircleThreadPiece {
089: private final String pieceName;
090: private final Color color;
091: private long execTime = 0;
092: private float fraction = 0;
093:
094: public CircleThreadPiece(String pieceName, Color color) {
095: this .pieceName = pieceName;
096: this .color = color;
097: }
098:
099: public int getAngle() {
100: return Math.round(360f * this .fraction);
101: }
102:
103: public int getFractionPercent() {
104: return Math.round(100f * this .fraction);
105: }
106:
107: public Color getColor() {
108: return this .color;
109: }
110:
111: public long getExecTime() {
112: return this .execTime;
113: }
114:
115: public String getPieceName() {
116: return this .pieceName;
117: }
118:
119: public void addExecTime(long execTime) {
120: this .execTime += execTime;
121: }
122:
123: public void reset() {
124: this .execTime = 0;
125: this .fraction = 0;
126: }
127:
128: public void setExecTime(long execTime) {
129: this .execTime = execTime;
130: }
131:
132: public void setFraction(long totalBusyTime) {
133: this .fraction = (float) this .execTime
134: / (float) totalBusyTime;
135: }
136: }
137:
138: private static final int LEGEND_BOX_SIZE = 10;
139:
140: private static ymageMatrix networkPicture = null;
141: private static long networkPictureDate = 0;
142:
143: private static BufferedImage peerloadPicture = null;
144: private static long peerloadPictureDate = 0;
145:
146: private static ymageMatrix bannerPicture = null; // [MN]
147: private static BufferedImage logo = null; // [MN]
148: private static long bannerPictureDate = 0; // [MN]
149:
150: public static ymageMatrix getSearchEventPicture(String eventID) {
151: plasmaSearchEvent event = plasmaSearchEvent.getEvent(eventID);
152: if (event == null)
153: return null;
154: yacySearch[] primarySearches = event.getPrimarySearchThreads();
155: yacySearch[] secondarySearches = event
156: .getSecondarySearchThreads();
157: if (primarySearches == null)
158: return null; // this was a local search and there are no threads
159:
160: // get a copy of a recent network picture
161: ymageMatrix eventPicture = getNetworkPicture(120000,
162: plasmaSwitchboard.getSwitchboard().getConfig(
163: "network.unit.name", "unspecified"),
164: plasmaSwitchboard.getSwitchboard().getConfig(
165: "network.unit.description", "unspecified"),
166: COL_BACKGROUND);
167: //if (eventPicture instanceof ymageMatrix) eventPicture = (ymageMatrix) eventPicture; //new ymageMatrix((ymageMatrix) eventPicture);
168: // TODO: fix cloning of ymageMatrix pictures
169:
170: // get dimensions
171: int cr = Math.min(eventPicture.getWidth(), eventPicture
172: .getHeight()) / 5 - 20;
173: int cx = eventPicture.getWidth() / 2;
174: int cy = eventPicture.getHeight() / 2;
175:
176: String hash;
177: int angle;
178:
179: // draw in the primary search peers
180: for (int j = 0; j < primarySearches.length; j++) {
181: eventPicture
182: .setColor((primarySearches[j].isAlive()) ? ymageMatrix.RED
183: : ymageMatrix.GREEN);
184: hash = primarySearches[j].target().hash;
185: angle = (int) (360 * yacySeed.dhtPosition(hash));
186: eventPicture.arcLine(cx, cy, cr - 20, cr, angle);
187: }
188:
189: // draw in the secondary search peers
190: if (secondarySearches != null) {
191: for (int j = 0; j < secondarySearches.length; j++) {
192: eventPicture
193: .setColor((secondarySearches[j].isAlive()) ? ymageMatrix.RED
194: : ymageMatrix.GREEN);
195: hash = secondarySearches[j].target().hash;
196: angle = (int) (360 * yacySeed.dhtPosition(hash));
197: eventPicture.arcLine(cx, cy, cr - 10, cr, angle - 1);
198: eventPicture.arcLine(cx, cy, cr - 10, cr, angle + 1);
199: }
200: }
201:
202: // draw in the search target
203: plasmaSearchQuery query = event.getQuery();
204: Iterator<String> i = query.queryHashes.iterator();
205: eventPicture.setColor(ymageMatrix.GREY);
206: while (i.hasNext()) {
207: hash = i.next();
208: angle = (int) (360 * yacySeed.dhtPosition(hash));
209: eventPicture.arcLine(cx, cy, cr - 20, cr, angle);
210: }
211:
212: return eventPicture;
213: }
214:
215: public static ymageMatrix getNetworkPicture(long maxAge,
216: String networkName, String networkTitle, String bgcolor) {
217: return getNetworkPicture(maxAge, 640, 480, 300, 300, 1000,
218: true, networkName, networkTitle, bgcolor);
219: }
220:
221: public static ymageMatrix getNetworkPicture(long maxAge, int width,
222: int height, int passiveLimit, int potentialLimit,
223: int maxCount, boolean corona, String networkName,
224: String networkTitle, String bgcolor) {
225: if ((networkPicture == null)
226: || ((System.currentTimeMillis() - networkPictureDate) > maxAge)) {
227: drawNetworkPicture(width, height, passiveLimit,
228: potentialLimit, maxCount, corona, networkName,
229: networkTitle, bgcolor);
230: }
231: return networkPicture;
232: }
233:
234: private static void drawNetworkPicture(int width, int height,
235: int passiveLimit, int potentialLimit, int maxCount,
236: boolean corona, String networkName, String networkTitle,
237: String bgcolor) {
238:
239: int innerradius = Math.min(width, height) / 5;
240: int outerradius = innerradius + innerradius
241: * yacyCore.seedDB.sizeConnected() / 100;
242: if (outerradius > innerradius * 2)
243: outerradius = innerradius * 2;
244:
245: if (yacyCore.seedDB == null)
246: return; // no other peers known
247:
248: networkPicture = new ymageMatrix(width, height,
249: ymageMatrix.MODE_SUB, bgcolor);
250:
251: // draw network circle
252: networkPicture.setColor(COL_DHTCIRCLE);
253: networkPicture.arc(width / 2, height / 2 + 20,
254: innerradius - 20, innerradius + 20, 0, 360);
255:
256: //System.out.println("Seed Maximum distance is " + yacySeed.maxDHTDistance);
257: //System.out.println("Seed Minimum distance is " + yacySeed.minDHTNumber);
258:
259: yacySeed seed;
260: long lastseen;
261:
262: // draw connected senior and principals
263: int count = 0;
264: int totalCount = 0;
265: Iterator<yacySeed> e = yacyCore.seedDB.seedsConnected(true,
266: false, null, (float) 0.0);
267:
268: while (e.hasNext() && count < maxCount) {
269: seed = (yacySeed) e.next();
270: if (seed != null) {
271: drawNetworkPicturePeer(networkPicture, width / 2,
272: height / 2 + 20, innerradius, outerradius,
273: seed, COL_ACTIVE_DOT, COL_ACTIVE_LINE,
274: COL_ACTIVE_TEXT, corona);
275: count++;
276: }
277: }
278: totalCount += count;
279:
280: // draw disconnected senior and principals that have been seen lately
281: count = 0;
282: e = yacyCore.seedDB.seedsSortedDisconnected(false,
283: yacySeed.LASTSEEN);
284: while (e.hasNext() && count < maxCount) {
285: seed = (yacySeed) e.next();
286: if (seed != null) {
287: lastseen = Math.abs((System.currentTimeMillis() - seed
288: .getLastSeenUTC()) / 1000 / 60);
289: if (lastseen > passiveLimit)
290: break; // we have enough, this list is sorted so we don't miss anything
291: drawNetworkPicturePeer(networkPicture, width / 2,
292: height / 2 + 20, innerradius, outerradius,
293: seed, COL_PASSIVE_DOT, COL_PASSIVE_LINE,
294: COL_PASSIVE_TEXT, corona);
295: count++;
296: }
297: }
298: totalCount += count;
299:
300: // draw juniors that have been seen lately
301: count = 0;
302: e = yacyCore.seedDB.seedsSortedPotential(false,
303: yacySeed.LASTSEEN);
304: while (e.hasNext() && count < maxCount) {
305: seed = (yacySeed) e.next();
306: if (seed != null) {
307: lastseen = Math.abs((System.currentTimeMillis() - seed
308: .getLastSeenUTC()) / 1000 / 60);
309: if (lastseen > potentialLimit)
310: break; // we have enough, this list is sorted so we don't miss anything
311: drawNetworkPicturePeer(networkPicture, width / 2,
312: height / 2 + 20, innerradius, outerradius,
313: seed, COL_POTENTIAL_DOT, COL_POTENTIAL_LINE,
314: COL_POTENTIAL_TEXT, corona);
315: count++;
316: }
317: }
318: totalCount += count;
319:
320: // draw my own peer
321: drawNetworkPicturePeer(networkPicture, width / 2,
322: height / 2 + 20, innerradius, outerradius,
323: yacyCore.seedDB.mySeed(), COL_WE_DOT, COL_WE_LINE,
324: COL_WE_TEXT, corona);
325:
326: // draw description
327: networkPicture.setColor(COL_HEADLINE);
328: ymageToolPrint.print(networkPicture, 2, 8, 0, "YACY NETWORK '"
329: + networkName.toUpperCase() + "'", -1);
330: ymageToolPrint.print(networkPicture, 2, 16, 0, networkTitle
331: .toUpperCase(), -1);
332: ymageToolPrint.print(networkPicture, width - 2, 8, 0,
333: "SNAPSHOT FROM " + new Date().toString().toUpperCase(),
334: 1);
335: ymageToolPrint.print(networkPicture, width - 2, 16, 0,
336: "DRAWING OF " + totalCount + " SELECTED PEERS", 1);
337:
338: // set timestamp
339: networkPictureDate = System.currentTimeMillis();
340: }
341:
342: private static void drawNetworkPicturePeer(ymageMatrix img, int x,
343: int y, int innerradius, int outerradius, yacySeed seed,
344: String colorDot, String colorLine, String colorText,
345: boolean corona) {
346: String name = seed.getName().toUpperCase() /*+ ":" + seed.hash + ":" + (((double) ((int) (100 * (((double) yacySeed.dhtPosition(seed.hash)) / ((double) yacySeed.maxDHTDistance))))) / 100.0)*/;
347: if (name.length() < shortestName)
348: shortestName = name.length();
349: if (name.length() > longestName)
350: longestName = name.length();
351: int angle = (int) (360 * seed.dhtPosition());
352: //System.out.println("Seed " + seed.hash + " has distance " + seed.dhtDistance() + ", angle = " + angle);
353: int linelength = 20
354: + outerradius
355: * (20 * (name.length() - shortestName)
356: / (longestName - shortestName) + (Math
357: .abs(seed.hash.hashCode()) % 20)) / 60;
358: if (linelength > outerradius)
359: linelength = outerradius;
360: int dotsize = 6 + 2 * (int) (seed.getLinkCount() / 500000L);
361: if (dotsize > 18)
362: dotsize = 18;
363: // draw dot
364: img.setColor(colorDot);
365: img.arcDot(x, y, innerradius, angle, dotsize);
366: // draw line to text
367: img.setColor(colorLine);
368: img.arcLine(x, y, innerradius + 18, innerradius + linelength,
369: angle);
370: // draw text
371: img.setColor(colorText);
372: ymageToolPrint.arcPrint(img, x, y, innerradius + linelength,
373: angle, name);
374:
375: // draw corona around dot for crawling activity
376: int ppm10 = seed.getPPM() / 10;
377: if ((corona) && (ppm10 > 0)) {
378: if (ppm10 > 3)
379: ppm10 = 3;
380: // draw a wave around crawling peers
381: long strength;
382: img.setColor("303030");
383: img.arcArc(x, y, innerradius, angle, dotsize + 1,
384: dotsize + 1, 0, 360);
385: int waveradius = innerradius / 2;
386: for (int r = 0; r < waveradius; r++) {
387: strength = (waveradius - r)
388: * (long) (0x08 * ppm10 * (1.0 + Math
389: .sin(Math.PI * 16 * r / waveradius)))
390: / waveradius;
391: //System.out.println("r = " + r + ", Strength = " + strength);
392: img.setColor((strength << 16) | (strength << 8)
393: | strength);
394: img.arcArc(x, y, innerradius, angle, dotsize + r,
395: dotsize + r, 0, 360);
396: }
397: }
398: }
399:
400: public static BufferedImage getPeerLoadPicture(long maxAge,
401: int width, int height, CircleThreadPiece[] pieces,
402: CircleThreadPiece fillRest) {
403: if ((peerloadPicture == null)
404: || ((System.currentTimeMillis() - peerloadPictureDate) > maxAge)) {
405: drawPeerLoadPicture(width, height, pieces, fillRest);
406: }
407: return peerloadPicture;
408: }
409:
410: private static void drawPeerLoadPicture(int width, int height,
411: CircleThreadPiece[] pieces, CircleThreadPiece fillRest) {
412: //prepare image
413: peerloadPicture = new BufferedImage(width, height,
414: BufferedImage.TYPE_INT_RGB);
415: Graphics2D g = peerloadPicture.createGraphics();
416: g.setBackground(COL_LOAD_BG);
417: g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
418: RenderingHints.VALUE_ANTIALIAS_ON);
419: g.clearRect(0, 0, width, height);
420:
421: int circ_w = Math.min(width, height) - 20; //width of the circle (r*2)
422: int circ_x = width - circ_w - 10; //x-coordinate of circle-left
423: int circ_y = 10; //y-coordinate of circle-top
424: int curr_angle = 0; //remember current angle
425:
426: int i;
427: for (i = 0; i < pieces.length; i++) {
428: // draw the piece
429: g.setColor(pieces[i].getColor());
430: g.fillArc(circ_x, circ_y, circ_w, circ_w, curr_angle,
431: pieces[i].getAngle());
432: curr_angle += pieces[i].getAngle();
433:
434: // draw it's legend line
435: drawLegendLine(g, 5, height - 5 - 15 * i, pieces[i]
436: .getPieceName()
437: + " (" + pieces[i].getFractionPercent() + " %)",
438: pieces[i].getColor());
439: }
440:
441: // fill the rest
442: g.setColor(fillRest.getColor());
443: //FIXME: better method to avoid gaps on rounding-differences?
444: g.fillArc(circ_x, circ_y, circ_w, circ_w, curr_angle,
445: 360 - curr_angle);
446: drawLegendLine(g, 5, height - 5 - 15 * i, fillRest
447: .getPieceName()
448: + " (" + fillRest.getFractionPercent() + " %)",
449: fillRest.getColor());
450:
451: //draw border around the circle
452: g.setColor(COL_BORDER);
453: g.drawArc(circ_x, circ_y, circ_w, circ_w, 0, 360);
454:
455: peerloadPictureDate = System.currentTimeMillis();
456: }
457:
458: private static void drawLegendLine(Graphics2D g, int x, int y,
459: String caption, Color item_color) {
460: g.setColor(item_color);
461: g.fillRect(x, y - LEGEND_BOX_SIZE, LEGEND_BOX_SIZE,
462: LEGEND_BOX_SIZE);
463: g.setColor(COL_BORDER);
464: g.drawRect(x, y - LEGEND_BOX_SIZE, LEGEND_BOX_SIZE,
465: LEGEND_BOX_SIZE);
466:
467: g.setColor(COL_NORMAL_TEXT);
468: g.drawChars(caption.toCharArray(), 0, caption.length(), x
469: + LEGEND_BOX_SIZE + 5, y);
470: }
471:
472: //[MN]
473: public static ymageMatrix getBannerPicture(long maxAge, int width,
474: int height, String bgcolor, String textcolor,
475: String bordercolor, String name, long links, long words,
476: String type, int ppm, String network, int peers,
477: long nlinks, long nwords, double nqph, long nppm) {
478: if ((bannerPicture == null)
479: || ((System.currentTimeMillis() - bannerPictureDate) > maxAge)) {
480: drawBannerPicture(width, height, bgcolor, textcolor,
481: bordercolor, name, links, words, type, ppm,
482: network, peers, nlinks, nwords, nqph, nppm, logo);
483: }
484: return bannerPicture;
485: }
486:
487: //[MN]
488: public static ymageMatrix getBannerPicture(long maxAge, int width,
489: int height, String bgcolor, String textcolor,
490: String bordercolor, String name, long links, long words,
491: String type, int ppm, String network, int peers,
492: long nlinks, long nwords, double nqph, long nppm,
493: BufferedImage newLogo) {
494: if ((bannerPicture == null)
495: || ((System.currentTimeMillis() - bannerPictureDate) > maxAge)) {
496: drawBannerPicture(width, height, bgcolor, textcolor,
497: bordercolor, name, links, words, type, ppm,
498: network, peers, nlinks, nwords, nqph, nppm, newLogo);
499: }
500: return bannerPicture;
501: }
502:
503: //[MN]
504: private static void drawBannerPicture(int width, int height,
505: String bgcolor, String textcolor, String bordercolor,
506: String name, long links, long words, String type, int ppm,
507: String network, int peers, long nlinks, long nwords,
508: double nqph, long nppm, BufferedImage newLogo) {
509:
510: int exprlength = 19;
511: logo = newLogo;
512: bannerPicture = new ymageMatrix(width, height,
513: ymageMatrix.MODE_REPLACE, bgcolor);
514:
515: // draw description
516: bannerPicture.setColor(textcolor);
517: ymageToolPrint.print(bannerPicture, 100, 12, 0, "PEER: "
518: + addTrailingBlanks(name, exprlength), -1);
519: ymageToolPrint.print(bannerPicture, 100, 22, 0, "LINKS: "
520: + addBlanksAndDots(links, exprlength), -1);
521: ymageToolPrint.print(bannerPicture, 100, 32, 0, "WORDS: "
522: + addBlanksAndDots(words, exprlength), -1);
523: ymageToolPrint.print(bannerPicture, 100, 42, 0, "TYPE: "
524: + addTrailingBlanks(type, exprlength), -1);
525: ymageToolPrint.print(bannerPicture, 100, 52, 0, "SPEED: "
526: + addTrailingBlanks(ppm + " PAGES/MINUTE", exprlength),
527: -1);
528:
529: ymageToolPrint.print(bannerPicture, 285, 12, 0, "NETWORK: "
530: + addTrailingBlanks(network + " [" + peers + "]",
531: exprlength), -1);
532: ymageToolPrint.print(bannerPicture, 285, 22, 0, "LINKS: "
533: + addBlanksAndDots(nlinks, exprlength), -1);
534: ymageToolPrint.print(bannerPicture, 285, 32, 0, "WORDS: "
535: + addBlanksAndDots(nwords, exprlength), -1);
536: ymageToolPrint.print(bannerPicture, 285, 42, 0,
537: "QUERIES: "
538: + addTrailingBlanks(nqph + " QUERIES/HOUR",
539: exprlength), -1);
540: ymageToolPrint.print(bannerPicture, 285, 52, 0,
541: "SPEED: "
542: + addTrailingBlanks(nppm + " PAGES/MINUTE",
543: exprlength), -1);
544:
545: if (logo != null) {
546: int x = (int) (100 / 2 - logo.getWidth() / 2);
547: int y = (int) (height / 2 - logo.getHeight() / 2);
548: bannerPicture.insertBitmap(logo, x, y, 0, 0,
549: ymageMatrix.FILTER_ANTIALIASING);
550: }
551:
552: if (!bordercolor.equals("")) {
553: bannerPicture.setColor(bordercolor);
554: bannerPicture.line(0, 0, 0, height - 1);
555: bannerPicture.line(0, 0, width - 1, 0);
556: bannerPicture.line(width - 1, 0, width - 1, height - 1);
557: bannerPicture.line(0, height - 1, width - 1, height - 1);
558: }
559:
560: // set timestamp
561: bannerPictureDate = System.currentTimeMillis();
562: }
563:
564: public static boolean logoIsLoaded() {
565: if (logo == null) {
566: return false;
567: }
568: return true;
569: }
570:
571: //[MN]
572: private static String addBlanksAndDots(long input, int length) {
573: return addBlanksAndDots(input + "", length);
574: }
575:
576: //[MN]
577: private static String addBlanksAndDots(String input, int length) {
578: input = addDots(input);
579: input = addTrailingBlanks(input, length);
580: return input;
581: }
582:
583: //[MN]
584: private static String addDots(String word) {
585: String tmp = "";
586: int len = word.length();
587: if (len > 3) {
588: while (len > 3) {
589: if (tmp.equals("")) {
590: tmp = word.substring(len - 3, len);
591: } else {
592: tmp = word.substring(len - 3, len) + "." + tmp;
593: }
594: word = word.substring(0, len - 3);
595: len = word.length();
596: }
597: word = word + "." + tmp;
598: }
599: return word;
600: }
601:
602: //[MN]
603: private static String addTrailingBlanks(String word, int length) {
604: if (length > word.length()) {
605: String blanks = "";
606: length = length - word.length();
607: int i = 0;
608: while (i++ < length) {
609: blanks += " ";
610: }
611: word = blanks + word;
612: }
613: return word;
614: }
615:
616: }
|