SquirrelJME/SquirrelJME

View on GitHub
modules/tool-classfile/src/main/java/net/multiphasicapps/classfile/BinaryName.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// 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 net.multiphasicapps.classfile;

import cc.squirreljme.runtime.cldc.debug.Debugging;
import cc.squirreljme.runtime.cldc.util.UnmodifiableIterator;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import net.multiphasicapps.collections.UnmodifiableList;

/**
 * This represents a binary name which consists of a class which is
 * separated internally by forwarded slashes.
 *
 * @since 2017/09/27
 */
public final class BinaryName
    implements Comparable<BinaryName>, Iterable<ClassIdentifier>
{
    /** The identifiers in the name. */
    private final ClassIdentifier[] _identifiers;
    
    /** String representation. */
    private Reference<String> _string;
    
    /** The package this is in. */
    private Reference<BinaryName> _package;
    
    /** The hash code. */
    private int _hashCode;
    
    /**
     * Initializes the binary name.
     *
     * @param __n The name to initialize.
     * @throws InvalidClassFormatException If the binary name is valid.
     * @throws NullPointerException On null arguments.
     * @since 2017/09/27
     */
    public BinaryName(String __n)
        throws InvalidClassFormatException, NullPointerException
    {
        if (__n == null)
            throw new NullPointerException("NARG");
        
        // Split
        List<ClassIdentifier> id = new ArrayList<>();
        for (int i = 0, n = __n.length(); i < n;)
        {
            // Identifiers are always split by forward slashes like UNIX
            // paths
            int ns = __n.indexOf('/', i);
            if (ns < 0)
                ns = n;
            
            // Split in
            id.add(new ClassIdentifier(__n.substring(i, ns)));
            
            // Skip
            i = ns + 1;
        }
        
        this._identifiers = id.<ClassIdentifier>toArray(
            new ClassIdentifier[id.size()]);
    }
    
    /**
     * Initializes the binary name by identifiers.
     * 
     * @param __ids The identifiers.
     * @throws IllegalArgumentException If there are no identifiers.
     * @throws NullPointerException On null arguments.
     * @since 2020/11/28
     */
    public BinaryName(ClassIdentifier... __ids)
        throws IllegalArgumentException, NullPointerException
    {
        if (__ids == null)
            throw new NullPointerException("NARG");
        
        /* {@squirreljme.error JC0h A binary name cannot have zero identifier
        fragments.} */
        if (__ids.length <= 0)
            throw new IllegalArgumentException("JC0h");
        
        // There cannot be any nulls here
        __ids = __ids.clone();
        for (ClassIdentifier id : __ids)
            if (id == null)
                throw new NullPointerException("NARG");
        
        this._identifiers = __ids;
    }
    
    /**
     * {@inheritDoc}
     * @since 2017/09/27
     */
    @Override
    public int compareTo(BinaryName __o)
    {
        ClassIdentifier[] a = this._identifiers,
            b = __o._identifiers;
        
        // Compare lengths first
        int an = a.length,
            bn = b.length;
        int rv = an - bn;
        if (rv != 0)
            return rv;
        
        // Then individual units
        for (int i = 0; i < an; i++)
        {
            ClassIdentifier x = a[i],
                y = b[i];
            
            rv = x.compareTo(y);
            if (rv != 0)
                return rv;
        }
        
        // Matches
        return 0;
    }
    
    /**
     * {@inheritDoc}
     * @since 2017/09/27
     */
    @Override
    public boolean equals(Object __o)
    {
        if (!(__o instanceof BinaryName))
            return false;
        
        BinaryName o = (BinaryName)__o;
        return Arrays.equals(this._identifiers, o._identifiers);
    }
    
    /**
     * {@inheritDoc}
     * @since 2017/09/27
     */
    @Override
    public int hashCode()
    {
        int rv = this._hashCode;
        if (rv != 0)
            return rv;
        
        // Calculate the hash, do a bunch of operations on it to try to
        // prevent potential collisions
        this._hashCode = (rv = ((Arrays.asList(this._identifiers).hashCode()
            /* - this._identifiers.length) ^ this.toString().hashCode(*/)));
        return rv;
    }
    
    /**
     * Returns all of the identifiers for the binary name.
     * 
     * @return The identifiers for this binary name.
     * @since 2022/08/24
     */
    public List<ClassIdentifier> identifiers()
    {
        return UnmodifiableList.of(Arrays.asList(this._identifiers));
    }
    
    /**
     * Returns the binary name of the package this class is within.
     *
     * @return The binary name of the owning package.
     * @since 2017/10/09
     */
    public BinaryName inPackage()
    {
        Reference<BinaryName> ref = this._package;
        BinaryName rv;
        
        if (ref == null || null == (rv = ref.get()))
        {
            StringBuilder sb = new StringBuilder();
            ClassIdentifier[] identifier = this._identifiers;
            
            for (int i = 0, n = identifier.length - 1; i < n; i++)
            {
                if (i > 0)
                    sb.append('/');
                sb.append(identifier[i]);
            }
            
            this._package = new SoftReference<>(
                (rv = new BinaryName(sb.toString())));
        }
        
        return rv;
    }
    
    /**
     * {@inheritDoc}
     * @since 2022/08/24
     */
    @Override
    public Iterator<ClassIdentifier> iterator()
    {
        return UnmodifiableIterator.of(this._identifiers);
    }
    
    /**
     * Resolves a class within a given package.
     * 
     * @param __name The identifier to resolve on top.
     * @return The binary name with the resolved identifier.
     * @throws NullPointerException On null arguments.
     * @since 2020/11/28
     */
    public BinaryName resolve(ClassIdentifier __name)
        throws NullPointerException
    {
        if (__name == null)
            throw new NullPointerException("NARG");
        
        ClassIdentifier[] current = this._identifiers;
        int n = current.length;
        
        // Build new binary name with this identifier on top
        ClassIdentifier[] rv = Arrays.copyOf(current, n + 1);
        rv[n] = __name;
        return new BinaryName(rv);
    }
    
    /**
     * Returns the simple name of the class.
     * 
     * @return The simple name.
     * @since 2020/11/28
     */
    public ClassIdentifier simpleName()
    {
        ClassIdentifier[] idents = this._identifiers;
        return idents[idents.length - 1];
    }
    
    /**
     * Converts this binary name to a class.
     * 
     * @return This as a class.
     * @since 2020/11/28
     */
    public ClassName toClass()
    {
        return new ClassName(this.toString());
    }
    
    /**
     * {@inheritDoc}
     * @since 2017/09/27
     */
    @Override
    public String toString()
    {
        Reference<String> ref = this._string;
        String rv;
        
        if (ref == null || null == (rv = ref.get()))
        {
            StringBuilder sb = new StringBuilder();
            
            for (ClassIdentifier i : this._identifiers)
            {
                if (sb.length() > 0)
                    sb.append('/');
                sb.append(i.identifier());
            }
            
            this._string = new WeakReference<>((rv = sb.toString()));
        }
        
        return rv;
    }
}