/* * Copyright 2014, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jf.smalidea.psi.impl; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.intellij.debugger.SourcePosition; import com.intellij.lang.ASTNode; import com.intellij.navigation.ItemPresentation; import com.intellij.navigation.ItemPresentationProviders; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.PsiModifier.ModifierConstant; import com.intellij.psi.impl.InheritanceImplUtil; import com.intellij.psi.impl.PsiClassImplUtil; import com.intellij.psi.impl.PsiImplUtil; import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiUtil; import com.intellij.util.IncorrectOperationException; import com.sun.jdi.Location; import com.sun.jdi.Method; import com.sun.jdi.ReferenceType; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jf.smalidea.SmaliIcons; import org.jf.smalidea.psi.SmaliElementTypes; import org.jf.smalidea.psi.iface.SmaliModifierListOwner; import org.jf.smalidea.psi.leaf.SmaliClassDescriptor; import org.jf.smalidea.psi.stub.SmaliClassStub; import javax.annotation.Nonnull; import javax.swing.*; import java.util.Collection; import java.util.List; public class SmaliClass extends SmaliStubBasedPsiElement<SmaliClassStub> implements PsiClass, SmaliModifierListOwner { public SmaliClass(@NotNull SmaliClassStub stub) { super(stub, SmaliElementTypes.CLASS); } public SmaliClass(@NotNull ASTNode node) { super(node); } @Nonnull @Override public String getName() { String name = getQualifiedName(); if (name == null) { return ""; } int lastDot = name.lastIndexOf('.'); if (lastDot < 0) { return name; } return name.substring(lastDot+1); } @Override public ItemPresentation getPresentation() { return ItemPresentationProviders.getItemPresentation(this); } @Nullable @Override public String getQualifiedName() { SmaliClassStatement classStatement = getStubOrPsiChild(SmaliElementTypes.CLASS_STATEMENT); if (classStatement == null) { return null; } return classStatement.getQualifiedName(); } @NotNull public String getPackageName() { String name = getQualifiedName(); if (name == null) { return ""; } int lastDot = name.lastIndexOf('.'); if (lastDot < 0) { return ""; } return name.substring(0, lastDot); } @Override public boolean hasTypeParameters() { // TODO: implement generics return false; } @Override public boolean isInterface() { return hasModifierProperty("interface"); } @Override public boolean isAnnotationType() { return hasModifierProperty("annotation"); } @Override public boolean isEnum() { return hasModifierProperty("enum"); } @Nullable public SmaliSuperStatement getSuperStatement() { return findChildByClass(SmaliSuperStatement.class); } @NotNull @Override public SmaliExtendsList getExtendsList() { return getRequiredStubOrPsiChild(SmaliElementTypes.EXTENDS_LIST); } @NotNull public SmaliImplementsStatement[] getImplementsStatements() { return findChildrenByClass(SmaliImplementsStatement.class); } @NotNull @Override public SmaliImplementsList getImplementsList() { return getRequiredStubOrPsiChild(SmaliElementTypes.IMPLEMENTS_LIST); } @NotNull @Override public SmaliClassType[] getExtendsListTypes() { return getExtendsList().getReferencedTypes(); } @NotNull @Override public SmaliClassType[] getImplementsListTypes() { return getImplementsList().getReferencedTypes(); } @Nullable @Override public PsiClass getSuperClass() { return PsiClassImplUtil.getSuperClass(this); } @Override public PsiClass[] getInterfaces() { return PsiClassImplUtil.getInterfaces(this); } @NotNull @Override public PsiClass[] getSupers() { return PsiClassImplUtil.getSupers(this); } @NotNull @Override public PsiClassType[] getSuperTypes() { return PsiClassImplUtil.getSuperTypes(this); } @NotNull @Override public SmaliField[] getFields() { SmaliField[] fields = getStubOrPsiChildren(SmaliElementTypes.FIELD, new SmaliField[0]); List<SmaliField> filteredFields = null; for (int i=fields.length-1; i>=0; i--) { SmaliField field = fields[i]; if (field.getName() == null) { if (filteredFields == null) { filteredFields = Lists.newArrayList(fields); } filteredFields.remove(i); } } if (filteredFields != null) { return filteredFields.toArray(new SmaliField[filteredFields.size()]); } return fields; } @NotNull @Override public SmaliMethod[] getMethods() { return getStubOrPsiChildren(SmaliElementTypes.METHOD, new SmaliMethod[0]); } @NotNull @Override public PsiMethod[] getConstructors() { return PsiImplUtil.getConstructors(this); } @NotNull @Override public PsiClass[] getInnerClasses() { return new PsiClass[0]; } @NotNull @Override public PsiClassInitializer[] getInitializers() { // TODO: do we need to return the <clinit> method here? return new PsiClassInitializer[0]; } @NotNull @Override public PsiField[] getAllFields() { return PsiClassImplUtil.getAllFields(this); } @NotNull @Override public PsiMethod[] getAllMethods() { return PsiClassImplUtil.getAllMethods(this); } @NotNull @Override public PsiClass[] getAllInnerClasses() { return new PsiClass[0]; } @Nullable @Override public PsiField findFieldByName(@NonNls String name, boolean checkBases) { return PsiClassImplUtil.findFieldByName(this, name, checkBases); } @Nullable @Override public PsiMethod findMethodBySignature(PsiMethod patternMethod, boolean checkBases) { return PsiClassImplUtil.findMethodBySignature(this, patternMethod, checkBases); } @NotNull @Override public PsiMethod[] findMethodsBySignature(PsiMethod patternMethod, boolean checkBases) { return PsiClassImplUtil.findMethodsBySignature(this, patternMethod, checkBases); } @NotNull @Override public PsiMethod[] findMethodsByName(@NonNls String name, boolean checkBases) { return PsiClassImplUtil.findMethodsByName(this, name, checkBases); } @NotNull @Override public List<Pair<PsiMethod, PsiSubstitutor>> findMethodsAndTheirSubstitutorsByName(@NonNls String name, boolean checkBases) { return PsiClassImplUtil.findMethodsAndTheirSubstitutorsByName(this, name, checkBases); } @NotNull @Override public List<Pair<PsiMethod, PsiSubstitutor>> getAllMethodsAndTheirSubstitutors() { return PsiClassImplUtil.getAllWithSubstitutorsByMap(this, PsiClassImplUtil.MemberType.METHOD); } @Nullable @Override public PsiClass findInnerClassByName(@NonNls String name, boolean checkBases) { return null; } @Nullable @Override public PsiElement getLBrace() { return null; } @Nullable @Override public PsiElement getRBrace() { return null; } @Nullable public SmaliClassStatement getClassStatement() { return getStubOrPsiChild(SmaliElementTypes.CLASS_STATEMENT); } @Nullable @Override public SmaliClassDescriptor getNameIdentifier() { SmaliClassStatement classStatement = getClassStatement(); if (classStatement == null) { return null; } return classStatement.getNameIdentifier(); } @Override public PsiElement getScope() { return null; } @Override public boolean isInheritor(@NotNull PsiClass baseClass, boolean checkDeep) { return InheritanceImplUtil.isInheritor(this, baseClass, checkDeep); } @Override public boolean isInheritorDeep(PsiClass baseClass, @Nullable PsiClass classToByPass) { return InheritanceImplUtil.isInheritorDeep(this, baseClass, classToByPass); } @Nullable @Override public PsiClass getContainingClass() { return null; } @NotNull @Override public Collection<HierarchicalMethodSignature> getVisibleSignatures() { return ImmutableList.of(); } @Override public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException { SmaliClassStatement classStatement = getClassStatement(); if (classStatement == null) { throw new IncorrectOperationException(); } SmaliClassTypeElement classTypeElement = classStatement.getNameElement(); if (classTypeElement == null) { throw new IncorrectOperationException(); } String expectedPath = "/" + getName() + ".smali"; VirtualFile virtualFile = this.getContainingFile().getVirtualFile(); if (virtualFile != null) { String actualPath = virtualFile.getPath(); if (actualPath.endsWith(expectedPath)) { getContainingFile().setName(name + ".smali"); } } String packageName = this.getPackageName(); String newName; if (packageName.length() > 0) { newName = packageName + "." + name; } else { newName = name; } classTypeElement.handleElementRename(newName); return this; } public void setPackageName(@NonNls @NotNull String packageName) { SmaliClassStatement classStatement = getClassStatement(); if (classStatement == null) { throw new IncorrectOperationException(); } SmaliClassTypeElement classTypeElement = classStatement.getNameElement(); if (classTypeElement == null) { throw new IncorrectOperationException(); } String newName; if (packageName.length() > 0) { newName = packageName + "." + getName(); } else { newName = getName(); } classTypeElement.handleElementRename(newName); } @Nullable @Override public PsiDocComment getDocComment() { return null; } @Override public boolean isDeprecated() { return false; } @Nullable @Override public PsiTypeParameterList getTypeParameterList() { return null; } @NotNull @Override public PsiTypeParameter[] getTypeParameters() { return new PsiTypeParameter[0]; } @Nullable @Override public SmaliModifierList getModifierList() { SmaliClassStatement classStatement = getStubOrPsiChild(SmaliElementTypes.CLASS_STATEMENT); if (classStatement == null) { return null; } return classStatement.getModifierList(); } @Override public boolean hasModifierProperty(@ModifierConstant @NonNls @NotNull String name) { SmaliModifierList smaliModifierList = getModifierList(); return smaliModifierList != null && smaliModifierList.hasModifierProperty(name); } @NotNull @Override public SmaliAnnotation[] getAnnotations() { return getStubOrPsiChildren(SmaliElementTypes.ANNOTATION, new SmaliAnnotation[0]); } @NotNull @Override public SmaliAnnotation[] getApplicableAnnotations() { return getAnnotations(); } @Nullable @Override public SmaliAnnotation findAnnotation(@NotNull @NonNls String qualifiedName) { for (SmaliAnnotation annotation: getAnnotations()) { if (qualifiedName.equals(annotation.getQualifiedName())) { return annotation; } } return null; } @NotNull @Override public SmaliAnnotation addAnnotation(@NotNull @NonNls String qualifiedName) { // TODO: implement this return null; } @Nullable public Location getLocationForSourcePosition(@Nonnull ReferenceType type, @Nonnull SourcePosition position) { SmaliMethod[] smaliMethods = findChildrenByType(SmaliElementTypes.METHOD, SmaliMethod.class); for (SmaliMethod smaliMethod: smaliMethods) { //TODO: check the start line+end line of the method int offset = smaliMethod.getOffsetForLine(position.getLine()); if (offset != -1) { List<Method> methods = type.methodsByName(smaliMethod.getName(), smaliMethod.getMethodPrototype().getText()); if (methods.size() > 0) { return methods.get(0).locationOfCodeIndex(offset/2); } } } return null; } @Override public boolean processDeclarations(@NotNull PsiScopeProcessor processor, @NotNull ResolveState state, PsiElement lastParent, @NotNull PsiElement place) { return PsiClassImplUtil.processDeclarationsInClass(this, processor, state, null, lastParent, place, PsiUtil.getLanguageLevel(place), false); } @Nullable @Override protected Icon getElementIcon(@IconFlags int flags) { return SmaliIcons.SmaliIcon; } }