/* *******************************************************************
 * Copyright (c) 1999-2001 Xerox Corporation,
 *               2002 Palo Alto Research Center, Incorporated (PARC).
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors:
 *     Xerox/PARC     initial implementation
 * ******************************************************************/


package org.aspectj.runtime.reflect;

import org.aspectj.lang.Signature;

import java.util.StringTokenizer;

abstract class SignatureImpl implements Signature {

	private static boolean useCache = true;

    int modifiers = -1;
    String name;
    String declaringTypeName;
    Class<?> declaringType;
    Cache stringCache;

    SignatureImpl(int modifiers, String name, Class<?> declaringType) {
        this.modifiers = modifiers;
        this.name = name;
        this.declaringType = declaringType;
    }

    protected abstract String createToString (StringMaker sm);

    /* Use a soft cache for the short, middle and long String representations */
	String toString (StringMaker sm) {
		String result = null;
		if (useCache) {
			if (stringCache == null) {
				try {
					stringCache = new CacheImpl();
				} catch (Throwable t) {
					useCache = false;
				}
			} else {
				result = stringCache.get(sm.cacheOffset);
			}
		}
		if (result == null) {
			result = createToString(sm);
		}
		if (useCache) {
			stringCache.set(sm.cacheOffset, result);
		}
		return result;
	}

    public final String toString() { return toString(StringMaker.middleStringMaker); }
    public final String toShortString() { return toString(StringMaker.shortStringMaker); }
    public final String toLongString() { return toString(StringMaker.longStringMaker); }

    public int getModifiers() {
        if (modifiers == -1) modifiers = extractInt(0);
        return modifiers;
    }
    public String getName() {
        if (name == null) name = extractString(1);
        return name;
    }
    public Class getDeclaringType() {
        if (declaringType == null) declaringType = extractType(2);
        return declaringType;
    }
    public String getDeclaringTypeName() {
    	if (declaringTypeName == null) {
    		declaringTypeName = getDeclaringType().getName();
    	}
    	return declaringTypeName;
    }

    String fullTypeName(Class<?> type) {
        if (type == null) return "ANONYMOUS";
        if (type.isArray()) return fullTypeName(type.getComponentType()) + "[]";
        return type.getName().replace('$', '.');
    }

    String stripPackageName(String name) {
        int dot = name.lastIndexOf('.');
        if (dot == -1) return name;
        return name.substring(dot+1);
    }

    String shortTypeName(Class<?> type) {
        if (type == null) return "ANONYMOUS";
        if (type.isArray()) return shortTypeName(type.getComponentType()) + "[]";
        return stripPackageName(type.getName()).replace('$', '.');
    }

    void addFullTypeNames(StringBuffer buf, Class[] types) {
        for (int i = 0; i < types.length; i++) {
            if (i > 0) buf.append(", ");
            buf.append(fullTypeName(types[i]));
        }
    }
    void addShortTypeNames(StringBuffer buf, Class[] types) {
        for (int i = 0; i < types.length; i++) {
            if (i > 0) buf.append(", ");
            buf.append(shortTypeName(types[i]));
        }
    }

    void addTypeArray(StringBuffer buf, Class[] types) {
        addFullTypeNames(buf, types);
    }

    // lazy version
    private String stringRep;
    ClassLoader lookupClassLoader = null;

    public void setLookupClassLoader(ClassLoader loader) {
        this.lookupClassLoader = loader;
    }

    private ClassLoader getLookupClassLoader() {
        if (lookupClassLoader == null) lookupClassLoader = this.getClass().getClassLoader();
        return lookupClassLoader;
    }

    public SignatureImpl(String stringRep) {
        this.stringRep = stringRep;
    }

    static final char SEP = '-';

    String extractString(int n) {
        //System.out.println(n + ":  from " + stringRep);

        int startIndex = 0;
        int endIndex = stringRep.indexOf(SEP);
        while (n-- > 0) {
            startIndex = endIndex+1;
            endIndex = stringRep.indexOf(SEP, startIndex);
        }
        if (endIndex == -1) endIndex = stringRep.length();

        //System.out.println("    " + stringRep.substring(startIndex, endIndex));

        return stringRep.substring(startIndex, endIndex);
    }

    int extractInt(int n) {
        String s = extractString(n);
        return Integer.parseInt(s, 16);
    }

    Class<?> extractType(int n) {
        String s = extractString(n);
        return Factory.makeClass(s,getLookupClassLoader());
    }



    static String[] EMPTY_STRING_ARRAY = new String[0];
    static Class[] EMPTY_CLASS_ARRAY = new Class[0];

    static final String INNER_SEP = ":";

    String[] extractStrings(int n) {
        String s = extractString(n);
        StringTokenizer st = new StringTokenizer(s, INNER_SEP);
        final int N = st.countTokens();
        String[] ret = new String[N];
        for (int i = 0; i < N; i++) ret[i]= st.nextToken();
        return ret;
    }
    Class[] extractTypes(int n) {
        String s = extractString(n);
        StringTokenizer st = new StringTokenizer(s, INNER_SEP);
        final int N = st.countTokens();
        Class[] ret = new Class[N];
        for (int i = 0; i < N; i++) ret[i]= Factory.makeClass(st.nextToken(),getLookupClassLoader());
        return ret;
    }

	/*
	 * Used for testing
	 */
	static void setUseCache (boolean b) {
		useCache = b;
	}

	static boolean getUseCache () {
		return useCache;
	}

	private interface Cache {

		String get(int cacheOffset);

		void set(int cacheOffset, String result);

	}

	// separate implementation so we don't need SoftReference to hold the field...
	private static final class CacheImpl implements Cache {
		private java.lang.ref.SoftReference<String[]> toStringCacheRef;

		public CacheImpl() {
			makeCache();
		}

		public String get(int cacheOffset) {
			String[] cachedArray = array();
			if (cachedArray == null) {
				return null;
			}
			return cachedArray[cacheOffset];
		}

		public void set(int cacheOffset, String result) {
			String[] cachedArray = array();
			if (cachedArray == null) {
				cachedArray = makeCache();
			}
			cachedArray[cacheOffset] = result;
		}

		private String[] array() {
			return toStringCacheRef.get();
		}

		private String[] makeCache() {
			String[] array = new String[3];
			toStringCacheRef = new java.lang.ref.SoftReference<>(array);
			return array;
		}

	}
}
