/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on Jun 10, 2006 * @author Fabio */ package org.python.pydev.outline; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.eclipse.swt.graphics.Image; import org.python.pydev.core.bundle.ImageCache; import org.python.pydev.core.docutils.StringUtils; import org.python.pydev.parser.ErrorDescription; import org.python.pydev.parser.jython.SimpleNode; import org.python.pydev.parser.jython.ast.Attribute; import org.python.pydev.parser.jython.ast.ClassDef; import org.python.pydev.parser.jython.ast.FunctionDef; import org.python.pydev.parser.jython.ast.If; import org.python.pydev.parser.jython.ast.Import; import org.python.pydev.parser.jython.ast.ImportFrom; import org.python.pydev.parser.jython.ast.Name; import org.python.pydev.parser.jython.ast.NameTok; import org.python.pydev.parser.jython.ast.aliasType; import org.python.pydev.parser.jython.ast.commentType; import org.python.pydev.parser.jython.ast.decoratorsType; import org.python.pydev.parser.visitors.NodeUtils; import org.python.pydev.parser.visitors.scope.ASTEntry; import org.python.pydev.parser.visitors.scope.ASTEntryWithChildren; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.ui.UIConstants; import com.aptana.shared_core.string.FastStringBuffer; public class ParsedItem implements Comparable<Object> { private ParsedItem parent; private ParsedItem[] children; private ASTEntryWithChildren astThis; //may be null if root private ASTEntryWithChildren[] astChildrenEntries; private ErrorDescription errorDesc; /** * Constructor for a child with valid ast. */ public ParsedItem(ParsedItem parent, ASTEntryWithChildren root, ASTEntryWithChildren[] astChildren) { this(astChildren, null); this.parent = parent; this.astThis = root; } /** * Constructor for a child with error. */ public ParsedItem(ParsedItem parent, ErrorDescription errorDesc) { this.parent = parent; this.setErrorDesc(errorDesc); } /** * Constructor for the root. */ public ParsedItem(ASTEntryWithChildren[] astChildren, ErrorDescription errorDesc) { this.astChildrenEntries = astChildren; this.setErrorDesc(errorDesc); } public ASTEntryWithChildren getAstThis() { return astThis; } public void setAstThis(ASTEntryWithChildren astThis) { this.setAstThis(astThis, null); } public void setAstThis(ASTEntryWithChildren astThis, ASTEntryWithChildren[] astChildrenEntries) { this.toStringCache = null; this.astThis = astThis; if (astChildrenEntries != null) { this.astChildrenEntries = astChildrenEntries; this.children = null; //the children must be recalculated... } } public ASTEntryWithChildren[] getAstChildrenEntries() { return astChildrenEntries; } public ErrorDescription getErrorDesc() { return errorDesc; } public void setErrorDesc(ErrorDescription errorDesc) { if (this.errorDesc == null && errorDesc == null) { return; // don't clear the caches } this.toStringCache = null; this.errorDesc = errorDesc; } private static final int QUALIFIER_PUBLIC = 0; private static final int QUALIFIER_PROTECTED = 1; private static final int QUALIFIER_PRIVATE = 2; private static int qualifierFromName(String name) { if (name.startsWith("__")) { if (!name.endsWith("__")) { return QUALIFIER_PRIVATE; } else { return QUALIFIER_PUBLIC; } } else if (name.startsWith("_")) { return QUALIFIER_PROTECTED; } else { return QUALIFIER_PUBLIC; } } // returns images based upon element type public Image getImage() { ImageCache imageCache = PydevPlugin.getImageCache(); if (astThis == null) { return imageCache.get(UIConstants.ERROR); } SimpleNode token = astThis.node; return getImageForNode(imageCache, token, astThis.parent); } public static Image getImageForNode(ImageCache imageCache, SimpleNode token, ASTEntry parent) { if (token instanceof ClassDef) { String className = NodeUtils.getNameFromNameTok((NameTok) ((ClassDef) token).name); switch (qualifierFromName(className)) { case QUALIFIER_PROTECTED: return imageCache.getImageDecorated(UIConstants.CLASS_ICON, UIConstants.PROTECTED_ICON, ImageCache.DECORATION_LOCATION_BOTTOM_RIGHT); case QUALIFIER_PRIVATE: return imageCache.getImageDecorated(UIConstants.CLASS_ICON, UIConstants.PRIVATE_ICON, ImageCache.DECORATION_LOCATION_BOTTOM_RIGHT); default: return imageCache.get(UIConstants.CLASS_ICON); } } else if (token instanceof FunctionDef) { FunctionDef functionDefToken = (FunctionDef) token; String methodName = NodeUtils.getNameFromNameTok((NameTok) ((FunctionDef) token).name); String qualifierIcon = null; switch (qualifierFromName(methodName)) { case QUALIFIER_PRIVATE: qualifierIcon = UIConstants.PRIVATE_ICON; break; case QUALIFIER_PROTECTED: qualifierIcon = UIConstants.PROTECTED_ICON; break; } String decorationIcon = null; if (functionDefToken.decs != null) { for (decoratorsType decorator : functionDefToken.decs) { if (decorator.func instanceof Name) { Name decoratorFuncName = (Name) decorator.func; if (decoratorFuncName.id.equals("staticmethod")) { decorationIcon = UIConstants.DECORATION_STATIC; } else if (decoratorFuncName.id.equals("classmethod")) { decorationIcon = UIConstants.DECORATION_CLASS; } } } } if (qualifierIcon != null) { //it's OK if the decorationIcon is null as that's properly handled in getImageDecorated. return imageCache.getImageDecorated(UIConstants.METHOD_ICON, qualifierIcon, ImageCache.DECORATION_LOCATION_BOTTOM_RIGHT, decorationIcon, ImageCache.DECORATION_LOCATION_TOP_RIGHT); } else if (decorationIcon != null) { return imageCache.getImageDecorated(UIConstants.METHOD_ICON, decorationIcon, ImageCache.DECORATION_LOCATION_TOP_RIGHT); } return imageCache.get(UIConstants.METHOD_ICON); } else if (token instanceof Import) { return imageCache.get(UIConstants.IMPORT_ICON); } else if (token instanceof If && NodeUtils.isIfMAinNode((If) token)) { return imageCache.get(UIConstants.MAIN_FUNCTION_ICON); } else if (token instanceof ImportFrom) { return imageCache.get(UIConstants.IMPORT_ICON); } else if (token instanceof commentType) { return imageCache.get(UIConstants.COMMENT); } else if (token instanceof Attribute || token instanceof Name || token instanceof NameTok) { String name = null; if (token instanceof Attribute) { Attribute attributeToken = (Attribute) token; name = NodeUtils.getNameFromNameTok((NameTok) (attributeToken).attr); } else if (token instanceof Name) { Name nameToken = (Name) token; name = nameToken.id; } else { NameTok nameTokToken = (NameTok) token; name = NodeUtils.getNameFromNameTok(nameTokToken); } String image; if (name.startsWith("__")) { if (name.endsWith("__")) { image = UIConstants.PUBLIC_ATTR_ICON; } else { image = UIConstants.PRIVATE_FIELD_ICON; } } else if (name.startsWith("_")) { image = UIConstants.PROTECTED_FIELD_ICON; } else { image = UIConstants.PUBLIC_ATTR_ICON; } if (parent != null && parent.node != null && parent.node instanceof ClassDef) { return imageCache.getImageDecorated(image, UIConstants.DECORATION_CLASS); } return imageCache.get(image); } else { return imageCache.get(UIConstants.ERROR); } } public ParsedItem[] getChildren() { if (children != null) { return children; } if (astChildrenEntries == null) { astChildrenEntries = new ASTEntryWithChildren[0]; } ArrayList<ParsedItem> items = new ArrayList<ParsedItem>(); //only the root can have an error as a child (from there on, the errors don't contain inner errors) if (this.parent == null && errorDesc != null && errorDesc.message != null) { items.add(new ParsedItem(this, errorDesc)); } for (ASTEntryWithChildren c : astChildrenEntries) { items.add(new ParsedItem(this, c, c.getChildren())); } children = items.toArray(new ParsedItem[items.size()]); return children; } public ParsedItem getParent() { return parent; } /** * When null, it must be rebuilt! */ private String toStringCache; public String toString() { if (toStringCache == null) { toStringCache = calcToString(); } return toStringCache; } private String calcToString() { if (errorDesc != null && errorDesc.message != null) { return errorDesc.message; } if (astThis == null) { return "null"; } else if (astThis.node instanceof If && NodeUtils.isIfMAinNode((If) astThis.node)) { return "__main__"; } else if (astThis.node instanceof Import) { aliasType[] imports = ((Import) astThis.node).names; FastStringBuffer retVal = new FastStringBuffer(); for (int i = 0; i < imports.length; i++) { aliasType aliasType = imports[i]; //as ... if (aliasType.asname != null) { retVal.append(((NameTok) aliasType.asname).id); retVal.append(" = "); } retVal.append(((NameTok) aliasType.name).id); retVal.append(", "); } //delete the last 2 chars retVal.deleteLast(); retVal.deleteLast(); return retVal.toString(); } else if (astThis.node instanceof ImportFrom) { // from wxPython.wx import * ImportFrom importToken = (ImportFrom) astThis.node; StringBuffer modules = new StringBuffer(); for (int i = 0; i < importToken.names.length; i++) { aliasType aliasType = importToken.names[i]; //as ... if (aliasType.asname != null) { modules.append(((NameTok) aliasType.asname).id); modules.append(" = "); } modules.append(((NameTok) aliasType.name).id); modules.append(","); } if (modules.length() == 0) { modules.append("*,"); //the comma will be deleted } modules.deleteCharAt(modules.length() - 1); return modules.toString() + " (" + ((NameTok) importToken.module).id + ")"; } else if (astThis.node instanceof commentType) { commentType type = (commentType) astThis.node; String rep = type.id.trim(); rep = StringUtils.split(rep, '\n').get(0); rep = StringUtils.split(rep, '\r').get(0); rep = rep.substring(1); rep = StringUtils.rightTrim(rep, '-'); return StringUtils.leftTrim(rep, '-'); } else { return NodeUtils.getFullRepresentationString(astThis.node); } } /** * @return rank for sorting ParserItems. When comparing * two items, first we compare class ranking, then titles */ public int getClassRanking() { int rank; if (astThis == null || (errorDesc != null && errorDesc.message != null)) { rank = -2; } else if (astThis.node instanceof ImportFrom) { rank = 0; } else if (astThis.node instanceof Import) { rank = 1; } else if (astThis.node instanceof commentType) { rank = -1; } else { rank = 10; } return rank; } public int compareTo(Object o) { if (!(o instanceof ParsedItem)) { return 0; } ParsedItem item = (ParsedItem) o; int myRank = getClassRanking(); int rank = item.getClassRanking(); if (myRank == rank) { if (rank == -1) { return astThis.node.beginLine < item.astThis.node.beginLine ? -1 : 1; } else { return toString().compareTo(item.toString()); } } else { return (myRank < rank ? -1 : 1); } } /** * Updates the structure of this parsed item (old structure) to be the same as the structure in the passed * parsed item (new structure) trying to reuse the existing children (if possible). * * This is usually only called when the structure actually changes (different number of nodes). A common case * is having a syntax error... */ public void updateTo(ParsedItem updateToItem) { this.toStringCache = null; this.errorDesc = updateToItem.errorDesc; this.astThis = updateToItem.astThis; this.astChildrenEntries = updateToItem.astChildrenEntries; ParsedItem[] newStructureChildren = updateToItem.getChildren(); //handle special cases... if (this.children == null) { this.children = newStructureChildren; return; } if (newStructureChildren.length == 0 || this.children.length == 0) { //nothing to actually update... (just set the new children directly) this.children = newStructureChildren; return; } ArrayList<ParsedItem> newChildren = new ArrayList<ParsedItem>(); //ok, something there... let's update the requested children... //(trying to maintain the existing nodes were possible) HashMap<String, List<ParsedItem>> childrensCache = new HashMap<String, List<ParsedItem>>(); for (ParsedItem existing : this.children) { String s = existing.toString(); List<ParsedItem> list = childrensCache.get(s); if (list == null) { list = new ArrayList<ParsedItem>(); childrensCache.put(s, list); } list.add(existing); } for (ParsedItem n : newStructureChildren) { ParsedItem similarChild = getSimilarChild(n, childrensCache); if (similarChild != null) { similarChild.updateTo(n); n = similarChild; } else { n.parent = this; } newChildren.add(n); } this.children = newChildren.toArray(new ParsedItem[newChildren.size()]); } private ParsedItem getSimilarChild(ParsedItem n, HashMap<String, List<ParsedItem>> childrensCache) { //try to get a similar child from the 'cache' List<ParsedItem> list = childrensCache.get(n.toString()); if (list != null && list.size() > 0) { return list.remove(0); } return null; } }