diff --git a/src/META-INF/plugin.xml b/src/META-INF/plugin.xml index 5055230..05e8f51 100644 --- a/src/META-INF/plugin.xml +++ b/src/META-INF/plugin.xml @@ -68,8 +68,8 @@ - + " + refactorings.get(ent)); + } + + MRI alg2 = new MRI(entities, properties.getAllClasses()); + System.out.println("\nStarting MMRI..."); + //alg2.printTableDistances(); + Map refactorings2 = alg2.run(); + System.out.println("Finished MMRI"); + for (String method : refactorings2.keySet()) { + System.out.println(method + " --> " + refactorings2.get(method)); + } + + Set common = new HashSet(refactorings.keySet()); + common.retainAll(refactorings2.keySet()); + System.out.println("Common for ARI and CCDA: "); + for (String move : common) { + System.out.print(move + " to "); + System.out.print(refactorings.get(move)); + if (!refactorings2.get(move).equals(refactorings.get(move))) { + System.out.print(" vs " + refactorings2.get(move)); + } + System.out.println(); + } + System.out.println(); + + AKMeans alg5 = new AKMeans(entities, 50); + System.out.println("\nStarting AKMeans..."); + Map refactorings5 = alg5.run(); + System.out.println("Finished AKMeans"); + for (String method : refactorings5.keySet()) { + System.out.println(method + " --> " + refactorings5.get(method)); + } + + Set refactoringsARIEC = new HashSet(refactorings5.keySet()); + refactoringsARIEC.retainAll(refactorings2.keySet()); + System.out.println("Common for ARI and EC: "); + for (String move : refactoringsARIEC) { + System.out.print(move + " to "); + System.out.print(refactorings5.get(move)); + if (!refactorings2.get(move).equals(refactorings5.get(move))) { + System.out.print(" vs " + refactorings2.get(move)); + } + System.out.println(); + } + System.out.println(); + + + HAC alg3 = new HAC(entities); + System.out.println("\nStarting HAC..."); + refactorings = alg3.run(); + System.out.println("Finished HAC"); + for (String method : refactorings.keySet()) { + System.out.println(method + " --> " + refactorings.get(method)); + } + + ARI alg4 = new ARI(entities); + System.out.println("\nStarting ARI..."); + refactorings = alg4.run(); + System.out.println("Finished ARI"); + for (String method : refactorings.keySet()) { + System.out.println(method + " --> " + refactorings.get(method)); + } + + + + } + }.execute(profile, metricsRun); + } + + @Override + @Nullable + protected JComponent getAdditionalActionSettings(Project project, BaseAnalysisActionDialog dialog) { + return new ProfileSelectionPanel(project); + } +} diff --git a/src/vector/model/AKMeans.java b/src/vector/model/AKMeans.java new file mode 100644 index 0000000..5dcb173 --- /dev/null +++ b/src/vector/model/AKMeans.java @@ -0,0 +1,195 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.sixrr.metrics.MetricCategory; + +import java.util.*; + +/** + * Created by Kivi on 17.05.2017. + */ +public class AKMeans { + public AKMeans(List entityList, int steps) { + this.steps = steps; + for (Entity e : entityList) { + if (e.getCategory() != MetricCategory.Class) { + points.add(e); + communityIds.put(e, ""); + entityByName.put(e.getName(), e); + } else { + N++; + allClasses.add((PsiClass) e.getPsiElement()); + } + } + } + + private void initializeCenters() { + List entities = new ArrayList(points); + Collections.shuffle(entities); + + for (int i = 0; i < N; ++i) { + Entity center = entities.get(i); + Set simpleCommunity = new HashSet(); + simpleCommunity.add(center); + communities.put(center.getName(), simpleCommunity); + } + } + + public Map run() { + Map refactorings = new HashMap(); + + initializeCenters(); + for (int step = 0; step < steps; ++step) { + boolean moving = false; + Map newCommunities = new HashMap(); + for (Entity entity : points) { + String newCenter = findNearestCommunity(entity); + if (newCenter.equals("")) { + continue; + } + if (!newCenter.equals(communityIds.get(entity))) { + moving = true; + } + + newCommunities.put(entity, newCenter); + if (!communityIds.get(entity).equals(newCenter)) { + System.out.println("Move " + entity.getName() + " to " + newCenter + ", " + distToCommunity(entity, newCenter)); + } + //moveToCommunity(entity, newCenter); + } + + for (Entity entity : newCommunities.keySet()) { + moveToCommunity(entity, newCommunities.get(entity)); + } + System.out.println(); + + if (!moving) { + break; + } + } + + for (String center : communities.keySet()) { + String newName = receiveClassName(center); + for (Entity entity : communities.get(center)) { + if (!entity.getClassName().equals(newName)) { + refactorings.put(entity.getName(), newName); + } + } + } + + return refactorings; + } + + private String receiveClassName(String center) { + String name = ""; + Integer maxClassCount = 0; + Map classCounts = new HashMap(); + for (Entity entity : communities.get(center)) { + String className = entity.getClassName(); + if (!classCounts.containsKey(className)) { + classCounts.put(className, 0); + } + + Integer count = classCounts.get(className); + count++; + classCounts.put(className, count); + } + + for (String className : classCounts.keySet()) { + if (maxClassCount < classCounts.get(className)) { + maxClassCount = classCounts.get(className); + name = className; + } + } + + if (name.equals("")) { + newClassCount++; + name = "NewClass" + newClassCount; + } + + return name; + } + + private String findNearestCommunity(Entity entity) { + double minD = Double.MAX_VALUE; + String id = ""; + for (String center : communities.keySet()) { + double d = distToCommunity(entity, center); + if (!canMove(entity, center)) { + d = Double.MAX_VALUE; + } + if (d < minD) { + minD = d; + id = center; + } + } + + return id; + } + + private boolean canMove(Entity entity, String center) { + if (entity.getCategory() != MetricCategory.Method) { + return true; + } + + PsiMethod method = (PsiMethod) entity.getPsiElement(); + Set cluster = communities.get(center); + for (Entity e : cluster) { + if (e.getCategory() != MetricCategory.Method) { + continue; + } + + PsiMethod component = (PsiMethod) e.getPsiElement(); + + Set supers = PSIUtil.getAllSupers(component, allClasses); + supers.retainAll(PSIUtil.getAllSupers(method)); + if (!supers.isEmpty()) { + return false; + } + } + + return true; + } + + private double distToCommunity(Entity entity, String center) { + double minD = 0.0; + for (Entity point : communities.get(center)) { + double d = entity.dist(point); + minD = Math.max(d, minD); + } + + return minD; + } + + private void moveToCommunity(Entity entity, String id) { + communities.get(id).add(entity); + communityIds.put(entity, id); + } + + private List points = new ArrayList(); + //private Set<> + private Map> communities = new HashMap>(); + private Map communityIds = new HashMap(); + private Map entityByName = new HashMap(); + private Set allClasses = new HashSet(); + private int N = 0; + private int steps = 0; + private int newClassCount = 0; +} diff --git a/src/vector/model/ARI.java b/src/vector/model/ARI.java new file mode 100644 index 0000000..1e9873a --- /dev/null +++ b/src/vector/model/ARI.java @@ -0,0 +1,152 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.sixrr.metrics.MetricCategory; + +import java.util.*; + +/** + * Created by Kivi on 16.05.2017. + */ +public class ARI { + public ARI(List entityList) { + methodsAndFields = new ArrayList(); + classes = new ArrayList(); + allClasses = new HashSet(); + for (Entity entity : entityList) { + String name = entity.getName(); + entityByName.put(name, entity); + if (entity.getCategory() == MetricCategory.Class) { + classes.add((ClassEntity) entity); + allClasses.add((PsiClass) entity.getPsiElement()); + } else { + methodsAndFields.add(entity); + } + } + } + + public Map run() { + Map refactorings = new HashMap(); + + for (Entity method : methodsAndFields) { + double minD = Double.MAX_VALUE; + int classId = -1; + for (int i = 0; i < classes.size(); i++) { + ClassEntity cl = classes.get(i); + if (method.getCategory() == MetricCategory.Method) { + PsiClass moveFromClass = ((PsiMethod) method.getPsiElement()).getContainingClass(); + PsiClass moveToClass = (PsiClass) cl.getPsiElement(); + + Set supersTo = PSIUtil.getAllSupers(moveToClass, allClasses);//new HashSet(Arrays.asList(moveToClass.getSupers())); + boolean isSuper = false; + + for (PsiClass sup : supersTo) { + if (sup.equals(moveFromClass)) { + isSuper = true; + break; + } + } + + Set supersFrom = PSIUtil.getAllSupers(moveFromClass, allClasses);//new HashSet(Arrays.asList(moveFromClass.getSupers())); + for (PsiClass sup : supersFrom) { + if (sup.equals(moveToClass)) { + isSuper = true; + break; + } + } + supersFrom.retainAll(supersTo); + boolean isOverride = false; + + if (isSuper) { + continue; + } + + for (PsiClass sup : supersFrom) { + PsiMethod[] methods = sup.getMethods(); + for (PsiMethod m : methods) { + if (m.equals(method)) { + isOverride = true; + break; + } + } + + if (isOverride) { + break; + } + } + + if (isOverride) { + continue; + } + } + + double d = method.dist(cl); + + if (d < minD) { + minD = d; + classId = i; + } + } + + if (classId == -1) { + System.out.println("HOW??? " + method.getName()); + } + + ClassEntity cl = classes.get(classId); + if (communityIds.containsKey(cl)) { + putMethodOrField(method, cl); + } else { + createCommunity(method, cl); + } + + System.out.println("Add " + method.getName() + " to " + cl.getName()); + } + + for (Entity ent : methodsAndFields) { + if (!ent.getClassName().equals(communityIds.get(ent))) { + refactorings.put(ent.getName(), communityIds.get(ent)); + } + } + + return refactorings; + } + + private void createCommunity(Entity method, Entity cl) { + communityIds.put(cl, cl.getName()); + communityIds.put(method, cl.getName()); + Set newCommunity = new HashSet(); + newCommunity.add(method); + newCommunity.add(cl); + communities.put(cl.getName(), newCommunity); + } + + private void putMethodOrField(Entity method, Entity cl) { + communityIds.put(method, cl.getName()); + communities.get(cl.getName()).add(method); + } + + private HashMap> communities = new HashMap>(); + private HashMap communityIds = new HashMap(); + + private List methodsAndFields; + private List classes; + private Set allClasses; + private HashMap entityByName = new HashMap(); +} diff --git a/src/vector/model/CCDA.java b/src/vector/model/CCDA.java new file mode 100644 index 0000000..3f05bb2 --- /dev/null +++ b/src/vector/model/CCDA.java @@ -0,0 +1,249 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiMethod; +import com.sixrr.metrics.MetricCategory; +import com.sixrr.metrics.utils.MethodUtils; + +import java.util.*; + +/** + * Created by Kivi on 02.05.2017. + */ +public class CCDA { + public CCDA(List entities) { + communityId = new HashMap(); + idCommunity = new ArrayList(); + nodes = new ArrayList(); + q = 0.0; + for (Entity ent : entities) { + if (ent.getCategory().equals(MetricCategory.Class)) { + Integer id = communityId.size() + 1; + communityId.put(ent.getName(), id); + idCommunity.add(ent.getName()); + } + } + for (Entity ent : entities) { + if (!ent.getCategory().equals(MetricCategory.Class) && communityId.containsKey(ent.getClassName())) { + nodes.add(ent); + communityId.put(ent.getName(), communityId.get(ent.getClassName())); + } + } + + aCoefficients = new ArrayList(Collections.nCopies(idCommunity.size() + 1, 0)); + buildGraph(); + } + + public void applyRefactorings(Map refactorings) { + for (String entity : refactorings.keySet()) { + String com = refactorings.get(entity); + if (!communityId.containsKey(com)) { + communityId.put(com, communityId.size() + 1); + idCommunity.add(com); + } + communityId.put(entity, communityId.get(com)); + } + } + + public void buildGraph() { + graph = new HashMap>(); + for (Entity ent : nodes) { + RelevantProperties rp = ent.getRelevantProperties(); + HashSet neighbors = new HashSet(); + if (graph.containsKey(ent.getName())) { + neighbors = graph.get(ent.getName()); + } + Set methods = rp.getAllMethods(); + for (PsiMethod method : methods) { + String name = MethodUtils.calculateSignature(method); + if (name.equals(ent.getName()) || !communityId.containsKey(name)) { + continue; + } + + neighbors.add(name); + if (!graph.containsKey(name)) { + graph.put(name, new HashSet()); + } + + graph.get(name).add(ent.getName()); + } + + for (PsiField field : rp.getAllFields()) { + String name = field.getContainingClass().getQualifiedName() + "." + field.getName(); + if (name.equals(ent.getName()) || !communityId.containsKey(name)) { + continue; + } + neighbors.add(name); + if (!graph.containsKey(name)) { + graph.put(name, new HashSet()); + } + + graph.get(name).add(ent.getName()); + } + + graph.put(ent.getName(), neighbors); + } + + System.out.println("Graph built:"); + for (String ent : graph.keySet()) { + System.out.println(ent); + for (String neighbor : graph.get(ent)) { + System.out.println(" -> " + neighbor); + } + } + System.out.println("-----"); + } + + public Map run() { + Map refactorings = new HashMap(); + q = calculateQualityIndex(); + System.out.println(q); + + Double dq = 1.0; + System.out.println("Running..."); + while (dq > eps) { + dq = 0.0; + int id = -1; + Integer community = -1; + for (int i = 0; i < nodes.size(); ++i) { + Entity ent = nodes.get(i); + for (int j = 1; j <= idCommunity.size(); ++j) { + if (j == communityId.get(ent.getName())) { + continue; + } + Double curdq = move(ent, j, true); + if (curdq > dq) { + dq = curdq; + id = i; + community = j; + } + } + } + + if (dq > eps) { + refactorings.put(nodes.get(id).getName(), idCommunity.get(community - 1)); + move(nodes.get(id), community, false); + communityId.put(nodes.get(id).getName(), community); + System.out.println("move " + nodes.get(id).getName() + " to " + idCommunity.get(community - 1)); + System.out.println("quality index is now: " + q); + System.out.println(); + } + } + + return refactorings; + } + + public Double move(Entity ent, Integer to, boolean rollback) { + String name = ent.getName(); + Integer from = communityId.get(name); + Double dq = 0.0; + dq += Math.pow(aCoefficients.get(from) * 1.0 / edges, 2); + dq += Math.pow(aCoefficients.get(to) * 1.0 / edges, 2); + + Integer aFrom = 0; + Integer aTo = 0; + Integer de = 0; + + for (String neighbor : graph.get(name)) { + if (communityId.get(neighbor).equals(from)) { + de--; + } else { + aFrom++; + } + + if (communityId.get(neighbor).equals(to)) { + de++; + } else { + aTo++; + } + } + + aFrom = aCoefficients.get(from) - aFrom; + aTo = aCoefficients.get(to) + aTo; + + if (!rollback) { + aCoefficients.add(from, aFrom); + aCoefficients.add(to, aTo); + } + + dq += de * 1.0 / edges; + dq -= Math.pow(aFrom * 1.0 / edges, 2); + dq -= Math.pow(aTo * 1.0 / edges, 2); + + if (!rollback) { + q += dq; + } + + return dq; + } + + public Double calculateQualityIndex() { + System.out.println("Calculating Q..."); + Double qI = 0.0; + edges = 0; + for (String node : graph.keySet()) { + edges += graph.get(node).size(); + } + + edges /= 2; + System.out.println(edges); + for (int i = 1; i <= idCommunity.size(); ++i) { + String com = idCommunity.get(i - 1); + Integer e = 0; + Integer a = 0; + + for (String node : graph.keySet()) { + if (!communityId.containsKey(node)) { + System.out.println("ERROR: unknown community"); + System.out.println(node); + } + + if (!communityId.get(node).equals(communityId.get(com))) { + continue; + } + for (String neighbor : graph.get(node)) { + if (communityId.get(neighbor).equals(communityId.get(com))) { + e++; + } else { + a++; + } + } + } + + e /= 2; + a += e; + aCoefficients.add(i, a); + qI += (e * 1.0 / edges) - Math.pow((a * 1.0 / edges), 2); + } + + return qI; + } + + private Map communityId; + private ArrayList idCommunity; + private ArrayList aCoefficients; + private Map> graph; + + private List nodes; + + private Double q; + private Integer edges; + + private static Double eps = 5e-4; +} diff --git a/src/vector/model/ClassEntity.java b/src/vector/model/ClassEntity.java new file mode 100644 index 0000000..0429a92 --- /dev/null +++ b/src/vector/model/ClassEntity.java @@ -0,0 +1,77 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.intellij.psi.PsiClass; +import com.sixrr.metrics.Metric; +import com.sixrr.metrics.MetricCategory; +import com.sixrr.metrics.metricModel.MetricsResult; +import com.sixrr.metrics.metricModel.MetricsRunImpl; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Created by Kivi on 04.04.2017. + */ +public class ClassEntity extends Entity { + public ClassEntity(String entity_name, MetricsRunImpl metricsRun, PropertiesFinder propertiesFinder) { + super(entity_name, metricsRun, propertiesFinder); + } + + public MetricCategory getCategory() { + return MetricCategory.Class; + } + + @Override + public String getClassName() { + return getName(); + } + + protected Double[] initializeVector(MetricsRunImpl metricsRun) { + Double[] vector = new Double[Dimension]; + for (int i = 0; i < Dimension; i++) { + vector[i] = 0.0; + } + MetricCategory category = getCategory(); + MetricsResult results = metricsRun.getResultsForCategory(category); + + for (Metric metric : metricsRun.getMetrics()) { + if (metric.getCategory().equals(category)) { + Integer id = components.get(metric.getAbbreviation()); + if (results.getValueForMetric(metric, getName()) != null) { + vector[id] = results.getValueForMetric(metric, getName()); + } + } + } + + return vector; + } + + protected HashSet findRelevantProperties() { + HashSet properties = new HashSet(); + properties.add(getName()); + + return properties; + } + + public Set getAllSupers(Set existing) { + return PSIUtil.getAllSupers((PsiClass) getPsiElement(), existing); + } +} diff --git a/src/vector/model/Entity.java b/src/vector/model/Entity.java new file mode 100644 index 0000000..51d9a89 --- /dev/null +++ b/src/vector/model/Entity.java @@ -0,0 +1,147 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiMethod; +import com.sixrr.metrics.MetricCategory; +import com.sixrr.metrics.Metric; +import com.sixrr.metrics.metricModel.MetricsResult; +import com.sixrr.metrics.metricModel.MetricsRunImpl; + +import java.util.*; + +/** + * Created by Kivi on 04.04.2017. + */ +public abstract class Entity { + + public Entity(String entity_name, MetricsRunImpl metricsRun, PropertiesFinder propertiesFinder) { + name = entity_name; + vector = initializeVector(metricsRun); + relevantProperties = propertiesFinder.getProperties(name); + psiEntity = propertiesFinder.getPsiElement(entity_name); + } + + public double dist(Entity entity) { + double ans = 0.0; + for (int i = 0; i < Dimension; i++) { + ans += Math.pow(vector[i] - entity.vector[i], 2); + } + + int rpIntersect = entity.relevantProperties.sizeOfIntersect(relevantProperties); + if (rpIntersect == 0) { + return Double.MAX_VALUE; + } + ans += 2.0 * (1 - (rpIntersect) / (1.0 * relevantProperties.size() + entity.relevantProperties.size() - + rpIntersect)); + + ans /= (Dimension + 2); + return Math.sqrt(ans); + } + + public static void normalize(ArrayList entities) { + for (int i = 0; i < Dimension; i++) { + Double mx = 0.0; + for (Entity entity : entities) { + mx = Math.max(mx, entity.vector[i]); + } + + if (mx == 0.0) { + continue; + } + + for (int j = 0; j < entities.size(); j++) { + entities.get(j).vector[i] /= mx; + } + } + } + + public void moveToClass(PsiClass newClass) { + Set oldClasses = relevantProperties.getAllClasses(); + for (PsiClass oldClass : oldClasses) { + relevantProperties.removeClass(oldClass); + } + relevantProperties.addClass(newClass); + } + + public void removeFromClass(PsiMethod method) { + relevantProperties.removeMethod(method); + } + + + public void removeFromClass(PsiField field) { + relevantProperties.removeField(field); + } + + public static final int Dimension = 4; + + public RelevantProperties getRelevantProperties() { + return relevantProperties; + } + + abstract MetricCategory getCategory(); + + public Double[] getVector() { + return vector; + } + + public String getName() { + return name; + } + + abstract public String getClassName(); + + protected Double[] vector; + private RelevantProperties relevantProperties; + private String name; + + protected abstract Double[] initializeVector(MetricsRunImpl metricsRun); + protected abstract HashSet findRelevantProperties(); + + protected static final Map components; + static { + Map comps = new HashMap(); + comps.put("DIT", 0); + comps.put("NOC", 1); + comps.put("FIC", 2); + comps.put("FOC", 3); + comps.put("FIM", 2); + comps.put("FOM", 3); + components = Collections.unmodifiableMap(comps); + } + + public void print() { + System.out.println(name + ": " + getCategory().name()); + System.out.print(" "); + for (Double comp : vector) { + System.out.print(comp); + System.out.print(" "); + } + System.out.println(); + + relevantProperties.printAll(); + } + + private PsiElement psiEntity; + + public PsiElement getPsiElement() { + return psiEntity; + } +} diff --git a/src/vector/model/FieldEntity.java b/src/vector/model/FieldEntity.java new file mode 100644 index 0000000..0ea0941 --- /dev/null +++ b/src/vector/model/FieldEntity.java @@ -0,0 +1,75 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.sixrr.metrics.Metric; +import com.sixrr.metrics.MetricCategory; +import com.sixrr.metrics.metricModel.MetricsResult; +import com.sixrr.metrics.metricModel.MetricsRunImpl; +import java.util.HashSet; + + +/** + * Created by Kivi on 04.04.2017. + */ +public class FieldEntity extends Entity { + public FieldEntity(String entity_name, MetricsRunImpl metricsRun, PropertiesFinder propertiesFinder) { + super(entity_name, metricsRun, propertiesFinder); + RelevantProperties rp = propertiesFinder.getProperties(entity_name); + double temp = rp.numberOfMethods(); + vector[2] = Double.valueOf(temp); + } + + public MetricCategory getCategory() { + return MetricCategory.Package; + } + + protected Double[] initializeVector(MetricsRunImpl metricsRun) { + Double[] vector = new Double[Dimension]; + for (int i = 0; i < Dimension; i++) { + vector[i] = 0.0; + } + MetricsResult classResults = metricsRun.getResultsForCategory(MetricCategory.Class); + String className = getClassName(); + for (Metric metric : metricsRun.getMetrics()) { + if (metric.getCategory().equals(MetricCategory.Class)) { + Integer id = components.get(metric.getAbbreviation()); + if (classResults.getValueForMetric(metric, className) != null) { + vector[id] = classResults.getValueForMetric(metric, className); + } + } + } + + vector[3] = 0.0; + + return vector; + } + + protected HashSet findRelevantProperties() { + HashSet properties = new HashSet(); + properties.add(getName()); + properties.add(getClassName()); + + return properties; + } + + @Override + public String getClassName() { + String name = getName(); + return name.substring(0, name.lastIndexOf('.')); + } +} diff --git a/src/vector/model/HAC.java b/src/vector/model/HAC.java new file mode 100644 index 0000000..eb6da1f --- /dev/null +++ b/src/vector/model/HAC.java @@ -0,0 +1,237 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.sixrr.metrics.MetricCategory; + +import java.util.*; + +/** + * Created by Kivi on 16.05.2017. + */ +public class HAC { + + private class Triple implements Comparable { + + Triple(Double _d, String _c1, String _c2) { + d = _d; + c1 = _c1; + c2 = _c2; + } + + public int compareTo(Triple t) { + int compD = d.compareTo(t.d); + if (compD != 0) { + return compD; + } + + int compC1 = c1.compareTo(t.c1); + if (compC1 != 0) { + return compC1; + } + + int compC2 = c2.compareTo(t.c2); + if (compC2 != 0) { + return compC2; + } + + return 0; + } + + public Double getD() { + return d; + } + + public String getC1() { + return c1; + } + + public String getC2() { + return c2; + } + + private Double d; + + private String c1, c2; + } + public HAC(List entityList) { + entities = entityList; + for (Entity entity : entities) { + final String name = entity.getName(); + final HashSet simpleCommunity = new HashSet(); + simpleCommunity.add(entity); + communities.put(name, simpleCommunity); + communityIds.put(entity, name); + entityByName.put(name, entity); + } + + for (String com1 : entityByName.keySet()) { + dists.put(com1, new HashMap()); + for (String com2 : entityByName.keySet()) { + Double d = entityByName.get(com1).dist(entityByName.get(com2)); + dists.get(com1).put(com2, d); + if (com1.compareTo(com2) < 0) { + distances.add(new Triple(d, com1, com2)); + } + } + } + + System.out.println(distances.first().getD()); + } + + public Map run() { + final Map refactorings = new HashMap(); + + double minD = 0.0; + while (minD < 1.0 && !distances.isEmpty()) { + Triple min = distances.first(); + minD = min.getD(); + String id1 = min.getC1(); + String id2 = min.getC2(); + + if (minD > 1.0) { + continue; + } + + /*if (entityByName.get(id1).getCategory() == MetricCategory.Class + && entityByName.get(id2).getCategory() == MetricCategory.Class) { + minD = Double.MAX_VALUE; + continue; + }*/ + + mergeCommunities(id1, id2); + System.out.println("Merge " + id1 + " and " + id2 + " to " + communityIds.get(entityByName.get(id1))); + } + + for (String center : communities.keySet()) { + String newName = receiveClassName(center); + for (Entity entity : communities.get(center)) { + if (!entity.getClassName().equals(newName)) { + refactorings.put(entity.getName(), newName); + } + } + } + + return refactorings; + } + + private String calculateClassName(String center) { + String name = ""; + Integer maxClassCount = 0; + Map classCounts = new HashMap(); + for (Entity entity : communities.get(center)) { + String className = entity.getClassName(); + if (!classCounts.containsKey(className)) { + classCounts.put(className, 0); + } + + Integer count = classCounts.get(className); + count++; + classCounts.put(className, count); + } + + for (String className : classCounts.keySet()) { + if (maxClassCount < classCounts.get(className)) { + maxClassCount = classCounts.get(className); + name = className; + } + } + + return name; + } + + private String receiveClassName(String center) { + String name = calculateClassName(center); + + if (name.equals("")) { + newClassCount++; + name = "NewClass" + newClassCount; + } + + return name; + } + + private void mergeCommunities(String id1, String id2) { + String newName = id1; + final Set merge = new HashSet(communities.get(id1)); + merge.addAll(communities.get(id2)); + int maxInClass = 0; + for (Entity ent : merge) { + if (ent.getCategory() == MetricCategory.Class) { + int inClass = 0; + for (Entity entity : merge) { + if (entity.getClassName().equals(ent.getName())) { + inClass++; + } + } + + if (inClass > maxInClass) { + maxInClass = inClass; + newName = ent.getName(); + } + } + } + + communities.remove(id1); + communities.remove(id2); + communities.put(newName, merge); + for (Entity ent : merge) { + communityIds.put(ent, newName); + } + + Double d = dists.get(id1).get(id2); + + dists.get(id1).remove(id2); + dists.get(id2).remove(id1); + + distances.remove(new Triple(d, id1, id2)); + distances.remove(new Triple(d, id2, id1)); + + for (String entity : communities.keySet()) { + if (entity.equals(id1) || entity.equals(id2)) { + continue; + } + Double d1 = dists.get(entity).get(id1); + Double d2 = dists.get(entity).get(id2); + Double newD = Math.max(d1, d2); + dists.get(id1).put(entity, newD); + dists.get(entity).put(id1, newD); + dists.get(entity).remove(id2); + dists.get(id2).remove(entity); + distances.remove(new Triple(d1, entity, id1)); + distances.remove(new Triple(d1, id1, entity)); + distances.remove(new Triple(d2, entity, id2)); + distances.remove(new Triple(d2, id2, entity)); + if (id1.compareTo(entity) < 0) { + distances.add(new Triple(newD, id1, entity)); + } else { + distances.add(new Triple(newD, entity, id1)); + } + } + } + + private HashMap> communities = new HashMap>(); + private HashMap communityIds = new HashMap(); + private Map> dists = new HashMap>(); + + private List entities; + private HashMap entityByName = new HashMap(); + + private SortedSet distances = new TreeSet(); + private static int SampleSize = 5; + private int newClassCount = 0; +} diff --git a/src/vector/model/MRI.java b/src/vector/model/MRI.java new file mode 100644 index 0000000..58db3b6 --- /dev/null +++ b/src/vector/model/MRI.java @@ -0,0 +1,221 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import java.util.*; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.sixrr.metrics.MetricCategory; +import de.lmu.ifi.dbs.elki.algorithm.clustering.kmeans.KMeansLloyd; +import de.lmu.ifi.dbs.elki.algorithm.clustering.kmeans.initialization.RandomlyGeneratedInitialMeans; +import de.lmu.ifi.dbs.elki.data.Cluster; +import de.lmu.ifi.dbs.elki.data.Clustering; +import de.lmu.ifi.dbs.elki.data.NumberVector; +import de.lmu.ifi.dbs.elki.data.model.KMeansModel; +import de.lmu.ifi.dbs.elki.data.type.TypeUtil; +import de.lmu.ifi.dbs.elki.database.Database; +import de.lmu.ifi.dbs.elki.database.StaticArrayDatabase; +import de.lmu.ifi.dbs.elki.database.ids.*; +import de.lmu.ifi.dbs.elki.database.relation.Relation; +import de.lmu.ifi.dbs.elki.datasource.ArrayAdapterDatabaseConnection; +import de.lmu.ifi.dbs.elki.datasource.DatabaseConnection; +import de.lmu.ifi.dbs.elki.distance.distancefunction.AbstractNumberVectorDistanceFunction; +import de.lmu.ifi.dbs.elki.distance.distancefunction.DistanceFunction; +import de.lmu.ifi.dbs.elki.algorithm.clustering.hierarchical.AnderbergHierarchicalClustering; +import de.lmu.ifi.dbs.elki.math.random.RandomFactory; + +/** + * Created by Kivi on 09.05.2017. + */ +public class MRI { + + public MRI(List entityList, Set existingClasses) { + entities = entityList; + allClasses = existingClasses; + } + + public Map run() { + Map refactorings = new HashMap(); + for (Entity entity : entities) { + if (entity.getCategory() != MetricCategory.Class) { + String className = entity.getClassName(); + double minDist = Double.MAX_VALUE; + int idClass = -1; + for (int i = 0; i < entities.size(); ++i) { + Entity classEnt = entities.get(i); + if (classEnt.getCategory().equals(MetricCategory.Class)) { + double dist = entity.dist(classEnt); + if (dist < minDist) { + minDist = dist; + idClass = i; + } + } + } + + if (idClass == -1) { + System.out.println("WARNING: " + entity.getName() + " has no nearest class"); + } else { + if (entities.get(idClass).getName().equals(className)) { + continue; + } + + if (entity.getCategory() == MetricCategory.Method) { + PsiMethod method = (PsiMethod) entity.getPsiElement(); + PsiClass moveFromClass = method.getContainingClass(); + PsiClass moveToClass = (PsiClass) entities.get(idClass).getPsiElement(); + + Set supersTo = PSIUtil.getAllSupers(moveToClass, allClasses);//new HashSet(Arrays.asList(moveToClass.getSupers())); + boolean isSuper = false; + + for (PsiClass sup : supersTo) { + if (sup.equals(moveFromClass)) { + isSuper = true; + break; + } + } + + Set supersFrom = PSIUtil.getAllSupers(moveFromClass, allClasses);//new HashSet(Arrays.asList(moveFromClass.getSupers())); + for (PsiClass sup : supersFrom) { + if (sup.equals(moveToClass)) { + isSuper = true; + break; + } + } + supersFrom.retainAll(supersTo); + boolean isOverride = false; + + if (isSuper) { + continue; + } + + for (PsiClass sup : supersFrom) { + PsiMethod[] methods = sup.getMethods(); + for (PsiMethod m : methods) { + if (m.equals(method)) { + isOverride = true; + break; + } + } + + if (isOverride) { + break; + } + } + + if (!isOverride) { + Entity newClass = entities.get(idClass); + refactorings.put(entity.getName(), newClass.getClassName()); + entity.moveToClass((PsiClass) newClass.getPsiElement()); + newClass.removeFromClass((PsiMethod) entity.getPsiElement()); + } + } else { + refactorings.put(entity.getName(), entities.get(idClass).getName()); + } + } + } + } + + return refactorings; + } + + public void printTableDistances() { + int maxLen = 0; + for (Entity ent : entities) { + maxLen = Math.max(maxLen, ent.getName().length() + 4); + } + + System.out.print(String.format("%1$" + maxLen + "s", "")); + for (Entity ent : entities) { + String name = String.format("%1$" + maxLen + "s", ent.getName()); + System.out.print(name); + } + System.out.println(); + + for (Entity ent : entities) { + String name = String.format("%1$" + maxLen + "s", ent.getName()); + System.out.print(name); + for (Entity entity : entities) { + double dist = ent.dist(entity); + String d = ""; + if (dist == Double.MAX_VALUE) { + d = String.format("%1$" + maxLen + "s", "inf"); + } else { + d = String.format(" %." + (maxLen - 4) + "f", dist); + } + System.out.print(d); + } + System.out.println(); + } + } + + /*public void ELKIrun() { + double[][] data = new double[entities.size()][1]; + for (int i = 0; i < entities.size(); ++i) { + data[i][0] = i; + } + + System.out.println(data.length); + DatabaseConnection dbc = new ArrayAdapterDatabaseConnection(data); + Database db = new StaticArrayDatabase(dbc, null); + db.initialize(); + + System.out.println("here 1"); + + RandomlyGeneratedInitialMeans init = new RandomlyGeneratedInitialMeans(RandomFactory.DEFAULT); + + KMeansLloyd km = new KMeansLloyd(new EntityDistance(), 5, 20, init); + System.out.println("here 2"); + Clustering c = km.run(db); + System.out.println("here 3"); + Relation rel = db.getRelation(TypeUtil.NUMBER_VECTOR_FIELD); + // We know that the ids must be a continuous range: + DBIDRange ids = (DBIDRange) rel.getDBIDs(); + + int i = 0; + for(Cluster clu : c.getAllClusters()) { + // K-means will name all clusters "Cluster" in lack of noise support: + System.out.println("#" + i + ": " + clu.getNameAutomatic()); + System.out.println("Size: " + clu.size()); + System.out.println("Center: " + clu.getModel().getPrototype().toString()); + // Iterate over objects: + System.out.print("Objects: "); + for(DBIDIter it = clu.getIDs().iter(); it.valid(); it.advance()) { + // To get the vector use: + NumberVector v = rel.get(it); + + // Offset within our DBID range: "line number" + final int offset = ids.getOffset(it); + System.out.print(" " + offset); + // Do NOT rely on using "internalGetIndex()" directly! + } + System.out.println(); + ++i; + } + } + + private class EntityDistance extends AbstractNumberVectorDistanceFunction { + @Override + public double distance(NumberVector v1, NumberVector v2) { + return entities.get(v1.intValue(0)).dist(entities.get(v2.intValue(0))); + } + } + */ + + List entities; + Set allClasses; +} diff --git a/src/vector/model/MethodEntity.java b/src/vector/model/MethodEntity.java new file mode 100644 index 0000000..2b2ebb0 --- /dev/null +++ b/src/vector/model/MethodEntity.java @@ -0,0 +1,83 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.sixrr.metrics.Metric; +import com.sixrr.metrics.MetricCategory; +import com.sixrr.metrics.metricModel.MetricsResult; +import com.sixrr.metrics.metricModel.MetricsRunImpl; + +import java.util.HashSet; +import java.util.List; + +/** + * Created by Kivi on 04.04.2017. + */ +public class MethodEntity extends Entity { + public MethodEntity(String entity_name, MetricsRunImpl metricsRun, PropertiesFinder propertiesFinder) { + super(entity_name, metricsRun, propertiesFinder); + } + + public MetricCategory getCategory() { + return MetricCategory.Method; + } + + protected Double[] initializeVector(MetricsRunImpl metricsRun) { + Double[] vector = new Double[Dimension]; + for (int i = 0; i < Dimension; i++) { + vector[i] = 0.0; + } + + MetricCategory category = getCategory(); + MetricsResult results = metricsRun.getResultsForCategory(category); + MetricsResult classResults = metricsRun.getResultsForCategory(MetricCategory.Class); + String className = getClassName(); + for (Metric metric : metricsRun.getMetrics()) { + if (metric.getCategory().equals(MetricCategory.Class)) { + Integer id = components.get(metric.getAbbreviation()); + if (classResults.getValueForMetric(metric, className) != null) { + vector[id] = classResults.getValueForMetric(metric, className); + } + } + } + + for (Metric metric : metricsRun.getMetrics()) { + if (metric.getCategory().equals(category)) { + Integer id = components.get(metric.getAbbreviation()); + if (results.getValueForMetric(metric, getName()) != null) { + vector[id] = results.getValueForMetric(metric, getName()); + } + } + } + + return vector; + } + + protected HashSet findRelevantProperties() { + HashSet properties = new HashSet(); + properties.add(getName()); + properties.add(getClassName()); + + return properties; + } + + @Override + public String getClassName() { + String name = getName(); + return name.substring(0, name.lastIndexOf('.')); + } +} diff --git a/src/vector/model/PSIUtil.java b/src/vector/model/PSIUtil.java new file mode 100644 index 0000000..e838841 --- /dev/null +++ b/src/vector/model/PSIUtil.java @@ -0,0 +1,88 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; + +import java.util.HashSet; +import java.util.Set; + +/** + * Created by Kivi on 16.05.2017. + */ +public class PSIUtil { + public static Set getAllSupers(PsiClass aClass, Set existing) { + Set allSupers = new HashSet(); + if (!existing.contains(aClass)) { + return allSupers; + } + + PsiClass[] supers = aClass.getSupers(); + for (PsiClass sup : supers) { + if (!existing.contains(sup)) { + continue; + } + allSupers.add(sup); + allSupers.addAll(getAllSupers(sup, existing)); + } + + return allSupers; + } + + public static Set getAllSupers(PsiClass aClass) { + Set allSupers = new HashSet(); + + PsiClass[] supers = aClass.getSupers(); + for (PsiClass sup : supers) { + allSupers.add(sup); + allSupers.addAll(getAllSupers(sup)); + } + + return allSupers; + } + + public static Set getAllSupers(PsiMethod method, Set existing) { + if (!existing.contains(method.getContainingClass())) { + return new HashSet(); + } + Set allSupers = new HashSet(); + + PsiMethod[] supers = method.findSuperMethods(); + for (PsiMethod m : supers) { + if (!existing.contains(m.getContainingClass())) { + continue; + } + allSupers.add(m); + allSupers.addAll(getAllSupers(m, existing)); + } + + return allSupers; + } + + public static Set getAllSupers(PsiMethod method) { + Set allSupers = new HashSet(); + + PsiMethod[] supers = method.findSuperMethods(); + for (PsiMethod m : supers) { + allSupers.add(m); + allSupers.addAll(getAllSupers(m)); + } + + return allSupers; + } +} diff --git a/src/vector/model/PropertiesFinder.java b/src/vector/model/PropertiesFinder.java new file mode 100644 index 0000000..5822878 --- /dev/null +++ b/src/vector/model/PropertiesFinder.java @@ -0,0 +1,389 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +/** + * Created by Kivi on 11.04.2017. + */ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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. + */ + +import com.intellij.analysis.AnalysisScope; +import com.intellij.openapi.progress.EmptyProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.tree.java.AnonymousClassElement; +import com.sixrr.metrics.utils.MethodUtils; + +import java.util.*; + +/** + * Created by Kivi on 10.04.2017. + */ +public class PropertiesFinder { + + public PsiElementVisitor createVisitor(final AnalysisScope analysisScope) { + final PsiElementVisitor visitor = new FileClassesCounter(); + ProgressManager.getInstance().runProcess(new Runnable() { + @Override + public void run() { + analysisScope.accept(visitor);; + } + }, new EmptyProgressIndicator()); + + return new FileVisitor(); + } + + public boolean hasElement(String name) { + return properties.containsKey(name); + } + + public RelevantProperties getProperties(String name) { + + return properties.get(name); + } + + public Set getAllFields() { + Set fields = new HashSet(); + for (String entity : properties.keySet()) { + RelevantProperties rp = properties.get(entity); + Set fs = rp.getAllFields(); + for (PsiField field : fs) { + if (!properties.containsKey(field.getContainingClass().getQualifiedName())) { + continue; + } + String name = field.getContainingClass().getQualifiedName() + "." + field.getName(); + fields.add(name); + } + } + + return fields; + } + + private HashMap properties = new HashMap(); + + private HashMap> methods = new HashMap>(); + private HashMap> fields = new HashMap>(); + + protected Map> parents = new HashMap>(); + + protected Map> superMethods = new HashMap>(); + + protected Map classByName = new HashMap(); + + protected Map methodByName = new HashMap(); + + protected Map fieldByName = new HashMap(); + + private Stack methodStack = new Stack(); + + private Set allClasses = new HashSet(); + + public Set getAllClasses() { + return allClasses; + } + + public Set getAllClassesNames() { + Set classesNames = new HashSet(); + for (PsiClass c : allClasses) { + classesNames.add(c.getQualifiedName()); + } + + return classesNames; + } + + public PsiElement getPsiElement(String name) { + if (methodByName.containsKey(name)) { + return methodByName.get(name); + } + if (classByName.containsKey(name)) { + return classByName.get(name); + } + if (fieldByName.containsKey(name)) { + return fieldByName.get(name); + } + + return null; + } + + + private class ClassCounter extends JavaRecursiveElementVisitor { + @Override + public void visitClass(PsiClass aClass) { + if (aClass.isEnum()) { + return; + } + if (aClass instanceof AnonymousClassElement) { + return; + } + + if (aClass.getQualifiedName() == null) { + return; + } + allClasses.add(aClass); + super.visitClass(aClass); + } + } + + + private class FileClassesCounter extends JavaElementVisitor { + + @Override + public void visitFile(final PsiFile file) { + System.out.println("!#! " + file.getName()); + + final PsiElementVisitor counter = new ClassCounter(); + ProgressManager.getInstance().runProcess(new Runnable() { + @Override + public void run() { + file.accept(counter); + } + }, new EmptyProgressIndicator()); + + } + } + + private class FileVisitor extends JavaElementVisitor { + + @Override + public void visitFile(final PsiFile file) { + System.out.println("!#! " + file.getName()); + + final PsiElementVisitor counter = new ClassCounter(); + ProgressManager.getInstance().runProcess(new Runnable() { + @Override + public void run() { + file.accept(counter); + } + }, new EmptyProgressIndicator()); + + final PsiElementVisitor visitor = new EntityVisitor(); + ProgressManager.getInstance().runProcess(new Runnable() { + @Override + public void run() { + file.accept(visitor); + } + }, new EmptyProgressIndicator()); + + for (String className : parents.keySet()) { + PsiClass aClass = classByName.get(className); + for (PsiClass parent : parents.get(className)) { + String parentName = parent.getQualifiedName(); + if (!properties.containsKey(parentName)) { + continue; + } + properties.get(parentName).addClass(aClass); + } + } + + + for (String methodName : superMethods.keySet()) { + PsiMethod method = methodByName.get(methodName); + for (PsiMethod parent : superMethods.get(methodName)) { + String parentName = MethodUtils.calculateSignature(parent); + if (!properties.containsKey(parentName)) { + continue; + } + properties.get(parentName).addOverrideMethod(method); + } + } + + for (String name : methods.keySet()) { + if (!methodByName.containsKey(name) && !fieldByName.containsKey(name) + && !classByName.containsKey(name)) { + continue; + } + for (PsiMethod method : methods.get(name)) { + if (methodByName.containsValue(method)) { + properties.get(name).addMethod(method); + } + } + } + + for (String name : fields.keySet()) { + for (PsiField field : fields.get(name)) { + if (fieldByName.containsValue(field)) { + properties.get(name).addField(field); + } + } + } + + for (String name : properties.keySet()) { + properties.get(name).retainClasses(classByName.values()); + properties.get(name).retainMethods(methodByName.values()); + } + + } + } + + private class EntityVisitor extends JavaRecursiveElementVisitor { + @Override + public void visitClass(PsiClass aClass) { + if (aClass.isEnum()) { + return; + } + if (aClass instanceof AnonymousClassElement) { + return; + } + + if (aClass.getQualifiedName() == null) { + return; + } + //allClasses.add(aClass); + + RelevantProperties rp = new RelevantProperties(); + String fullName = aClass.getQualifiedName(); + + classByName.put(fullName, aClass); + rp.addClass(aClass); + //System.out.println(" !@! " + aClass.getName() + " : " + aClass.getQualifiedName()); + super.visitClass(aClass); + PsiField[] fields = aClass.getAllFields(); + for (PsiField field : fields) { + //System.out.println(" " + field.getName()); + rp.addField(field); + } + //System.out.println(); + PsiMethod[] methods = aClass.getAllMethods(); + for (PsiMethod method : methods) { + rp.addMethod(method); + //System.out.println(" " + method.getContainingClass().getName() + "." + method.getName()); + } + //System.out.println(); + + Set supers = PSIUtil.getAllSupers(aClass);//aClass.getSupers(); + for (PsiClass sup : supers) { + if (sup.isInterface()) { + rp.addClass(sup); + } else { + if (!parents.containsKey(fullName)) { + parents.put(fullName, new HashSet()); + } + + parents.get(fullName).add(sup); + } + } + + properties.put(fullName, rp); + + //System.out.println(); + } + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + //System.out.println(" !*! " + expression.getText()); + PsiElement elem = expression.resolve(); + //System.out.println(" " + elem.getClass().getName()); + if (elem instanceof PsiField) { + PsiField field = (PsiField) elem; + if (methodStack.empty()) { + return; + } + PsiMethod method = methodStack.peek(); + String fullMethodName = MethodUtils.calculateSignature(method); + String fullFieldName = field.getContainingClass().getQualifiedName() + "." + field.getName(); + //properties.get(fullMethodName).addField(field); + if (!fields.containsKey(fullMethodName)) { + fields.put(fullMethodName, new HashSet()); + } + fields.get(fullMethodName).add(field); + + if (!methods.containsKey(fullFieldName)) { + methods.put(fullFieldName, new HashSet()); + } + + methods.get(fullFieldName).add(method); + } + + //System.out.println(); + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + PsiMethod element = expression.resolveMethod(); + if (methodStack.empty()) { + return; + } + PsiMethod caller = methodStack.peek(); + String callerName = MethodUtils.calculateSignature(caller); + if (!methods.containsKey(callerName)) { + methods.put(callerName, new HashSet()); + } + methods.get(callerName).add(element); + } + + @Override + public void visitMethod(PsiMethod method) { + if (method.getContainingClass() == null) { + return; + } + if (method.getContainingClass().isInterface()) { + return; + } + String methodName = MethodUtils.calculateSignature(method); + methodByName.put(methodName, method); + //System.out.println(" !%! " + methodName); + + RelevantProperties rp = new RelevantProperties(); + rp.addMethod(method); + rp.addClass(method.getContainingClass()); + properties.put(methodName, rp); + methodStack.push(method); + super.visitMethod(method); + methodStack.pop(); + + Set methods = PSIUtil.getAllSupers(method, allClasses); + superMethods.put(methodName, new HashSet()); + for (PsiMethod met : methods) { + superMethods.get(methodName).add(met); + //System.out.println(" " + met.getContainingClass().getQualifiedName() + "." + met.getName()); + } + + //System.out.println(); + } + + @Override + public void visitField(PsiField field) { + if (field.getContainingClass() == null) { + return; + } + String name = field.getContainingClass().getQualifiedName() + "." + field.getName(); + //System.out.println(" !$! " + name); + + if (!properties.containsKey(name)) { + RelevantProperties rp = new RelevantProperties(); + properties.put(name, rp); + } + properties.get(name).addClass(field.getContainingClass()); + properties.get(name).addField(field); + fieldByName.put(name, field); + } + } +} diff --git a/src/vector/model/RelevantProperties.java b/src/vector/model/RelevantProperties.java new file mode 100644 index 0000000..e6afa3c --- /dev/null +++ b/src/vector/model/RelevantProperties.java @@ -0,0 +1,140 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model; + +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiParameter; +import com.sixrr.metrics.utils.MethodUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by Kivi on 11.04.2017. + */ +public class RelevantProperties { + public RelevantProperties() { + methods = new HashSet(); + classes = new HashSet(); + fields = new HashSet(); + overrideMethods = new HashSet(); + }; + + public void removeMethod(PsiMethod method) { + methods.remove(method); + } + + public void removeField(PsiField field) { + fields.remove(field); + } + + public void removeClass(PsiClass aClass) { + classes.remove(aClass); + } + + public void addMethod(PsiMethod method) { + methods.add(method); + } + + public void addClass(PsiClass aClass) { + classes.add(aClass); + } + + public void addField(PsiField field) { + fields.add(field); + } + + public void retainMethods(Collection methodsSet) { + methods.retainAll(methodsSet); + } + + public void retainClasses(Collection classSet) { + classes.retainAll(classSet); + } + + public void addOverrideMethod(PsiMethod method) { + overrideMethods.add(method); + } + + public Integer numberOfMethods() { + return methods.size(); + } + + public Set getAllFields() { + return new HashSet(fields); + } + + public Set getAllMethods() { + return new HashSet(methods); + } + + public Set getAllClasses() { + return new HashSet(classes); + } + + public int size() { + return classes.size() + fields.size() + methods.size(); + } + + public int sizeOfIntersect(RelevantProperties rp) { + int ans = 0; + Set c = new HashSet(classes); + c.retainAll(rp.classes); + ans += c.size(); + + Set m = new HashSet(methods); + m.addAll(overrideMethods); + Set rpm = rp.methods; + rpm.addAll(rp.overrideMethods); + m.retainAll(rpm); + ans += m.size(); + + Set f = new HashSet(fields); + f.retainAll(rp.fields); + ans += f.size(); + + return ans; + } + + public void printAll() { + System.out.print(" "); + for (PsiClass aClass : classes) { + System.out.print(aClass.getQualifiedName() + " "); + } + System.out.println(); + + System.out.print(" "); + for (PsiMethod method : methods) { + System.out.print(MethodUtils.calculateSignature(method) + ' '); + } + System.out.println(); + + System.out.print(" "); + for (PsiField field : fields) { + System.out.print(field.getContainingClass().getQualifiedName() + "." + field.getName() + " "); + } + System.out.println(); + } + + private HashSet methods; + private HashSet classes; + private HashSet fields; + private HashSet overrideMethods; +} diff --git a/src/vector/model/examples/example1/Computer.java b/src/vector/model/examples/example1/Computer.java new file mode 100644 index 0000000..9e2cf1f --- /dev/null +++ b/src/vector/model/examples/example1/Computer.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model.examples.example1; + +/** + * Created by Kivi on 02.05.2017. + */ +public class Computer { + Computer() { + System.out.println("Constructor of Computer class."); + } + + void computer_method() { + System.out.println("Power gone! Shut down your PC soon..."); + } + + public static void main(String[] args) { + Computer my = new Computer(); + Laptop your = new Laptop(); + + my.computer_method(); + your.laptop_method(); + } +} + diff --git a/src/vector/model/examples/example1/Laptop.java b/src/vector/model/examples/example1/Laptop.java new file mode 100644 index 0000000..8f03b19 --- /dev/null +++ b/src/vector/model/examples/example1/Laptop.java @@ -0,0 +1,31 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model.examples.example1; + +/** + * Created by Kivi on 02.05.2017. + */ +public class Laptop { + Laptop() { + System.out.println("Constructor of Laptop class."); + } + + void laptop_method() { + System.out.println("99% Battery available."); + } +} + diff --git a/src/vector/model/examples/example2/class_A.java b/src/vector/model/examples/example2/class_A.java new file mode 100644 index 0000000..f9d3597 --- /dev/null +++ b/src/vector/model/examples/example2/class_A.java @@ -0,0 +1,46 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model.examples.example2; + +/** + * Created by Kivi on 05.05.2017. + */ +class class_A +{ + static void methodA1() + { + attributeA1=0; + methodA2(); + } + + static void methodA2() + { + attributeA2=0; + attributeA1=0; + } + + static void methodA3() + { + attributeA1=0; + attributeA2=0; + methodA1(); + methodA2(); + } + + static int attributeA1; + static int attributeA2; +} diff --git a/src/vector/model/examples/example2/class_B.java b/src/vector/model/examples/example2/class_B.java new file mode 100644 index 0000000..12b5e0b --- /dev/null +++ b/src/vector/model/examples/example2/class_B.java @@ -0,0 +1,46 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 vector.model.examples.example2; + +/** + * Created by Kivi on 05.05.2017. + */ +class class_B +{ + static void methodB1() + { + class_A.attributeA1=0; + class_A.attributeA2=0; + class_A.methodA1(); + } + + static void methodB2() + { + attributeB1=0; + attributeB2=0; + } + + static void methodB3() + { + attributeB1=0; + methodB1(); + methodB2(); + } + + static int attributeB1; + static int attributeB2; +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/JavaMetricProvider.java b/stockmetrics/src/com/sixrr/stockmetrics/JavaMetricProvider.java index 6dfb4d5..942567f 100644 --- a/stockmetrics/src/com/sixrr/stockmetrics/JavaMetricProvider.java +++ b/stockmetrics/src/com/sixrr/stockmetrics/JavaMetricProvider.java @@ -104,6 +104,8 @@ private static void initializeClassMetrics(Collection metrics) { metrics.add(new TodoCommentCountClassMetric()); metrics.add(new TrueCommentRatioClassMetric()); metrics.add(new WeightedMethodComplexityMetric()); + metrics.add(new FanInClassMetric()); + metrics.add(new FanOutClassMetric()); } private static void initializeInterfaceMetrics(Collection metrics) { @@ -181,6 +183,8 @@ private static void initializeMethodMetrics(Collection metrics) { metrics.add(new SourceLinesOfCodeMethodMetric()); metrics.add(new TodoCommentCountMethodMetric()); metrics.add(new TrueCommentRatioMethodMetric()); + metrics.add(new FanInMethodMetric()); + metrics.add(new FanOutMethodMetric()); } private static void initializeModuleMetrics(Collection metrics) { @@ -329,7 +333,10 @@ private static void initializeProjectMetrics(Collection metrics) { @NotNull @Override public List getPrebuiltProfiles() { - final List out = new ArrayList(10); + final List out = new ArrayList(12); + out.add(createRefactoringProfile()); + /*out.add(createChidamberKemererProfile()); + final List out = new ArrayList(11); out.add(createChidamberKemererProfile()); out.add(createClassCountProfile()); out.add(createCodeSizeProfile()); @@ -340,9 +347,32 @@ public List getPrebuiltProfiles() { out.add(createMartinProfile()); out.add(createMoodProfile()); out.add(createTestProfile()); + out.add(createFanProfile()); + */ return out; } + private static PrebuiltMetricProfile createRefactoringProfile() { + final PrebuiltMetricProfile profile = + new PrebuiltMetricProfile(StockMetricsBundle.message("refactoring.metrics.profile.name")); + profile.addMetric(DepthOfInheritanceMetric.class); + profile.addMetric(NumChildrenMetric.class); + profile.addMetric(FanInClassMetric.class); + profile.addMetric(FanOutClassMetric.class); + profile.addMetric(FanInMethodMetric.class); + profile.addMetric(FanOutMethodMetric.class); + return profile; + } + + private static PrebuiltMetricProfile createFanProfile() { + final PrebuiltMetricProfile profile = new PrebuiltMetricProfile(StockMetricsBundle.message("fan.profile.name")); + profile.addMetric(FanInClassMetric.class); + profile.addMetric(FanOutClassMetric.class); + profile.addMetric(FanInMethodMetric.class); + profile.addMetric(FanOutMethodMetric.class); + return profile; + } + private static PrebuiltMetricProfile createChidamberKemererProfile() { final PrebuiltMetricProfile profile = new PrebuiltMetricProfile(StockMetricsBundle.message("chidamber.kemerer.metrics.profile.name")); diff --git a/stockmetrics/src/com/sixrr/stockmetrics/classCalculators/FanClassCalculator.java b/stockmetrics/src/com/sixrr/stockmetrics/classCalculators/FanClassCalculator.java new file mode 100644 index 0000000..652ee51 --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/classCalculators/FanClassCalculator.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.classCalculators; + +import com.intellij.openapi.util.Key; +import com.intellij.psi.PsiClass; +import com.sixrr.metrics.Metric; +import com.sixrr.metrics.MetricsExecutionContext; +import com.sixrr.metrics.MetricsResultsHolder; +import com.sixrr.stockmetrics.utils.FieldUsageMap; +import com.sixrr.stockmetrics.utils.FieldUsageMapImpl; + +import java.util.*; + +/** + * @author Aleksandr Chudov. + */ +public abstract class FanClassCalculator extends ClassCalculator { + protected final Map> metrics = new HashMap>(); + protected final Collection visitedClasses = new ArrayList(); + protected final Key fieldUsageKey = new Key("FieldUsageMap"); + + @Override + public void beginMetricsRun(Metric metric, MetricsResultsHolder resultsHolder, MetricsExecutionContext executionContext) { + final FieldUsageMap map = executionContext.getUserData(fieldUsageKey); + if(map == null) { + executionContext.putUserData(fieldUsageKey, new FieldUsageMapImpl()); + } + super.beginMetricsRun(metric, resultsHolder, executionContext); + } + + @Override + public void endMetricsRun() { + for (final PsiClass aClass : visitedClasses) { + postMetric(aClass, metrics.get(aClass).size()); + } + super.endMetricsRun(); + } +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/classCalculators/FanInClassCalculator.java b/stockmetrics/src/com/sixrr/stockmetrics/classCalculators/FanInClassCalculator.java new file mode 100644 index 0000000..2b865b6 --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/classCalculators/FanInClassCalculator.java @@ -0,0 +1,91 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.classCalculators; + +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.sixrr.stockmetrics.utils.FieldUsageMap; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +/** + * @author Aleksandr Chudov. + */ +public class FanInClassCalculator extends FanClassCalculator { + private final Stack classes = new Stack(); + + @Override + protected PsiElementVisitor createVisitor() { + return new Visitor(); + } + + private class Visitor extends JavaRecursiveElementVisitor { + @Override + public void visitClass(PsiClass aClass) { + if (!isConcreteClass(aClass)) { + return; + } + if (!metrics.containsKey(aClass)) { + metrics.put(aClass, new HashSet()); + } + classes.push(aClass); + visitedClasses.add(aClass); + super.visitClass(aClass); + classes.pop(); + final FieldUsageMap map = executionContext.getUserData(fieldUsageKey); + final PsiField[] fields = aClass.getFields(); + for (final PsiField field : fields) { + final Set references = map.calculateFieldUsagePoints(field); + for (final PsiReference reference : references) { + final PsiElement element = reference.getElement(); + final PsiClass fieldClass = PsiTreeUtil.getParentOfType(element, PsiClass.class); + if (fieldClass == null || fieldClass.equals(aClass)) { + continue; + } + final Set classes = metrics.get(aClass); + classes.add(fieldClass); + } + } + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + } + + @Override + public void visitCallExpression(PsiCallExpression callExpression) { + super.visitCallExpression(callExpression); + final PsiMethod method = callExpression.resolveMethod(); + if (method == null) { + return; + } + final PsiClass aClass = method.getContainingClass(); + if (aClass == null || classes.empty() || classes.peek().equals(aClass)) { + return; + } + final Set s = metrics.containsKey(aClass) ? metrics.get(aClass) : new HashSet(); + s.add(classes.peek()); + metrics.put(aClass, s); + } + } +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/classCalculators/FanOutClassCalculator.java b/stockmetrics/src/com/sixrr/stockmetrics/classCalculators/FanOutClassCalculator.java new file mode 100644 index 0000000..e9493ad --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/classCalculators/FanOutClassCalculator.java @@ -0,0 +1,101 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.classCalculators; + +import com.intellij.openapi.util.Key; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.sixrr.metrics.Metric; +import com.sixrr.metrics.MetricsExecutionContext; +import com.sixrr.metrics.MetricsResultsHolder; +import com.sixrr.stockmetrics.utils.FieldUsageMap; +import com.sixrr.stockmetrics.utils.FieldUsageMapImpl; +import com.sixrr.stockmetrics.utils.MethodCallMap; +import com.sixrr.stockmetrics.utils.MethodCallMapImpl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +/** + * @author Aleksandr Chudov. + */ +public class FanOutClassCalculator extends FanClassCalculator { + private final Stack classes = new Stack(); + + @Override + protected PsiElementVisitor createVisitor() { + return new Visitor(); + } + + private class Visitor extends JavaRecursiveElementVisitor { + + @Override + public void visitClass(PsiClass aClass) { + if (!isConcreteClass(aClass)) { + return; + } + if (!metrics.containsKey(aClass)) { + metrics.put(aClass, new HashSet()); + } + classes.push(aClass); + visitedClasses.add(aClass); + super.visitClass(aClass); + classes.pop(); + final FieldUsageMap map = executionContext.getUserData(fieldUsageKey); + final PsiField[] fields = aClass.getFields(); + for (final PsiField field : fields) { + final Set references = map.calculateFieldUsagePoints(field); + for (final PsiReference reference : references) { + final PsiElement element = reference.getElement(); + final PsiClass fieldClass = PsiTreeUtil.getParentOfType(element, PsiClass.class); + if (fieldClass == null || fieldClass.equals(aClass)) { + continue; + } + final Set s = metrics.containsKey(fieldClass) ? metrics.get(fieldClass) : new HashSet(); + s.add(aClass); + metrics.put(fieldClass, s); + } + } + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + } + + @Override + public void visitCallExpression(PsiCallExpression callExpression) { + super.visitCallExpression(callExpression); + if (classes.empty()) { + return; + } + final PsiMethod method = callExpression.resolveMethod(); + if (method == null || classes.peek().equals(method.getContainingClass())) { + return; + } + final PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return; + } + metrics.get(classes.peek()).add(aClass); + } + } +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/classMetrics/FanInClassMetric.java b/stockmetrics/src/com/sixrr/stockmetrics/classMetrics/FanInClassMetric.java new file mode 100644 index 0000000..e600352 --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/classMetrics/FanInClassMetric.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.classMetrics; + +import com.sixrr.metrics.MetricCalculator; +import com.sixrr.metrics.MetricType; +import com.sixrr.stockmetrics.classCalculators.FanInClassCalculator; +import com.sixrr.stockmetrics.i18n.StockMetricsBundle; +import org.jetbrains.annotations.NotNull; + +/** + * Created by Aleksandr Chudov on 27.03.2017. + */ +public class FanInClassMetric extends ClassMetric { + @NotNull + @Override + public String getDisplayName() { + return StockMetricsBundle.message("fan.in.class.metric.display.name"); + } + + @NotNull + @Override + public String getAbbreviation() { + return StockMetricsBundle.message("fan.in.class.metric.abbreviation"); + } + + @NotNull + @Override + public MetricType getType() { + return MetricType.Count; + } + + @NotNull + @Override + public MetricCalculator createCalculator() { + return new FanInClassCalculator(); + } +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/classMetrics/FanOutClassMetric.java b/stockmetrics/src/com/sixrr/stockmetrics/classMetrics/FanOutClassMetric.java new file mode 100644 index 0000000..8ca0d33 --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/classMetrics/FanOutClassMetric.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.classMetrics; + +import com.sixrr.metrics.MetricCalculator; +import com.sixrr.metrics.MetricType; +import com.sixrr.stockmetrics.classCalculators.FanOutClassCalculator; +import com.sixrr.stockmetrics.i18n.StockMetricsBundle; +import org.jetbrains.annotations.NotNull; + +/** + * Created by Aleksandr Chudov on 28.03.2017. + */ +public class FanOutClassMetric extends ClassMetric { + @NotNull + @Override + public String getDisplayName() { + return StockMetricsBundle.message("fan.out.class.metric.display.name"); + } + + @NotNull + @Override + public String getAbbreviation() { + return StockMetricsBundle.message("fan.out.class.metric.abbreviation"); + } + + @NotNull + @Override + public MetricType getType() { + return MetricType.Count; + } + + @NotNull + @Override + public MetricCalculator createCalculator() { + return new FanOutClassCalculator(); + } +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/i18n/StockMetricsBundle.properties b/stockmetrics/src/com/sixrr/stockmetrics/i18n/StockMetricsBundle.properties index 5d0a934..75a52ab 100644 --- a/stockmetrics/src/com/sixrr/stockmetrics/i18n/StockMetricsBundle.properties +++ b/stockmetrics/src/com/sixrr/stockmetrics/i18n/StockMetricsBundle.properties @@ -373,8 +373,18 @@ junit.testing.metrics.profile.name=JUnit testing metrics class.count.metrics.profile.name=Class count metrics file.count.metrics.profile.name=Number of files metrics chidamber.kemerer.metrics.profile.name=Chidamber-Kemerer metrics +refactoring.metrics.profile.name=Refactoring features abstractness.display.name=Abstractness abstractness.abbreviation=A number.of.packages.abbreviation=P number.of.children.display.name=Number of children -number.of.children.abbreviation=NOC \ No newline at end of file +number.of.children.abbreviation=NOC +fan.profile.name=Fan metrics +fan.in.class.metric.display.name=Fan-In class metric +fan.in.class.metric.abbreviation=FIC +fan.out.class.metric.display.name=Fan-Out class metric +fan.out.class.metric.abbreviation=FOC +fan.in.method.metric.display.name=Fan-In method metric +fan.in.method.metric.abbreviation=FIM +fan.out.method.metric.display.name=Fan-Out method metric +fan.out.method.metric.abbreviation=FOM \ No newline at end of file diff --git a/stockmetrics/src/com/sixrr/stockmetrics/methodCalculators/FanInMethodCalculator.java b/stockmetrics/src/com/sixrr/stockmetrics/methodCalculators/FanInMethodCalculator.java new file mode 100644 index 0000000..ef18dc6 --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/methodCalculators/FanInMethodCalculator.java @@ -0,0 +1,70 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.methodCalculators; + +import com.intellij.psi.*; +import com.sixrr.metrics.utils.BucketedCount; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Stack; + +/** + * @author Aleksandr Chudov. + */ +public class FanInMethodCalculator extends MethodCalculator { + private final BucketedCount metrics = new BucketedCount(); + private final Collection visitedMethods = new ArrayList(); + private final Stack methods = new Stack(); + + @Override + public void endMetricsRun() { + for (PsiMethod method : visitedMethods) { + postMetric(method, metrics.getBucketValue(method)); + } + super.endMetricsRun(); + } + + @Override + protected PsiElementVisitor createVisitor() { + return new Visitor(); + } + + private class Visitor extends JavaRecursiveElementVisitor { + @Override + public void visitMethod(PsiMethod method) { + methods.push(method); + visitedMethods.add(method); + super.visitMethod(method); + methods.pop(); + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + } + + @Override + public void visitCallExpression(PsiCallExpression callExpression) { + super.visitCallExpression(callExpression); + final PsiMethod method = callExpression.resolveMethod(); + if (method == null || !methods.empty() && methods.peek().equals(method)) { + return; + } + metrics.incrementBucketValue(method); + } + } +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/methodCalculators/FanOutMethodCalculator.java b/stockmetrics/src/com/sixrr/stockmetrics/methodCalculators/FanOutMethodCalculator.java new file mode 100644 index 0000000..e4b22d8 --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/methodCalculators/FanOutMethodCalculator.java @@ -0,0 +1,75 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.methodCalculators; + +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.sixrr.metrics.utils.BucketedCount; +import com.sixrr.metrics.utils.MethodUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Stack; + +/** + * @author Aleksandr Chudov. + */ +public class FanOutMethodCalculator extends MethodCalculator { + private final Collection visitedMethods = new ArrayList(); + private final BucketedCount metrics = new BucketedCount(); + private final Stack methods = new Stack(); + + @Override + protected PsiElementVisitor createVisitor() { + return new Visitor(); + } + + @Override + public void endMetricsRun() { + for (final PsiMethod method : visitedMethods) { + postMetric(method, metrics.getBucketValue(method)); + } + super.endMetricsRun(); + } + + private class Visitor extends JavaRecursiveElementVisitor { + @Override + public void visitMethod(PsiMethod method) { + methods.push(method); + visitedMethods.add(method); + super.visitMethod(method); + methods.pop(); + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + } + + @Override + public void visitCallExpression(PsiCallExpression callExpression) { + super.visitCallExpression(callExpression); + if (methods.empty()) { + return; + } + final PsiMethod method = callExpression.resolveMethod(); + if (method == null || methods.peek().equals(method)) { + return; + } + metrics.incrementBucketValue(methods.peek()); + } + } +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/methodMetrics/FanInMethodMetric.java b/stockmetrics/src/com/sixrr/stockmetrics/methodMetrics/FanInMethodMetric.java new file mode 100644 index 0000000..31dea1d --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/methodMetrics/FanInMethodMetric.java @@ -0,0 +1,53 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.methodMetrics; + +import com.sixrr.metrics.MetricCalculator; +import com.sixrr.metrics.MetricType; +import com.sixrr.stockmetrics.i18n.StockMetricsBundle; +import com.sixrr.stockmetrics.methodCalculators.FanInMethodCalculator; +import com.sixrr.stockmetrics.methodCalculators.MethodCalculator; +import org.jetbrains.annotations.NotNull; + +/** + * Created by Aleksandr Chudov on 02.04.2017. + */ +public class FanInMethodMetric extends MethodMetric { + @NotNull + @Override + public String getDisplayName() { + return StockMetricsBundle.message("fan.in.method.metric.display.name"); + } + + @NotNull + @Override + public String getAbbreviation() { + return StockMetricsBundle.message("fan.in.method.metric.abbreviation"); + } + + @NotNull + @Override + public MetricType getType() { + return MetricType.Count; + } + + @NotNull + @Override + public MetricCalculator createCalculator() { + return new FanInMethodCalculator(); + } +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/methodMetrics/FanOutMethodMetric.java b/stockmetrics/src/com/sixrr/stockmetrics/methodMetrics/FanOutMethodMetric.java new file mode 100644 index 0000000..acee8b9 --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/methodMetrics/FanOutMethodMetric.java @@ -0,0 +1,52 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.methodMetrics; + +import com.sixrr.metrics.MetricCalculator; +import com.sixrr.metrics.MetricType; +import com.sixrr.stockmetrics.i18n.StockMetricsBundle; +import com.sixrr.stockmetrics.methodCalculators.FanOutMethodCalculator; +import org.jetbrains.annotations.NotNull; + +/** + * Created by Aleksandr Chudov on 02.04.2017. + */ +public class FanOutMethodMetric extends MethodMetric { + @NotNull + @Override + public String getDisplayName() { + return StockMetricsBundle.message("fan.out.method.metric.display.name"); + } + + @NotNull + @Override + public String getAbbreviation() { + return StockMetricsBundle.message("fan.out.method.metric.abbreviation"); + } + + @NotNull + @Override + public MetricType getType() { + return MetricType.Count; + } + + @NotNull + @Override + public MetricCalculator createCalculator() { + return new FanOutMethodCalculator(); + } +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/utils/FieldUsageMap.java b/stockmetrics/src/com/sixrr/stockmetrics/utils/FieldUsageMap.java new file mode 100644 index 0000000..e6b7e59 --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/utils/FieldUsageMap.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.utils; + +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiReference; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * @author Aleksandr Chudov. + */ +public interface FieldUsageMap { + @NotNull + Set calculateFieldUsagePoints(PsiField field); + + @NotNull + Set calculateTestFieldUsagePoints(PsiField field); + + @NotNull + Set calculateProductFieldUsagePoints(PsiField field); +} diff --git a/stockmetrics/src/com/sixrr/stockmetrics/utils/FieldUsageMapImpl.java b/stockmetrics/src/com/sixrr/stockmetrics/utils/FieldUsageMapImpl.java new file mode 100644 index 0000000..456275e --- /dev/null +++ b/stockmetrics/src/com/sixrr/stockmetrics/utils/FieldUsageMapImpl.java @@ -0,0 +1,100 @@ +/* + * Copyright 2005-2017 Sixth and Red River Software, Bas Leijdekkers + * + * Licensed 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 com.sixrr.stockmetrics.utils; + +import com.intellij.psi.*; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.Query; +import com.sixrr.metrics.utils.TestUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * @author Aleksandr Chudov. + */ +public class FieldUsageMapImpl implements FieldUsageMap { + private final Map, Set> fieldUsagePointMap = + new HashMap, Set>(1024); + private final Map, Set> fieldTestUsagePointMap = + new HashMap, Set>(1024); + private final Map, Set> fieldProductUsagePointMap = + new HashMap, Set>(1024); + + @NotNull + @Override + public Set calculateFieldUsagePoints(PsiField field) { + return getUsages(fieldUsagePointMap, field); + } + + @NotNull + @Override + public Set calculateTestFieldUsagePoints(PsiField field) { + return getUsages(fieldTestUsagePointMap, field); + } + + @NotNull + @Override + public Set calculateProductFieldUsagePoints(PsiField field) { + return getUsages(fieldProductUsagePointMap, field); + } + + private Set getUsages(Map, Set> map, PsiField field) { + final SmartPsiElementPointer pointer = getPointer(field); + if (!map.containsKey(pointer)) { + calculateUsages(field); + } + return map.get(pointer); + } + + @NotNull + private SmartPsiElementPointer getPointer(PsiField field) { + final SmartPointerManager manager = SmartPointerManager.getInstance(field.getProject()); + return manager.createSmartPsiElementPointer(field); + } + + private void calculateUsages(PsiField field) { + final Set allUsages = new HashSet(); + final Set testUsages = new HashSet(); + final Set productUsages = new HashSet(); + + final Query query = ReferencesSearch.search(field); + for (final PsiReference reference : query) { + final PsiElement element = reference.getElement(); + + final PsiClass referenceClass = PsiTreeUtil.getParentOfType(element, PsiClass.class); + if (referenceClass == null) { + continue; + } + allUsages.add(reference); + if (TestUtils.isTest(referenceClass)) { + testUsages.add(reference); + } + if (TestUtils.isProduction(referenceClass)) { + productUsages.add(reference); + } + } + final SmartPsiElementPointer pointer = getPointer(field); + fieldUsagePointMap.put(pointer, allUsages); + fieldTestUsagePointMap.put(pointer, testUsages); + fieldProductUsagePointMap.put(pointer, productUsages); + } +}