lose embedded source jars from upstream branch
[debian/openrocket] / core / src / net / sf / openrocket / gui / print / visitor / PartsDetailVisitorStrategy.java
1 /*
2  * PartsDetailVisitorStrategy.java
3  */
4 package net.sf.openrocket.gui.print.visitor;
5
6 import com.itextpdf.text.*;
7 import com.itextpdf.text.pdf.PdfPCell;
8 import com.itextpdf.text.pdf.PdfPTable;
9 import com.itextpdf.text.pdf.PdfWriter;
10 import net.sf.openrocket.gui.main.ComponentIcons;
11 import net.sf.openrocket.gui.print.ITextHelper;
12 import net.sf.openrocket.gui.print.PrintUtilities;
13 import net.sf.openrocket.gui.print.PrintableFinSet;
14 import net.sf.openrocket.logging.LogHelper;
15 import net.sf.openrocket.material.Material;
16 import net.sf.openrocket.rocketcomponent.*;
17 import net.sf.openrocket.startup.Application;
18 import net.sf.openrocket.unit.Unit;
19 import net.sf.openrocket.unit.UnitGroup;
20 import net.sf.openrocket.util.Coordinate;
21
22 import javax.swing.*;
23 import java.text.NumberFormat;
24 import java.util.Collection;
25 import java.util.List;
26 import java.util.Set;
27
28 /**
29  * A visitor strategy for creating documentation about parts details.
30  */
31 public class PartsDetailVisitorStrategy {
32
33     /**
34      * The logger.
35      */
36     private static final LogHelper log = Application.getLogger();
37
38     /**
39      * The number of columns in the table.
40      */
41     private static final int TABLE_COLUMNS = 7;
42
43     /**
44      * The parts detail is represented as an iText table.
45      */
46     PdfPTable grid;
47
48     /**
49      * The iText document.
50      */
51     protected Document document;
52
53     /**
54      * The direct iText writer.
55      */
56     protected PdfWriter writer;
57
58     /**
59      * The stages selected.
60      */
61     protected Set<Integer> stages;
62
63     /**
64      * State variable to track the level of hierarchy.
65      */
66     protected int level = 0;
67
68     private static final String LINES = "Lines: ";
69     private static final String MASS = "Mass: ";
70     private static final String LEN = "Len: ";
71     private static final String THICK = "Thick: ";
72     private static final String INNER = "in ";
73     private static final String DIAMETER = "Dia";
74     private static final String OUTER = "out";
75     private static final String WIDTH = "Width";
76     private static final String LENGTH = "Length";
77     private static final String SHROUD_LINES = "Shroud Lines";
78     private static final String AFT_DIAMETER = "Aft Dia: ";
79     private static final String FORE_DIAMETER = "Fore Dia: ";
80     private static final String PARTS_DETAIL = "Parts Detail";
81
82     /**
83      * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
84      *
85      * @param doc              The iText document
86      * @param theWriter        The direct iText writer
87      * @param theStagesToVisit The stages to be visited by this strategy
88      */
89     public PartsDetailVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
90         document = doc;
91         writer = theWriter;
92         stages = theStagesToVisit;
93         PrintUtilities.addText(doc, PrintUtilities.BIG_BOLD, PARTS_DETAIL);
94     }
95
96     /**
97      * Print the parts detail.
98      *
99      * @param root  the root component
100      */
101     public void writeToDocument (final RocketComponent root) {
102         goDeep(root.getChildren());
103     }
104
105     /**
106      * Recurse through the given rocket component.
107      *
108      * @param theRc an array of rocket components; all children will be visited recursively
109      */
110     protected void goDeep (final List<RocketComponent> theRc) {
111         level++;
112         for (RocketComponent rocketComponent : theRc) {
113             handle(rocketComponent);
114         }
115         level--;
116     }
117
118     /**
119      * Add a line to the detail report based upon the type of the component.
120      *
121      * @param component  the component to print the detail for
122      */
123     private void handle (RocketComponent component) {
124         //This ugly if-then-else construct is not object oriented.  Originally it was an elegant, and very OO savy, design
125         //using the Visitor pattern.  Unfortunately, it was misunderstood and was removed.
126         if (component instanceof Stage) {
127             try {
128                 if (grid != null) {
129                     document.add(grid);
130                 }
131                 document.add(ITextHelper.createPhrase(component.getName()));
132                 grid = new PdfPTable(TABLE_COLUMNS);
133                 grid.setWidthPercentage(100);
134                 grid.setHorizontalAlignment(Element.ALIGN_LEFT);
135             }
136             catch (DocumentException e) {
137             }
138
139             List<RocketComponent> rc = component.getChildren();
140             goDeep(rc);
141         }
142         else if (component instanceof LaunchLug) {
143             LaunchLug ll = (LaunchLug) component;
144             grid.addCell(iconToImage(component));
145             grid.addCell(createNameCell(component.getName(), true));
146
147             grid.addCell(createMaterialCell(ll.getMaterial()));
148             grid.addCell(createOuterInnerDiaCell(ll));
149             grid.addCell(createLengthCell(component.getLength()));
150             grid.addCell(createMassCell(component.getMass()));
151         }
152         else if (component instanceof NoseCone) {
153             NoseCone nc = (NoseCone) component;
154             grid.addCell(iconToImage(component));
155             grid.addCell(createNameCell(component.getName(), true));
156             grid.addCell(createMaterialCell(nc.getMaterial()));
157             grid.addCell(ITextHelper.createCell(nc.getType().getName(), PdfPCell.BOTTOM));
158             grid.addCell(createLengthCell(component.getLength()));
159             grid.addCell(createMassCell(component.getMass()));
160             List<RocketComponent> rc = component.getChildren();
161             goDeep(rc);
162         }
163         else if (component instanceof Transition) {
164             Transition tran = (Transition) component;
165             grid.addCell(iconToImage(component));
166             grid.addCell(createNameCell(component.getName(), true));
167             grid.addCell(createMaterialCell(tran.getMaterial()));
168
169             Chunk fore = new Chunk(FORE_DIAMETER + toLength(tran.getForeRadius() * 2));
170             fore.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
171             Chunk aft = new Chunk(AFT_DIAMETER + toLength(tran.getAftRadius() * 2));
172             aft.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
173             final PdfPCell cell = ITextHelper.createCell();
174             cell.addElement(fore);
175             cell.addElement(aft);
176             grid.addCell(cell);
177             grid.addCell(createLengthCell(component.getLength()));
178             grid.addCell(createMassCell(component.getMass()));
179
180             List<RocketComponent> rc = component.getChildren();
181             goDeep(rc);
182         }
183         else if (component instanceof BodyTube) {
184             BodyTube bt = (BodyTube) component;
185             grid.addCell(iconToImage(component));
186             grid.addCell(createNameCell(component.getName(), true));
187             grid.addCell(createMaterialCell(bt.getMaterial()));
188             grid.addCell(createOuterInnerDiaCell(bt));
189             grid.addCell(createLengthCell(component.getLength()));
190             grid.addCell(createMassCell(component.getMass()));
191             List<RocketComponent> rc = component.getChildren();
192             goDeep(rc);
193         }
194         else if (component instanceof FinSet) {
195             handleFins((FinSet) component);
196         }
197         else if (component instanceof BodyComponent) {
198             grid.addCell(component.getName());
199             grid.completeRow();
200             List<RocketComponent> rc = component.getChildren();
201             goDeep(rc);
202         }
203         else if (component instanceof ExternalComponent) {
204             ExternalComponent ext = (ExternalComponent) component;
205             grid.addCell(iconToImage(component));
206             grid.addCell(createNameCell(component.getName(), true));
207
208             grid.addCell(createMaterialCell(ext.getMaterial()));
209             grid.addCell(ITextHelper.createCell());
210             grid.addCell(createLengthCell(component.getLength()));
211             grid.addCell(createMassCell(component.getMass()));
212
213             List<RocketComponent> rc = component.getChildren();
214             goDeep(rc);
215         }
216         else if (component instanceof InnerTube) {
217             InnerTube it = (InnerTube) component;
218             grid.addCell(iconToImage(component));
219             final PdfPCell pCell = createNameCell(component.getName(), true);
220             grid.addCell(pCell);
221             grid.addCell(createMaterialCell(it.getMaterial()));
222             grid.addCell(createOuterInnerDiaCell(it));
223             grid.addCell(createLengthCell(component.getLength()));
224             grid.addCell(createMassCell(component.getMass()));
225
226             List<RocketComponent> rc = component.getChildren();
227             goDeep(rc);
228         }
229         else if (component instanceof RadiusRingComponent) {
230             RadiusRingComponent rrc = (RadiusRingComponent) component;
231             grid.addCell(iconToImage(component));
232             grid.addCell(createNameCell(component.getName(), true));
233             grid.addCell(createMaterialCell(rrc.getMaterial()));
234             if (component instanceof Bulkhead) {
235                 grid.addCell(createDiaCell(rrc.getOuterRadius()*2));
236             }
237             else {
238                 grid.addCell(createOuterInnerDiaCell(rrc));
239             }
240             grid.addCell(createLengthCell(component.getLength()));
241             grid.addCell(createMassCell(component.getMass()));
242             List<RocketComponent> rc = component.getChildren();
243             goDeep(rc);
244         }
245         else if (component instanceof RingComponent) {
246             RingComponent ring = (RingComponent) component;
247             grid.addCell(iconToImage(component));
248             grid.addCell(createNameCell(component.getName(), true));
249             grid.addCell(createMaterialCell(ring.getMaterial()));
250             grid.addCell(createOuterInnerDiaCell(ring));
251             grid.addCell(createLengthCell(component.getLength()));
252             grid.addCell(createMassCell(component.getMass()));
253
254             List<RocketComponent> rc = component.getChildren();
255             goDeep(rc);
256         }
257         else if (component instanceof ShockCord) {
258             ShockCord ring = (ShockCord) component;
259             PdfPCell cell = ITextHelper.createCell();
260             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
261             cell.setPaddingBottom(12f);
262             grid.addCell(iconToImage(component));
263             grid.addCell(createNameCell(component.getName(), true));
264             grid.addCell(createMaterialCell(ring.getMaterial()));
265             grid.addCell(cell);
266             grid.addCell(createLengthCell(ring.getCordLength()));
267             grid.addCell(createMassCell(component.getMass()));
268         }
269         else if (component instanceof Parachute) {
270             Parachute chute = (Parachute) component;
271             PdfPCell cell = ITextHelper.createCell();
272             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
273             cell.setPaddingBottom(12f);
274             grid.addCell(iconToImage(component));
275             grid.addCell(createNameCell(component.getName(), true));
276             grid.addCell(createMaterialCell(chute.getMaterial()));
277             grid.addCell(createDiaCell(chute.getDiameter()));
278             grid.addCell(createLengthCell(component.getLength()));
279             grid.addCell(createMassCell(component.getMass()));
280
281             grid.addCell(iconToImage(null));
282             grid.addCell(createNameCell(SHROUD_LINES, true));
283             grid.addCell(createMaterialCell(chute.getLineMaterial()));
284             grid.addCell(createLinesCell(chute.getLineCount()));
285             grid.addCell(createLengthCell(chute.getLineLength()));
286             grid.addCell(cell);
287         }
288         else if (component instanceof Streamer) {
289             Streamer ring = (Streamer) component;
290             PdfPCell cell = ITextHelper.createCell();
291             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
292             cell.setPaddingBottom(12f);
293             grid.addCell(iconToImage(component));
294             grid.addCell(createNameCell(component.getName(), true));
295             grid.addCell(createMaterialCell(ring.getMaterial()));
296             grid.addCell(createStrip(ring));
297             grid.addCell(createLengthCell(component.getLength()));
298             grid.addCell(createMassCell(component.getMass()));
299         }
300         else if (component instanceof RecoveryDevice) {
301             RecoveryDevice device = (RecoveryDevice) component;
302             PdfPCell cell = ITextHelper.createCell();
303             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
304             cell.setPaddingBottom(12f);
305             grid.addCell(iconToImage(component));
306             grid.addCell(createNameCell(component.getName(), true));
307             grid.addCell(createMaterialCell(device.getMaterial()));
308             grid.addCell(cell);
309             grid.addCell(createLengthCell(component.getLength()));
310             grid.addCell(createMassCell(component.getMass()));
311         }
312         else if (component instanceof MassObject) {
313             PdfPCell cell = ITextHelper.createCell();
314             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
315             cell.setPaddingBottom(12f);
316
317             grid.addCell(iconToImage(component));
318             final PdfPCell nameCell = createNameCell(component.getName(), true);
319             nameCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
320             nameCell.setPaddingBottom(12f);
321             grid.addCell(nameCell);
322             grid.addCell(cell);
323             grid.addCell(createDiaCell(((MassObject) component).getRadius() * 2));
324             grid.addCell(cell);
325             grid.addCell(createMassCell(component.getMass()));
326         }
327     }
328
329     /**
330      * Close the strategy by adding the last grid to the document.
331      */
332     public void close () {
333         try {
334             if (grid != null) {
335                 document.add(grid);
336             }
337         }
338         catch (DocumentException e) {
339             log.error("Could not write last cell to document.", e);
340         }
341     }
342
343     /**
344      * Create a cell to document an outer 'diameter'.  This is used for components that have no inner diameter, such as
345      * a solid parachute or bulkhead.
346      *
347      * @param diameter  the diameter in default length units
348      *
349      * @return a formatted cell containing the diameter
350      */
351     private PdfPCell createDiaCell (final double diameter) {
352         PdfPCell result = new PdfPCell();
353         Phrase p = new Phrase();
354         p.setLeading(12f);
355         result.setVerticalAlignment(Element.ALIGN_TOP);
356         result.setBorder(Rectangle.BOTTOM);
357         Chunk c = new Chunk();
358         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
359         c.append(DIAMETER);
360         p.add(c);
361
362         c = new Chunk();
363         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
364         c.append(OUTER);
365         p.add(c);
366
367         c = new Chunk();
368         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
369         c.append(" " + toLength(diameter));
370         p.add(c);
371         result.addElement(p);
372         return result;
373     }
374
375     /**
376      * Create a PDF cell for a streamer.
377      *
378      * @param component  a component that is a Coaxial
379      * @return  the PDF cell that has the streamer documented
380      */
381     private PdfPCell createStrip (final Streamer component) {
382         PdfPCell result = new PdfPCell();
383         Phrase p = new Phrase();
384         p.setLeading(12f);
385         result.setVerticalAlignment(Element.ALIGN_TOP);
386         result.setBorder(Rectangle.BOTTOM);
387         Chunk c = new Chunk();
388         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
389         c.append(LENGTH);
390         p.add(c);
391
392         c = new Chunk();
393         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
394         c.append(" " + toLength(component.getStripLength()));
395         p.add(c);
396         result.addElement(p);
397
398         Phrase pw = new Phrase();
399         pw.setLeading(14f);
400         c = new Chunk();
401         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
402         c.append(WIDTH);
403         pw.add(c);
404
405         c = new Chunk();
406         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
407         c.append("  " + toLength(component.getStripWidth()));
408         pw.add(c);
409         result.addElement(pw);
410
411         return result;
412     }
413
414     /**
415      * Create a PDF cell that documents both an outer and an inner diameter of a component.
416      *
417      * @param component  a component that is a Coaxial
418      * @return  the PDF cell that has the outer and inner diameters documented
419      */
420     private PdfPCell createOuterInnerDiaCell (final Coaxial component) {
421         PdfPCell result = new PdfPCell();
422         Phrase p = new Phrase();
423         p.setLeading(12f);
424         result.setVerticalAlignment(Element.ALIGN_TOP);
425         result.setBorder(Rectangle.BOTTOM);
426         Chunk c = new Chunk();
427         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
428         c.append(DIAMETER);
429         p.add(c);
430
431         c = new Chunk();
432         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
433         c.append(OUTER);
434         p.add(c);
435
436         c = new Chunk();
437         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
438         c.append(" " + toLength(component.getOuterRadius() * 2));
439         p.add(c);
440         createInnerDiaCell(component, result);
441         result.addElement(p);
442         return result;
443     }
444
445     /**
446      * Add inner diameter data to a cell.
447      *
448      * @param component  a component that is a Coaxial
449      * @param cell       the PDF cell to add the inner diameter data to
450      */
451     private void createInnerDiaCell (final Coaxial component, PdfPCell cell) {
452         Phrase p = new Phrase();
453         p.setLeading(14f);
454         Chunk c = new Chunk();
455         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
456         c.append(DIAMETER);
457         p.add(c);
458
459         c = new Chunk();
460         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
461         c.append(INNER);
462         p.add(c);
463
464         c = new Chunk();
465         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
466         c.append("  " + toLength(component.getInnerRadius() * 2));
467         p.add(c);
468         cell.addElement(p);
469     }
470
471     /**
472      * Add PDF cells for a fin set.
473      *
474      * @param theFinSet  the fin set
475      */
476     private void handleFins (FinSet theFinSet) {
477
478         Image img = null;
479         java.awt.Image awtImage = new PrintableFinSet(theFinSet).createImage();
480
481         Collection<Coordinate> x = theFinSet.getComponentBounds();
482
483         try {
484             img = Image.getInstance(writer, awtImage, 0.25f);
485         }
486         catch (Exception e) {
487             log.error("Could not write image to document.", e);
488         }
489
490         grid.addCell(iconToImage(theFinSet));
491         grid.addCell(createNameCell(theFinSet.getName() + " (" + theFinSet.getFinCount() + ")", true));
492         grid.addCell(createMaterialCell(theFinSet.getMaterial()));
493         grid.addCell(ITextHelper.createCell(THICK + toLength(theFinSet.getThickness()), PdfPCell.BOTTOM));
494         final PdfPCell pCell = new PdfPCell();
495         pCell.setBorder(Rectangle.BOTTOM);
496         pCell.addElement(img);
497
498         grid.addCell(ITextHelper.createCell());
499         grid.addCell(createMassCell(theFinSet.getMass()));
500
501         List<RocketComponent> rc = theFinSet.getChildren();
502         goDeep(rc);
503     }
504
505     /**
506      * Create a length formatted cell.
507      *
508      * @param length  the length, in default length units
509      *
510      * @return a PdfPCell that is formatted with the length
511      */
512     protected PdfPCell createLengthCell (double length) {
513         return ITextHelper.createCell(LEN + toLength(length), PdfPCell.BOTTOM);
514     }
515
516     /**
517      * Create a mass formatted cell.
518      *
519      * @param mass  the mass, in default mass units
520      *
521      * @return a PdfPCell that is formatted with the mass
522      */
523     protected PdfPCell createMassCell (double mass) {
524         return ITextHelper.createCell(MASS + toMass(mass), PdfPCell.BOTTOM);
525     }
526
527     /**
528      * Create a (shroud) line count formatted cell.
529      *
530      * @param count  the number of shroud lines
531      *
532      * @return a PdfPCell that is formatted with the line count
533      */
534     protected PdfPCell createLinesCell (int count) {
535         return ITextHelper.createCell(LINES + count, PdfPCell.BOTTOM);
536     }
537
538     /**
539      * Create a cell formatted for a name (or any string for that matter).
540      *
541      * @param v  the string to format into a PDF cell
542      * @param withIndent  if true, then an indention is made scaled to the level of the part in the parent hierarchy
543      *
544      * @return a PdfPCell that is formatted with the string <code>v</code>
545      */
546     protected PdfPCell createNameCell (String v, boolean withIndent) {
547         PdfPCell result = new PdfPCell();
548         result.setBorder(Rectangle.BOTTOM);
549         Chunk c = new Chunk();
550         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
551         if (withIndent) {
552             for (int x = 0; x < (level - 2) * 10; x++) {
553                 c.append(" ");
554             }
555         }
556         c.append(v);
557         result.setColspan(2);
558         result.addElement(c);
559         return result;
560     }
561
562     /**
563      * Create a cell that describes a material.
564      *
565      * @param material  the material
566      *
567      * @return a PdfPCell that is formatted with a description of the material
568      */
569     protected PdfPCell createMaterialCell (Material material) {
570         PdfPCell cell = ITextHelper.createCell();
571         cell.setLeading(13f, 0);
572
573         Chunk c = new Chunk();
574         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
575         c.append(toMaterialName(material));
576         cell.addElement(c);
577         Chunk density = new Chunk();
578         density.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
579         density.append(toMaterialDensity(material));
580         cell.addElement(density);
581         return cell;
582     }
583
584     /**
585      * Get the icon of the particular type of rocket component and conver it to an image in a PDF cell.
586      *
587      * @param visitable  the rocket component to create a cell with it's image
588      *
589      * @return a PdfPCell that is just an image that can be put into a PDF
590      */
591     protected PdfPCell iconToImage (final RocketComponent visitable) {
592         if (visitable != null) {
593             final ImageIcon icon = (ImageIcon) ComponentIcons.getLargeIcon(visitable.getClass());
594             try {
595                 if (icon != null) {
596                     Image im = Image.getInstance(icon.getImage(), null);
597                     if (im != null) {
598                         im.scaleToFit(icon.getIconWidth() * 0.6f, icon.getIconHeight() * 0.6f);
599                         PdfPCell cell = new PdfPCell(im);
600                         cell.setFixedHeight(icon.getIconHeight() * 0.6f);
601                         cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
602                         cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
603                         cell.setBorder(PdfPCell.NO_BORDER);
604                         return cell;
605                     }
606                 }
607             }
608             catch (Exception e) {
609             }
610         }
611         PdfPCell cell = new PdfPCell();
612         cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
613         cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
614         cell.setBorder(PdfPCell.NO_BORDER);
615         return cell;
616     }
617
618     /**
619      * Format the length as a displayable string.
620      *
621      * @param length the length (assumed to be in default length units)
622      *
623      * @return a string representation of the length with unit abbreviation
624      */
625     protected String toLength (double length) {
626         final Unit defaultUnit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
627         return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(length)) + defaultUnit.toString();
628     }
629
630     /**
631      * Format the mass as a displayable string.
632      *
633      * @param mass  the mass (assumed to be in default mass units)
634      *
635      * @return a string representation of the mass with mass abbreviation
636      */
637     protected String toMass (double mass) {
638         final Unit defaultUnit = UnitGroup.UNITS_MASS.getDefaultUnit();
639         return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(mass)) + defaultUnit.toString();
640     }
641
642     /**
643      * Get a displayable string of the material's name.
644      *
645      * @param material  the material to output
646      *
647      * @return the material name
648      */
649     protected String toMaterialName (Material material) {
650         return material.getName();
651     }
652
653     /**
654      * Format the material density as a displayable string.
655      *
656      * @param material  the material to output
657      *
658      * @return a string representation of the material density
659      */
660     protected String toMaterialDensity (Material material) {
661         return " (" + material.getType().getUnitGroup().getDefaultUnit().toStringUnit(material.getDensity()) + ")";
662     }
663
664 }