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: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2008 Sun Microsystems, Inc.
038: */
039: package org.netbeans.modules.spring.beans.editor;
040:
041: import org.netbeans.modules.spring.beans.utils.*;
042: import java.io.IOException;
043: import java.util.ArrayList;
044: import java.util.HashSet;
045: import java.util.List;
046: import java.util.Set;
047: import javax.lang.model.element.Element;
048: import javax.lang.model.element.ElementKind;
049: import javax.lang.model.element.ExecutableElement;
050: import javax.lang.model.element.Modifier;
051: import javax.lang.model.element.TypeElement;
052: import javax.lang.model.type.DeclaredType;
053: import javax.lang.model.type.TypeKind;
054: import javax.lang.model.type.TypeMirror;
055: import javax.swing.text.Document;
056: import org.netbeans.api.java.source.CompilationController;
057: import org.netbeans.api.java.source.ElementUtilities;
058: import org.netbeans.api.java.source.JavaSource;
059: import org.netbeans.api.java.source.Task;
060: import org.netbeans.modules.editor.NbEditorUtilities;
061: import org.netbeans.modules.spring.api.Action;
062: import org.netbeans.modules.spring.api.beans.model.SpringBean;
063: import org.netbeans.modules.spring.api.beans.model.SpringBeans;
064: import org.netbeans.modules.spring.api.beans.model.SpringConfigModel;
065: import org.openide.filesystems.FileObject;
066: import org.openide.util.Exceptions;
067: import org.w3c.dom.Node;
068:
069: /**
070: * Finds the actual class which is implementing the specified bean.
071: *
072: * Uses a recursive logic as follows
073: *
074: * <pre>
075: * findImplementationClass(bean) :
076: * type = null;
077: *
078: * if(bean has parent) {
079: * getMergedAttributes(parent); // walk the ancestor chain and find all attributes
080: * }
081: *
082: * if(bean has factory-bean attribute defined) {
083: * type = findImplementationClass(factory-bean);
084: * } else if(bean has class attribute defined) {
085: * type = class attrib value;
086: * } else if(bean has parent attribute) {
087: * type = findImplementationClass(parent);
088: * }
089: *
090: * if(bean has factory-method) {
091: * type = findFactoryMethodReturnType(type, factory-method-name);
092: * }
093: *
094: * return type;
095: * </pre>
096: * @author Rohan Ranade (Rohan.Ranade@Sun.COM)
097: */
098: public class BeanClassFinder {
099:
100: private static final String ID_ATTRIB = "id"; // NOI18N
101: private static final String NAME_ATTRIB = "name"; // NOI18N
102:
103: private Node beanNode;
104: private FileObject fileObject;
105: private Set<String> walkedBeanNames;
106: private Document document;
107: private String startBeanName;
108: private SpringBean startBean;
109:
110: public BeanClassFinder(Node beanNode, Document document) {
111: this .beanNode = beanNode;
112: this .document = document;
113: this .fileObject = NbEditorUtilities.getFileObject(document);
114: this .walkedBeanNames = new HashSet<String>();
115: this .startBean = SpringXMLConfigEditorUtils.getMergedBean(
116: beanNode, document);
117: startBeanName = getBeanIdOrName(beanNode);
118: }
119:
120: public String findImplementationClass() {
121: walkedBeanNames.add(startBeanName);
122: return findImplementationClass(startBean);
123: }
124:
125: private String findImplementationClass(SpringBean logicalBean) {
126: String implClass = null;
127: boolean staticFlag = false;
128:
129: if (StringUtils.hasText(logicalBean.getFactoryBean())) {
130: implClass = findImplementationClass(logicalBean
131: .getFactoryBean());
132: staticFlag = false;
133: } else if (StringUtils.hasText(logicalBean.getClassName())) {
134: implClass = logicalBean.getClassName();
135: staticFlag = true;
136: }
137:
138: if (logicalBean.getFactoryMethod() != null) {
139: implClass = getFactoryMethodReturnTypeName(implClass,
140: logicalBean.getFactoryMethod(), staticFlag);
141: }
142:
143: return implClass;
144: }
145:
146: private String findImplementationClass(final String beanName) {
147: if (walkedBeanNames.contains(beanName)) {
148: // possible circular dep - bail out
149: return null;
150: }
151:
152: final String[] clazz = { null };
153: try {
154: SpringConfigModel model = SpringConfigModel
155: .forFileObject(fileObject);
156: if (model == null) {
157: return null;
158: }
159:
160: model.runReadAction(new Action<SpringBeans>() {
161:
162: public void run(SpringBeans springBeans) {
163: SpringBean bean = springBeans.findBean(beanName);
164: bean = SpringXMLConfigEditorUtils.getMergedBean(
165: bean, document);
166: if (bean == null) {
167: return;
168: }
169:
170: String beanName = getBeanIdOrName(bean);
171: walkedBeanNames.add(beanName);
172: clazz[0] = findImplementationClass(bean);
173: }
174: });
175: } catch (IOException ex) {
176: Exceptions.printStackTrace(ex);
177: }
178:
179: return clazz[0];
180: }
181:
182: /**
183: * Tries to search for a factory method with a specified name on the class.
184: * Due to current limitations of the model, if more than one factory method
185: * is found, we are not able to disambiguate based on the parameter types of
186: * the factory method.
187: *
188: * Hence we return a null in such a scenario.
189: *
190: */
191: private String getFactoryMethodReturnTypeName(
192: final String implClass, final String factoryMethodName,
193: final boolean staticFlag) {
194: final String[] retVal = { null };
195:
196: if (!StringUtils.hasText(factoryMethodName)) {
197: return null;
198: }
199:
200: try {
201: JavaSource js = SpringXMLConfigEditorUtils
202: .getJavaSource(document);
203: if (js == null) {
204: return null;
205: }
206:
207: js.runUserActionTask(new Task<CompilationController>() {
208:
209: public void run(CompilationController cc)
210: throws Exception {
211: TypeElement te = SpringXMLConfigEditorUtils
212: .findClassElementByBinaryName(implClass, cc);
213: if (te == null) {
214: return;
215: }
216:
217: FactoryMethodFinder factoryMethodFinder = new FactoryMethodFinder(
218: te, factoryMethodName, staticFlag, cc
219: .getElementUtilities());
220: List<ExecutableElement> methods = factoryMethodFinder
221: .findMethods();
222: if (methods.size() != 1) {
223: return;
224: }
225:
226: ExecutableElement method = methods.get(0);
227: if (method.getReturnType().getKind() != TypeKind.DECLARED) {
228: return;
229: }
230:
231: DeclaredType dt = (DeclaredType) method
232: .getReturnType();
233: retVal[0] = ElementUtilities
234: .getBinaryName((TypeElement) dt.asElement());
235: }
236: }, true);
237: } catch (IOException ex) {
238: Exceptions.printStackTrace(ex);
239: }
240:
241: return retVal[0];
242: }
243:
244: private static class FactoryMethodFinder {
245: private TypeElement te;
246: private String factoryMethodName;
247: private boolean staticFlag;
248: private ElementUtilities eu;
249:
250: public FactoryMethodFinder(TypeElement te,
251: String factoryMethodName, boolean staticFlag,
252: ElementUtilities eu) {
253: this .te = te;
254: this .factoryMethodName = factoryMethodName;
255: this .staticFlag = staticFlag;
256: this .eu = eu;
257: }
258:
259: public List<ExecutableElement> findMethods() {
260: Iterable<? extends Element> list = eu.getMembers(te
261: .asType(), new ElementUtilities.ElementAcceptor() {
262:
263: public boolean accept(Element e, TypeMirror type) {
264: if (e.getKind() == ElementKind.METHOD) {
265: TypeElement te = (TypeElement) e
266: .getEnclosingElement();
267: if (te.getQualifiedName().contentEquals(
268: "java.lang.Object")) { // NOI18N
269: return false;
270: }
271:
272: // match name
273: if (!e.getSimpleName().toString().equals(
274: factoryMethodName)) {
275: return false;
276: }
277:
278: ExecutableElement method = (ExecutableElement) e;
279: // match static
280: boolean isStatic = method.getModifiers()
281: .contains(Modifier.STATIC);
282: if (isStatic != staticFlag) {
283: return false;
284: }
285: return true;
286: }
287:
288: return false;
289: }
290: });
291:
292: List<ExecutableElement> retList = new ArrayList<ExecutableElement>();
293: for (Element e : list) {
294: ExecutableElement ee = (ExecutableElement) e;
295: retList.add(ee);
296: }
297:
298: return retList;
299: }
300: }
301:
302: private String getBeanIdOrName(SpringBean bean) {
303: if (bean.getId() != null) {
304: return bean.getId();
305: }
306:
307: if (bean.getNames().size() > 0) {
308: return bean.getNames().get(0);
309: }
310:
311: return null;
312: }
313:
314: private String getBeanIdOrName(Node beanNode) {
315: if (SpringXMLConfigEditorUtils
316: .hasAttribute(beanNode, ID_ATTRIB)) {
317: return SpringXMLConfigEditorUtils.getAttribute(beanNode,
318: ID_ATTRIB);
319: }
320:
321: if (SpringXMLConfigEditorUtils.hasAttribute(beanNode,
322: NAME_ATTRIB)) {
323: String names = SpringXMLConfigEditorUtils.getAttribute(
324: beanNode, NAME_ATTRIB);
325: return StringUtils.tokenize(names,
326: SpringXMLConfigEditorUtils.BEAN_NAME_DELIMITERS)
327: .get(0);
328: }
329:
330: return null;
331: }
332: }
|