/******************************************************************************* * Copyright (c) 2015 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.properties.editor.util; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.Signature; import org.springframework.ide.eclipse.boot.core.BootActivator; import org.springframework.ide.eclipse.editor.support.util.StringUtil; import org.springframework.ide.eclipse.editor.support.yaml.schema.YType; /** * @author Kris De Volder */ public class Type implements YType { private final String erasure; private final Type[] params; public Type(String erasure, Type[] params) { this.erasure = erasure; this.params = params; } public boolean isGeneric() { return params!=null; } public String getErasure() { return erasure; } public Type[] getParams() { return params; } @Override public String toString() { StringBuilder buf = new StringBuilder(); toString(buf); return buf.toString(); } private void toString(StringBuilder buf) { buf.append(getErasure()); if (isGeneric()) { buf.append("<"); boolean first = true; for (Type param : getParams()) { if (!first) { buf.append(","); } param.toString(buf); first = false; } buf.append(">"); } } /** * Attempt to convert given typeSignature to a corresponding Type object. * <p> * Not all valid typeSig have a representation as a Type object. This may * return null if no corresponding representation can be constructed. */ public static Type fromSignature(String typeSig, IType context) { //TODO: does this work correctly with nested types (i.e like Map$Entry) Type type = TYPE_FROM_SIG.get(typeSig); if (type!=null) { return type; } int kind = Signature.getTypeSignatureKind(typeSig); //Essentially, Type object only able to represent class types with with generic parameters // as long as these generic parameters are fully concrete (i.e. do not contain unbound type // variables. For now only support the simplest case (no generics) and bail out returning null if we // see something we don't understand. if (kind==Signature.CLASS_TYPE_SIGNATURE) { boolean shouldResolve = typeSig.charAt(0)==Signature.C_UNRESOLVED; String erasure = Signature.getTypeErasure(typeSig); String pkg = Signature.getSignatureQualifier(erasure); String nam = Signature.getSignatureSimpleName(erasure); String[] params = Signature.getTypeParameters(typeSig); String[] args = Signature.getTypeArguments(typeSig); if (shouldResolve) { erasure = tryToResolve(qualifiedName(pkg, nam), context); } else { erasure = qualifiedName(pkg, nam); } if (ArrayUtils.hasElements(params)) { //TODO: handle this case return null; } else if (ArrayUtils.hasElements(args)) { Type[] argTypes = new Type[args.length]; for (int i = 0; i < argTypes.length; i++) { argTypes[i] = fromSignature(args[i], context); } return new Type(erasure, argTypes); } else { return new Type(erasure, null); } } else if (kind==Signature.ARRAY_TYPE_SIGNATURE) { Type elementType = fromSignature(Signature.getElementType(typeSig), context); if (elementType!=null) { int arrayCount = Signature.getArrayCount(typeSig); return elementType.asArray(arrayCount); } } return null; } public Type asArray(int arrayCount) { Assert.isLegal(arrayCount>0); StringBuilder arrayErasure = new StringBuilder(erasure); for (int i = 0; i < arrayCount; i++) { arrayErasure.append("[]"); } return new Type(arrayErasure.toString(), params); } private static String qualifiedName(String pkg, String nam) { if (StringUtil.hasText(pkg)) { return pkg + "." + nam; } else { return nam; } } private static String tryToResolve(String typeName, IType context) { try { String[][] resolved = context.resolveType(typeName); if (ArrayUtils.hasElements(resolved)) { String pkg = resolved[0][0]; String nam = resolved[0][1]; if (StringUtil.hasText(pkg)) { return pkg+"."+nam; } else { //No . in front of default package return nam; } } } catch (Exception e) { BootActivator.log(e); } return typeName; } ////////////////////////////////////////////////// /** * Map of some known / common type signatures and their corresponding 'Type' representation. * Note that springboot metadata 'normalizes' all primitive types to their corresponding box * types. So we do the same here. */ private static final Map<String,Type> TYPE_FROM_SIG = new HashMap<String, Type>(); static { sig2type("B", Byte.class); sig2type("C", Character.class); sig2type("D", Double.class); sig2type("F", Float.class); sig2type("I", Integer.class); sig2type("J", Long.class); sig2type("S", Short.class); sig2type("V", Void.class); sig2type("Z", Boolean.class); } private static void sig2type(String sig, Class<?> cls) { TYPE_FROM_SIG.put(sig, TypeParser.parse(cls.getName())); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((erasure == null) ? 0 : erasure.hashCode()); result = prime * result + Arrays.hashCode(params); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Type other = (Type) obj; if (erasure == null) { if (other.erasure != null) return false; } else if (!erasure.equals(other.erasure)) return false; if (!Arrays.equals(params, other.params)) return false; return true; } }