1 /*
   2  * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.lwawt.macosx;
  27 
  28 
  29 import java.awt.*;
  30 import java.awt.geom.Rectangle2D;
  31 import java.awt.image.BufferedImage;
  32 import java.awt.print.*;
  33 
  34 import javax.print.*;
  35 import javax.print.attribute.PrintRequestAttributeSet;
  36 
  37 import sun.java2d.*;
  38 import sun.print.*;
  39 
  40 public class CPrinterJob extends RasterPrinterJob {
  41     // NOTE: This uses RasterPrinterJob as a base, but it doesn't use
  42     // all of the RasterPrinterJob functions. RasterPrinterJob will
  43     // break down printing to pieces that aren't necessary under MacOSX
  44     // printing, such as controlling the # of copies and collating. These
  45     // are handled by the native printing. RasterPrinterJob is kept for
  46     // future compatibility and the state keeping that it handles.
  47 
  48     private static String sShouldNotReachHere = "Should not reach here.";
  49 
  50     private boolean noDefaultPrinter = false;
  51 
  52     private static Font defaultFont;
  53 
  54     // This is the NSPrintInfo for this PrinterJob. Protect multi thread
  55     //  access to it. It is used by the pageDialog, jobDialog, and printLoop.
  56     //  This way the state of these items is shared across these calls.
  57     //  PageFormat data is passed in and set on the fNSPrintInfo on a per call
  58     //  basis.
  59     private long fNSPrintInfo = -1;
  60     private Object fNSPrintInfoLock = new Object();
  61 
  62     static {
  63         // AWT has to be initialized for the native code to function correctly.
  64         Toolkit.getDefaultToolkit();
  65     }
  66 
  67     /**
  68      * Presents a dialog to the user for changing the properties of
  69      * the print job.
  70      * This method will display a native dialog if a native print
  71      * service is selected, and user choice of printers will be restricted
  72      * to these native print services.
  73      * To present the cross platform print dialog for all services,
  74      * including native ones instead use
  75      * <code>printDialog(PrintRequestAttributeSet)</code>.
  76      * <p>
  77      * PrinterJob implementations which can use PrintService's will update
  78      * the PrintService for this PrinterJob to reflect the new service
  79      * selected by the user.
  80      * @return <code>true</code> if the user does not cancel the dialog;
  81      * <code>false</code> otherwise.
  82      * @exception HeadlessException if GraphicsEnvironment.isHeadless()
  83      * returns true.
  84      * @see java.awt.GraphicsEnvironment#isHeadless
  85      */
  86     public boolean printDialog() throws HeadlessException {
  87         if (GraphicsEnvironment.isHeadless()) {
  88             throw new HeadlessException();
  89         }
  90 
  91         if (noDefaultPrinter) {
  92             return false;
  93         }
  94 
  95         return jobSetup(getPageable(), checkAllowedToPrintToFile());
  96     }
  97 
  98     /**
  99      * Displays a dialog that allows modification of a
 100      * <code>PageFormat</code> instance.
 101      * The <code>page</code> argument is used to initialize controls
 102      * in the page setup dialog.
 103      * If the user cancels the dialog then this method returns the
 104      * original <code>page</code> object unmodified.
 105      * If the user okays the dialog then this method returns a new
 106      * <code>PageFormat</code> object with the indicated changes.
 107      * In either case, the original <code>page</code> object is
 108      * not modified.
 109      * @param page the default <code>PageFormat</code> presented to the
 110      *            user for modification
 111      * @return    the original <code>page</code> object if the dialog
 112      *            is cancelled; a new <code>PageFormat</code> object
 113      *          containing the format indicated by the user if the
 114      *          dialog is acknowledged.
 115      * @exception HeadlessException if GraphicsEnvironment.isHeadless()
 116      * returns true.
 117      * @see java.awt.GraphicsEnvironment#isHeadless
 118      * @since     1.2
 119      */
 120     public PageFormat pageDialog(PageFormat page) throws HeadlessException {
 121         if (GraphicsEnvironment.isHeadless()) {
 122             throw new HeadlessException();
 123         }
 124 
 125         if (noDefaultPrinter) {
 126             return page;
 127         }
 128 
 129         PageFormat pageClone = (PageFormat) page.clone();
 130         boolean doIt = pageSetup(pageClone, null);
 131         return doIt ? pageClone : page;
 132     }
 133 
 134     /**
 135      * Clones the <code>PageFormat</code> argument and alters the
 136      * clone to describe a default page size and orientation.
 137      * @param page the <code>PageFormat</code> to be cloned and altered
 138      * @return clone of <code>page</code>, altered to describe a default
 139      *                      <code>PageFormat</code>.
 140      */
 141     public PageFormat defaultPage(PageFormat page) {
 142         PageFormat newPage = (PageFormat)page.clone();
 143         getDefaultPage(newPage);
 144         return newPage;
 145     }
 146 
 147     protected void setAttributes(PrintRequestAttributeSet attributes) throws PrinterException {
 148         super.setAttributes(attributes);
 149 
 150         if (attributes == null) {
 151             return;
 152         }
 153 
 154         // See if this has an NSPrintInfo in it.
 155         NSPrintInfo nsPrintInfo = (NSPrintInfo)attributes.get(NSPrintInfo.class);
 156         if (nsPrintInfo != null) {
 157             fNSPrintInfo = nsPrintInfo.getValue();
 158         }
 159     }
 160 
 161     volatile boolean onEventThread;
 162 
 163     private void completePrintLoop() {
 164         Runnable r = new Runnable() { public void run() {
 165             synchronized(this) {
 166                 performingPrinting = false;
 167             }
 168         }};
 169 
 170         if (onEventThread) {
 171             try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
 172         } else {
 173             r.run();
 174         }
 175     }
 176 
 177 
 178     public void print(PrintRequestAttributeSet attributes) throws PrinterException {
 179         // NOTE: Some of this code is copied from RasterPrinterJob.
 180 
 181 
 182         // this code uses javax.print APIs
 183         // this will make it print directly to the printer
 184         // this will not work if the user clicks on the "Preview" button
 185         // However if the printer is a StreamPrintService, its the right path.
 186         PrintService psvc = getPrintService();
 187         if (psvc instanceof StreamPrintService) {
 188             spoolToService(psvc, attributes);
 189             return;
 190         }
 191 
 192 
 193         setAttributes(attributes);
 194 
 195         /* Get the range of pages we are to print. If the
 196          * last page to print is unknown, then we print to
 197          * the end of the document. Note that firstPage
 198          * and lastPage are 0 based page indices.
 199          */
 200         int numPages = mDocument.getNumberOfPages();
 201 
 202         int firstPage = getFirstPage();
 203         int lastPage = getLastPage();
 204         if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES) {
 205             int totalPages = mDocument.getNumberOfPages();
 206             if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) {
 207                 lastPage = mDocument.getNumberOfPages() - 1;
 208             }
 209         }
 210 
 211         try {
 212             synchronized (this) {
 213                 performingPrinting = true;
 214                 userCancelled = false;
 215             }
 216 
 217             if (EventQueue.isDispatchThread()) {
 218                 // This is an AWT EventQueue, and this print rendering loop needs to block it.
 219 
 220                 onEventThread = true;
 221 
 222                 try {
 223                     // Fire off the print rendering loop on the AppKit thread, and don't have
 224                     //  it wait and block this thread.
 225                     if (printLoop(false, firstPage, lastPage)) {
 226                         // Fire off the EventConditional that will what until the condition is met,
 227                         //  but will still process AWTEvent's as they occur.
 228                         new EventDispatchAccess() {
 229                             public boolean evaluate() {
 230                                 return performingPrinting;
 231                             }
 232                         }.pumpEventsAndWait();
 233                     }
 234                 } catch (Exception e) {
 235                     e.printStackTrace();
 236                 }
 237             } else {
 238                 // Fire off the print rendering loop on the AppKit, and block this thread
 239                 //  until it is done.
 240                 // But don't actually block... we need to come back here!
 241                 onEventThread = false;
 242 
 243                 try {
 244                     printLoop(true, firstPage, lastPage);
 245                 } catch (Exception e) {
 246                     e.printStackTrace();
 247                 }
 248             }
 249         } finally {
 250             synchronized (this) {
 251                 // NOTE: Native code shouldn't allow exceptions out while
 252                 // printing. They should cancel the print loop.
 253                 performingPrinting = false;
 254                 notify();
 255             }
 256         }
 257 
 258         // Normalize the collated, # copies, numPages, first/last pages. Need to
 259         //  make note of pageRangesAttr.
 260 
 261         // Set up NSPrintInfo with the java settings (PageFormat & Paper).
 262 
 263         // Create an NSView for printing. Have knowsPageRange return YES, and give the correct
 264         //  range, or MAX? if unknown. Have rectForPage do a peekGraphics check before returning
 265         //  the rectangle. Have drawRect do the real render of the page. Have printJobTitle do
 266         //  the right thing.
 267 
 268         // Call NSPrintOperation, it will call NSView.drawRect: for each page.
 269 
 270         // NSView.drawRect: will create a CPrinterGraphics with the current CGContextRef, and then
 271         //  pass this Graphics onto the Printable with the appropriate PageFormat and index.
 272 
 273         // Need to be able to cancel the NSPrintOperation (using code from RasterPrinterJob, be
 274         //  sure to initialize userCancelled and performingPrinting member variables).
 275 
 276         // Extensions available from AppKit: Print to PDF or EPS file!
 277     }
 278 
 279     /**
 280      * Returns the resolution in dots per inch across the width
 281      * of the page.
 282      */
 283     protected double getXRes() {
 284         // NOTE: This is not used in the CPrinterJob code path.
 285         return 0;
 286     }
 287 
 288     /**
 289      * Returns the resolution in dots per inch down the height
 290      * of the page.
 291      */
 292     protected double getYRes() {
 293         // NOTE: This is not used in the CPrinterJob code path.
 294         return 0;
 295     }
 296 
 297     /**
 298      * Must be obtained from the current printer.
 299      * Value is in device pixels.
 300      * Not adjusted for orientation of the paper.
 301      */
 302     protected double getPhysicalPrintableX(Paper p) {
 303         // NOTE: This is not used in the CPrinterJob code path.
 304         return 0;
 305     }
 306 
 307     /**
 308      * Must be obtained from the current printer.
 309      * Value is in device pixels.
 310      * Not adjusted for orientation of the paper.
 311      */
 312     protected double getPhysicalPrintableY(Paper p) {
 313         // NOTE: This is not used in the CPrinterJob code path.
 314         return 0;
 315     }
 316 
 317     /**
 318      * Must be obtained from the current printer.
 319      * Value is in device pixels.
 320      * Not adjusted for orientation of the paper.
 321      */
 322     protected double getPhysicalPrintableWidth(Paper p) {
 323         // NOTE: This is not used in the CPrinterJob code path.
 324         return 0;
 325     }
 326 
 327     /**
 328      * Must be obtained from the current printer.
 329      * Value is in device pixels.
 330      * Not adjusted for orientation of the paper.
 331      */
 332     protected double getPhysicalPrintableHeight(Paper p) {
 333         // NOTE: This is not used in the CPrinterJob code path.
 334         return 0;
 335     }
 336 
 337     /**
 338      * Must be obtained from the current printer.
 339      * Value is in device pixels.
 340      * Not adjusted for orientation of the paper.
 341      */
 342     protected double getPhysicalPageWidth(Paper p) {
 343         // NOTE: This is not used in the CPrinterJob code path.
 344         return 0;
 345     }
 346 
 347     /**
 348      * Must be obtained from the current printer.
 349      * Value is in device pixels.
 350      * Not adjusted for orientation of the paper.
 351      */
 352     protected double getPhysicalPageHeight(Paper p) {
 353         // NOTE: This is not used in the CPrinterJob code path.
 354         return 0;
 355     }
 356 
 357     /**
 358      * Begin a new page. This call's Window's
 359      * StartPage routine.
 360      */
 361     protected void startPage(PageFormat format, Printable painter, int index) throws PrinterException {
 362         // NOTE: This is not used in the CPrinterJob code path.
 363         throw new PrinterException(sShouldNotReachHere);
 364     }
 365 
 366     /**
 367      * End a page.
 368      */
 369     protected void endPage(PageFormat format, Printable painter, int index) throws PrinterException {
 370         // NOTE: This is not used in the CPrinterJob code path.
 371         throw new PrinterException(sShouldNotReachHere);
 372     }
 373 
 374     /**
 375      * Prints the contents of the array of ints, 'data'
 376      * to the current page. The band is placed at the
 377      * location (x, y) in device coordinates on the
 378      * page. The width and height of the band is
 379      * specified by the caller.
 380      */
 381     protected void printBand(byte[] data, int x, int y, int width, int height) throws PrinterException {
 382         // NOTE: This is not used in the CPrinterJob code path.
 383         throw new PrinterException(sShouldNotReachHere);
 384     }
 385 
 386     /**
 387      * Called by the print() method at the start of
 388      * a print job.
 389      */
 390     protected void startDoc() throws PrinterException {
 391         // NOTE: This is not used in the CPrinterJob code path.
 392         throw new PrinterException(sShouldNotReachHere);
 393     }
 394 
 395     /**
 396      * Called by the print() method at the end of
 397      * a print job.
 398      */
 399     protected void endDoc() throws PrinterException {
 400         // NOTE: This is not used in the CPrinterJob code path.
 401         throw new PrinterException(sShouldNotReachHere);
 402     }
 403 
 404     /* Called by cancelDoc */
 405     protected native void abortDoc();
 406 
 407     /**
 408      * Displays the page setup dialog placing the user's
 409      * settings into 'page'.
 410      */
 411     public boolean pageSetup(PageFormat page, Printable painter) {
 412         CPrinterDialog printerDialog = new CPrinterPageDialog(null, this, page, painter);
 413         printerDialog.setVisible(true);
 414         boolean result = printerDialog.getRetVal();
 415         printerDialog.dispose();
 416         return result;
 417     }
 418 
 419     /**
 420      * Displays the print dialog and records the user's settings
 421      * into this object. Return false if the user cancels the
 422      * dialog.
 423      * If the dialog is to use a set of attributes, useAttributes is true.
 424      */
 425     private boolean jobSetup(Pageable doc, boolean allowPrintToFile) {
 426         CPrinterDialog printerDialog = new CPrinterJobDialog(null, this, doc, allowPrintToFile);
 427         printerDialog.setVisible(true);
 428         boolean result = printerDialog.getRetVal();
 429         printerDialog.dispose();
 430         return result;
 431     }
 432 
 433     /**
 434      * Alters the orientation and Paper to match defaults obtained
 435      * from a printer.
 436      */
 437     private native void getDefaultPage(PageFormat page);
 438 
 439     /**
 440      * validate the paper size against the current printer.
 441      */
 442     protected native void validatePaper(Paper origPaper, Paper newPaper );
 443 
 444     // The following methods are CPrinterJob specific.
 445 
 446     protected void finalize() {
 447         if (fNSPrintInfo != -1) {
 448             dispose(fNSPrintInfo);
 449         }
 450     }
 451 
 452     private native long createNSPrintInfo();
 453     private native void dispose(long printInfo);
 454 
 455     private long getNSPrintInfo() {
 456         // This is called from the native side.
 457         synchronized (fNSPrintInfoLock) {
 458             if (fNSPrintInfo == -1) {
 459                 fNSPrintInfo = createNSPrintInfo();
 460             }
 461             return fNSPrintInfo;
 462         }
 463     }
 464 
 465     private native boolean printLoop(boolean waitUntilDone, int firstPage, int lastPage) throws PrinterException;
 466 
 467     private PageFormat getPageFormat(int pageIndex) {
 468         // This is called from the native side.
 469         PageFormat page;
 470         try {
 471             page = getPageable().getPageFormat(pageIndex);
 472         } catch (Exception e) {
 473             return null;
 474         }
 475         return page;
 476     }
 477 
 478     private Printable getPrintable(int pageIndex) {
 479         // This is called from the native side.
 480         Printable painter;
 481         try {
 482             painter = getPageable().getPrintable(pageIndex);
 483         } catch (Exception e) {
 484             return null;
 485         }
 486         return painter;
 487     }
 488 
 489     private String getPrinterName(){
 490         // This is called from the native side.
 491         PrintService service = getPrintService();
 492         if (service == null) return null;
 493         return service.getName();
 494     }
 495 
 496     private void setPrinterServiceFromNative(String printerName) {
 497         // This is called from the native side.
 498         PrintService[] services = PrintServiceLookup.lookupPrintServices(DocFlavor.SERVICE_FORMATTED.PAGEABLE, null);
 499 
 500         for (int i = 0; i < services.length; i++) {
 501             PrintService service = services[i];
 502 
 503             if (printerName.equals(service.getName())) {
 504                 try {
 505                     setPrintService(service);
 506                 } catch (PrinterException e) {
 507                     // ignored
 508                 }
 509                 return;
 510             }
 511         }
 512     }
 513 
 514     private Rectangle2D getPageFormatArea(PageFormat page) {
 515         Rectangle2D.Double pageFormatArea =
 516             new Rectangle2D.Double(page.getImageableX(),
 517                     page.getImageableY(),
 518                     page.getImageableWidth(),
 519                     page.getImageableHeight());
 520         return pageFormatArea;
 521     }
 522 
 523     private boolean cancelCheck() {
 524         // This is called from the native side.
 525 
 526         // This is used to avoid deadlock
 527         // We would like to just call if isCancelled(),
 528         // but that will block the AppKit thread against whomever is holding the synchronized lock
 529         boolean cancelled = (performingPrinting && userCancelled);
 530         if (cancelled) {
 531             try {
 532                 LWCToolkit.invokeLater(new Runnable() { public void run() {
 533                     try {
 534                     cancelDoc();
 535                     } catch (PrinterAbortException pae) {
 536                         // no-op, let the native side handle it
 537                     }
 538                 }}, null);
 539             } catch (java.lang.reflect.InvocationTargetException ite) {}
 540         }
 541         return cancelled;
 542     }
 543 
 544     private PeekGraphics createFirstPassGraphics(PrinterJob printerJob, PageFormat page) {
 545         // This is called from the native side.
 546         BufferedImage bimg = new BufferedImage((int)Math.round(page.getWidth()), (int)Math.round(page.getHeight()), BufferedImage.TYPE_INT_ARGB_PRE);
 547         PeekGraphics peekGraphics = createPeekGraphics(bimg.createGraphics(), printerJob);
 548         Rectangle2D pageFormatArea = getPageFormatArea(page);
 549         initPrinterGraphics(peekGraphics, pageFormatArea);
 550         return peekGraphics;
 551     }
 552 
 553     private void printToPathGraphics(    final PeekGraphics graphics, // Always an actual PeekGraphics
 554                                         final PrinterJob printerJob, // Always an actual CPrinterJob
 555                                         final Printable painter, // Client class
 556                                         final PageFormat page, // Client class
 557                                         final int pageIndex,
 558                                         final long context) throws PrinterException {
 559         // This is called from the native side.
 560         Runnable r = new Runnable() { public void run() {
 561             try {
 562                 SurfaceData sd = CPrinterSurfaceData.createData(page, context); // Just stores page into an ivar
 563                 if (defaultFont == null) {
 564                     defaultFont = new Font("Dialog", Font.PLAIN, 12);
 565                 }
 566                 Graphics2D delegate = new SunGraphics2D(sd, Color.black, Color.white, defaultFont);
 567 
 568                 Graphics2D pathGraphics = new CPrinterGraphics(delegate, printerJob); // Just stores delegate into an ivar
 569                 Rectangle2D pageFormatArea = getPageFormatArea(page);
 570                 initPrinterGraphics(pathGraphics, pageFormatArea);
 571                 painter.print(pathGraphics, page, pageIndex);
 572                 delegate.dispose();
 573                 delegate = null;
 574         } catch (PrinterException pe) { throw new java.lang.reflect.UndeclaredThrowableException(pe); }
 575         }};
 576 
 577         if (onEventThread) {
 578             try { EventQueue.invokeAndWait(r);
 579             } catch (java.lang.reflect.InvocationTargetException ite) {
 580                 Throwable te = (Throwable)ite.getTargetException();
 581                 if (te instanceof PrinterException) throw (PrinterException)te;
 582                 else te.printStackTrace();
 583             } catch (Exception e) { e.printStackTrace(); }
 584         } else {
 585             r.run();
 586         }
 587 
 588     }
 589 
 590     // Returns either 1. an array of 3 object (PageFormat, Printable, PeekGraphics) or 2. null
 591     private Object[] getPageformatPrintablePeekgraphics(final int pageIndex) {
 592         final Object[] ret = new Object[3];
 593         final PrinterJob printerJob = this;
 594 
 595         Runnable r = new Runnable() { public void run() { synchronized(ret) {
 596             try {
 597                 Pageable pageable = getPageable();
 598                 PageFormat pageFormat = pageable.getPageFormat(pageIndex);
 599                 if (pageFormat != null) {
 600                     Printable printable = pageable.getPrintable(pageIndex);
 601                     if (printable != null) {
 602                         BufferedImage bimg = new BufferedImage((int)Math.round(pageFormat.getWidth()), (int)Math.round(pageFormat.getHeight()), BufferedImage.TYPE_INT_ARGB_PRE);
 603                         PeekGraphics peekGraphics = createPeekGraphics(bimg.createGraphics(), printerJob);
 604                         Rectangle2D pageFormatArea = getPageFormatArea(pageFormat);
 605                         initPrinterGraphics(peekGraphics, pageFormatArea);
 606 
 607                         // Do the assignment here!
 608                         ret[0] = pageFormat;
 609                         ret[1] = printable;
 610                         ret[2] = peekGraphics;
 611                     }
 612                 }
 613             } catch (Exception e) {} // Original code bailed on any exception
 614         }}};
 615 
 616         if (onEventThread) {
 617             try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
 618         } else {
 619             r.run();
 620         }
 621 
 622         synchronized(ret) {
 623             if (ret[2] != null)
 624                 return ret;
 625             return null;
 626         }
 627     }
 628 
 629     private Rectangle2D printAndGetPageFormatArea(final Printable printable, final Graphics graphics, final PageFormat pageFormat, final int pageIndex) {
 630         final Rectangle2D[] ret = new Rectangle2D[1];
 631 
 632         Runnable r = new Runnable() { public void run() { synchronized(ret) {
 633             try {
 634                 int pageResult = printable.print(graphics, pageFormat, pageIndex);
 635                 if (pageResult != Printable.NO_SUCH_PAGE) {
 636                     ret[0] = getPageFormatArea(pageFormat);
 637                 }
 638             } catch (Exception e) {} // Original code bailed on any exception
 639         }}};
 640 
 641         if (onEventThread) {
 642             try { EventQueue.invokeAndWait(r); } catch (Exception e) { e.printStackTrace(); }
 643         } else {
 644             r.run();
 645         }
 646 
 647         synchronized(ret) { return ret[0]; }
 648     }
 649 
 650     // upcall from native
 651     private static void detachPrintLoop(final long target, final long arg) {
 652         new Thread() { public void run() {
 653             _safePrintLoop(target, arg);
 654         }}.start();
 655     }
 656     private static native void _safePrintLoop(long target, long arg);
 657 
 658     @Override
 659     protected void startPage(PageFormat arg0, Printable arg1, int arg2, boolean arg3) throws PrinterException {
 660         // TODO Auto-generated method stub
 661     }
 662 }