SquirrelJME/SquirrelJME

View on GitHub
tools/squirreljme-debugger/src/main/java/cc/squirreljme/debugger/ShownContextMethod.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// Multi-Phasic Applications: SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package cc.squirreljme.debugger;

import java.awt.BorderLayout;
import java.util.Objects;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import net.multiphasicapps.classfile.ClassFile;
import net.multiphasicapps.classfile.Method;
import net.multiphasicapps.classfile.MethodFlags;
import net.multiphasicapps.classfile.MethodNameAndType;

/**
 * Shows the current context method.
 *
 * @since 2024/01/25
 */
public class ShownContextMethod
    extends JPanel
    implements ContextThreadFrameListener
{
    /** The current context. */
    protected final ContextThreadFrame context;
    
    /** The debugger state. */
    protected final DebuggerState state;
    
    /** Information label. */
    protected final JLabel info;
    
    /** Variables used. */
    protected final ShownContextVariables variables;
    
    /** The preferences to use for local classes. */
    protected final Preferences preferences;
    
    /** Currently shown method. */
    private volatile ShownMethod _shownMethod;
    
    /** The method we are looking at. */
    private volatile InfoMethod _lookingAt;
    
    /**
     * Initializes the context method shower.
     *
     * @param __state The current state.
     * @param __context The context to show.
     * @param __preferences Preferences to use.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/25
     */
    public ShownContextMethod(DebuggerState __state,
        ContextThreadFrame __context, Preferences __preferences)
    {
        if (__state == null || __context == null)
            throw new NullPointerException("NARG");
        
        // Store for later
        this.state = __state;
        this.context = __context;
        this.preferences = __preferences;
        
        // Border layout is pretty clean
        this.setLayout(new BorderLayout());
        
        // Information label
        JLabel info = new JLabel();
        info.setHorizontalAlignment(SwingConstants.CENTER);
        this.info = info;
        this.add(info, BorderLayout.PAGE_START);
        
        // Add view of local variables
        ShownContextVariables variables = new ShownContextVariables(__state,
            __context);
        this.variables = variables;
        this.add(variables, BorderLayout.PAGE_END);
        
        // Set listener for this to update everything
        __context.addListener(this);
        
        // Request that everything gets updated
        Utils.swingInvoke(this::update);
    }
    
    /**
     * {@inheritDoc}
     * @since 2024/01/25
     */
    @Override
    public void contextChanged(InfoThread __oldThread, InfoFrame __oldFrame,
        FrameLocation __oldLocation, InfoThread __newThread,
        InfoFrame __newFrame, FrameLocation __newLocation)
    {
        // Use normal update
        this.update();
    }
    
    /**
     * Updates the current item.
     *
     * @since 2024/01/26
     */
    public void update()
    {
        ShownMethod current = this._shownMethod;
        
        // Get frame and the method we are in
        InfoFrame inFrame = this.context.getFrame();
        InfoMethod inMethod = (inFrame == null ? null : inFrame.inMethod());
        
        // If there is no context, then we cannot show anything
        if (inMethod == null || inFrame == null)
        {
            // Remove old one if it is there
            if (current != null)
            {
                this.remove(current);
                current = null;
            }
        }
        
        // Do we need to replace the method being shown?
        else if (current == null || !Objects.equals(this._lookingAt, inMethod))
        {
            // Remove old one if it is there
            if (current != null)
            {
                this.remove(current);
                current = null;
            }
            
            // Setup new view for the current method
            if (inMethod != null)
            {
                // Should we load a local class or remote?
                MethodViewer remote = new RemoteMethodViewer(this.state,
                    inMethod);
                Method localMethod = ShownContextMethod.__possiblyLocal(
                    this.state, remote, this.preferences);
                MethodViewer viewer;
                if (localMethod != null)
                    viewer = new JavaMethodViewer(localMethod);
                
                // Use remote method
                else
                    viewer = remote;
                
                // Initialize view
                current = new ShownMethod(this.state, viewer, this.context,
                    true);
                this._shownMethod = current;
                this._lookingAt = inMethod;
                this.add(current, BorderLayout.CENTER);
                
                // Make sure it gets updated
                current.shownUpdate();
            }
        }
        
        // Update it regardless
        else
            current.shownUpdate();
        
        // If there is nothing, just say as such...
        if (current == null)
            this.info.setText("Nothing");
        
        // Otherwise describe the current frame
        else
            this.info.setText(String.format("%s @ %d",
                inMethod, inFrame.location.index));
        
        // Update variables
        this.variables.update();
        
        // Repaint
        Utils.revalidate(this.info);
        Utils.revalidate(this);
    }
    
    /**
     * Should this use local classes?
     *
     * @param __state The state to load from.
     * @param __inMethod The method to look in.
     * @param __preferences The preferences used.
     * @return If a local class should be loaded in.
     * @throws NullPointerException On null arguments.
     * @since 2024/01/29
     */
    private static Method __possiblyLocal(DebuggerState __state,
        MethodViewer __inMethod, Preferences __preferences)
        throws NullPointerException
    {
        if (__inMethod == null || __preferences == null)
            throw new NullPointerException("NARG");
        
        // Ignore for native and abstract as there will never be byte code
        // Also if we do not know the class we are in, we cannot do anything
        // either
        if (__inMethod.isAbstract() || __inMethod.isNative() ||
            __inMethod.inClass() == null)
            return null;
        
        // If we are doing local classes only or when the VM has no
        // instructions to give... then we use a local class assuming it
        // exists
        InstructionViewer[] instructions = __inMethod.instructions();
        if (instructions == null || instructions.length == 0 ||
            __preferences.isLocalClassOnly())
        {
            // We need to load the class first
            ClassFile[] classFiles = Utils.loadClass(__inMethod.inClass(),
                __preferences);
            if (classFiles == null || classFiles.length == 0)
                return null;
            
            // Try to find the matching method in the first class that matches
            MethodNameAndType wantNat = __inMethod.methodNameAndType();
            for (ClassFile classFile : classFiles)
                for (Method method : classFile.methods())
                    if (Objects.equals(wantNat, method.nameAndType()))
                        return method;
        }
        
        // Use remote class
        return null;
    }
}