| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.j2ee.ejbverification; |
| |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.SourcePositions; |
| import java.io.IOException; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import org.netbeans.api.java.source.CompilationInfo; |
| import org.netbeans.api.java.source.TreeUtilities; |
| import org.netbeans.spi.editor.hints.ErrorDescription; |
| import org.netbeans.spi.editor.hints.ErrorDescriptionFactory; |
| import org.netbeans.spi.editor.hints.Fix; |
| import org.netbeans.api.lexer.Token; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.api.java.lexer.JavaTokenId; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.modules.j2ee.api.ejbjar.EjbJar; |
| import org.netbeans.modules.j2ee.common.J2eeProjectCapabilities; |
| import org.netbeans.modules.j2ee.dd.api.common.VersionNotSupportedException; |
| import org.netbeans.modules.j2ee.dd.api.ejb.Ejb; |
| import org.netbeans.modules.j2ee.dd.api.ejb.EjbJarMetadata; |
| import org.netbeans.modules.j2ee.dd.api.ejb.Session; |
| import org.netbeans.modules.j2ee.ejbverification.EJBProblemContext.SessionData; |
| import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelAction; |
| import org.netbeans.modules.j2ee.metadata.model.api.MetadataModelException; |
| import org.netbeans.spi.editor.hints.Severity; |
| import org.netbeans.spi.java.hints.HintContext; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.Exceptions; |
| |
| /** |
| * |
| * @author Tomasz.Slota@Sun.COM |
| */ |
| public class HintsUtils { |
| |
| private static final Logger LOG = Logger.getLogger(HintsUtils.class.getName()); |
| private static final String CACHED_CONTEXT = "cached-ejbProblemContext-"; |
| |
| public static ErrorDescription createProblem(Element subject, CompilationInfo cinfo, |
| String description) { |
| return createProblem(subject, cinfo, description, Severity.ERROR, Collections.<Fix>emptyList()); |
| } |
| |
| public static ErrorDescription createProblem(Element subject, CompilationInfo cinfo, |
| String description, Severity severity) { |
| return createProblem(subject, cinfo, description, severity, Collections.<Fix>emptyList()); |
| } |
| |
| public static ErrorDescription createProblem(Element subject, CompilationInfo cinfo, String description, |
| Severity severity, Fix fix) { |
| return createProblem(subject, cinfo, description, severity, Collections.singletonList(fix)); |
| } |
| |
| public static ErrorDescription createProblem(Element subject, CompilationInfo cinfo, String description, Fix fix) { |
| return createProblem(subject, cinfo, description, Severity.ERROR, Collections.singletonList(fix)); |
| } |
| |
| public static ErrorDescription createProblem(Element subject, CompilationInfo cinfo, |
| String description, Severity severity, List<Fix> fixes) { |
| ErrorDescription err = null; |
| List<Fix> fixList = fixes == null ? Collections.<Fix>emptyList() : fixes; |
| |
| // by default place error annotation on the element being checked |
| Tree elementTree = cinfo.getTrees().getTree(subject); |
| |
| if (elementTree != null) { |
| TextSpan underlineSpan = getUnderlineSpan(cinfo, elementTree); |
| |
| err = ErrorDescriptionFactory.createErrorDescription( |
| severity, description, fixList, cinfo.getFileObject(), |
| underlineSpan.getStartOffset(), underlineSpan.getEndOffset()); |
| |
| } else { |
| // report problem |
| } |
| |
| return err; |
| } |
| |
| /** |
| * Says whether the given version is of the EJB version 3.0 and higher. BTW, annotation EJB model always returns EJB |
| * 3.0. |
| * |
| * @param ejbVersion string representation of the EJB version |
| * @return {@code true} if the version is equal or higher than EJB3.0, {@code false} otherwise |
| */ |
| public static boolean isEjb30Plus(String ejbVersion) { |
| return org.netbeans.modules.j2ee.dd.api.ejb.EjbJar.VERSION_3_0.equals(ejbVersion) |
| || org.netbeans.modules.j2ee.dd.api.ejb.EjbJar.VERSION_3_1.equals(ejbVersion) |
| || org.netbeans.modules.j2ee.dd.api.ejb.EjbJar.VERSION_3_2.equals(ejbVersion); |
| } |
| |
| /** |
| * This method returns the part of the syntax tree to be highlighted. It will be usually the class/method/variable |
| * identifier. |
| */ |
| public static TextSpan getUnderlineSpan(CompilationInfo info, Tree tree) { |
| SourcePositions srcPos = info.getTrees().getSourcePositions(); |
| |
| int startOffset = (int) srcPos.getStartPosition(info.getCompilationUnit(), tree); |
| int endOffset = (int) srcPos.getEndPosition(info.getCompilationUnit(), tree); |
| |
| Tree startSearchingForNameIndentifierBehindThisTree = null; |
| |
| if (TreeUtilities.CLASS_TREE_KINDS.contains(tree.getKind())) { |
| startSearchingForNameIndentifierBehindThisTree = ((ClassTree) tree).getModifiers(); |
| |
| } else if (tree.getKind() == Tree.Kind.METHOD) { |
| startSearchingForNameIndentifierBehindThisTree = ((MethodTree) tree).getReturnType(); |
| } else if (tree.getKind() == Tree.Kind.VARIABLE) { |
| startSearchingForNameIndentifierBehindThisTree = ((VariableTree) tree).getType(); |
| } |
| |
| if (startSearchingForNameIndentifierBehindThisTree != null) { |
| int searchStart = (int) srcPos.getEndPosition(info.getCompilationUnit(), |
| startSearchingForNameIndentifierBehindThisTree); |
| |
| TokenSequence tokenSequence = info.getTreeUtilities().tokensFor(tree); |
| |
| if (tokenSequence != null) { |
| boolean eob = false; |
| tokenSequence.move(searchStart); |
| |
| do { |
| eob = !tokenSequence.moveNext(); |
| } while (!eob && tokenSequence.token().id() != JavaTokenId.IDENTIFIER); |
| |
| if (!eob) { |
| Token identifier = tokenSequence.token(); |
| startOffset = identifier.offset(info.getTokenHierarchy()); |
| endOffset = startOffset + identifier.length(); |
| } |
| } |
| } |
| |
| return new TextSpan(startOffset, endOffset); |
| } |
| |
| /** |
| * Represents a span of text |
| */ |
| public static class TextSpan { |
| |
| private int startOffset; |
| private int endOffset; |
| |
| public TextSpan(int startOffset, int endOffset) { |
| this.startOffset = startOffset; |
| this.endOffset = endOffset; |
| } |
| |
| public int getStartOffset() { |
| return startOffset; |
| } |
| |
| public int getEndOffset() { |
| return endOffset; |
| } |
| } |
| |
| public static boolean isContainingKnownClasses(ExecutableElement method) { |
| if (method.getReturnType().getKind() == TypeKind.ERROR) { |
| return false; |
| } |
| |
| for (TypeMirror type : method.getThrownTypes()) { |
| if (type.getKind() == TypeKind.ERROR) { |
| return false; |
| } |
| } |
| |
| for (VariableElement variableElement : method.getParameters()) { |
| if (variableElement.asType().getKind() == TypeKind.ERROR) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Gets problem context used by standard EJB hints. This method can be used for @TriggerTreeKind based hints. |
| * Uses cached value if found, otherwise creates a new one which stores into the CompilationInfo. |
| * |
| * @param context Hints API context |
| * @return EJB hint's context |
| */ |
| public static EJBProblemContext getOrCacheContext(HintContext context) { |
| Element element = context.getInfo().getTrees().getElement(context.getPath()); |
| String elementType = element.asType().toString(); |
| return getOrCacheContext(context, elementType); |
| } |
| |
| /** |
| * Gets problem context used by standard EJB hints. |
| * Uses cached value if found, otherwise creates a new one which stores into the CompilationInfo. |
| * |
| * @param context Hints API context |
| * @param elementType FQN of the element where should be hint applied |
| * @return EJB hint's context |
| */ |
| public static EJBProblemContext getOrCacheContext(HintContext context, String elementType) { |
| Object cached = context.getInfo().getCachedValue(CACHED_CONTEXT + elementType); |
| if (cached == null) { |
| LOG.log(Level.FINEST, "HintContext doesn''t contain cached EJBProblemContext which is going to be created for type: {0}", elementType); |
| EJBProblemContext newContext = createEJBProblemContext(context); |
| context.getInfo().putCachedValue(CACHED_CONTEXT + elementType, newContext, CompilationInfo.CacheClearPolicy.ON_SIGNATURE_CHANGE); |
| return newContext; |
| } else { |
| LOG.log(Level.FINEST, "EJBProblemContext cached value used."); |
| return (EJBProblemContext) cached; |
| } |
| } |
| |
| private static EJBProblemContext createEJBProblemContext(final HintContext context) { |
| final CompilationInfo info = context.getInfo(); |
| final FileObject file = info.getFileObject(); |
| |
| final Project project = FileOwnerQuery.getOwner(file); |
| if (project == null) { |
| return null; |
| } |
| |
| J2eeProjectCapabilities projCap = J2eeProjectCapabilities.forProject(project); |
| if (projCap == null || (!projCap.isEjb30Supported() && !projCap.isEjb31LiteSupported())) { |
| return null; |
| } |
| |
| final EjbJar ejbModule = EjbJar.getEjbJar(file); |
| if (ejbModule == null) { |
| return null; |
| } |
| |
| try { |
| return ejbModule.getMetadataModel().runReadAction(new MetadataModelAction<EjbJarMetadata, EJBProblemContext>() { |
| @Override |
| public EJBProblemContext run(EjbJarMetadata metadata) { |
| long startTime = Calendar.getInstance().getTimeInMillis(); |
| String ejbVersion = metadata.getRoot().getVersion().toString(); |
| if (!HintsUtils.isEjb30Plus(ejbVersion)) { |
| return null; // Only EJB 3.0+ are supported |
| } |
| |
| Element element = info.getTrees().getElement(context.getPath()); |
| if (element instanceof TypeElement) { |
| TypeElement javaClass = (TypeElement) element; |
| Ejb ejb = metadata.findByEjbClass(javaClass.getQualifiedName().toString()); |
| |
| // precompute EJB information |
| String[] businessLocal = new String[0]; |
| String[] businessRemote = new String[0]; |
| String sessionType = ""; |
| try { |
| if (ejb instanceof Session) { |
| Session session = ((Session) ejb); |
| businessLocal = session.getBusinessLocal(); |
| businessRemote = session.getBusinessRemote(); |
| sessionType = session.getSessionType(); |
| } |
| } catch (VersionNotSupportedException ex) { |
| LOG.log(Level.INFO, ex.getMessage(), ex); |
| } |
| |
| if (LOG.isLoggable(Level.FINE)) { |
| long timeElapsed = Calendar.getInstance().getTimeInMillis() - startTime; |
| LOG.log(Level.FINE, "processed class {0} in {1} ms", new Object[]{javaClass.getSimpleName(), timeElapsed}); |
| } |
| return new EJBProblemContext(project, ejbModule, file, javaClass, ejb, new SessionData(businessLocal, businessRemote, sessionType)); |
| } else { |
| return null; |
| } |
| } |
| }); |
| } catch (MetadataModelException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| return null; |
| } |
| } |