/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.modules.jackpot.cmds; import com.sun.source.tree.AssignmentTree; import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompoundAssignmentTree; import com.sun.source.tree.IdentifierTree; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.ModifiersTree; import com.sun.source.tree.Tree; import com.sun.source.tree.UnaryTree; import com.sun.source.tree.VariableTree; import com.sun.source.util.TreePath; import com.sun.source.util.TreePathScanner; import java.io.IOException; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import org.netbeans.api.jackpot.QueryContext; import org.netbeans.api.jackpot.QueryOperations; import org.netbeans.api.jackpot.TreePathTransformer; import org.netbeans.api.java.source.CancellableTask; import org.netbeans.api.java.source.CompilationController; import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.ElementHandle; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.TreePathHandle; import org.netbeans.api.java.source.WorkingCopy; import org.openide.ErrorManager; /** * Minimizes field access modifiers based on project usage.

This transformer * demonstrates two-pass operation within the Java Source framework. The first * pass locates all variable declarations and references, and stores them in a * map using persistent tree and element references. This pass is started * directly by this transformer in its init() method, which is * invoked prior to transformer execution. The second pass runs as a * regular transformation, looking up each compilation unit's variables to see * what modifications it needs and applies them. The ordering of these two * passes is important, because only modifications made by the regular * transformation pass are displayed to the user and committed to the source files. */ public class MinimizeFieldAccess extends TreePathTransformer { private JavaSource javaSource; private Map,VariableReferences> refs; private static final int SETUSE = 1; private static final int GETUSE = 2; private static final int CLASSSHIFT = 4; private static final int PACKAGESHIFT = 2; private static final int WORLDSHIFT = 0; private static final int DECLARATION = 1 << 6; private static final int worldMask = (SETUSE | GETUSE) << WORLDSHIFT; private static final int packageMask = (SETUSE | GETUSE) << PACKAGESHIFT; // properties set by PropertySheetInfo private boolean ignorePackagePrivate; private boolean ignoreConstants; public boolean getIgnorePackagePrivate() { return ignorePackagePrivate; } public void setIgnorePackagePrivate(boolean b) { ignorePackagePrivate = b; } public boolean getIgnoreConstants() { return ignoreConstants; } public void setIgnoreConstants(boolean b) { ignoreConstants = b; } @Override public void init(QueryContext context, JavaSource javaSource) { super.init(context, javaSource); this.javaSource = javaSource; refs = new HashMap,VariableReferences>(); scanFields(); } @Override public void destroy() { refs = null; super.destroy(); } @Override public Void visitVariable(VariableTree tree, Object unused) { WorkingCopy wc = getWorkingCopy(); VariableElement var = (VariableElement)wc.getTrees().getElement(getCurrentPath()); VariableReferences varRefs = var != null ? refs.get(ElementHandle.create(var)) : null; if (varRefs != null) { // if "interesting" variable Set flags = var.getModifiers(); Set newFlags = EnumSet.noneOf(Modifier.class); newFlags.addAll(flags); TypeElement owner = QueryOperations.getDeclaringClass(var); if (flags.contains(Modifier.PUBLIC) && hasPublicUsage(varRefs.usage)) { // see if public usage is limited to subclasses boolean onlyProtected = true; Types types = wc.getTypes(); TypeMirror classType = owner.asType(); for (Reference r : varRefs.references) if (!types.isSubtype(getClassType(r.element.resolve(wc)), classType)) { onlyProtected = false; break; } if (onlyProtected) { newFlags.remove(Modifier.PUBLIC); newFlags.add(Modifier.PROTECTED); } } if ((flags.contains(Modifier.PUBLIC) || flags.contains(Modifier.PROTECTED)) && !hasPublicUsage(varRefs.usage)) { newFlags.remove(Modifier.PUBLIC); newFlags.remove(Modifier.PROTECTED); } if (!ignorePackagePrivate && !hasPublicUsage(varRefs.usage) && !hasPackageUsage(varRefs.usage)) { assert !newFlags.contains(Modifier.PUBLIC) && !newFlags.contains(Modifier.PROTECTED); newFlags.add(Modifier.PRIVATE); } if (!flags.equals(newFlags)) { ModifiersTree oldMods = tree.getModifiers(); ModifiersTree newMods = wc.getTreeMaker().Modifiers(newFlags, oldMods.getAnnotations()); String note = oldMods.toString() + " => " + newMods.toString(); addChange(new TreePath(getCurrentPath(), oldMods), newMods, note); } } return super.visitVariable(tree, unused); } private void scanFields() { final ReferenceScanner scanner = new ReferenceScanner(); CancellableTask task = new CancellableTask() { private boolean cancelled = false; public void run(CompilationController cc) throws IOException { if (!cancelled) { cc.toPhase(JavaSource.Phase.RESOLVED); scanner.scan(cc.getCompilationUnit(), cc); } } public void cancel() { cancelled = true; } }; try { javaSource.runUserActionTask(task, false); } catch (IOException e) { ErrorManager.getDefault().notify(e); } } private Element getReferringElement(TreePath path, CompilationInfo info) { Element e = info.getTrees().getElement(path); return e != null ? e : getReferringElement(path.getParentPath(), info); } static TypeMirror getClassType(Element e) { while (!(e instanceof TypeElement)) e = e.getEnclosingElement(); return e.asType(); } static boolean hasPublicUsage(int usage) { return (usage & worldMask) != 0; } static boolean hasPackageUsage(int usage) { return (usage & packageMask) != 0; } /** * A description of a references to a variable element. */ private static class VariableReferences { VariableElement var; TreePathHandle declaration; int usage; List references; // these elements are only valid when the ReferenceScanner is scanning TypeElement cls; PackageElement pkg; VariableReferences(VariableElement var) { this.var = var; cls = QueryOperations.getDeclaringClass(var); pkg = QueryOperations.getDeclaringPackage(cls); references = new ArrayList(); } } /** * A reference to an element by a single tree node. */ private static class Reference { ElementHandle element; TreePathHandle tree; } /** * A scanner which finds all variable declarations and references in a project. */ private class ReferenceScanner extends TreePathScanner { private TypeElement currentClass; private PackageElement currentPackage; private boolean isInteresting(Element sym, CompilationInfo info) { // ignore non-instance variables if (sym == null || !(sym instanceof VariableElement) || !(sym.getEnclosingElement() instanceof TypeElement)) return false; TypeElement owner = (TypeElement)sym.getEnclosingElement(); // ignore compiler-generated fields if (info.getElementUtilities().isSynthetic(sym)) return false; // ignore enum fields, whose flags are synthetic if (owner.getKind() == ElementKind.ENUM) return false; // ignore fields in anonymous classes, since they aren't accessible if (owner.getNestingKind() == NestingKind.ANONYMOUS) return false; // ignore static constants if possible Set flags = sym.getModifiers(); if (ignoreConstants && flags.contains(Modifier.STATIC) && flags.contains(Modifier.FINAL)) return false; // ignore non-public fields if possible if (ignorePackagePrivate && !flags.contains(Modifier.PUBLIC) && !flags.contains(Modifier.PROTECTED)) return false; return true; } private void add(Element e, TreePath path, int usage, CompilationInfo info) { if (!isInteresting(e, info)) return; assert e instanceof VariableElement; VariableElement var = (VariableElement)e; TreePathHandle handle = TreePathHandle.create(path, info); VariableReferences uses = refs.get(var); if (uses == null) { uses = new VariableReferences(var); if ((usage & DECLARATION) != 0) { assert path.getLeaf() instanceof VariableTree; uses.declaration = handle; } refs.put(ElementHandle.create(var), uses); } Reference ref = new Reference(); ref.element = ElementHandle.create(getReferringElement(path, info)); ref.tree = handle; uses.references.add(ref); int flags = usage << (currentClass == uses.cls ? CLASSSHIFT : currentPackage == uses.pkg ? PACKAGESHIFT : WORLDSHIFT); uses.usage |= flags; } @Override public Void visitClass(ClassTree tree, CompilationInfo info) { currentClass = (TypeElement)info.getTrees().getElement(getCurrentPath()); currentPackage = info.getElements().getPackageOf(currentClass); super.visitClass(tree, info); currentClass = null; currentPackage = null; return null; } @Override public Void visitAssignment(AssignmentTree tree, CompilationInfo info) { Tree lhs = tree.getVariable(); if (lhs instanceof MemberSelectTree) { MemberSelectTree t = (MemberSelectTree)lhs; scan(t.getExpression(), info); TreePath path = new TreePath(getCurrentPath(), t); Element e = info.getTrees().getElement(path); add(e, path, SETUSE, info); } else if (lhs instanceof IdentifierTree) { TreePath path = getCurrentPath(); Element e = info.getTrees().getElement(path); add(e, path, SETUSE, info); } else scan(lhs, info); return scan(tree.getExpression(), info); } @Override public Void visitCompoundAssignment(CompoundAssignmentTree tree, CompilationInfo info) { Tree lhs = tree.getVariable(); if (lhs instanceof MemberSelectTree) { MemberSelectTree t = (MemberSelectTree)lhs; scan(t.getExpression(), info); TreePath path = new TreePath(getCurrentPath(), t); Element e = info.getTrees().getElement(path); add(e, path, SETUSE | GETUSE, info); } else if (lhs instanceof IdentifierTree) { TreePath path = new TreePath(getCurrentPath(), lhs); Element e = info.getTrees().getElement(path); add(e, path, SETUSE | GETUSE, info); } else scan(lhs, info); return scan(tree.getExpression(), info); } @Override public Void visitUnary(UnaryTree tree, CompilationInfo info) { Tree arg = tree.getExpression(); switch (tree.getKind()) { case PREFIX_INCREMENT: case PREFIX_DECREMENT: case POSTFIX_INCREMENT: case POSTFIX_DECREMENT: if (arg instanceof MemberSelectTree) { MemberSelectTree t = (MemberSelectTree)arg; scan(t.getExpression(), info); TreePath path = new TreePath(getCurrentPath(), t); Element e = info.getTrees().getElement(path); add(e, path, SETUSE | GETUSE, info); } else if (arg instanceof IdentifierTree) { TreePath path = new TreePath(getCurrentPath(), arg); Element e = info.getTrees().getElement(path); add(e, path, SETUSE | GETUSE, info); } else scan(arg, info); break; default: scan(arg, info); } return null; } @Override public Void visitIdentifier(IdentifierTree tree, CompilationInfo info) { TreePath path = getCurrentPath(); Element e = info.getTrees().getElement(path); add(e, path, GETUSE, info); return super.visitIdentifier(tree, info); } @Override public Void visitVariable(VariableTree tree, CompilationInfo info) { TreePath path = getCurrentPath(); Element e = info.getTrees().getElement(path); int flags = DECLARATION; if (tree.getInitializer() != null) flags |= SETUSE; add(e, path, flags, info); scan(tree.getInitializer(), info); return null; } @Override public Void visitMemberSelect(MemberSelectTree tree, CompilationInfo info) { TreePath path = getCurrentPath(); Element e = info.getTrees().getElement(path); add(e, path, GETUSE, info); return super.visitMemberSelect(tree, info); } } }