001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.websvc.core;
043:
044: import com.sun.source.tree.AnnotationTree;
045: import com.sun.source.tree.AssignmentTree;
046: import com.sun.source.tree.BlockTree;
047: import com.sun.source.tree.ClassTree;
048: import com.sun.source.tree.ExpressionTree;
049: import com.sun.source.tree.MethodTree;
050: import com.sun.source.tree.ModifiersTree;
051: import com.sun.source.tree.PrimitiveTypeTree;
052: import com.sun.source.tree.Tree;
053: import com.sun.source.tree.Tree.Kind;
054: import com.sun.source.tree.VariableTree;
055: import java.io.IOException;
056: import java.net.URL;
057: import java.util.ArrayList;
058: import java.util.Collection;
059: import java.util.Collections;
060: import java.util.HashSet;
061: import java.util.List;
062: import java.util.Map;
063: import java.util.Set;
064: import javax.lang.model.element.AnnotationMirror;
065: import javax.lang.model.element.AnnotationValue;
066: import javax.lang.model.element.Element;
067: import javax.lang.model.element.ElementKind;
068: import javax.lang.model.element.ExecutableElement;
069: import javax.lang.model.element.Modifier;
070: import javax.lang.model.element.TypeElement;
071: import javax.lang.model.element.VariableElement;
072: import javax.lang.model.type.TypeKind;
073: import javax.lang.model.util.ElementFilter;
074: import javax.swing.SwingUtilities;
075: import org.netbeans.api.java.classpath.ClassPath;
076: import org.netbeans.api.java.source.CancellableTask;
077: import org.netbeans.api.java.source.ClasspathInfo;
078: import org.netbeans.api.java.source.Comment;
079: import org.netbeans.api.java.source.Comment.Style;
080: import org.netbeans.api.java.source.CompilationController;
081: import org.netbeans.api.java.source.JavaSource;
082: import org.netbeans.api.java.source.TreeMaker;
083: import org.netbeans.modules.j2ee.common.method.MethodModelSupport;
084: import org.netbeans.modules.websvc.api.support.java.GenerationUtils;
085: import org.netbeans.modules.websvc.api.support.java.SourceUtils;
086: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
087: import org.openide.cookies.SaveCookie;
088: import org.openide.loaders.DataObject;
089: import org.openide.util.NbBundle;
090: import org.openide.util.RequestProcessor;
091: import static org.netbeans.api.java.source.JavaSource.Phase;
092: import org.netbeans.api.java.source.WorkingCopy;
093: import org.netbeans.api.progress.ProgressHandle;
094: import org.netbeans.api.progress.ProgressHandleFactory;
095: import org.netbeans.modules.j2ee.api.ejbjar.EjbJar;
096: import org.netbeans.modules.j2ee.common.method.MethodCustomizer;
097: import org.netbeans.modules.j2ee.common.method.MethodCustomizerFactory;
098: import org.netbeans.modules.j2ee.common.method.MethodModel;
099: import org.openide.ErrorManager;
100: import org.openide.filesystems.FileObject;
101:
102: /**
103: * Helper for adding WS Operation to Web Service.
104: * @author Milan Kuchtiak
105: */
106: public class AddWsOperationHelper {
107: private static final ClassPath EMPTY_PATH = ClassPathSupport
108: .createClassPath(new URL[0]);
109:
110: private final String name;
111: private final boolean createAnnotations;
112: private MethodModel method;
113:
114: public AddWsOperationHelper(String name, boolean flag) {
115: this .name = name;
116: this .createAnnotations = flag;
117: }
118:
119: public AddWsOperationHelper(String name) {
120: this (name, true);
121: }
122:
123: protected MethodModel getPrototypeMethod() {
124: return MethodModel.create(
125: NbBundle.getMessage(AddWsOperationHelper.class,
126: "TXT_DefaultOperationName"), //NOI18N
127: "java.lang.String", //NOI18N
128: "", Collections.<MethodModel.Variable> emptyList(),
129: Collections.<String> emptyList(), Collections
130: .<Modifier> emptySet());
131: }
132:
133: public String getTitle() {
134: return name;
135: }
136:
137: protected MethodCustomizer createDialog(FileObject fileObject,
138: MethodModel methodModel) throws IOException {
139:
140: return MethodCustomizerFactory.operationMethod(getTitle(),
141: methodModel, ClasspathInfo.create(EMPTY_PATH, // boot classpath
142: ClassPath.getClassPath(fileObject,
143: ClassPath.COMPILE), // classpath from dependent projects and libraries
144: ClassPath.getClassPath(fileObject,
145: ClassPath.SOURCE)), // source classpath
146: getExistingMethods(fileObject));
147: }
148:
149: public void addMethod(FileObject fileObject, String className)
150: throws IOException {
151: if (className == null) {
152: return;
153: }
154: method = getPrototypeMethod();
155: MethodCustomizer methodCustomizer = createDialog(fileObject,
156: method);
157: if (methodCustomizer.customizeMethod()) {
158: try {
159:
160: method = methodCustomizer.getMethodModel();
161: okButtonPressed(method, fileObject, className);
162: } catch (IOException ioe) {
163: ErrorManager.getDefault().notify(ioe);
164: }
165: } else { //user pressed cancel button
166: method = null;
167: }
168: }
169:
170: /**
171: * Variant of addMethod(FileObject, String)which returns the final MethodModel.
172: */
173: public MethodModel getMethodModel(FileObject fileObject,
174: String className) throws IOException {
175: addMethod(fileObject, className);
176: return method;
177: }
178:
179: protected void okButtonPressed(MethodModel method,
180: FileObject implClassFo, String className)
181: throws IOException {
182: addOperation(method, implClassFo);
183: }
184:
185: protected FileObject getDDFile(FileObject fileObject) {
186: return EjbJar.getEjbJar(fileObject).getDeploymentDescriptor();
187: }
188:
189: /*
190: * Adds a method definition to the the implementation class
191: */
192: private void addOperation(final MethodModel methodModel,
193: final FileObject implClassFo) {
194: final JavaSource targetSource = JavaSource
195: .forFileObject(implClassFo);
196: final ProgressHandle handle = ProgressHandleFactory
197: .createHandle(NbBundle
198: .getMessage(AddWsOperationHelper.class,
199: "MSG_AddingNewOperation", methodModel
200: .getName()));
201: handle.start(100);
202: final String[] seiClass = new String[1];
203: final CancellableTask<WorkingCopy> modificationTask = new CancellableTask<WorkingCopy>() {
204: public void run(WorkingCopy workingCopy) throws IOException {
205: workingCopy.toPhase(Phase.RESOLVED);
206: MethodTree method = MethodModelSupport
207: .createMethodTree(workingCopy, methodModel);
208: if (method != null) {
209: TreeMaker make = workingCopy.getTreeMaker();
210: TypeElement typeElement = SourceUtils
211: .getPublicTopLevelElement(workingCopy);
212: if (typeElement != null) {
213:
214: boolean increaseProgress = true;
215:
216: if (createAnnotations) {
217: if (seiClass[0] == null) {
218: seiClass[0] = getEndpointInterface(
219: typeElement, workingCopy);
220: } else {
221: seiClass[0] = null;
222: increaseProgress = false;
223: }
224: }
225:
226: if (increaseProgress)
227: handle.progress(20);
228:
229: ClassTree javaClass = workingCopy.getTrees()
230: .getTree(typeElement);
231: TypeElement webMethodAn = workingCopy
232: .getElements().getTypeElement(
233: "javax.jws.WebMethod"); //NOI18N
234: TypeElement webParamAn = workingCopy
235: .getElements().getTypeElement(
236: "javax.jws.WebParam"); //NOI18N
237:
238: // Public modifier
239: ModifiersTree modifiersTree = make
240: .Modifiers(
241: Collections
242: .<Modifier> singleton(Modifier.PUBLIC),
243: Collections
244: .<AnnotationTree> emptyList());
245:
246: // add @WebMethod annotation
247: if (createAnnotations && seiClass[0] == null) {
248:
249: String methodName = method.getName()
250: .toString();
251: // find value for @WebMethod:oparationName
252: String operationName = findNewOperationName(
253: typeElement, workingCopy,
254: methodName);
255:
256: AssignmentTree opName = make.Assignment(
257: make.Identifier("operationName"),
258: make.Literal(operationName)); //NOI18N
259:
260: AnnotationTree webMethodAnnotation = make
261: .Annotation(
262: make.QualIdent(webMethodAn),
263: Collections
264: .<ExpressionTree> singletonList(opName));
265: modifiersTree = make
266: .addModifiersAnnotation(
267: modifiersTree,
268: webMethodAnnotation);
269:
270: // add @Oneway annotation
271:
272: boolean isOneWay = false;
273: if (Kind.PRIMITIVE_TYPE == method
274: .getReturnType().getKind()) {
275: PrimitiveTypeTree primitiveType = (PrimitiveTypeTree) method
276: .getReturnType();
277: if (TypeKind.VOID == primitiveType
278: .getPrimitiveTypeKind()) {
279: if (method.getThrows().size() == 0) {
280: isOneWay = true;
281: TypeElement oneWayAn = workingCopy
282: .getElements()
283: .getTypeElement(
284: "javax.jws.Oneway"); //NOI18N
285: AnnotationTree oneWayAnnotation = make
286: .Annotation(
287: make
288: .QualIdent(oneWayAn),
289: Collections
290: .<ExpressionTree> emptyList());
291:
292: modifiersTree = make
293: .addModifiersAnnotation(
294: modifiersTree,
295: oneWayAnnotation);
296: }
297: }
298: }
299: if (!methodName.equals(operationName)) {
300: // generate Request/Response wrapper annotations to avoid class conflicts
301: // this enables to generate operations with identical method names
302: String packagePrefix = getPackagePrefix(typeElement
303: .getQualifiedName().toString());
304:
305: TypeElement reqWrapperAn = workingCopy
306: .getElements()
307: .getTypeElement(
308: "javax.xml.ws.RequestWrapper"); //NOI18N
309: AssignmentTree className = make
310: .Assignment(
311: make
312: .Identifier("className"),
313: make
314: .Literal(packagePrefix
315: + operationName)); //NOI18N
316: AnnotationTree reqWrapperAnnotation = make
317: .Annotation(
318: make
319: .QualIdent(reqWrapperAn),
320: Collections
321: .<ExpressionTree> singletonList(className));
322: modifiersTree = make
323: .addModifiersAnnotation(
324: modifiersTree,
325: reqWrapperAnnotation);
326: if (!isOneWay) {
327: TypeElement resWrapperAn = workingCopy
328: .getElements()
329: .getTypeElement(
330: "javax.xml.ws.ResponseWrapper"); //NOI18N
331: className = make.Assignment(make
332: .Identifier("className"),
333: make.Literal(packagePrefix
334: + operationName
335: + "Response")); //NOI18N
336: AnnotationTree resWrapperAnnotation = make
337: .Annotation(
338: make
339: .QualIdent(resWrapperAn),
340: Collections
341: .<ExpressionTree> singletonList(className));
342: modifiersTree = make
343: .addModifiersAnnotation(
344: modifiersTree,
345: resWrapperAnnotation);
346: }
347: }
348: }
349:
350: if (increaseProgress)
351: handle.progress(40);
352:
353: // add @WebParam annotations
354: List<? extends VariableTree> parameters = method
355: .getParameters();
356: List<VariableTree> newParameters = new ArrayList<VariableTree>();
357:
358: if (createAnnotations && seiClass[0] == null) {
359: for (VariableTree param : parameters) {
360: AnnotationTree paramAnnotation = make
361: .Annotation(
362: make
363: .QualIdent(webParamAn),
364: Collections
365: .<ExpressionTree> singletonList(make
366: .Assignment(
367: make
368: .Identifier("name"),
369: make
370: .Literal(param
371: .getName()
372: .toString()))) //NOI18N
373: );
374: GenerationUtils genUtils = GenerationUtils
375: .newInstance(workingCopy);
376: newParameters.add(genUtils
377: .addAnnotation(param,
378: paramAnnotation));
379: }
380: } else {
381: newParameters.addAll(parameters);
382: }
383:
384: if (increaseProgress)
385: handle.progress(70);
386: // create new (annotated) method
387: MethodTree annotatedMethod = typeElement
388: .getKind() == ElementKind.CLASS ? make
389: .Method(modifiersTree,
390: method.getName(), method
391: .getReturnType(),
392: method.getTypeParameters(),
393: newParameters, method
394: .getThrows(),
395: getMethodBody(method
396: .getReturnType()), //NOI18N
397: (ExpressionTree) method
398: .getDefaultValue())
399: : make.Method(modifiersTree, method
400: .getName(), method
401: .getReturnType(), method
402: .getTypeParameters(),
403: newParameters, method
404: .getThrows(),
405: (BlockTree) null,
406: (ExpressionTree) method
407: .getDefaultValue());
408: Comment comment = Comment.create(Style.JAVADOC,
409: -2, -2, -2, NbBundle.getMessage(
410: AddWsOperationHelper.class,
411: "TXT_WSOperation"));
412: make.addComment(annotatedMethod, comment, true);
413:
414: if (increaseProgress)
415: handle.progress(90);
416: ClassTree modifiedClass = make.addClassMember(
417: javaClass, annotatedMethod);
418: workingCopy.rewrite(javaClass, modifiedClass);
419: }
420: }
421: }
422:
423: public void cancel() {
424: }
425: };
426:
427: if (SwingUtilities.isEventDispatchThread()) {
428: RequestProcessor.getDefault().post(new Runnable() {
429: public void run() {
430: try {
431: targetSource.runModificationTask(
432: modificationTask).commit();
433: // add method to SEI class
434: if (seiClass[0] != null) {
435: ClassPath sourceCP = ClassPath
436: .getClassPath(implClassFo,
437: ClassPath.SOURCE);
438: FileObject seiFo = sourceCP
439: .findResource(seiClass[0].replace(
440: '.', '/')
441: + ".java"); //NOI18N
442: if (seiFo != null) {
443: JavaSource seiSource = JavaSource
444: .forFileObject(seiFo);
445: seiSource.runModificationTask(
446: modificationTask).commit();
447: saveFile(seiFo);
448: }
449: }
450: saveFile(implClassFo);
451: } catch (IOException ex) {
452: ErrorManager.getDefault().notify(ex);
453: } finally {
454: handle.finish();
455: }
456: }
457: });
458: } else {
459: try {
460: targetSource.runModificationTask(modificationTask)
461: .commit();
462: // add method to SEI class
463: if (seiClass[0] != null) {
464: ClassPath sourceCP = ClassPath.getClassPath(
465: implClassFo, ClassPath.SOURCE);
466: FileObject seiFo = sourceCP
467: .findResource(seiClass[0].replace('.', '/')
468: + ".java"); //NOI18N
469: if (seiFo != null) {
470: JavaSource seiSource = JavaSource
471: .forFileObject(seiFo);
472: seiSource.runModificationTask(modificationTask)
473: .commit();
474: saveFile(seiFo);
475: }
476: }
477: saveFile(implClassFo);
478: } catch (IOException ex) {
479: ErrorManager.getDefault().notify(ex);
480: } finally {
481: handle.finish();
482: }
483: }
484:
485: }
486:
487: private String getEndpointInterface(TypeElement classEl,
488: CompilationController controller) {
489: TypeElement wsElement = controller.getElements()
490: .getTypeElement("javax.jws.WebService"); //NOI18N
491: if (wsElement != null) {
492: List<? extends AnnotationMirror> annotations = classEl
493: .getAnnotationMirrors();
494: for (AnnotationMirror anMirror : annotations) {
495: if (controller.getTypes().isSameType(
496: wsElement.asType(),
497: anMirror.getAnnotationType())) {
498: Map<? extends ExecutableElement, ? extends AnnotationValue> expressions = anMirror
499: .getElementValues();
500: for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : expressions
501: .entrySet()) {
502: if (entry.getKey().getSimpleName()
503: .contentEquals("endpointInterface")) { //NOI18N
504: String value = (String) expressions.get(
505: entry.getKey()).getValue();
506: if (value != null) {
507: TypeElement seiEl = controller
508: .getElements().getTypeElement(
509: value);
510: if (seiEl != null) {
511: return seiEl.getQualifiedName()
512: .toString();
513: }
514: }
515: }
516: }
517: } // end if
518: }
519: }
520: return null;
521: }
522:
523: private String findNewOperationName(TypeElement classEl,
524: CompilationController controller, String suggestedMethodName)
525: throws IOException {
526:
527: TypeElement methodElement = controller.getElements()
528: .getTypeElement("javax.jws.WebMethod"); //NOI18N
529: Set<String> operationNames = new HashSet<String>();
530: if (methodElement != null) {
531: List<ExecutableElement> methods = getMethods(controller,
532: classEl);
533: for (ExecutableElement m : methods) {
534: String opName = null;
535: List<? extends AnnotationMirror> annotations = m
536: .getAnnotationMirrors();
537: for (AnnotationMirror anMirror : annotations) {
538: if (controller.getTypes().isSameType(
539: methodElement.asType(),
540: anMirror.getAnnotationType())) {
541: Map<? extends ExecutableElement, ? extends AnnotationValue> expressions = anMirror
542: .getElementValues();
543: for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : expressions
544: .entrySet()) {
545: if (entry.getKey().getSimpleName()
546: .contentEquals("operationName")) { //NOI18N
547: opName = (String) expressions.get(
548: entry.getKey()).getValue();
549: break;
550: }
551: }
552: } // end if
553: if (opName != null)
554: break;
555: } //enfd for
556: if (opName == null)
557: opName = m.getSimpleName().toString();
558: operationNames.add(opName);
559: }
560: }
561: return findNewOperationName(operationNames, suggestedMethodName);
562: }
563:
564: private String findNewOperationName(Set<String> operationNames,
565: String suggestedMethodName) {
566: int i = 0;
567: String newName = suggestedMethodName; //NOI18N
568: while (operationNames.contains(newName)) {
569: newName = suggestedMethodName + "_" + String.valueOf(++i); //NOI18N
570: }
571: return newName;
572: }
573:
574: private String getPackagePrefix(String className) {
575: int lastDot = className.indexOf("."); //NOI18N
576: if (lastDot > 0)
577: return className.substring(0, lastDot + 1);
578: else
579: return "";
580: }
581:
582: private void saveFile(FileObject file) throws IOException {
583: DataObject dataObject = DataObject.find(file);
584: if (dataObject != null) {
585: SaveCookie cookie = dataObject.getCookie(SaveCookie.class);
586: if (cookie != null)
587: cookie.save();
588: }
589: }
590:
591: private String getMethodBody(Tree returnType) {
592: String body = null;
593: if (Kind.PRIMITIVE_TYPE == returnType.getKind()) {
594: TypeKind type = ((PrimitiveTypeTree) returnType)
595: .getPrimitiveTypeKind();
596: if (TypeKind.VOID == type)
597: body = ""; //NOI18N
598: else if (TypeKind.BOOLEAN == type)
599: body = "return false;"; // NOI18N
600: else if (TypeKind.INT == type)
601: body = "return 0;"; // NOI18N
602: else if (TypeKind.LONG == type)
603: body = "return 0;"; // NOI18N
604: else if (TypeKind.FLOAT == type)
605: body = "return 0.0;"; // NOI18N
606: else if (TypeKind.DOUBLE == type)
607: body = "return 0.0;"; // NOI18N
608: else if (TypeKind.BYTE == type)
609: body = "return 0;"; // NOI18N
610: else if (TypeKind.SHORT == type)
611: body = "return 0;"; // NOI18N
612: else if (TypeKind.CHAR == type)
613: body = "return ' ';"; // NOI18N
614: else
615: body = "return null"; //NOI18N
616: } else
617: body = "return null"; //NOI18N
618: return "{\n\t\t"
619: + NbBundle.getMessage(AddWsOperationHelper.class,
620: "TXT_TodoComment") + "\n" + body + "\n}";
621: }
622:
623: /*
624: protected static MethodsNode getMethodsNode() {
625: Node[] nodes = Utilities.actionsGlobalContext().lookup(new Lookup.Template<Node>(Node.class)).allInstances().toArray(new Node[0]);
626: if (nodes.length != 1) {
627: return null;
628: }
629: return nodes[0].getLookup().lookup(MethodsNode.class);
630: }
631: */
632:
633: private Collection<MethodModel> getExistingMethods(
634: FileObject implClass) {
635: JavaSource javaSource = JavaSource.forFileObject(implClass);
636: final ResultHolder<MethodModel> result = new ResultHolder<MethodModel>();
637: if (javaSource != null) {
638: CancellableTask<CompilationController> task = new CancellableTask<CompilationController>() {
639: public void run(CompilationController controller)
640: throws IOException {
641: controller.toPhase(Phase.ELEMENTS_RESOLVED);
642: TypeElement typeElement = SourceUtils
643: .getPublicTopLevelElement(controller);
644: if (typeElement != null) {
645: // find methods
646: List<ExecutableElement> allMethods = getMethods(
647: controller, typeElement);
648: Collection<MethodModel> wsOperations = new ArrayList<MethodModel>();
649: boolean foundWebMethodAnnotation = false;
650: for (ExecutableElement method : allMethods) {
651: // check if return type is a valid type
652: if (method.getReturnType().getKind() == TypeKind.ERROR)
653: break;
654: // check if param types are valid types
655:
656: boolean validParamTypes = true;
657: List<? extends VariableElement> params = method
658: .getParameters();
659: for (VariableElement param : params) {
660: if (param.asType().getKind() == TypeKind.ERROR) {
661: validParamTypes = false;
662: break;
663: }
664: }
665: if (validParamTypes) {
666: MethodModel methodModel = MethodModelSupport
667: .createMethodModel(controller,
668: method);
669: wsOperations.add(methodModel);
670: }
671: } // for
672: result.setResult(wsOperations);
673: }
674: }
675:
676: public void cancel() {
677: }
678: };
679: try {
680: javaSource.runUserActionTask(task, true);
681: } catch (IOException ex) {
682: ErrorManager.getDefault().notify(ex);
683: }
684: }
685: return result.getResult();
686: }
687:
688: private List<ExecutableElement> getMethods(
689: CompilationController controller, TypeElement classElement)
690: throws IOException {
691: List<? extends Element> members = classElement
692: .getEnclosedElements();
693: List<ExecutableElement> methods = ElementFilter
694: .methodsIn(members);
695: List<ExecutableElement> publicMethods = new ArrayList<ExecutableElement>();
696: for (ExecutableElement method : methods) {
697: //Set<Modifier> modifiers = method.getModifiers();
698: //if (modifiers.contains(Modifier.PUBLIC)) {
699: publicMethods.add(method);
700: //}
701: }
702: return publicMethods;
703: }
704:
705: /** Holder class for result
706: */
707: private class ResultHolder<E> {
708: private Collection<E> result;
709:
710: public Collection<E> getResult() {
711: return result;
712: }
713:
714: public void setResult(Collection<E> result) {
715: this.result = result;
716: }
717: }
718: }
|