KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > beehive > netui > compiler > genmodel > GenStrutsApp


1 /*
2  * Copyright 2004 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * $Header:$
17  */

18 package org.apache.beehive.netui.compiler.genmodel;
19
20 import org.apache.beehive.netui.compiler.CompilerUtils;
21 import org.apache.beehive.netui.compiler.FlowControllerInfo;
22 import org.apache.beehive.netui.compiler.JpfLanguageConstants;
23 import org.apache.beehive.netui.compiler.MergedControllerAnnotation;
24 import org.apache.beehive.netui.compiler.Diagnostics;
25 import org.apache.beehive.netui.compiler.FatalCompileTimeException;
26 import org.apache.beehive.netui.compiler.model.ActionModel;
27 import org.apache.beehive.netui.compiler.model.FormBeanModel;
28 import org.apache.beehive.netui.compiler.model.ForwardModel;
29 import org.apache.beehive.netui.compiler.model.MessageResourcesModel;
30 import org.apache.beehive.netui.compiler.model.StrutsApp;
31 import org.apache.beehive.netui.compiler.typesystem.declaration.AnnotationInstance;
32 import org.apache.beehive.netui.compiler.typesystem.declaration.ClassDeclaration;
33 import org.apache.beehive.netui.compiler.typesystem.declaration.MethodDeclaration;
34 import org.apache.beehive.netui.compiler.typesystem.declaration.Modifier;
35 import org.apache.beehive.netui.compiler.typesystem.declaration.PackageDeclaration;
36 import org.apache.beehive.netui.compiler.typesystem.declaration.ParameterDeclaration;
37 import org.apache.beehive.netui.compiler.typesystem.declaration.TypeDeclaration;
38 import org.apache.beehive.netui.compiler.typesystem.env.AnnotationProcessorEnvironment;
39 import org.apache.beehive.netui.compiler.typesystem.type.DeclaredType;
40 import org.apache.beehive.netui.compiler.typesystem.type.TypeInstance;
41 import org.apache.beehive.netui.compiler.typesystem.type.ClassType;
42 import org.apache.xmlbeans.XmlCursor;
43 import org.apache.xmlbeans.XmlException;
44 import org.apache.xmlbeans.XmlObject;
45
46 import java.io.File JavaDoc;
47 import java.io.FileNotFoundException JavaDoc;
48 import java.io.FileOutputStream JavaDoc;
49 import java.io.IOException JavaDoc;
50 import java.io.PrintStream JavaDoc;
51 import java.util.ArrayList JavaDoc;
52 import java.util.Collection JavaDoc;
53 import java.util.Date JavaDoc;
54 import java.util.Iterator JavaDoc;
55 import java.util.List JavaDoc;
56
57
58 public class GenStrutsApp
59         extends StrutsApp
60         implements JpfLanguageConstants
61 {
62     private ClassDeclaration _jclass;
63     private String JavaDoc _containingPackage;
64     private File JavaDoc _strutsConfigFile;
65     private File JavaDoc _sourceFile;
66     private AnnotationProcessorEnvironment _env;
67     private FlowControllerInfo _fcInfo;
68     private Diagnostics _diagnostics;
69     
70     protected void recalculateStrutsConfigFile()
71         throws XmlException, IOException JavaDoc, FatalCompileTimeException
72     {
73         _strutsConfigFile = calculateStrutsConfigFile(); // caching this
74
}
75
76     FlowControllerInfo getFlowControllerInfo()
77     {
78         return _fcInfo;
79     }
80
81     public GenStrutsApp( File JavaDoc sourceFile, ClassDeclaration jclass, AnnotationProcessorEnvironment env,
82                          FlowControllerInfo fcInfo, boolean checkOnly, Diagnostics diagnostics )
83         throws XmlException, IOException JavaDoc, FatalCompileTimeException
84     {
85         super( jclass.getQualifiedName() );
86         
87         _jclass = jclass;
88         _containingPackage = jclass.getPackage().getQualifiedName();
89         _sourceFile = sourceFile;
90         _env = env;
91         assert fcInfo != null;
92         _fcInfo = fcInfo;
93         _diagnostics = diagnostics;
94         
95         recalculateStrutsConfigFile();
96         
97         if ( checkOnly ) return;
98         
99         if ( _jclass != null )
100         {
101             MergedControllerAnnotation mca = fcInfo.getMergedControllerAnnotation();
102             setNestedPageFlow( mca.isNested() );
103             setLongLivedPageFlow( mca.isLongLived() );
104             addMessageResources( mca.getMessageResources() ); // messageResources is deprecated
105
addMessageBundles( mca.getMessageBundles() ); // messageBundles is not
106
addSimpleActions( mca.getSimpleActions() );
107             setMultipartHandler( mca.getMultipartHandler() );
108             GenForwardModel.addForwards( mca.getForwards(), this, _jclass, this, null );
109             
110             // TODO: comment
111
addForward( new ForwardModel( "_auto", "", this ) );
112             
113             GenExceptionModel.addCatches( mca.getCatches(), this, _jclass, this, this );
114             addTilesDefinitionsConfigs( mca.getTilesDefinitionsConfigs() );
115             setAdditionalValidatorConfigs( mca.getCustomValidatorConfigs() );
116
117             addActionMethods();
118             addFormBeans( _jclass );
119         }
120         
121         if ( fcInfo != null )
122         {
123             setSharedFlows( fcInfo.getSharedFlowTypeNames() );
124             setReturnToActionDisabled( ! fcInfo.isNavigateToActionEnabled() );
125             setReturnToPageDisabled( ! fcInfo.isNavigateToPageEnabled() );
126         }
127     }
128     
129     private void addFormBeans( ClassDeclaration jclass )
130     {
131         Collection JavaDoc innerTypes = CompilerUtils.getClassNestedTypes( jclass );
132         
133         for ( Iterator JavaDoc ii = innerTypes.iterator(); ii.hasNext(); )
134         {
135             TypeDeclaration innerType = ( TypeDeclaration ) ii.next();
136             if ( innerType instanceof ClassDeclaration )
137             {
138                 ClassDeclaration innerClass = ( ClassDeclaration ) innerType;
139                 
140                 if ( innerType.hasModifier( Modifier.PUBLIC )
141                      && CompilerUtils.isAssignableFrom( PAGEFLOW_FORM_CLASS_NAME, innerClass, _env ) )
142                 {
143                     addFormBean( innerClass, null );
144                 }
145             }
146         }
147         
148     }
149     
150     String JavaDoc addFormBean( TypeDeclaration formType, ActionModel usedByAction )
151     {
152         String JavaDoc formClass = CompilerUtils.getFormClassName( formType, _env );
153
154         //
155
// Use the actual type of form to create the name.
156
// This avoids conflicts if there are multiple forms using the
157
// ANY_FORM_CLASS_NAME type.
158
//
159
String JavaDoc actualType = CompilerUtils.getLoadableName( formType );
160
161         //
162
// See if the app already has a form-bean of this type. If so,
163
// we'll just use it; otherwise, we need to create it.
164
//
165
boolean usesPageFlowScopedFormBean = usedByAction != null ? usedByAction.getFormMember() != null : false;
166         List JavaDoc existingBeans = getFormBeansByActualType( actualType, Boolean.valueOf( usesPageFlowScopedFormBean ) );
167         String JavaDoc formBeanName;
168
169         if ( existingBeans != null )
170         {
171             assert existingBeans.size() > 0;
172             formBeanName = ( ( FormBeanModel ) existingBeans.get( 0 ) ).getName();
173         }
174         else
175         {
176             formBeanName = getFormNameForType( actualType, usesPageFlowScopedFormBean );
177             addFormBean( new FormBeanModel( formBeanName, formClass, actualType, usesPageFlowScopedFormBean, this ) );
178             getMessageResourcesFromForm( formType, usedByAction );
179         }
180         
181         return formBeanName;
182     }
183     
184     private void addMessageResources( Collection JavaDoc messageResources )
185     {
186         if ( messageResources != null )
187         {
188             for ( Iterator JavaDoc ii = messageResources.iterator(); ii.hasNext(); )
189             {
190                 AnnotationInstance ann = ( AnnotationInstance ) ii.next();
191                 addMessageResources( new GenMessageBundleModel( this, ann ) );
192             }
193         }
194     }
195     
196     private void addMessageBundles( Collection JavaDoc messageBundles )
197     {
198         if ( messageBundles != null )
199         {
200             for ( Iterator JavaDoc ii = messageBundles.iterator(); ii.hasNext(); )
201             {
202                 AnnotationInstance ann = ( AnnotationInstance ) ii.next();
203                 addMessageResources( new GenMessageBundleModel( this, ann ) );
204             }
205         }
206     }
207     
208     private void addSimpleActions( Collection JavaDoc simpleActionAnnotations )
209     {
210         if ( simpleActionAnnotations != null )
211         {
212             for ( Iterator JavaDoc ii = simpleActionAnnotations.iterator(); ii.hasNext(); )
213             {
214                 AnnotationInstance ann = ( AnnotationInstance ) ii.next();
215                 addActionMapping( new GenSimpleActionModel( ann, this, _jclass ) );
216             }
217         }
218     }
219     
220     private void setMultipartHandler( String JavaDoc mpHandler )
221     {
222         if ( mpHandler != null )
223         {
224             if ( mpHandler.equals( MULTIPART_HANDLER_MEMORY_STR ) )
225             {
226                 setMultipartHandlerClassName( MULTIPART_HANDLER_MEMORY_CLASSNAME );
227             }
228             else if ( mpHandler.equals( MULTIPART_HANDLER_DISK_STR ) )
229             {
230                 setMultipartHandlerClassName( MULTIPART_HANDLER_DISK_CLASSNAME );
231             }
232             else
233             {
234                 assert mpHandler.equals( MULTIPART_HANDLER_DISABLED_STR );
235                 setMultipartHandlerClassName( "none" );
236             }
237         }
238     }
239     
240     private void addTilesDefinitionsConfigs( List JavaDoc tilesDefinitionsConfigs )
241     {
242         if ( tilesDefinitionsConfigs == null || tilesDefinitionsConfigs.isEmpty() )
243         {
244             return;
245         }
246
247         List JavaDoc paths = new ArrayList JavaDoc();
248
249         for ( Iterator JavaDoc ii = tilesDefinitionsConfigs.iterator(); ii.hasNext(); )
250         {
251             String JavaDoc definitionsConfig = ( String JavaDoc ) ii.next();
252
253             if ( definitionsConfig != null && definitionsConfig.length() > 0 )
254             {
255                 paths.add( definitionsConfig );
256             }
257         }
258
259         setTilesDefinitionsConfigs( paths );
260     }
261
262     private void addActionMethods()
263     {
264         MethodDeclaration[] actionMethods = CompilerUtils.getClassMethods( _jclass, ACTION_TAG_NAME );
265         
266         for ( int i = 0; i < actionMethods.length; i++ )
267         {
268             MethodDeclaration actionMethod = actionMethods[i];
269             
270             if ( ! actionMethod.hasModifier( Modifier.ABSTRACT ) )
271             {
272                 ActionModel actionModel = new GenActionModel( actionMethod, this, _jclass );
273                 addActionMapping( actionModel );
274                 ParameterDeclaration[] params = actionMethod.getParameters();
275                 
276                 if ( params.length > 0 )
277                 {
278                     ParameterDeclaration param1 = params[0];
279                     TypeInstance paramType = param1.getType();
280                     
281                     if ( paramType instanceof DeclaredType )
282                     {
283                         getMessageResourcesFromForm( CompilerUtils.getDeclaration( ( DeclaredType ) paramType ), actionModel );
284                     }
285                 }
286             }
287         }
288     }
289     
290     private void getMessageResourcesFromForm( TypeDeclaration formTypeDecl, ActionModel actionModel )
291     {
292         if ( ! ( formTypeDecl instanceof ClassDeclaration ) ) return;
293         
294         ClassDeclaration formClassDecl = ( ClassDeclaration ) formTypeDecl;
295         
296         while ( true )
297         {
298             AnnotationInstance ann = CompilerUtils.getAnnotation( formClassDecl, FORM_BEAN_TAG_NAME );
299             
300             if ( ann != null )
301             {
302                 String JavaDoc defaultMessageResources = CompilerUtils.getString( ann, MESSAGE_BUNDLE_ATTR, true );
303                 
304                 if ( defaultMessageResources != null )
305                 {
306                     for ( Iterator JavaDoc ii = getMessageResourcesList().iterator(); ii.hasNext(); )
307                     {
308                         MessageResourcesModel i = ( MessageResourcesModel ) ii.next();
309                         if ( i.getParameter().equals( defaultMessageResources ) ) return;
310                     }
311                     
312                     MessageResourcesModel mrm = new MessageResourcesModel( this );
313                     String JavaDoc key = "formMessages:" + CompilerUtils.getLoadableName( formClassDecl );
314                     mrm.setKey( key );
315                     mrm.setParameter( defaultMessageResources );
316                     mrm.setReturnNull( true );
317                     addMessageResources( mrm );
318                     if ( actionModel != null ) actionModel.setFormBeanMessageResourcesKey( key );
319                 }
320             }
321             
322             ClassType superType = formClassDecl.getSuperclass();
323             if ( superType == null ) break;
324             formClassDecl = superType.getClassTypeDeclaration();
325         }
326     }
327     
328     protected String JavaDoc getMergeFileName()
329     {
330         return getFlowControllerInfo().getMergedControllerAnnotation().getStrutsMerge();
331     }
332     
333     public void writeToFile()
334         throws FileNotFoundException JavaDoc, IOException JavaDoc, XmlException, FatalCompileTimeException
335     {
336         writeToFile( getMergeFile( getMergeFileName() ) );
337     }
338     
339     public boolean isStale()
340             throws FatalCompileTimeException
341     {
342         return isStale( getMergeFile( getMergeFileName() ) );
343     }
344     
345     protected boolean isModuleDeclaredInWebXml()
346     {
347         // Only the root page flow (which generates a module for path "/") is declared in web.xml
348
PackageDeclaration pkg = _jclass.getPackage();
349         return ! isSharedFlow() && pkg == null || pkg.getQualifiedName().length() == 0;
350     }
351     
352     String JavaDoc getOutputFileURI( String JavaDoc filePrefix )
353     {
354         return getOutputFileURI( filePrefix, _containingPackage, false );
355     }
356     
357     String JavaDoc getStrutsConfigURI()
358     {
359         return getStrutsConfigURI( _containingPackage, false );
360     }
361
362     protected String JavaDoc getContainingPackage()
363     {
364         return _containingPackage;
365     }
366     
367     private File JavaDoc calculateStrutsConfigFile()
368         throws XmlException, IOException JavaDoc, FatalCompileTimeException
369     {
370         String JavaDoc webappBuildRoot = CompilerUtils.getWebBuildRoot( getEnv() );
371         File JavaDoc strutsConfigFile = new File JavaDoc( webappBuildRoot + getStrutsConfigURI() );
372         
373         //
374
// For the root Controller.jpf and for Global.app, we have to look in web.xml to get the output location.
375
// See the comment on getAlternateLocation for a rationale...
376
//
377
if ( isModuleDeclaredInWebXml() )
378         {
379             String JavaDoc alternateLocation = getAlternateLocation( strutsConfigFile );
380             if ( alternateLocation != null ) return new File JavaDoc( webappBuildRoot + alternateLocation );
381         }
382         
383         return strutsConfigFile;
384     }
385     
386     /**
387      * Tell whether the struts output file (jpf-struts-config-*.xml) is out of date, based on the
388      * file times of the source file and the (optional) struts-merge file.
389      */

390     public boolean isStale( File JavaDoc mergeFile )
391     {
392         //
393
// We always write the root-level JPF and Global.app, because the struts XML
394
// config files for these modules are provided by default, and may be out of
395
// date, even if the file modification times don't indicate that this is true.
396
//
397
if ( isModuleDeclaredInWebXml() )
398         {
399             return true;
400         }
401         
402         //
403
// We can write to the file if it doesn't exist yet.
404
//
405
if ( ! _strutsConfigFile.exists() )
406         {
407             return true;
408         }
409         
410         long lastWrite = _strutsConfigFile.lastModified();
411         
412         if ( mergeFile != null && mergeFile.exists() && mergeFile.lastModified() > lastWrite )
413         {
414             return true;
415         }
416         
417         if ( _sourceFile.lastModified() > lastWrite )
418         {
419             return true;
420         }
421         
422         return false;
423     }
424     /**
425      * In some cases, canWrite() does not guarantee that a FileNotFoundException will not
426      * be thrown when trying to write to a file. This method actually tries to overwrite
427      * the file as a test to see whether it's possible.
428      */

429     public boolean canWrite()
430     {
431         if ( ! _strutsConfigFile.canWrite() )
432         {
433             return false;
434         }
435         
436         try
437         {
438             //
439
// This appears to be the only way to predict whether the file can actually be
440
// written to; it may be that canWrite() returns true, but the file permissions
441
// (NTFS only?) will cause an exception to be thrown.
442
//
443
new FileOutputStream JavaDoc( _strutsConfigFile, true ).close();
444         }
445         catch ( FileNotFoundException JavaDoc e )
446         {
447             return false;
448         }
449         catch ( IOException JavaDoc e )
450         {
451             return false;
452         }
453         
454         return true;
455     }
456         
457     
458     public void writeToFile( File JavaDoc strutsMergeFile )
459         throws FileNotFoundException JavaDoc, IOException JavaDoc, XmlException, FatalCompileTimeException
460     {
461         _strutsConfigFile.getParentFile().mkdirs();
462         PrintStream JavaDoc out = new PrintStream JavaDoc( new FileOutputStream JavaDoc( _strutsConfigFile ) );
463         writeXml( out, strutsMergeFile, CompilerUtils.getWebBuildRoot( getEnv() ) );
464         out.close();
465     }
466     
467     public File JavaDoc getStrutsConfigFile()
468     {
469         return _strutsConfigFile;
470     }
471     
472     private static boolean isAtElement( XmlCursor curs, String JavaDoc localName )
473     {
474         return curs.getName().getLocalPart().equals( localName );
475     }
476     
477     /**
478      * Two special files, the module configs for the root module and "-global", are registered in
479      * web.xml explicitly. If the user is pointing to an alternate (e.g., old) location for these
480      * files, we need to compile to that location.
481      */

482     private String JavaDoc getAlternateLocation( File JavaDoc strutsConfigFile )
483         throws XmlException, IOException JavaDoc, FatalCompileTimeException
484     {
485         String JavaDoc webappContentRoot = CompilerUtils.getWebContentRoot( getEnv() );
486         File JavaDoc webXmlFile = new File JavaDoc( webappContentRoot + '/' + StrutsApp.WEBINF_DIR_NAME + "/web.xml" );
487         
488         if ( ! webXmlFile.canRead() )
489         {
490             _diagnostics.addWarning( _jclass, "warning.could-not-read-web-xml", webappContentRoot );
491             return null;
492         }
493         
494         String JavaDoc strutsConfigFileName = strutsConfigFile.getName();
495         
496         //
497
// We're going to parse web.xml in a "loose" way, so we can accept both the Servlet 2.3 and Servlet 2.4 versions
498
// (and beyond) of the schema. We're looking for this fragment:
499
//
500
// <servlet-name>action</servlet-name>
501
// <init-param>
502
// <param-name>config</param-name>
503
// <param-value>/WEB-INF/.pageflow-struts-generated/jpf-struts-config.xml</param-value>
504
// </init-param>
505
// ...
506
//
507
// If this refers to the basename of our generated struts-config file, we'll use it to determine (override) the
508
// location of the output file.
509
//
510
XmlObject webXmlDoc = XmlObject.Factory.parse( webXmlFile );
511         XmlCursor curs = webXmlDoc.newCursor();
512         if ( curs.toFirstChild() && curs.toFirstChild() )
513         {
514             do
515             {
516                 if ( isAtElement( curs, "servlet" ) )
517                 {
518                     XmlCursor i = curs.newCursor();
519                     i.toFirstChild();
520                     do
521                     {
522                         if ( isAtElement( i, "servlet-name" ) && i.getTextValue().equals( "action" ) )
523                         {
524                             XmlCursor j = curs.newCursor();
525                             j.toFirstChild();
526                             
527                             do
528                             {
529                                 if ( isAtElement( j, "init-param" ) )
530                                 {
531                                     XmlCursor k = j.newCursor();
532                                     k.toFirstChild();
533                                     boolean isConfig = false;
534                                     String JavaDoc alternateLocation = null;
535                                     
536                                     do
537                                     {
538                                         if ( isAtElement( k, "param-name" ) && k.getTextValue().startsWith( "config" ) )
539                                         {
540                                             isConfig = true;
541                                         }
542                                         else if ( isAtElement( k, "param-value" ) )
543                                         {
544                                             alternateLocation =
545                                                 parseAlternateLocation( k.getTextValue(), strutsConfigFileName );
546                                         }
547                                     } while ( k.toNextSibling() );
548                                     
549                                     if ( isConfig && alternateLocation != null )
550                                     {
551                                         return alternateLocation;
552                                     }
553                                 }
554                             } while ( j.toNextSibling() );
555                             
556                             //
557
// We found the action servlet, but no init-param gave an alternate location for our
558
// struts-config output file.
559
//
560
return null;
561                         }
562                     } while ( i.toNextSibling() );
563                 }
564             } while ( curs.toNextSibling() );
565         }
566         
567         return null;
568     }
569     
570     private static String JavaDoc parseAlternateLocation( String JavaDoc paramValue, String JavaDoc strutsConfigFileName )
571     {
572         //
573
// If the referenced struts-config file has the same name as the file
574
// we're going to generate, use the referenced file (its location may be
575
// different than our default location).
576
//
577
if ( paramValue.indexOf( strutsConfigFileName ) != -1 )
578         {
579             //
580
// This may be a comma-separated list of files. Find the right one.
581
//
582
if ( paramValue.indexOf( "," ) != -1 )
583             {
584                 String JavaDoc[] files = paramValue.split( "," );
585                 for ( int k = 0; k < files.length; ++k )
586                 {
587                     if ( files[k].indexOf( strutsConfigFileName ) != -1 )
588                     {
589                         return files[k].trim();
590                     }
591                 }
592             }
593             else
594             {
595                 return paramValue;
596             }
597         }
598         
599         return null;
600     }
601     
602     public File JavaDoc getMergeFile( String JavaDoc mergeFileName )
603         throws FatalCompileTimeException
604     {
605         if ( mergeFileName != null )
606         {
607             return CompilerUtils.getFileRelativeToSourceFile( _jclass, mergeFileName, getEnv() );
608         }
609         
610         return null;
611     }
612
613     protected String JavaDoc getHeaderComment( File JavaDoc mergeFile )
614         throws FatalCompileTimeException
615     {
616         StringBuffer JavaDoc comment = new StringBuffer JavaDoc( " Generated from " );
617         comment.append( getWebappRelativePath( _sourceFile ) );
618         if ( mergeFile != null )
619         {
620             comment.append( " and " ).append( getWebappRelativePath( mergeFile ) );
621         }
622         comment.append( " on " ).append( new Date JavaDoc().toString() ).append( ' ' );
623         return comment.toString();
624     }
625     
626     private String JavaDoc getWebappRelativePath( File JavaDoc file )
627         throws FatalCompileTimeException
628     {
629         String JavaDoc filePath = file.getAbsoluteFile().getPath();
630         String JavaDoc[] sourceRoots = CompilerUtils.getWebSourceRoots( _env );
631         
632         //
633
// Look through the source roots.
634
//
635
for ( int i = 0; i < sourceRoots.length; i++ )
636         {
637             String JavaDoc sourceRoot = sourceRoots[i];
638             
639             if ( filePath.startsWith( sourceRoot ) )
640             {
641                 return file.toString().substring( sourceRoot.length() ).replace( '\\', '/' );
642             }
643         }
644         
645         //
646
// Look in the web content root.
647
//
648
String JavaDoc webContentRoot = CompilerUtils.getWebContentRoot( getEnv() );
649         
650         if ( filePath.startsWith( webContentRoot ) )
651         {
652             return file.toString().substring( webContentRoot.length() ).replace( '\\', '/' );
653         }
654         
655         assert false : "could not calculate webapp-relative file from " + file;
656         return file.toString();
657     }
658     
659     AnnotationProcessorEnvironment getEnv()
660     {
661         return _env;
662     }
663 }
664
Popular Tags