View Javadoc

1   package org.devaki.nextobjects.workspace.models.graphics;
2   /*
3   
4   nextobjects Copyright (C) 2001-2005 Emmanuel Florent
5   
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by the
8   Free Software Foundation; either version 2 of the License, or (at your
9   option) any later version.
10  
11  This program is distributed in the hope that it will
12  be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13  of MERCHANTABILITY or FITNESS FOR A PARTICULAR
14  PURPOSE. See the GNU General Public License for more details.
15  
16  You should have received a copy of the GNU General Public License along
17  with this program; if not, write to the Free Software Foundation, Inc., 59
18  Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19  
20  */
21  import java.io.Serializable;
22  import java.awt.Cursor;
23  import java.awt.Dimension;
24  import java.awt.Graphics2D;
25  import java.awt.Color;
26  import java.awt.Point;
27  import java.awt.Polygon;
28  import java.awt.event.MouseEvent;
29  import java.awt.event.MouseMotionAdapter;
30  import java.util.Vector;
31  import javax.swing.event.MouseInputAdapter;
32  import org.devaki.nextobjects.constants.CstGraphics;
33  import org.devaki.nextobjects.ui.menus.MenuFactory;
34  import org.devaki.nextobjects.ui.toolbars.NOToolBar2;
35  import org.devaki.nextobjects.ui.workspace.models.EditorFactory;
36  import org.devaki.nextobjects.util.ModelMan;
37  import org.devaki.nextobjects.workspace.models.PhysicalModel;
38  import org.devaki.nextobjects.workspace.models.BaseModel;
39  import org.devaki.nextobjects.workspace.models.columns.Column;
40  import org.devaki.nextobjects.workspace.models.objects.BaseClass;
41  import org.devaki.nextobjects.workspace.models.objects.BaseObject;
42  import org.devaki.nextobjects.workspace.models.objects.Constraint;
43  import org.devaki.nextobjects.workspace.models.objects.Table;
44  import org.devaki.nextobjects.workspace.models.objects.InheritanceLink;
45  import org.devaki.nextobjects.workspace.models.columns.ColumnType;
46  /***
47   * This class is responsible for drawing Physical Data Models
48   *
49   * @see http://www.devaki.org/pdm.html
50   * @author <a href="mailto:eflorent@devaki.org">Emmanuel Florent</a>
51   *
52   */
53  public class PhysicalView extends BaseModelView implements Serializable
54  {
55      /***
56       * Construct a new PhysicalView object
57       * @param theDatabase the context database
58       */
59      public PhysicalView(final PhysicalModel theDatabase)
60      {
61          // Initialization
62          super(theDatabase);
63          this.setVisible(true);
64          // Mouse Listeners
65          this.drawingArea.addMouseListener(new MyMouseListener());
66          this.drawingArea.addMouseMotionListener(
67              new MyMouseMotionListener());
68      }
69      /***
70       * Create a constraint from two table.
71       * This is called by manageDND
72       * The constraint is local to the arrival table,
73       *  like if the fields where moved...
74       * @param pStartTable the start table
75       * @param pEndTable the end table
76       * @return the constraint
77       */
78      private Constraint createConstraint(
79          final Table pStartTable,
80          final Table pEndTable)
81      {
82          //foreign table
83          Vector vfk = new Vector();
84          if (pStartTable.getPrimaryKeys().size() < 1)
85          {
86              Column newColumn = getNewStandardPk(pStartTable);
87              pStartTable.getData().addElement(newColumn);
88              vfk.addElement(newColumn);
89          }
90          else
91          {
92              // at the moment work only with the first PK.
93              // should have done addAll() ?
94              vfk.addElement(pStartTable.getPrimaryKeys().firstElement());
95          }
96          // local table to the constraint
97          Vector vlk = new Vector();
98          //optionally add one PK
99          if (pEndTable.getPrimaryKeys().size() < 1)
100         {
101             Column newColumn2 = getNewStandardPk(pEndTable);
102             pEndTable.getData().addElement(newColumn2);
103             //vlk.addElement(newColumn2);
104         }
105         //anyway add FK to local table
106         Column newColumn = getNewStandardFk(pStartTable, pEndTable);
107         pEndTable.getData().addElement(newColumn);
108         vlk.addElement(newColumn);
109         // call the constructor
110         Constraint constraint =
111             new Constraint(
112                 (PhysicalModel) this.myModel,
113                 pEndTable,
114                 vfk,
115                 pStartTable,
116                 vlk);
117         return constraint;
118     }
119     /***
120      * Get a new , standard primary key for a table
121      *
122      * @param pBaseClass the table
123      * @return the column
124      */
125     private static Column getNewStandardPk(final BaseClass pBaseClass)
126     {
127         Column column =
128             new Column(
129                 pBaseClass.getCode() + " id",
130                 pBaseClass.getCode() + "_id",
131                 new ColumnType("INTEGER", "", ""),
132                 "",
133                 "",
134                 true,
135                 true,
136                 true,
137                 true,
138                 true,
139                 "",
140                 "",
141                 "",
142                 "",
143                 "",
144                 "",
145                 pBaseClass);
146         column.setParent(pBaseClass);
147         return column;
148     }
149     /***
150      * get a new , standard foreign key
151      * take the PK of the foreign table and
152      * add it as a F into the table.
153      *
154      * @param pParentTable the parent table
155       * @param pChildTable the child table
156      * @return the column
157      */
158     public static Column getNewStandardFk(
159         final Table pParentTable,
160         final Table pChildTable)
161     {
162         Column column;
163         Vector pk = pParentTable.getPrimaryKeys();
164         // at the moment work only with the first PK
165         if (pk.size() == 0)
166         { // This should never happen but
167             //in case we provide such an escape.
168             column = getNewStandardPk(pParentTable);
169         }
170         else
171         {
172             column =
173                 new Column(
174                     ((Column) pk.elementAt(0)).getName(),
175                     ((Column) pk.elementAt(0)).getCode(),
176                     ((Column) pk.elementAt(0)).getType(),
177                     "",
178                     "",
179                     false,
180                     true,
181                     false,
182                     false,
183                     false,
184                     "",
185                     "",
186                     "",
187                     "",
188                     "",
189                     "",
190                     pChildTable);
191         }
192         column.setParent(pParentTable);
193         return column;
194     }
195     /***
196      * manage d'n'd
197      */
198     public final void manageDnD()
199     {
200         // If the original object is an entity
201         if (ModelMan.getDraggedObject() instanceof Table)
202         {
203             // If the targeted object is an entity
204             if (ModelMan.getDropTargetObject() instanceof Table)
205             {
206                 if (NOToolBar2.getCurrentTool()
207                     == NOToolBar2.TOOL_CONSTRAINT)
208                 {
209                     ModelMan.addConstraint(
210                         (PhysicalModel) ModelMan.getCurrentModel(),
211                         createConstraint(
212                             (Table) ModelMan.getDraggedObject(),
213                             (Table) ModelMan.getDropTargetObject()));
214                 }
215                 else if (
216                     NOToolBar2.getCurrentTool()
217                         == NOToolBar2.TOOL_INHERITANCE)
218                 {
219                     if (!ModelMan
220                         .getDraggedObject()
221                         .equals(ModelMan.getDropTargetObject()))
222                     {
223                         InheritanceLink newInheritanceLink =
224                             new InheritanceLink(
225                                 this.myModel,
226                                 (BaseClass) ModelMan.getDraggedObject(),
227                                 (BaseClass) ModelMan.getDropTargetObject());
228                         ModelMan.addInheritanceLink(
229                             (BaseModel) myModel,
230                             newInheritanceLink);
231                     }
232                 }
233                 // Reset dragging
234                 ModelMan.setDraggedObject(null);
235                 ModelMan.setDropTargetObject(null);
236                 drawingArea.repaint();
237             }
238         }
239     }
240     /***
241      * The mouse listener who listen clicks (but not moves)
242      */
243     class MyMouseListener extends MouseInputAdapter
244     {
245         /***
246          * What to do when mouse is pressed.
247          * @param e the mouse event
248          */
249         public final void mousePressed(final MouseEvent e)
250         {
251             this.evaluatePopup(e);
252             // Get mouse position
253             x1 = e.getX();
254             y1 = e.getY();
255             if ((NOToolBar2.getCurrentTool() != NOToolBar2.TOOL_RESIZE)
256                 && (NOToolBar2.getCurrentTool() != NOToolBar2.TOOL_TABLE)
257                 && (NOToolBar2.getCurrentTool()
258                     != NOToolBar2.TOOL_ASSOCIATION))
259             {
260                 ObjectView[] tmp = getVisibleObjectView();
261                 // Loop for the association links and inheritance links.
262                 int j = 0;
263                 boolean gotIt = false;
264                 while (j < tmp.length)
265                 {
266                     if (((Polygon) tmp[j].getSurface()).contains(x1, y1))
267                     {
268                         // Define the current object
269                         gotIt = true;
270                         dx = x1 - tmp[j].getLocation().x;
271                         dy = y1 - tmp[j].getLocation().y;
272                         if (e.isControlDown())
273                         {
274                             ModelMan.addCurrentObject(tmp[j].myObject);
275                         }
276                         else
277                         {
278                             ModelMan.setCurrentObject(tmp[j].myObject);
279                         }
280                         j = tmp.length; //break;
281                     }
282                     ++j;
283                 }
284                 if (!gotIt)
285                 {
286                     ModelMan.resetCurrentObjects();
287                 }
288                 if (e.getClickCount() == 2)
289                 {
290                     NOToolBar2.setCurrentTool(-1);
291                     EditorFactory.getObjectEdit(
292                         ModelMan.getCurrentObject());
293                 }
294             }
295             // Make different things according to the current tool
296             switch (NOToolBar2.getCurrentTool())
297             {
298                 case NOToolBar2.TOOL_INHERITANCE :
299                 case NOToolBar2.TOOL_CONSTRAINT :
300                     {
301                         ModelMan.setDraggedObject(
302                             ModelMan.getCurrentObject());
303                         break;
304                     }
305                 case NOToolBar2.TOOL_TABLE :
306                     {
307                         ModelMan.addTable(
308                             (PhysicalModel) myModel,
309                             e.getPoint());
310                         // Record old location
311                         ModelMan.moveCurrentObjects(0, 0);
312                         break;
313                     }
314                 case NOToolBar2.TOOL_LABEL :
315                     {
316                         ModelMan.addLabel(myModel, e.getPoint());
317                         // Record old location
318                         ModelMan.moveCurrentObjects(0, 0);
319                         break;
320                     }
321                 default :
322                     //do nothing
323             }
324             if (ModelMan.getCurrentObject() == null
325                 && e.getClickCount() == 2)
326             {
327                 NOToolBar2.setCurrentTool(-1);
328                 EditorFactory.getModelEdit();
329             }
330             // Repaint drawing area
331             drawingArea.repaint();
332         }
333         /***
334          * What to do when mouse is released.
335          * @param e the mouse event
336          */
337         public final void mouseReleased(final MouseEvent e)
338         {
339             this.evaluatePopup(e);
340             fullRefresh = true;
341             ModelMan.resetActionLocations();
342             calculateRectangle();
343             x2 = e.getX();
344             y2 = e.getY();
345             if (NOToolBar2.getCurrentTool() == NOToolBar2.TOOL_SELECTION
346                 && ModelMan.getCurrentObject() != null)
347             {
348                 ModelMan.moveCurrentObjects(x2 - x1, y2 - y1);
349             }
350             else if (
351                 (NOToolBar2.getCurrentTool() == NOToolBar2.TOOL_CONSTRAINT
352                     || NOToolBar2.getCurrentTool()
353                         == NOToolBar2.TOOL_INHERITANCE)
354                     && ModelMan.getDraggedObject() != null
355                     && ModelMan.getDropTargetObject() != null)
356             {
357                 manageDnD();
358             }
359             NOToolBar2.setCurrentTool(NOToolBar2.TOOL_SELECTION);
360             drawingArea.repaint();
361         }
362         /***
363          * Evaluate if popup menu has been requested
364          * @param e mouse event
365          */
366         private void evaluatePopup(final MouseEvent e)
367         {
368             if (e.isPopupTrigger()
369                 && (NOToolBar2.getCurrentTool() != NOToolBar2.TOOL_TABLE))
370             {
371                 MenuFactory.getModelPopupMenu().setData(
372                     e.getX(),
373                     e.getY());
374                 MenuFactory.getModelPopupMenu().show(
375                     drawingArea,
376                     e.getX(),
377                     e.getY());
378             }
379         }
380     }
381     /***
382      * The mouse listener who listen moves (but not cliks)
383      */
384     class MyMouseMotionListener extends MouseMotionAdapter
385     {
386         /***
387          * What to do when mouse is dragged
388          * @param e the mouse event
389          */
390         public final void mouseDragged(final MouseEvent e)
391         {
392             Graphics2D ptrGphx2D = (Graphics2D) drawingArea.getGraphics();
393             x2 = e.getX();
394             y2 = e.getY();
395             // Define what to do according to the selected tool
396             switch (NOToolBar2.getCurrentTool())
397             {
398                 // Moving an object
399                 case NOToolBar2.TOOL_SELECTION :
400                     if (ModelMan.getCurrentObjects().size() > 0)
401                     {
402                         if (ModelMan.getCurrentObject().getObjectView()
403                             instanceof LineView)
404                         {
405                             // it's nonsense to move a line using a vector
406                             x1 = 0;
407                             y1 = 0;
408                             dx = 0;
409                             dy = 0;
410                         }
411                         ModelMan
412                             .getCurrentObject()
413                             .getObjectView()
414                             .setActionLocation(
415                             x2 - dx,
416                             y2 - dy);
417                     }
418                     break;
419                     // Resizing an object
420                 case NOToolBar2.TOOL_RESIZE :
421                     if (ModelMan.getCurrentObject() != null)
422                     {
423                         // Get informations about the object to resize
424                         int curX =
425                             (int) ModelMan
426                                 .getCurrentObject()
427                                 .getObjectView()
428                                 .getLocation()
429                                 .getX();
430                         int curY =
431                             (int) ModelMan
432                                 .getCurrentObject()
433                                 .getObjectView()
434                                 .getLocation()
435                                 .getY();
436                         int curWidth =
437                             (int) ModelMan
438                                 .getCurrentObject()
439                                 .getObjectView()
440                                 .getSize()
441                                 .getWidth();
442                         int curHeight =
443                             (int) ModelMan
444                                 .getCurrentObject()
445                                 .getObjectView()
446                                 .getSize()
447                                 .getHeight();
448                         // Resize according to the current selection point
449                         switch (getCursor().getType())
450                         {
451                             case SelectionPoint.NORTH_WEST :
452                                 if ((curWidth + curX - x2
453                                     >= CstGraphics.MIN_OBJECT_SIZE.getWidth())
454                                     && (curHeight + curY - y2
455                                         >= CstGraphics
456                                             .MIN_OBJECT_SIZE
457                                             .getHeight()))
458                                 {
459                                     ModelMan
460                                         .getCurrentObject()
461                                         .getObjectView()
462                                         .setLocation(
463                                         new Point(x2, y2));
464                                     ModelMan
465                                         .getCurrentObject()
466                                         .getObjectView()
467                                         .setSize(
468                                         new Dimension(
469                                             curWidth + curX - x2,
470                                             curHeight + curY - y2));
471                                 }
472                                 break;
473                             case SelectionPoint.NORTH :
474                                 if (curHeight + curY - y2
475                                     >= CstGraphics
476                                         .MIN_OBJECT_SIZE
477                                         .getHeight())
478                                 {
479                                     ModelMan
480                                         .getCurrentObject()
481                                         .getObjectView()
482                                         .setLocation(
483                                         new Point(curX, y2));
484                                     ModelMan
485                                         .getCurrentObject()
486                                         .getObjectView()
487                                         .setSize(
488                                         new Dimension(
489                                             curWidth,
490                                             curHeight + curY - y2));
491                                 }
492                                 break;
493                             case SelectionPoint.NORTH_EAST :
494                                 if ((x2 - curX
495                                     >= CstGraphics.MIN_OBJECT_SIZE.getWidth())
496                                     && (curHeight + curY - y2
497                                         >= CstGraphics
498                                             .MIN_OBJECT_SIZE
499                                             .getHeight()))
500                                 {
501                                     ModelMan
502                                         .getCurrentObject()
503                                         .getObjectView()
504                                         .setLocation(
505                                         new Point(curX, y2));
506                                     ModelMan
507                                         .getCurrentObject()
508                                         .getObjectView()
509                                         .setSize(
510                                         new Dimension(
511                                             x2 - curX,
512                                             curHeight + curY - y2));
513                                 }
514                                 break;
515                             case SelectionPoint.WEST :
516                                 if (curWidth + curX - x2
517                                     >= CstGraphics.MIN_OBJECT_SIZE.getWidth())
518                                 {
519                                     ModelMan
520                                         .getCurrentObject()
521                                         .getObjectView()
522                                         .setLocation(
523                                         new Point(x2, curY));
524                                     ModelMan
525                                         .getCurrentObject()
526                                         .getObjectView()
527                                         .setSize(
528                                         new Dimension(
529                                             curWidth + curX - x2,
530                                             curHeight));
531                                 }
532                                 break;
533                             case SelectionPoint.EAST :
534                                 if (x2 - curX
535                                     >= CstGraphics.MIN_OBJECT_SIZE.getWidth())
536                                 {
537                                     ModelMan
538                                         .getCurrentObject()
539                                         .getObjectView()
540                                         .setSize(
541                                         new Dimension(
542                                             x2 - curX,
543                                             curHeight));
544                                 }
545                                 break;
546                             case SelectionPoint.SOUTH_WEST :
547                                 if ((curWidth + curX - x2
548                                     >= CstGraphics.MIN_OBJECT_SIZE.getWidth())
549                                     && (y2 - curY
550                                         >= CstGraphics
551                                             .MIN_OBJECT_SIZE
552                                             .getHeight()))
553                                 {
554                                     ModelMan
555                                         .getCurrentObject()
556                                         .getObjectView()
557                                         .setLocation(
558                                         new Point(x2, curY));
559                                     ModelMan
560                                         .getCurrentObject()
561                                         .getObjectView()
562                                         .setSize(
563                                         new Dimension(
564                                             curWidth + curX - x2,
565                                             y2 - curY));
566                                 }
567                                 break;
568                             case SelectionPoint.SOUTH :
569                                 if (y2 - curY
570                                     >= CstGraphics
571                                         .MIN_OBJECT_SIZE
572                                         .getHeight())
573                                 {
574                                     ModelMan
575                                         .getCurrentObject()
576                                         .getObjectView()
577                                         .setSize(
578                                         new Dimension(curWidth, y2 - curY));
579                                 }
580                                 break;
581                             case SelectionPoint.SOUTH_EAST :
582                                 if ((y2 - curY
583                                     >= CstGraphics.MIN_OBJECT_SIZE.getHeight()
584                                     && x2 - curX
585                                         >= CstGraphics
586                                             .MIN_OBJECT_SIZE
587                                             .getWidth()))
588                                 {
589                                     ModelMan
590                                         .getCurrentObject()
591                                         .getObjectView()
592                                         .setSize(
593                                         new Dimension(
594                                             x2 - curX,
595                                             y2 - curY));
596                                 }
597                                 break;
598                             default :
599                                 //Do nothing
600                         }
601                     }
602                     break;
603                     // Drawing a relation
604                 case NOToolBar2.TOOL_INHERITANCE :
605                 case NOToolBar2.TOOL_CONSTRAINT :
606                     // If there is a dragged object find the targeted object
607                     if (ModelMan.getDraggedObject() != null)
608                     {
609                         // Draw a line between the currently dragged object
610                         ptrGphx2D.setColor(
611                             CstGraphics.MODEL_BACKGROUND_COLOR);
612                         ptrGphx2D.fillRect(
613                             Math.min(x1, x2),
614                             Math.min(y1, y2),
615                             Math.abs(x2 - x1),
616                             Math.abs(y2 - y1));
617                         ptrGphx2D.setColor(Color.BLACK);
618                         ptrGphx2D.drawLine(x1, y1, x2, y2);
619                         // Loop for the entities to find the targeted object
620                         for (int i = 0;
621                             i < ((PhysicalModel) myModel).countTable();
622                             i++)
623                         {
624                             if (((Polygon) ((PhysicalModel) myModel)
625                                 .getTableAt(i)
626                                 .getObjectView()
627                                 .getSurface())
628                                 .contains(
629                                     x2
630                                         + e.getComponent().getX()
631                                         + getViewPosition().x,
632                                     y2
633                                         + e.getComponent().getY()
634                                         + getViewPosition().y))
635                             {
636                                 ModelMan.setDropTargetObject(
637                                     (BaseObject)
638                                         (
639                                             (
640                                                 PhysicalModel) myModel)
641                                                     .getTableAt(
642                                         i));
643                                 break;
644                             }
645                         }
646                     }
647                     break;
648                 default :
649                     //do nothing
650             }
651             // Refresh the drawing area
652             drawingArea.repaint();
653         }
654         /***
655          * What to do when mouse is moved.
656          * @param e the mouse event
657          */
658         public final void mouseMoved(final MouseEvent e)
659         {
660             if ((NOToolBar2.getCurrentTool() != NOToolBar2.TOOL_TABLE)
661                 && (NOToolBar2.getCurrentTool()
662                     != NOToolBar2.TOOL_ASSOCIATION)
663                 && (NOToolBar2.getCurrentTool()
664                     != NOToolBar2.TOOL_ASSOCIATION_LINK))
665             {
666                 SelectionPoint[] tmp;
667                 int position = 4;
668                 boolean resizing = false;
669                 if (ModelMan.getCurrentObject() != null)
670                 {
671                     tmp =
672                         ((ObjectView) ModelMan
673                             .getCurrentObject()
674                             .getObjectView())
675                             .getSelectionPoints();
676                     // Object is selected
677                     for (int j = 0; j < tmp.length; j++)
678                     {
679                         // Mouse is over a Selection Point
680                         if (tmp[j]
681                             .getSurface()
682                             .contains(e.getX(), e.getY()))
683                         {
684                             resizing = true;
685                             position = j + 4;
686                             break;
687                         }
688                     }
689                 }
690                 // Change cursor according to the Selection Point position
691                 if (resizing)
692                 {
693                     setCursor(new Cursor(position));
694                     NOToolBar2.setCurrentTool(NOToolBar2.TOOL_RESIZE);
695                 }
696                 else
697                 {
698                     setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
699                     if (NOToolBar2.getCurrentTool()
700                         == NOToolBar2.TOOL_RESIZE)
701                     {
702                         NOToolBar2.setCurrentTool(
703                             NOToolBar2.TOOL_SELECTION);
704                     }
705                 }
706             }
707         }
708     }
709     /***
710      * set if refresh rectangle / drawing
711      * @param b refresh
712      */
713     public final void setFullRefresh(final boolean b)
714     {
715         fullRefresh = b;
716     }
717     /***
718      * return the view size
719      * @return the location
720      */
721     public final Dimension getViewSize()
722     {
723         return jScrollPane.getViewport().getViewSize();
724     }
725     /***
726      * Calculate the visibles objects
727      * @return the objects views
728      */
729     public final ObjectView[] getVisibleObjectView()
730     {
731         ObjectView[] b =
732             new ObjectView[((PhysicalModel) myModel)
733                 .getInheritanceLinks()
734                 .size()
735                 + ((PhysicalModel) myModel).getTables().size()
736                 + ((PhysicalModel) myModel).getConstraints().size()
737                 + ((PhysicalModel) myModel).getLabels().size()];
738         int offset = 0;
739         for (int i = 0;
740             i < ((PhysicalModel) myModel).getInheritanceLinks().size();
741             i++)
742         {
743             b[offset] =
744                 ((PhysicalModel) myModel)
745                     .getInheritanceLinkAt(i)
746                     .getObjectView();
747             offset++;
748         }
749         for (int i = 0;
750             i < ((PhysicalModel) myModel).getConstraints().size();
751             i++)
752         {
753             b[offset] =
754                 ((PhysicalModel) myModel)
755                     .getConstraintAt(i)
756                     .getObjectView();
757             offset++;
758         }
759         for (int i = 0;
760             i < ((PhysicalModel) myModel).getTables().size();
761             i++)
762         {
763             b[offset] =
764                 ((PhysicalModel) myModel).getTableAt(i).getObjectView();
765             offset++;
766         }
767         for (int i = 0;
768             i < ((PhysicalModel) myModel).getLabels().size();
769             i++)
770         {
771             b[offset] =
772                 (ObjectView) ((PhysicalModel) myModel)
773                     .getLabels()
774                     .elementAt(
775                     i);
776             offset++;
777         }
778         return b;
779     }
780 }