KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > j2ee > ejbcore > api > methodcontroller > EntityMethodController


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.modules.j2ee.ejbcore.api.methodcontroller;
20
21 import org.netbeans.modules.j2ee.common.method.MethodModel;
22 import java.io.IOException JavaDoc;
23 import java.rmi.RemoteException JavaDoc;
24 import java.util.Collections JavaDoc;
25 import java.util.HashSet JavaDoc;
26 import java.util.LinkedList JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Set JavaDoc;
29 import javax.lang.model.element.ExecutableElement;
30 import javax.lang.model.element.Modifier;
31 import org.netbeans.modules.j2ee.dd.api.ejb.CmpField;
32 import org.netbeans.modules.j2ee.dd.api.ejb.CmrField;
33 import org.netbeans.modules.j2ee.dd.api.ejb.EjbJar;
34 import org.netbeans.modules.j2ee.dd.api.ejb.EjbRelation;
35 import org.netbeans.modules.j2ee.dd.api.ejb.EjbRelationshipRole;
36 import org.netbeans.modules.j2ee.dd.api.ejb.Entity;
37 import org.netbeans.modules.j2ee.dd.api.ejb.MethodParams;
38 import org.netbeans.modules.j2ee.dd.api.ejb.Query;
39 import org.netbeans.modules.j2ee.dd.api.ejb.QueryMethod;
40 import org.netbeans.modules.j2ee.dd.api.ejb.Relationships;
41 import org.openide.ErrorManager;
42 import org.openide.filesystems.FileObject;
43
44 /**
45  *
46  * @author Chris Webster
47  * @author Martin Adamek
48  */

49 public final class EntityMethodController extends AbstractMethodController {
50     
51     private final FileObject ejbClassFO;
52     private final Entity model;
53     private final EjbJar parent;
54     private final Set JavaDoc<Modifier> modifiersPublicAbstract = new HashSet JavaDoc<Modifier>(2);
55
56     public EntityMethodController(FileObject ejbClassFO, Entity model, EjbJar parent) {
57         super(ejbClassFO, model);
58         this.ejbClassFO= ejbClassFO;
59         this.model = model;
60         this.parent = parent;
61         modifiersPublicAbstract.add(Modifier.PUBLIC);
62         modifiersPublicAbstract.add(Modifier.ABSTRACT);
63     }
64
65     public Entity getModelCopy() {
66         return (Entity) model.clone();
67     }
68     
69     public List JavaDoc getMethods(CmpField field) {
70         return getMethods(field.getFieldName());
71     }
72
73     public List JavaDoc getMethods(CmrField field) {
74         return getMethods(field.getCmrFieldName());
75     }
76
77     public void deleteQueryMapping(ExecutableElement method, FileObject ddFileObject) throws IOException JavaDoc {
78         Query[] queries = model.getQuery();
79         for (Query query : queries) {
80             String JavaDoc methodName = query.getQueryMethod().getMethodName();
81             if (method.getSimpleName().contentEquals(methodName)) {
82                 model.removeQuery(query);
83                 parent.write(ddFileObject);
84                 return;
85             }
86         }
87     }
88
89     /**
90      * Deletes CMP field (consists of following subtasks):<br>
91      * <ul>
92      * <li>delete findBy<field> method from interfaces (it should be only in Home interfaces,
93      * but local and remote interfaces are checked also)</li>
94      * <li>delete get<field> and set<field> from local/remote/business interface</li>
95      * <li>delete get<field> and set<field> from main bean class</li>
96      * <li>delete findBy<field> query from deployment descriptor (ejb-jar.xml)</li>
97      * <li>delete CMP field from deployment descriptor (ejb-jar.xml)</li>
98      * </ul>
99      * @param field
100      * @param dd
101      * @throws java.io.IOException
102      */

103     public void deleteField(CmpField field, FileObject ddFileObject) throws IOException JavaDoc {
104         Query query = null;
105         String JavaDoc fieldName = field.getFieldName();
106         // find findBy<field> query in DD
107
query = findQueryByCmpField(fieldName);
108         // remove findBy<field> method from local interfaces (should be only in local home)
109
for (String JavaDoc clazz : getLocalInterfaces()) {
110             MethodModel method = getFinderMethod(clazz, fieldName, getGetterMethod(getBeanClass(), fieldName));
111             if (method != null) {
112                 removeMethodFromClass(clazz, method);
113             }
114         }
115         // remove findBy<field> method from remote interfaces (should be only in remote home)
116
for (String JavaDoc clazz : getRemoteInterfaces()) {
117             MethodModel method = getFinderMethod(clazz, fieldName, getGetterMethod(getBeanClass(), fieldName));
118             if (method != null) {
119                 removeMethodFromClass(clazz, method);
120             }
121         }
122         removeMethodsFromBean(getMethods(fieldName));
123         updateFieldAccessors(fieldName, false, false, false, false);
124         // remove findBy<field> query from DD
125
if (query != null) {
126             model.removeQuery(query);
127         }
128         // remove CMP field from DD
129
model.removeCmpField(field);
130         parent.write(ddFileObject);
131     }
132
133     private Query findQueryByCmpField(String JavaDoc fieldName) {
134         Query[] queries = model.getQuery();
135         for (Query query : queries) {
136             String JavaDoc queryMethodName = query.getQueryMethod().getMethodName();
137             if (prependAndUpper(fieldName, "findBy").equals(queryMethodName)) {
138                 return query;
139             }
140         }
141         return null;
142     }
143
144     private void removeMethodsFromBean(List JavaDoc<MethodModel> methods) throws IOException JavaDoc {
145         for (MethodModel method : methods) {
146             // remove get/set<field> from local/remote/business interfaces
147
if (hasLocal()) {
148                 ClassMethodPair classMethodPair = getInterface(method, true);
149                 if (classMethodPair != null) {
150                     removeMethodFromClass(classMethodPair.getClassName(), classMethodPair.getMethodModel());
151                 }
152             }
153             if (hasRemote()) {
154                 ClassMethodPair classMethodPair = getInterface(method, false);
155                 if (classMethodPair != null) {
156                     removeMethodFromClass(classMethodPair.getClassName(), classMethodPair.getMethodModel());
157                 }
158             }
159             // remove get/set<field> from main bean class
160
removeMethodFromClass(getBeanClass(), method);
161         }
162     }
163
164     /**
165      * Deletes CMP field (consists of following subtasks):<br>
166      * <ul>
167      * <li>delete get<field> and set<field> from local/remote/business interface</li>
168      * <li>delete get<field> and set<field> from main bean class</li>
169      * <li>delete relationship from deployment descriptor (ejb-jar.xml)</li>
170      * </ul>
171      * @param field
172      * @param dd
173      * @throws java.io.IOException
174      */

175     public void deleteField(CmrField field, FileObject ddFileObject) throws IOException JavaDoc {
176         List JavaDoc<MethodModel> methods = getMethods(field.getCmrFieldName());
177         removeMethodsFromBean(methods);
178         // remove relation from DD
179
deleteRelationships(field.getCmrFieldName());
180         parent.write(ddFileObject);
181     }
182
183     public static String JavaDoc getMethodName(String JavaDoc fieldName, boolean get) {
184         String JavaDoc prefix = get ? "get" : "set"; //NOI18N;
185
return prependAndUpper(fieldName, prefix);
186     }
187
188 // private static String getFieldName(String methodName) {
189
// if (methodName.length() < 3) {
190
// return null;
191
// }
192
// String prefix = methodName.substring(0, 3);
193
// if (prefix.equals("set") || prefix.equals("get")) {
194
// return lower(methodName.substring(3, methodName.length()));
195
// }
196
// return null;
197
// }
198

199     @Override JavaDoc
200     public boolean hasJavaImplementation(MethodModel intfView) {
201         return hasJavaImplementation(getMethodTypeFromInterface(intfView));
202     }
203
204     @Override JavaDoc
205     public boolean hasJavaImplementation(MethodType methodType) {
206         return !(isCMP() && (isFinder(methodType.getKind()) || isSelect(methodType.getKind())));
207     }
208
209     @Override JavaDoc
210     public MethodType getMethodTypeFromImpl(MethodModel implView) {
211         MethodType methodType = null;
212         if (implView.getName().startsWith("ejbCreate") || implView.getName().startsWith("ejbPostCreate")) { //NOI18N
213
methodType = new MethodType.CreateMethodType(implView);
214         } else if (!implView.getName().startsWith("ejb")) { //NOI18N
215
methodType = new MethodType.BusinessMethodType(implView);
216         } else if (implView.getName().startsWith("ejbFind")) { //NOI18N
217
methodType = new MethodType.FinderMethodType(implView);
218         } else if (implView.getName().startsWith("ejbHome")) { //NOI18N
219
methodType = new MethodType.HomeMethodType(implView);
220         }
221         return methodType;
222     }
223
224     @Override JavaDoc
225     public MethodType getMethodTypeFromInterface(MethodModel clientView) {
226         MethodType methodType;
227         if (findInClass(model.getLocalHome(), clientView) || findInClass(model.getHome(), clientView)) {
228             if (clientView.getName().startsWith("create")) { //NOI18N
229
methodType = new MethodType.CreateMethodType(clientView);
230             } else if (clientView.getName().startsWith("find")) { //NOI18N
231
methodType = new MethodType.FinderMethodType(clientView);
232             } else {
233                 methodType = new MethodType.HomeMethodType(clientView);
234             }
235         } else {
236             methodType = new MethodType.BusinessMethodType(clientView);
237         }
238         return methodType;
239     }
240
241     public AbstractMethodController.GenerateFromImpl createGenerateFromImpl() {
242         return new EntityGenerateFromImplVisitor();
243     }
244
245     public AbstractMethodController.GenerateFromIntf createGenerateFromIntf() {
246         return new EntityGenerateFromIntfVisitor(ejbClassFO, model);
247     }
248
249     public void addSelectMethod(MethodModel selectMethod, String JavaDoc ejbql, FileObject ddFileObject) throws IOException JavaDoc {
250         addMethodToClass(getBeanClass(), selectMethod);
251         addEjbQl(selectMethod, ejbql, ddFileObject);
252     }
253
254     public void addEjbQl(MethodModel clientView, String JavaDoc ejbql, FileObject ddFileObject) throws IOException JavaDoc {
255         if (isBMP()) {
256             super.addEjbQl(clientView, ejbql, ddFileObject);
257         }
258         model.addQuery(buildQuery(clientView, ejbql));
259         parent.write(ddFileObject);
260     }
261
262     public void addField(MethodModel.Variable field, FileObject ddFile, boolean localGetter, boolean localSetter,
263         boolean remoteGetter, boolean remoteSetter, String JavaDoc description) throws IOException JavaDoc {
264         String JavaDoc beanClass = getBeanClass();
265         addSetterMethod(beanClass, field, modifiersPublicAbstract, false, model);
266         addGetterMethod(beanClass, field, modifiersPublicAbstract, false, model);
267         final String JavaDoc fieldName = field.getName();
268         updateFieldAccessors(fieldName, localGetter, localSetter, remoteGetter, remoteSetter);
269         CmpField cmpField = model.newCmpField();
270         cmpField.setFieldName(field.getName());
271         cmpField.setDescription(description);
272         model.addCmpField(cmpField);
273         parent.write(ddFile);
274     }
275
276     private MethodModel addSetterMethod(String JavaDoc javaClass, MethodModel.Variable field, Set JavaDoc<Modifier> modifiers, boolean remote, Entity entity) {
277         MethodModel method = createSetterMethod(javaClass, field, modifiers, remote);
278         addMethod(javaClass, method, entity);
279         return method;
280     }
281
282     private MethodModel addGetterMethod(String JavaDoc javaClass, MethodModel.Variable variable, Set JavaDoc<Modifier> modifiers, boolean remote, Entity entity) {
283         MethodModel method = createGetterMethod(javaClass, variable, modifiers, remote);
284         addMethod(javaClass, method, entity);
285         return method;
286     }
287
288     private void addMethod(String JavaDoc javaClass, MethodModel method, Entity entity) {
289         // try to add method as the last CMP field getter/setter in class
290
try {
291             addMethodToClass(javaClass, method);
292         } catch (IOException JavaDoc e) {
293             ErrorManager.getDefault().notify(e);
294         }
295
296         //TODO: RETOUCHE insert into specified position
297
// List cmpFields = new ArrayList();
298
// CmpField[] cmpFieldArray = e.getCmpField();
299
// for (int i = 0; i < cmpFieldArray.length; i++) {
300
// cmpFields.add(cmpFieldArray[i].getFieldName());
301
// }
302
// int index = -1;
303
// for (Iterator it = javaClass.getContents().iterator(); it.hasNext();) {
304
// Object elem = (Object) it.next();
305
// if (elem instanceof Method) {
306
// String fieldName = getFieldName(((Method) elem).getName());
307
// if (cmpFields.contains(fieldName)) {
308
// index = javaClass.getContents().indexOf(elem);
309
//
310
// }
311
// }
312
// }
313
// if (index != -1) {
314
// javaClass.getContents().add(index + 1, method);
315
// } else {
316
// javaClass.getContents().add(method);
317
// }
318
}
319
320     private MethodModel createGetterMethod(String JavaDoc javaClass, MethodModel.Variable field, Set JavaDoc<Modifier> modifiers, boolean remote) {
321         final String JavaDoc fieldName = field.getName();
322         List JavaDoc<String JavaDoc> exceptions = remote ? Collections.<String JavaDoc>singletonList(RemoteException JavaDoc.class.getName()) : Collections.<String JavaDoc>emptyList();
323         MethodModel method = MethodModel.create(
324                 getMethodName(fieldName, true),
325                 "void",
326                 "",
327                 Collections.singletonList(field),
328                 exceptions,
329                 modifiers
330                 );
331         return method;
332     }
333
334     private MethodModel createSetterMethod(String JavaDoc javaClass, MethodModel.Variable field, Set JavaDoc<Modifier> modifiers, boolean remote) {
335         final String JavaDoc fieldName = field.getName();
336         List JavaDoc<String JavaDoc> exceptions = remote ? Collections.<String JavaDoc>singletonList(RemoteException JavaDoc.class.getName()) : Collections.<String JavaDoc>emptyList();
337         MethodModel method = MethodModel.create(
338                 getMethodName(fieldName, false),
339                 "void",
340                 "",
341                 Collections.singletonList(field),
342                 exceptions,
343                 modifiers
344                 );
345         return method;
346     }
347
348     private boolean isBMP() {
349         return Entity.PERSISTENCE_TYPE_BEAN.equals(model.getPersistenceType());
350     }
351
352     public boolean isCMP() {
353         return !isBMP();
354     }
355
356     private boolean isFinder(MethodType.Kind methodType) {
357         return methodType == MethodType.Kind.FINDER;
358     }
359
360     private boolean isSelect(MethodType.Kind methodType) {
361         return methodType == MethodType.Kind.SELECT;
362     }
363
364     public String JavaDoc createDefaultQL(MethodType methodType) {
365         String JavaDoc ejbql = null;
366         if (isFinder(methodType.getKind()) && isCMP()) {
367             ejbql = "SELECT OBJECT(o) \nFROM " + model.getAbstractSchemaName() + " o";
368         }
369
370         if (isSelect(methodType.getKind())) {
371             ejbql = "SELECT COUNT(o) \nFROM " + model.getAbstractSchemaName() + " o";
372         }
373
374         return ejbql;
375     }
376
377     private Query buildQuery(MethodModel clientView, String JavaDoc ejbql) {
378         Query query = model.newQuery();
379         QueryMethod queryMethod = query.newQueryMethod();
380         queryMethod.setMethodName(clientView.getName());
381         MethodParams mParams = queryMethod.newMethodParams();
382         for (MethodModel.Variable parameter : clientView.getParameters()) {
383             mParams.addMethodParam(parameter.getType());
384         }
385         queryMethod.setMethodParams(mParams);
386         query.setQueryMethod(queryMethod);
387         query.setEjbQl(ejbql);
388         return query;
389     }
390
391     private static String JavaDoc prependAndUpper(String JavaDoc fullName, String JavaDoc prefix) {
392         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(fullName);
393         buffer.setCharAt(0, Character.toUpperCase(buffer.charAt(0)));
394         return prefix+buffer.toString();
395     }
396
397 // private static String lower(String fullName) {
398
// StringBuffer buffer = new StringBuffer(fullName);
399
// buffer.setCharAt(0, Character.toLowerCase(buffer.charAt(0)));
400
// return buffer.toString();
401
// }
402

403     private boolean isEjbUsed(EjbRelationshipRole role, String JavaDoc ejbName, String JavaDoc fieldName) {
404         return role != null &&
405                role.getRelationshipRoleSource() != null &&
406                ejbName.equals(role.getRelationshipRoleSource().getEjbName()) &&
407                fieldName.equals(role.getCmrField().getCmrFieldName());
408     }
409
410     private boolean relationContainsField(EjbRelation relation, String JavaDoc ejbName, String JavaDoc fieldName) {
411         return
412             isEjbUsed(relation.getEjbRelationshipRole(), ejbName, fieldName) ||
413                isEjbUsed(relation.getEjbRelationshipRole2(), ejbName, fieldName);
414     }
415
416     private void deleteRelationships(String JavaDoc fieldName) {
417         String JavaDoc ejbName = model.getEjbName();
418         Relationships relationships = parent.getSingleRelationships();
419         if (relationships != null) {
420             EjbRelation[] relations = relationships.getEjbRelation();
421             if (relations != null) {
422                 for (int i = 0; i < relations.length; i++) {
423                     if (relationContainsField(relations[i], ejbName, fieldName)) {
424                         boolean uniDirectional = false;
425                         EjbRelationshipRole role = relations[i].getEjbRelationshipRole();
426                         if (isEjbUsed(role, ejbName, fieldName)) {
427                             role.setCmrField(null);
428                         } else {
429                             uniDirectional = role.getCmrField()==null;
430                         }
431                         role = relations[i].getEjbRelationshipRole2();
432                         if (isEjbUsed(role, ejbName, fieldName)) {
433                             role.setCmrField(null);
434                         } else {
435                             uniDirectional = role.getCmrField()==null;
436                         }
437                         if (uniDirectional) {
438                             relationships.removeEjbRelation(relations[i]);
439                         }
440                     }
441                 }
442                 if (relationships.sizeEjbRelation() == 0) {
443                     parent.setRelationships(null);
444                 }
445             }
446         }
447     }
448
449     private List JavaDoc<MethodModel> getMethods(String JavaDoc propName) {
450         assert propName != null;
451         List JavaDoc<MethodModel> resultList = new LinkedList JavaDoc<MethodModel>();
452         String JavaDoc ejbClass = getBeanClass();
453         MethodModel getMethod = getGetterMethod(ejbClass, propName);
454         if (getMethod != null) {
455             resultList.add(getMethod);
456             MethodModel setMethod = getSetterMethod(ejbClass, propName, getMethod.getReturnType());
457             if (setMethod != null) {
458                 resultList.add(setMethod);
459             }
460         }
461         return resultList;
462     }
463
464     public MethodModel getGetterMethod(String JavaDoc javaClass, String JavaDoc fieldName) {
465         if (javaClass == null || fieldName == null) {
466             return null;
467         }
468         MethodModel method = MethodModel.create(
469                 getMethodName(fieldName, true),
470                 "void",
471                 "",
472                 Collections.<MethodModel.Variable>emptyList(),
473                 Collections.<String JavaDoc>emptyList(),
474                 Collections.<Modifier>emptySet()
475                 );
476         return findInClass(javaClass, method) ? method : null;
477     }
478
479     public MethodModel getGetterMethod(String JavaDoc fieldName, boolean local) {
480         return getGetterMethod(getBeanInterface(local, true), fieldName);
481     }
482
483     public MethodModel getSetterMethod(String JavaDoc classElement, String JavaDoc fieldName, String JavaDoc type) {
484         if (classElement == null) {
485             return null;
486         }
487         if (type == null) {
488             return null;
489         }
490         MethodModel method = MethodModel.create(
491                 getMethodName(fieldName, true),
492                 "void",
493                 "",
494                 Collections.singletonList(MethodModel.Variable.create(type, "arg0")),
495                 Collections.<String JavaDoc>emptyList(),
496                 Collections.<Modifier>emptySet()
497                 );
498         return findInClass(classElement, method) ? method : null;
499     }
500
501     public MethodModel getSetterMethod(String JavaDoc fieldName, boolean local) {
502         MethodModel.Variable field = getField(fieldName, true);
503         if (field == null) {
504             return null;
505         } else {
506             return getSetterMethod(getBeanInterface(local, true), fieldName, field.getType());
507         }
508     }
509
510     /**
511      * Tries to find finder method for given CMP field
512      * @param classElement class to look in
513      * @param fieldName field for which we want to get the finder
514      * @param getterMethod getter method for field, it is used for detection of field type
515      * @return found method which conforms to: findBy<field>, null if method was not found
516      */

517     public MethodModel getFinderMethod(String JavaDoc classElement, String JavaDoc fieldName, MethodModel getterMethod) {
518         if (getterMethod == null) {
519             return null;
520         }
521         MethodModel method = MethodModel.create(
522                 getMethodName(fieldName, true),
523                 "void",
524                 "",
525                 Collections.singletonList(MethodModel.Variable.create(getterMethod.getReturnType(), "arg0")),
526                 Collections.<String JavaDoc>emptyList(),
527                 Collections.<Modifier>emptySet()
528                 );
529         return findInClass(classElement, method) ? method : null;
530     }
531
532     @Override JavaDoc
533     public boolean supportsMethodType(MethodType.Kind methodType) {
534         return !isSelect(methodType) || isCMP();
535     }
536
537     private void updateFieldAccessors(String JavaDoc fieldName, boolean localGetter, boolean localSetter, boolean remoteGetter,
538             boolean remoteSetter) {
539         updateFieldAccessor(fieldName, true, true, localGetter);
540         updateFieldAccessor(fieldName, false, true, localSetter);
541         updateFieldAccessor(fieldName, true, false, remoteGetter);
542         updateFieldAccessor(fieldName, false, false, remoteSetter);
543     }
544
545     public void updateFieldAccessor(String JavaDoc fieldName, boolean getter, boolean local, boolean shouldExist) {
546         MethodModel.Variable field = getField(fieldName, true);
547         if (field == null) {
548             return;
549         }
550         String JavaDoc businessInterface = getBeanInterface(local, true);
551         if (businessInterface != null) {
552             MethodModel method;
553             if (getter) {
554                 method = getGetterMethod(businessInterface, fieldName);
555             } else {
556                 method = getSetterMethod(businessInterface, fieldName, field.getType());
557             }
558             if (shouldExist) {
559                 if (method == null) {
560                     if (getter) {
561                         addGetterMethod(businessInterface, field, Collections.<Modifier>emptySet(), !local, model);
562                     } else {
563                         addSetterMethod(businessInterface, field, Collections.<Modifier>emptySet(), !local, model);
564                     }
565                 }
566             } else if (method != null) {
567                 try {
568                     removeMethodFromClass(businessInterface, method);
569                 } catch (IOException JavaDoc e) {
570                     ErrorManager.getDefault().notify(e);
571                 }
572
573             }
574         }
575     }
576
577     private MethodModel.Variable getField(String JavaDoc fieldName, boolean create) {
578         String JavaDoc beanClass = getBeanClass();
579         MethodModel getterMethod = getGetterMethod(beanClass, fieldName);
580         if (getterMethod == null) {
581             if (!create) {
582                 return null;
583             }
584             MethodModel.Variable field = MethodModel.Variable.create(String JavaDoc.class.getName(), fieldName);
585             createGetterMethod(getBeanClass(), field, modifiersPublicAbstract, false);
586             return field;
587         } else {
588             String JavaDoc type = getterMethod.getReturnType();
589             if (type == null) {
590                 return null;
591             } else {
592                 return MethodModel.Variable.create(type, fieldName);
593             }
594         }
595     }
596
597 }
598
Popular Tags