/* * Copyright (C) 2012 Sony Mobile Communications AB * * This file is part of ApkAnalyser. * * 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 andreflect.gui.chart; import gui.AbstractMainFrame; import java.awt.BorderLayout; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeSet; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JPanel; import mereflect.CollaborateClassContext; import mereflect.MEClass; import mereflect.MEField; import mereflect.MEMethod; import mereflect.UnknownClass; import mereflect.UnknownContext; import mereflect.UnknownResContext; import analyser.gui.ClassTreeRenderer; import analyser.gui.FlagIcon; import analyser.gui.MainFrame; import analyser.logic.InvSnooper; import analyser.logic.RefClass; import analyser.logic.RefContext; import analyser.logic.RefFieldAccess; import analyser.logic.RefInvokation; import analyser.logic.RefMethod; import analyser.logic.RefPackage; import analyser.logic.Reference; import analyser.logic.ReferredReference; import andreflect.Util; import andreflect.gui.chart.shape.ClassShape; import andreflect.gui.chart.shape.FolderShape; import andreflect.gui.chart.shape.PackageShape; import com.mxgraph.canvas.mxGraphics2DCanvas; import com.mxgraph.model.mxGeometry; import com.mxgraph.model.mxIGraphModel; import com.mxgraph.swing.mxGraphComponent; import com.mxgraph.swing.mxGraphOutline; import com.mxgraph.swing.handler.mxRubberband; import com.mxgraph.util.mxConstants; import com.mxgraph.util.mxRectangle; import com.mxgraph.util.mxUtils; public class GraphPanel extends JPanel { public static final String SHAPE_PACKAGE = "package_shapestyle"; public static final String SHAPE_FOLDER = "folder_shapestyle"; public static final String SHAPE_CLASS = "class_shapestyle"; public static final String STYLE_PACKAGE = "package"; public static final String STYLE_FOLDER = "folder"; public static final String STYLE_CLASS = "class"; public static final int CHAR_SPACING = 3; public static final int LINE_SPACING = 6; public static final ImageIcon FOLDING_ICON = mxGraphComponent.DEFAULT_COLLAPSED_ICON; protected RefContext mCurrentRefContext = null; protected Reference mCurrentReference = null; private static final long serialVersionUID = -6561623072112577140L; protected CustomGraphComponent mGraphComponent; protected CustomGraph mGraph; protected mxIGraphModel mModel; protected mxRubberband rubberband; static { mxGraphics2DCanvas.putShape(SHAPE_PACKAGE, new PackageShape()); mxGraphics2DCanvas.putShape(SHAPE_FOLDER, new FolderShape()); mxGraphics2DCanvas.putShape(SHAPE_CLASS, new ClassShape()); } private void removeAll(Object cell) { if (cell == null) { cell = mGraph.getDefaultParent(); } Object[] children = mGraph.getChildVertices(cell); for (Object child : children) { removeAll(child); mGraph.removeCells(new Object[] { child }); } } private boolean isInnerClass(int index, String name, RefClass clazz) { HashMap<RefClass, TreeSet<RefClass>> classMap = mGraphComponent.getGraph().getClassMap(); if (index != -1) { Iterator<RefClass> i = classMap.keySet().iterator(); while (i.hasNext()) { RefClass refc = i.next(); if (refc.getParent() == clazz.getParent() //in same package && refc.getName().equals(name.substring(0, index))) { TreeSet<RefClass> set = classMap.get(refc); if (set == null) { set = new TreeSet<RefClass>(); classMap.put(refc, set); } set.add(clazz); //System.out.println(name + " inner of " + refc.getName()); return true; } } return isInnerClass(name.indexOf('$', index + 1), name, clazz); } else { classMap.put(clazz, null); return false; } } public void refresh() { mModel.beginUpdate(); mGraph.clearSelection(); generateVertex(mCurrentRefContext); if (!(mCurrentReference instanceof RefContext)) { renderDependecy(mCurrentReference); } mModel.endUpdate(); } public void loadContext(Reference ref) { Reference refCtx = ref; if (mCurrentReference == ref) { return; } mModel.beginUpdate(); mGraph.clearSelection(); while (!(refCtx instanceof RefContext)) { refCtx = refCtx.getParent(); } if (((RefContext) refCtx).getContext().isMidlet() == true) { if (mCurrentRefContext != refCtx) { generateVertex((RefContext) refCtx); } } renderDependecy(ref); mModel.endUpdate(); } private void generateVertex(RefContext refCtx) { removeAll(null); mCurrentRefContext = refCtx; ArrayList<Reference> refPackages = new ArrayList<Reference>(); traverse(refCtx, refPackages, RefPackage.class); insertPacakge(refPackages, true); Collection<Reference> refResourceCtxes = MainFrame.getInstance().getResolver().getReferenceResources(); for (Reference refResourceCtx : refResourceCtxes) { if (refResourceCtx.getReferred() instanceof UnknownContext || refResourceCtx.getReferred() instanceof UnknownResContext) { //ignore unknown reference context continue; } ArrayList<Reference> refResourcePackages = new ArrayList<Reference>(); traverse(refResourceCtx, refResourcePackages, RefPackage.class); insertPacakge(refResourcePackages, false); } removeUnnessaryPackage(null); mGraph.doLayoutFirst(); } private void renderDependecy(Reference ref) { mCurrentReference = ref; mGraph.cellsFolded(new Object[] { mGraph.getDefaultParent() }, true, true); //class list of dependency //Object could be MEClass for inherit, MEMethod for invokation, MEField for access HashMap<MEClass, ArrayList<Object>> dep = new HashMap<MEClass, ArrayList<Object>>(); if (!(ref instanceof RefContext)) { ArrayList<Reference> refClasses = new ArrayList<Reference>(); traverse(ref, refClasses, RefClass.class); for (Reference r : refClasses) { RefClass refClass = (RefClass) r; try { for (MEClass parent : InvSnooper.findClassParents(refClass.getMEClass())) { MEClass p = parent; //System.out.println(parent.getName()); if (parent instanceof UnknownClass) { CollaborateClassContext sctx = MainFrame.getInstance().getResolver().getReferenceContext(); try { p = sctx.getMEClass(parent.getName()); } catch (ClassNotFoundException e) { //System.out.println(" not found super" + parent.getName()); } } if (!dep.containsKey(p)) { dep.put(p, new ArrayList<Object>()); } dep.get(p).add(p); } } catch (Throwable e1) { e1.printStackTrace(); } String unknownSuperClassName = refClass.getMEClass().getUnknownSuperClassName(); if (unknownSuperClassName != null) { CollaborateClassContext sctx = MainFrame.getInstance().getResolver().getReferenceContext(); try { MEClass superClass = sctx.getMEClass(unknownSuperClassName); if (!dep.containsKey(superClass)) { dep.put(superClass, new ArrayList<Object>()); } dep.get(superClass).add(superClass); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { } } for (Reference refx : refClass.getChildren()) { if (refx instanceof RefMethod) { RefMethod refMethod = (RefMethod) refx; //internal field access for (ReferredReference referRef : refMethod.getReferredReference(true)) { if (referRef instanceof RefFieldAccess) { RefFieldAccess refFieldAccess = (RefFieldAccess) referRef; MEField f = refFieldAccess.getAccess().field; MEClass c = refFieldAccess.getAccess().clazz; if (!dep.containsKey(c)) { dep.put(c, new ArrayList<Object>()); } dep.get(c).add(f); } } //external field access for (ReferredReference referRef : refMethod.getReferredReference(false)) { if (referRef instanceof RefFieldAccess) { RefFieldAccess refFieldAccess = (RefFieldAccess) referRef; MEField f = refFieldAccess.getAccess().field; MEClass c = refFieldAccess.getAccess().clazz; if (!dep.containsKey(c)) { dep.put(c, new ArrayList<Object>()); } dep.get(c).add(f); } } for (Reference refxx : refMethod.getChildren()) { RefInvokation refInvokation = (RefInvokation) refxx; MEMethod m = refInvokation.getInvokation().method; MEClass c = refInvokation.getInvokation().clazz; if (!dep.containsKey(c)) { dep.put(c, new ArrayList<Object>()); } dep.get(c).add(m); } }// if (refx instanceof RefMethod) }// for (Reference refx : refClass.getChildren()) }//for (Reference r: refClasses) }//ref instanceof RefContext UnfoldDependency(dep, ref, null, ref instanceof RefClass, ref instanceof RefContext); mGraph.doLayoutAgain(); } private void UnfoldDependency(HashMap<MEClass, ArrayList<Object>> dep, Reference selected, Object cell, boolean unfoldClass, boolean unfoldPackage) { if (cell == null) { cell = mGraph.getDefaultParent(); } Object[] children = mGraph.getChildVertices(cell); for (Object child : children) { Object val = mModel.getValue(child); if (val instanceof ClassComponent) { ClassComponent classSplit = (ClassComponent) val; classSplit.resetToArch(); classSplit.clean(); RefClass refClass = classSplit.getRefClass(); if (selected == refClass || (selected instanceof RefPackage && refClass.getParent() == selected)) { classSplit.setFocus(); } boolean hit = false; ArrayList<RefClass> classes = new ArrayList<RefClass>(); classes.add(refClass); HashMap<RefClass, TreeSet<RefClass>> classMap = mGraphComponent.getGraph().getClassMap(); if (classMap.containsKey(refClass)) { TreeSet<RefClass> tree = classMap.get(refClass); if (tree != null) { for (RefClass ref : tree) { classes.add(ref); if (!classSplit.isFocus()) { if (selected == ref || (selected instanceof RefPackage && refClass.getParent() == selected)) { classSplit.setFocus(); } } } } } for (RefClass refc : classes) { MEClass clazz = refc.getMEClass(); if (dep.containsKey(clazz)) { for (Object o : dep.get(clazz)) { if (o instanceof MEClass) { if (clazz == o) { hit = true; classSplit.setDepSuper(); } } else if (o instanceof MEField) { for (Reference ro : refc.getChildren()) { if (ro.getReferred() == o) { hit = true; classSplit.addField((MEField) o); classSplit.setDependecy(); } } } else if (o instanceof MEMethod) { for (Reference ro : refc.getChildren()) { if (ro.getReferred() == o) { hit = true; classSplit.addMethod((MEMethod) o); classSplit.setDependecy(); } } } } if (!hit) { //this could be the same hashcode //System.out.println(" not hit " + clazz.getName()); } } } if (!classSplit.isArch()) { if (unfoldClass) { recursiveUnfolder(child); } else { recursiveUnfolder(mModel.getParent(child)); } } else if (unfoldPackage) { recursiveUnfolder(mModel.getParent(child)); } Rectangle size = calculateClassSize(classSplit, child); mxGeometry geo = mGraph.getModel().getGeometry(child); if (mModel.isCollapsed(child)) { geo.setAlternateBounds(new mxRectangle(geo.getX(), geo.getY(), size.getWidth(), size.getHeight())); } else { geo = (mxGeometry) geo.clone(); geo.setWidth(size.getWidth()); geo.setHeight(size.getHeight()); mGraph.cellsResized(new Object[] { child }, new mxRectangle[] { geo }); } } else { UnfoldDependency(dep, selected, child, unfoldClass, unfoldPackage); } } } private void recursiveUnfolder(Object cell) { if (cell == null) { return; } mGraph.cellsFolded(new Object[] { cell }, false, false); recursiveUnfolder(mModel.getParent(cell)); } private void traverse(Reference ref, ArrayList<Reference> result, Class<? extends Reference> clazz) { if (clazz.isInstance(ref)) { result.add(ref); } Iterator<Reference> refs = ref.getChildren().iterator(); while (refs.hasNext()) { Reference child = (refs.next()); if (clazz.isInstance(child)) { result.add(child); } else { traverse(child, result, clazz); } } } private void removeUnnessaryPackage(Object parent) { if (parent == null) { parent = mGraph.getDefaultParent(); } Object parentVal = mModel.getValue(parent); Object[] children = mGraph.getChildVertices(parent); if (children.length == 1) { Object onlyChild = children[0]; Object val = mModel.getValue(onlyChild); if (val instanceof PackageComponent && parentVal instanceof PackageComponent && ((PackageComponent) parentVal).getRefPackage() == null) { PackageComponent split = new PackageComponent(((PackageComponent) parentVal).getName() + "." + ((PackageComponent) val).getName(), ((PackageComponent) parentVal).isMidlet); split.setHasInnerPacakge(((PackageComponent) parentVal).hasInnerPackage() || ((PackageComponent) val).hasInnerPackage()); mModel.setValue(parent, split); Object[] childrenChildren = mGraph.getChildVertices(onlyChild); for (Object child : childrenChildren) { mGraph.addCell(child, parent); } mGraph.removeCells(new Object[] { onlyChild }); removeUnnessaryPackage(parent); return; } } for (Object child : children) { removeUnnessaryPackage(child); } } private void insertPacakge(ArrayList<Reference> refPackages, boolean isMidlet) { //make sure packages are sorted that parent package should be a head of sub package Collections.sort(refPackages); //insert all packages for (Reference refx : refPackages) { RefPackage refPackage = (RefPackage) refx; Object packageObj; String[] names = refPackage.getName().split("\\."); if (names.length > 1) { Object p = mGraph.getDefaultParent(); for (int i = 0; i < names.length - 1; i++) { Object[] children = mGraph.getChildCells(p); Object found = null; for (Object child : children) { Object val = mGraph.getModel().getValue(child); if (val instanceof PackageComponent && ((PackageComponent) val).isSameName(names[i]) && ((PackageComponent) val).isMidlet == isMidlet) { found = child; ((PackageComponent) val).setHasInnerPacakge(true); break; } } if (found == null) { p = mGraph.insertVertex(p, null, new PackageComponent(names[i], isMidlet), 20, 20, 20, 10, STYLE_PACKAGE + ";noLabel=true;resizable=true"); } else { p = found; } } packageObj = mGraph.insertVertex(p, null, new PackageComponent(names[names.length - 1], refPackage, isMidlet), 20, 20, 20, 10, STYLE_PACKAGE + ";noLabel=true;resizable=true"); } else { packageObj = mGraph.insertVertex(mGraph.getDefaultParent(), null, new PackageComponent(refPackage.getName(), refPackage, isMidlet), 20, 20, 20, 10, STYLE_PACKAGE + ";noLabel=true;resizable=true"); } //insert all classes List<RefClass> list = Arrays.asList(refPackage.getChildren().toArray(new RefClass[0])); //sort the class because the outer class should be a head of inner class Collections.sort(list); Iterator<RefClass> i = list.iterator(); while (i.hasNext()) { RefClass refClass = i.next(); String name = refClass.getName(); if (isInnerClass(name.indexOf('$'), name, refClass) == false) { ClassComponent classSplit = new ClassComponent(refClass); Object v = mGraph.insertVertex(packageObj, null, classSplit, 0, 0, 0, 0, STYLE_CLASS + ";noLabel=true;fillColor=white;resizable=false"); Rectangle size = calculateClassSize(classSplit, v); mxGeometry geo = mGraph.getModel().getGeometry(v); geo = (mxGeometry) geo.clone(); geo.setWidth(size.getWidth()); geo.setHeight(size.getHeight()); mGraph.cellsResized(new Object[] { v }, new mxRectangle[] { geo }); mGraph.cellsFolded(new Object[] { v }, true, false); } } } } private Rectangle getLineSize(String text, Object cell, boolean firstClassName) { double scale = 1; Rectangle fontRect = mxUtils.getSizeForString(text, mxUtils.getFont(mGraph.getView().getState(cell, true) .getStyle()), scale).getRectangle(); FlagIcon im = ClassTreeRenderer.ICON_CLASS; double imageheight = Math.min(im.getIconHeight(), fontRect.height); double imagewidth = imageheight / im.getIconHeight() * im.getIconWidth(); //ratio is fixed it will changed in canvas.drawImage() if (firstClassName) { return new Rectangle((int) (CHAR_SPACING + imagewidth + CHAR_SPACING + fontRect.width + CHAR_SPACING + FOLDING_ICON.getIconWidth() * 2 + CHAR_SPACING * 2), (int) fontRect.getHeight()); } else { return new Rectangle((int) (CHAR_SPACING + imagewidth + CHAR_SPACING + fontRect.width + CHAR_SPACING), (int) fontRect.getHeight()); } } private Rectangle calculateClassSize(ClassComponent classSplit, Object cell) { MEClass clazz = classSplit.getMEClass(); Rectangle ret = getLineSize(clazz.getClassName(), cell, true); boolean hasField = false; for (MEField field : classSplit.getFields()) { String text = field.getName() + " : " + Util.shortenClassName(field.getType().toString()); Rectangle oneLine = getLineSize(text, cell, false); ret.height += oneLine.height; if (ret.width < oneLine.width) { ret.width = oneLine.width; } hasField = true; } if (!hasField) { ret.height += GraphPanel.LINE_SPACING; } boolean hasMethod = false; for (MEMethod method : classSplit.getMethods()) { hasMethod = true; String text = method.getFormattedName() + "(" + method.getArgumentsStringUml() + ") : " + Util.shortenClassName(method.getReturnClassString()); Rectangle oneLine = getLineSize(text, cell, false); ret.height += oneLine.height; if (ret.width < oneLine.width) { ret.width = oneLine.width; } } if (!hasMethod) { ret.height += GraphPanel.LINE_SPACING; } return ret; } protected AbstractMainFrame m_mainFrame; public GraphPanel(AbstractMainFrame mf) { m_mainFrame = mf; // Stores a reference to the graph and creates the command history mGraph = new CustomGraph(); mGraphComponent = new CustomGraphComponent(mGraph); mModel = mGraph.getModel(); // Do not change the scale and translation after files have been loaded mGraph.setResetViewOnRootChange(false); // Puts everything together setLayout(new BorderLayout()); add(mGraphComponent, BorderLayout.CENTER); // Installs rubberband selection and handling for some special // keystrokes such as F2, Control-C, -V, X, A etc. installHandlers(); installListeners(); getGraphComponent().getGraph().setCellsResizable(true); getGraphComponent().setConnectable(false); getGraphComponent().getGraphHandler().setCloneEnabled(false); getGraphComponent().getGraphHandler().setImagePreview(false); Map<String, Object> style; style = new HashMap<String, Object>(); style.put(mxConstants.STYLE_SHAPE, SHAPE_PACKAGE); style.put(mxConstants.STYLE_FOLDABLE, "true"); mGraph.getStylesheet().putCellStyle(STYLE_PACKAGE, style); style = new HashMap<String, Object>(); style.put(mxConstants.STYLE_SHAPE, SHAPE_FOLDER); style.put(mxConstants.STYLE_FOLDABLE, "true"); mGraph.getStylesheet().putCellStyle(STYLE_FOLDER, style); style = new HashMap<String, Object>(); style.put(mxConstants.STYLE_SHAPE, SHAPE_CLASS); style.put(mxConstants.STYLE_FOLDABLE, "true"); mGraph.getStylesheet().putCellStyle(STYLE_CLASS, style); } protected void installHandlers() { rubberband = new mxRubberband(mGraphComponent); } protected void mouseWheelMoved(MouseWheelEvent e) { if (e.getWheelRotation() < 0) { mGraphComponent.zoomIn(); } else { mGraphComponent.zoomOut(); } double scale = mGraphComponent.getGraph().getView().getScale(); double percent = Math.round(100 * scale); double newscale = Math.min(8, Math.max(0.05, percent / 100)); mGraphComponent.zoomTo(newscale, mGraphComponent .isCenterZoom()); } protected void installListeners() { // Installs mouse wheel listener for zooming MouseWheelListener wheelTracker = new MouseWheelListener() { @Override public void mouseWheelMoved(MouseWheelEvent e) { if (e.getSource() instanceof mxGraphOutline || e.isControlDown()) { GraphPanel.this.mouseWheelMoved(e); } } }; // Handles mouse wheel events in the outline and graph component mGraphComponent.addMouseWheelListener(wheelTracker); // Installs the popup menu in the graph component mGraphComponent.getGraphControl().addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } }); } public mxGraphComponent getGraphComponent() { return mGraphComponent; } /** * * @param name * @param action * @return a new Action bound to the specified string name */ public Action bind(String name, final Action action) { return bind(name, action, null); } public static ImageIcon createImageIcon(String path) { java.net.URL imgURL = Thread.currentThread().getContextClassLoader().getResource(path); if (imgURL != null) { return new ImageIcon(imgURL); } else { System.err.println("Couldn't find file: " + path); return null; } } /** * * @param name * @param action * @return a new Action bound to the specified string name and icon */ @SuppressWarnings("serial") public Action bind(String name, final Action action, String iconUrl) { return new AbstractAction(name, (iconUrl != null) ? createImageIcon(iconUrl) : null) { @Override public void actionPerformed(ActionEvent e) { action.actionPerformed(new ActionEvent(getGraphComponent(), e .getID(), e.getActionCommand())); } }; } }