View Javadoc

1   /*
2    * UML2MMBase module.
3    *
4    * The contents of this file are subject to the Mozilla Public License
5    * Version 1.0 (the "License"); you may not use this file except in
6    * compliance with the License. You may obtain a copy of the License at
7    * http://www.mozilla.org/MPL/
8    *
9    * Software distributed under the License is distributed on an "AS IS"
10   * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
11   * License for the specific language governing rights and limitations
12   * under the License.
13   */
14  
15  package com.finalist.mmbase2uml;
16  
17  import net.sf.mmapps.modules.config.*;
18  
19  import java.net.URL;
20  import java.io.IOException;
21  import java.io.FileOutputStream;
22  import java.io.File;
23  import java.io.OutputStream;
24  import java.util.*;
25  
26  import com.finalist.uml14.simpleuml.*;
27  import com.finalist.mmbase.umlprofile.MMBaseUMLProfile;
28  
29  /***
30   * The MMBase2UMLTester reads an MMBase application and creates a XMI file containg
31   * a UML description of the MMBase application.
32   *
33   * @author kors
34   * @version $Revision: 1.3 $, $Date: 2004/10/23 08:54:40 $
35   */
36  public class MMBase2UML {
37  
38     private HashMap typeMappings = new HashMap();
39  
40     /***
41      * This application parses an MMBase application and than writes an XMI file
42      * that contains the UML description of the MMBase application.
43      *
44      * @param args The first argument parsed is either a directory with multiple
45      * MMBase-applications, a directory with one single MMBase application or a
46      * remote url to a online MMBase application.
47      * The second argument may be the name of the output directory.
48      */
49     public static void main(String args[]) {
50  
51        MMBase2UML mmbase2uml = new MMBase2UML();
52  
53        if (args.length > 2) {
54           mmbase2uml.parseApplication(args[0], args[1], args[2]);
55        }
56        if (args.length > 1) {
57           mmbase2uml.parseApplication(args[0], args[1]);
58        }
59        else if (args.length > 0) {
60           mmbase2uml.parseApplication(args[0], null);
61        }
62        else {
63           new DirChooserGui();
64        }
65     }
66  
67  
68     /***
69      * Analyses the sourcePath that was given. Than start generating the correct XMI
70      * file(s). Also generate a class diagram in DI format.
71      *
72      * @param sourcePath The path to be parsed.
73      * @param targetPath The path to write the XMI files to.
74      */
75     public void parseApplication(String sourcePath, String targetPath) {
76        parseApplication(sourcePath, targetPath, "true");
77     }
78  
79     /***
80      * Analyses the sourcePath that was given. Than start generating the correct XMI
81      * file(s).
82      *
83      * @param sourcePath The path to be parsed.
84      * @param targetPath The path to write the XMI files to.
85      * @param createClassDiagram "true" if a class diagram in DI format should be generated.
86      */
87     public void parseApplication(String sourcePath, String targetPath, String createClassDiagram) {
88  
89        boolean offline = true;
90        boolean singleApp = true;
91        boolean addClassDiagram = true;
92        if (createClassDiagram != null && !createClassDiagram.equals("true")) {
93           addClassDiagram = false;
94        }
95  
96        if (sourcePath.endsWith(File.separator)) {
97           sourcePath = sourcePath.substring(0, sourcePath.length() - 1);
98        }
99        if (sourcePath.indexOf("rmi://") >= 0) {
100          offline = false;
101       }
102       else if (!new File(sourcePath + ".xml").isFile()) {
103          singleApp = false;
104       }
105 
106       ApplicationConfiguration appConf = null;
107       SimpleModel simpleModel = null;
108 
109       if (singleApp) {
110          if (offline) {
111             // This concerns a single application.
112             System.out.println("Parsing a single offline application");
113             appConf = readSingleApp(sourcePath);
114          }
115          else {
116             // This conerns a single remote appliation.
117             System.out.println("Parsing a single online remote cloud.");
118             appConf = readRemoteApp(sourcePath);
119          }
120 
121          if (appConf != null) {
122             simpleModel = createModel(appConf, addClassDiagram);
123             writeModel(simpleModel, targetPath);
124          }
125       }
126       else {
127          // This concerns multiple applications.
128          System.out.println("Parsing multiple offline applications");
129 
130          Configuration conf = readMultipleApp(sourcePath);
131          if (conf != null) {
132             ApplicationConfigurations appConfs = conf.getApplicationConfigurations();
133             System.out.println("Found " + appConfs.size() + " application(s).");
134 
135             for (int i = 0; i < appConfs.size(); i++) {
136                appConf = appConfs.getApplicationConfiguration(i);
137                simpleModel = createModel(appConf, addClassDiagram);
138 
139                writeModel(simpleModel, targetPath);
140             }
141          }
142       }
143 
144       // If appConf is still null at this point, something went wrong.
145       if (appConf == null) {
146          System.out.println("No application found.");
147       }
148    }
149 
150    /***
151     * Reads the MMBase application from the filepath.
152     *
153     * @param filePath A reference to a directory with a single MMBase application.
154     * @return A Configuration containing all the information about the MMBase
155     * application.
156     */
157    public ApplicationConfiguration readSingleApp(String filePath) {
158 
159       MMBaseFileConfigurationReader reader = new MMBaseFileConfigurationReader();
160       ApplicationConfiguration appConf = null;
161 
162       try {
163          File file = new File(filePath + ".xml");
164          appConf = ConfigurationXMLReader.createApplicationConfiguration(new Configuration(), reader.readFile(file));
165 
166          File dir = new File(filePath);
167 
168          NodeManagerConfigurations nodes = null;
169 
170          try {
171             Configuration configuration = new Configuration();
172             reader.readConfiguration(configuration, dir);
173             nodes = configuration.getNodeManagerConfigurations();
174          }
175          catch (Exception e) {
176             System.out.println("Couldn't read all the nodes.");
177             e.printStackTrace();
178          }
179 
180          if (nodes != null) {
181             for (int i = 0; i < nodes.size(); i++) {
182                appConf.addNodeManagerConfiguration(nodes.getNodeManagerConfiguration(i));
183             }
184          }
185       }
186       catch (IOException e) {
187          System.out.println("Error while reading a single MMBase application from: ");
188          e.printStackTrace();
189       }
190       return appConf;
191    }
192 
193    /***
194     * Reads multiple applications from the specified directory.
195     *
196     * @param dirPath A reference to a directory with multiple applications.
197     * @return A configuration containing multiple ApplicationConfigurations.
198     */
199    public Configuration readMultipleApp(String dirPath) {
200       File file = new File(dirPath);
201       Configuration conf = new Configuration();
202 
203       try {
204          if (file.getCanonicalPath().endsWith(("applications"))) {
205             file = file.getParentFile();
206          }
207          MMBaseFileConfigurationReader reader = new MMBaseFileConfigurationReader();
208          reader.readConfiguration(conf, file);
209 
210          return conf;
211       }
212       catch (IOException e) {
213          System.out.println("Error while reading multiple applications from a directory.");
214          e.printStackTrace();
215       }
216       return conf;
217    }
218 
219    /***
220     * Reads a remote MMBase cloud and returns a Configuration with all the
221     * information about one application.
222     *
223     * @param url A reference to the remotecloud.
224     * @return A Configuration containing the information of one application.
225     */
226    public ApplicationConfiguration readRemoteApp(String url) {
227 
228       ApplicationConfiguration conf = null;
229 
230       try {
231          URLApplicationConfigurationReader urlReader = new URLApplicationConfigurationReader();
232          conf = urlReader.getConfiguration(new URL(url));
233       }
234       catch (Exception mfue) {
235          System.out.println("Error reading the remote MMBase application.");
236          mfue.printStackTrace();
237       }
238       return conf;
239    }
240 
241    /***
242     * Initialize a model with some standard needed features.
243     *
244     * @param appConf configuration of the application.
245     * @return The initialized model which can be extended with the needed classes.
246     */
247    public SimpleModel initModel(ApplicationConfiguration appConf) {
248 
249       // Create the model.
250       SimpleModel simpleModel = new SimpleModel();
251 
252       // Create the default types:
253       SimpleClassifier stringClass  = new SimpleDataType(MMBaseUMLProfile.MMBASE_STRING_TYPE, SimpleModelElement.PUBLIC);
254       SimpleClassifier intClass     = new SimpleDataType(MMBaseUMLProfile.MMBASE_INT_TYPE, SimpleModelElement.PUBLIC);
255       SimpleClassifier htmlClass    = new SimpleDataType(MMBaseUMLProfile.MMBASE_HTML_TYPE, SimpleModelElement.PUBLIC);
256       SimpleClassifier byteClass    = new SimpleDataType(MMBaseUMLProfile.MMBASE_BYTE_TYPE, SimpleModelElement.PUBLIC);
257       SimpleClassifier floatClass   = new SimpleDataType(MMBaseUMLProfile.MMBASE_FLOAT_TYPE, SimpleModelElement.PUBLIC);
258       SimpleClassifier dateClass    = new SimpleDataType(MMBaseUMLProfile.MMBASE_DATE_TYPE, SimpleModelElement.PUBLIC);
259       SimpleClassifier booleanClass = new SimpleDataType(MMBaseUMLProfile.MMBASE_BOOLEAN_TYPE, SimpleModelElement.PUBLIC);
260 
261       simpleModel.addSimpleClassifier(stringClass);
262       simpleModel.addSimpleClassifier(intClass);
263       simpleModel.addSimpleClassifier(htmlClass);
264       simpleModel.addSimpleClassifier(byteClass);
265       simpleModel.addSimpleClassifier(floatClass);
266       simpleModel.addSimpleClassifier(dateClass);
267       simpleModel.addSimpleClassifier(booleanClass);
268 
269       typeMappings.put(MMBaseUMLProfile.MMBASE_STRING_TYPE, stringClass);
270       typeMappings.put(MMBaseUMLProfile.MMBASE_INT_TYPE, intClass);
271       typeMappings.put(MMBaseUMLProfile.MMBASE_HTML_TYPE, htmlClass);
272       typeMappings.put(MMBaseUMLProfile.MMBASE_BYTE_TYPE, byteClass);
273       typeMappings.put(MMBaseUMLProfile.MMBASE_FLOAT_TYPE, floatClass);
274       typeMappings.put(MMBaseUMLProfile.MMBASE_DATE_TYPE, dateClass);
275       typeMappings.put(MMBaseUMLProfile.MMBASE_BOOLEAN_TYPE, booleanClass);
276       typeMappings.put(MMBaseUMLProfile.MMBASE_EVENTTIME_TYPE, dateClass);
277       typeMappings.put(MMBaseUMLProfile.MMBASE_HTML_TYPE, htmlClass);
278       typeMappings.put(MMBaseUMLProfile.MMBASE_INTEGER_TYPE, intClass);
279       typeMappings.put(MMBaseUMLProfile.MMBASE_FIELD_TYPE, stringClass);
280       typeMappings.put(MMBaseUMLProfile.MMBASE_NEWFILE_TYPE, byteClass);
281       typeMappings.put(MMBaseUMLProfile.MMBASE_NEWIMAGE_TYPE, byteClass);
282 
283       // Create the standard handlers IMAGES
284       if (appConf.getNodeManagerConfigurations().getNodeManagerConfiguration("images") == null) {
285          SimpleClassifier imagesClass = new SimpleUmlClass("images", SimpleModelElement.PUBLIC);
286          SimpleAttribute imagesTitleAttribute = new SimpleAttribute("title", SimpleModelElement.PUBLIC, stringClass);
287          SimpleAttribute imagesDescriptionAttribute = new SimpleAttribute("description", SimpleModelElement.PUBLIC, stringClass);
288          SimpleAttribute imagesHandleAttribute = new SimpleAttribute("handle", SimpleModelElement.PUBLIC, byteClass);
289          imagesClass.addSimpleAttribute(imagesTitleAttribute);
290          imagesClass.addSimpleAttribute(imagesDescriptionAttribute);
291          imagesClass.addSimpleAttribute(imagesHandleAttribute);
292          simpleModel.addSimpleClassifier(imagesClass);
293       }
294 
295       // Add the standard definition for the ATTACHMENTS.
296       if (appConf.getNodeManagerConfigurations().getNodeManagerConfiguration("attachments") == null) {
297          SimpleClassifier attachmentsClass = new SimpleUmlClass("attachments", SimpleModelElement.PUBLIC);
298          SimpleAttribute attachmentsTitleAttribute = new SimpleAttribute("title", SimpleModelElement.PUBLIC, stringClass);
299          SimpleAttribute attachmentsdescriptionAttribute = new SimpleAttribute("description", SimpleModelElement.PUBLIC, stringClass);
300          SimpleAttribute attachmentsFilenameAttribute = new SimpleAttribute("filename", SimpleModelElement.PUBLIC, byteClass);
301          attachmentsClass.addSimpleAttribute(attachmentsTitleAttribute);
302          attachmentsClass.addSimpleAttribute(attachmentsdescriptionAttribute);
303          attachmentsClass.addSimpleAttribute(attachmentsFilenameAttribute);
304          simpleModel.addSimpleClassifier(attachmentsClass);
305       }
306 
307       // Add the standard definition for the URLS.
308       if (appConf.getNodeManagerConfigurations().getNodeManagerConfiguration("urls") == null) {
309          SimpleClassifier urlsClass = new SimpleUmlClass("urls", SimpleModelElement.PUBLIC);
310          SimpleAttribute urlsNameAttribute = new SimpleAttribute("name", SimpleModelElement.PUBLIC, stringClass);
311          SimpleAttribute urlsDescriptionAttribute = new SimpleAttribute("description", SimpleModelElement.PUBLIC, stringClass);
312          SimpleAttribute urlsUrlAttribute = new SimpleAttribute("url", SimpleModelElement.PUBLIC, stringClass);
313          urlsClass.addSimpleAttribute(urlsNameAttribute);
314          urlsClass.addSimpleAttribute(urlsDescriptionAttribute);
315          urlsClass.addSimpleAttribute(urlsUrlAttribute);
316          simpleModel.addSimpleClassifier(urlsClass);
317       }
318 
319       return simpleModel;
320    }
321 
322    /***
323     * Parses the information of the MMBase application and returns a SimpleModel
324     * representing the application. Include a Class Diagram as well.
325     *
326     * @param appConf The application configuration.
327     * @return A SimpleModel that represents the given application.
328     */
329    public SimpleModel createModel(ApplicationConfiguration appConf) {
330       return createModel(appConf, true);
331    }
332 
333    /***
334     * Parses the information of the MMBase application and returns a SimpleModel
335     * representing the application. Optional a class diagram will be added.
336     *
337     * @param appConf The application configuration.
338     * @param addClassDiagram if true, a class diagram will be added to the model im DI format.
339     * @return A SimpleModel that represents the given application.
340     */
341    public SimpleModel createModel(ApplicationConfiguration appConf, boolean addClassDiagram) {
342 
343       if (appConf == null) {
344          System.out.println("!! The application is null");
345       }
346 
347       System.out.println("PARSING APPLICATION: " + appConf.getName());
348       SimpleModel simpleModel = initModel(appConf);
349       if (simpleModel == null) {
350          System.out.println("Model is null!");
351       }
352       simpleModel.setName(appConf.getName());
353 
354       NodeManagerConfigurations nodeConfs = appConf.getNodeManagerConfigurations();
355       if (nodeConfs == null) {
356          System.out.println("nodeConfs is null!");
357       }
358       RelationManagerConfigurations relConfs = appConf.getRelationManagerConfigurations();
359       if (relConfs == null) {
360          System.out.println("relConfs is null!");
361       }
362 
363       SimpleClassifier appClass = null;
364       SimpleAttribute appAttribute = null;
365       NodeManagerConfiguration nodeConf = null;
366       FieldConfiguration fieldConf = null;
367 
368       // First run through the application and create all classes and attributes.
369       for (int i = 0; i < nodeConfs.size(); i++) {
370          nodeConf = nodeConfs.getNodeManagerConfiguration(i);
371 
372          appClass = new SimpleUmlClass(nodeConf.getName(), SimpleModelElement.PUBLIC);
373 
374          String extendsClassName = nodeConf.getExtends();
375          if (extendsClassName.equalsIgnoreCase(MMBaseUMLProfile.POSREL_RELATION_BUILDER)
376                                                                      || extendsClassName.equalsIgnoreCase(MMBaseUMLProfile.INSREL_RELATION_BUILDER)) {
377             simpleModel.setStereoType(MMBaseUMLProfile.STEREOTYPE_CLASS_RELATION , appClass);
378          }
379          else {
380             simpleModel.setStereoType(MMBaseUMLProfile.STEREOTYPE_CLASS_MMBASE, appClass);
381          }
382 
383          // The documentation on the class.
384          Properties descriptions = nodeConf.getDescriptions();
385          String description = descriptions.getProperty(MMBaseUMLProfile.DUTCH_LANGUAGE_ID);
386 
387          if (description == null) {
388             description = descriptions.getProperty(MMBaseUMLProfile.ENGLISH_LANGUAGE_ID);
389          }
390          if (description == null) {
391             if (descriptions.elements().hasMoreElements()) {
392                description = "" + descriptions.elements().nextElement();
393             }
394          }
395          if (description != null) {
396             simpleModel.addTaggedValue("documentation", description, appClass);
397          }
398          // Than the fields.
399          FieldConfigurations fieldConfs = nodeConf.getFieldConfigurations();
400          for (int j = 0; j < fieldConfs.size(); j++) {
401             fieldConf = fieldConfs.getFieldConfiguration(j);
402             if (fieldConf == null) {
403                System.out.println("Fieldconf is null!");
404             }
405             if (fieldConf.getGUIType() == null) {
406                System.out.println("ERROR: guiType is not set for field: " + fieldConf.getName() + " in builder: " + nodeConf.getName());
407                System.out.println("Using string now as gui type.");
408             }
409             String guiType = fieldConf.getGUIType();
410             if (guiType != null) {
411                guiType = guiType.toLowerCase();
412             }
413             else {
414                guiType = MMBaseUMLProfile.MMBASE_STRING_TYPE.toLowerCase();
415             }
416             SimpleClassifier type = (SimpleClassifier) typeMappings.get(guiType);
417 
418             if (type == null) {
419                System.out.println("Type unknown: " + guiType + ". Using string instead.");
420                type = (SimpleClassifier) typeMappings.get(MMBaseUMLProfile.MMBASE_STRING_TYPE);
421             }
422 
423             appAttribute = new SimpleAttribute(fieldConf.getName(), SimpleModelElement.PUBLIC, type);
424 
425             if (fieldConf.isNotNull()) {
426                simpleModel.setStereoType(MMBaseUMLProfile.STEREOTYPE_ATTRIBUTE_REQUIRED, appAttribute);
427             }
428 
429             if (guiType.equalsIgnoreCase(type.getName())) {
430                if (fieldConf.isNotNull()) {
431                   simpleModel.addTaggedValue(MMBaseUMLProfile.TAGGED_VALUE_ATTRIBUTE_MINSIZE, "1", appAttribute);
432                }
433                else {
434                   simpleModel.addTaggedValue(MMBaseUMLProfile.TAGGED_VALUE_ATTRIBUTE_MINSIZE, "0", appAttribute);
435                }
436                String maxSize = fieldConf.getSize();
437                if (maxSize == null) {
438                   maxSize = MMBaseUMLProfile.TAGGED_VALUE_ATTRIBUTE_MAXSIZE;
439                }
440                simpleModel.addTaggedValue(MMBaseUMLProfile.TAGGED_VALUE_ATTRIBUTE_MAXSIZE, maxSize, appAttribute);
441             }
442 
443             appClass.addSimpleAttribute(appAttribute);
444          }
445          simpleModel.addSimpleClassifier(appClass);
446       }
447 
448       // Than run through the application to setup the generalizations.
449       SimpleGeneralization simpleGeneralization = null;
450       for (int i = 0; i < nodeConfs.size(); i++) {
451          nodeConf = nodeConfs.getNodeManagerConfiguration(i);
452 
453          SimpleClassifier childClass = simpleModel.getSimpleClassifierRelative(nodeConf.getName());
454          SimpleClassifier parentClass = simpleModel.getSimpleClassifierRelative(nodeConf.getExtends());
455          if (childClass != null && parentClass != null) {
456             simpleGeneralization = new SimpleGeneralization(childClass, parentClass);
457             simpleModel.addSimpleGeneralization(simpleGeneralization);
458          }
459       }
460 
461       // Than run through the application to setup the relations.
462       RelationManagerConfiguration relConf = null;
463 
464       if (relConfs != null) {
465          for (int i = 0; i < relConfs.size(); i++) {
466             relConf = relConfs.getRelationManagerConfiguration(i);
467             SimpleClassifier sourceClass = simpleModel.getSimpleClassifierRelative(relConf.getSourceNodeManagerName());
468             SimpleClassifier destinationClass = simpleModel.getSimpleClassifierRelative(relConf.getDestinationNodeManagerName());
469 
470             if (sourceClass != null && destinationClass != null) {
471                SimpleAssociationEnd sourceEnd = new SimpleAssociationEnd(sourceClass.getName(), sourceClass, 0, -1, true);
472                SimpleAssociationEnd destinationEnd = new SimpleAssociationEnd(destinationClass.getName(), destinationClass, 0, -1, true);
473                SimpleAssociation assoc = new SimpleAssociation(relConf.getRelationTypeName(), sourceEnd, destinationEnd);
474                simpleModel.addSimpleAssociation(assoc);
475             }
476          }
477       }
478 
479       if (addClassDiagram) {
480          SimpleDiagram simpleDiagram = new SimpleDiagram(simpleModel.getName());
481          simpleModel.addSimpleDiagram(simpleDiagram);
482       }
483 
484       return simpleModel;
485    }
486 
487    /***
488     * Writes the model to a file. The file has the "<<name of the model>>.xmi".
489     *
490     * @param simpleModel The model that has to be written to the file.
491     * @param outputDir the directory to write the model to.
492     */
493    public void writeModel(SimpleModel simpleModel, String outputDir) {
494       String dir = "";
495       if (outputDir != null && !"".equals(outputDir)) {
496          dir = outputDir + File.separator;
497       }
498 
499       System.out.println("Writing the model to a file.");
500       try {
501          OutputStream outputStream = new FileOutputStream(dir + simpleModel.getName() + ".xmi");
502          simpleModel.writeModel(outputStream);
503       }
504       catch (IOException ioe) {
505          System.out.println("Error writing the file.");
506          ioe.printStackTrace();
507       }
508    }
509 }
510 
511 /***
512  * $Log: MMBase2UML.java,v $
513  * Revision 1.3  2004/10/23 08:54:40  ekkelenkamp
514  * magicdraw 8 support
515  *
516  * Revision 1.16  2004/06/25 08:25:49  jeoffrey
517  * fixed bug that reads defaultdata as builders for a singleapp
518  *
519  * Revision 1.15  2004/01/28 07:32:24  rudie
520  * Fixed package name
521  *
522  * Revision 1.14  2004/01/20 10:12:13  rudie
523  * Style fixes.
524  *
525  * Revision 1.13  2004/01/20 08:45:29  rudie
526  * Added more checks on empty guiTypes so no null pointers will occur.
527  *
528  * Revision 1.12  2004/01/14 15:33:41  rudie
529  * Fixed style
530  *
531  * Revision 1.11  2004/01/12 10:42:19  kors
532  * Added a check to see if images, urls and attachments are already defined. If so, dont add them to the model automatically.
533  *
534  * Revision 1.10  2003/11/20 16:02:40  kors
535  * SimpleUml was changed. Relations can now only be added to the SimpleModel and not to the SimpleUmlPackages anymore.
536  *
537  * Revision 1.9  2003/11/12 14:19:57  kors
538  * Removed debug code.
539  *
540  * Revision 1.8  2003/11/12 07:45:37  kors
541  * Stylecheck fix.
542  *
543  * Revision 1.7  2003/11/11 13:40:29  kors
544  * An applications directory instead of an config directory can now be passed to the multiapp application.
545  *
546  * Revision 1.6  2003/11/11 09:39:17  kors
547  * The createModel function now also adds an empty diagram to the model.
548  *
549  * Revision 1.5  2003/11/07 15:59:17  kors
550  * added newfile and newimage mappings
551  *
552  * Revision 1.4  2003/11/07 14:46:27  kors
553  * Changed the application so it now uses the new SimpleUml API.
554  *
555  * Revision 1.3  2003/10/30 15:58:14  kors
556  * The relationname is changed from getName into getRelationTypeName.
557  *
558  * Revision 1.2  2003/10/30 13:01:38  kors
559  * Bugs fixed: incorrect output dir, maxfieldsize nullpointer, guitype unknown nullpointer.
560  *
561  * Revision 1.1  2003/10/30 09:13:10  rudie
562  * moved to other src tree
563  *
564  * Revision 1.1  2003/10/29 13:41:58  kors
565  * *** empty log message ***
566  *
567  *
568  */
569