/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.java.server; import com.google.inject.Singleton; import org.eclipse.che.jdt.dom.ASTNodes; import org.eclipse.che.jdt.javadoc.JavaElementLabels; import org.eclipse.jdt.core.BindingKey; import org.eclipse.jdt.core.Flags; import org.eclipse.jdt.core.IAnnotation; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.ILocalVariable; import org.eclipse.jdt.core.IMemberValuePair; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeParameter; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @author Evgen Vidolob */ @Singleton public class SourcesFromBytecodeGenerator { public static final String METHOD_BODY = " /* compiled code */ "; private static final String COMMENT = new String( "\n // Failed to get sources. Instead, stub sources have been generated.\n // Implementation of methods is unavailable.\n"); private static final String TAB = " "; public String generateSource(IType type) throws JavaModelException { StringBuilder builder = new StringBuilder(); builder.append(COMMENT); builder.append("package ").append(type.getPackageFragment().getElementName()).append(";\n"); generateType(type, builder, TAB); return builder.toString(); } private void generateType(IType type, StringBuilder builder, String indent) throws JavaModelException { int flags = 0; appendAnnotationLabels(type.getAnnotations(), flags, builder, indent.substring(TAB.length())); builder.append(indent.substring(TAB.length())); builder.append(getModifiers(type.getFlags(), type.getFlags())).append(' ').append(getJavaType(type)).append(' ') .append(type.getElementName()); if (type.isResolved()) { BindingKey key = new BindingKey(type.getKey()); if (key.isParameterizedType()) { String[] typeArguments = key.getTypeArguments(); appendTypeArgumentSignaturesLabel(type, typeArguments, flags, builder); } else { String[] typeParameters = Signature.getTypeParameters(key.toSignature()); appendTypeParameterSignaturesLabel(typeParameters, builder); } } else { appendTypeParametersLabels(type.getTypeParameters(), flags, builder); } if (!"java.lang.Object".equals(type.getSuperclassName()) && !"java.lang.Enum".equals(type.getSuperclassName())) { builder.append(" extends "); if (type.getSuperclassTypeSignature() != null) { // appendTypeSignatureLabel(type, type.getSuperclassTypeSignature(), flags, builder); builder.append(Signature.toString(type.getSuperclassTypeSignature())); } else { builder.append(type.getSuperclassName()); } } if (!type.isAnnotation()) { if (type.getSuperInterfaceNames().length != 0) { builder.append(" implements "); String[] signatures = type.getSuperInterfaceTypeSignatures(); if (signatures.length == 0) { signatures = type.getSuperInterfaceNames(); } for (String interfaceFqn : signatures) { builder.append(Signature.toString(interfaceFqn)).append(", "); } builder.delete(builder.length() - 2, builder.length()); } } builder.append(" {\n"); List<IField> fields = new ArrayList<>(); if (type.isEnum()) { builder.append(indent); for (IField field : type.getFields()) { if (field.isEnumConstant()) { builder.append(field.getElementName()).append(", "); } else { fields.add(field); } } if (", ".equals(builder.substring(builder.length() - 2))) { builder.delete(builder.length() - 2, builder.length()); } builder.append(";\n"); } else { fields.addAll(Arrays.asList(type.getFields())); } for (IField field : fields) { if (Flags.isSynthetic(field.getFlags())) { continue; } appendAnnotationLabels(field.getAnnotations(), flags, builder, indent); builder.append(indent).append(getModifiers(field.getFlags(), type.getFlags())); if (builder.charAt(builder.length() - 1) != ' ') { builder.append(' '); } builder.append(Signature.toCharArray(field.getTypeSignature().toCharArray())).append(' ') .append(field.getElementName()); if (field.getConstant() != null) { builder.append(" = "); if (field.getConstant() instanceof String) { builder.append('"').append(field.getConstant()).append('"'); } else { builder.append(field.getConstant()); } } builder.append(";\n"); } builder.append('\n'); for (IMethod method : type.getMethods()) { if (method.getElementName().equals("<clinit>") || Flags.isSynthetic(method.getFlags())) { continue; } appendAnnotationLabels(method.getAnnotations(), flags, builder, indent); BindingKey resolvedKey = method.isResolved() ? new BindingKey(method.getKey()) : null; String resolvedSig = (resolvedKey != null) ? resolvedKey.toSignature() : null; builder.append(indent).append(getModifiers(method.getFlags(), type.getFlags())); if (builder.charAt(builder.length() - 1) != ' ') { builder.append(' '); } if (resolvedKey != null) { if (resolvedKey.isParameterizedMethod()) { String[] typeArgRefs = resolvedKey.getTypeArguments(); if (typeArgRefs.length > 0) { appendTypeArgumentSignaturesLabel(method, typeArgRefs, flags, builder); builder.append(' '); } } else { String[] typeParameterSigs = Signature.getTypeParameters(resolvedSig); if (typeParameterSigs.length > 0) { appendTypeParameterSignaturesLabel(typeParameterSigs, builder); builder.append(' '); } } } else if (method.exists()) { ITypeParameter[] typeParameters = method.getTypeParameters(); if (typeParameters.length > 0) { appendTypeParametersLabels(typeParameters, flags, builder); builder.append(' '); } } if (!method.isConstructor()) { String returnTypeSig = resolvedSig != null ? Signature.getReturnType(resolvedSig) : method.getReturnType(); appendTypeSignatureLabel(method, returnTypeSig, 0, builder); builder.append(' '); // builder.append(Signature.toCharArray(method.getReturnType().toCharArray())).append(' '); } builder.append(method.getElementName()); builder.append('('); for (ILocalVariable variable : method.getParameters()) { builder.append(Signature.toString(variable.getTypeSignature())); builder.append(' ').append(variable.getElementName()).append(", "); } if (builder.charAt(builder.length() - 1) == ' ') { builder.delete(builder.length() - 2, builder.length()); } builder.append(')'); String[] exceptionTypes = method.getExceptionTypes(); if (exceptionTypes != null && exceptionTypes.length != 0) { builder.append(' ').append("throws "); for (String exceptionType : exceptionTypes) { builder.append(Signature.toCharArray(exceptionType.toCharArray())).append(", "); } builder.delete(builder.length() - 2, builder.length()); } if (type.isInterface() || type.isAnnotation()) { builder.append(";\n\n"); } else { builder.append(" {").append(METHOD_BODY).append("}\n\n"); } } for (IType iType : type.getTypes()) { generateType(iType, builder, indent + indent); } builder.append(indent.substring(TAB.length())); builder.append("}\n"); } protected void appendAnnotationLabels(IAnnotation[] annotations, long flags, StringBuilder builder, String indent) throws JavaModelException { for (IAnnotation annotation : annotations) { builder.append(indent); appendAnnotationLabel(annotation, flags, builder); builder.append('\n'); } } public void appendAnnotationLabel(IAnnotation annotation, long flags, StringBuilder builder) throws JavaModelException { builder.append('@'); appendTypeSignatureLabel(annotation, Signature.createTypeSignature(annotation.getElementName(), false), flags, builder); IMemberValuePair[] memberValuePairs = annotation.getMemberValuePairs(); if (memberValuePairs.length == 0) return; builder.append('('); for (int i = 0; i < memberValuePairs.length; i++) { if (i > 0) builder.append(JavaElementLabels.COMMA_STRING); IMemberValuePair memberValuePair = memberValuePairs[i]; builder.append(getMemberName(annotation, annotation.getElementName(), memberValuePair.getMemberName())); builder.append('='); appendAnnotationValue(annotation, memberValuePair.getValue(), memberValuePair.getValueKind(), flags, builder); } builder.append(')'); } /** * Returns the simple name of the given member. * * @param enclosingElement * the enclosing element * @param typeName * the name of the member's declaring type * @param memberName * the name of the member * @return the simple name of the member */ protected String getMemberName(IJavaElement enclosingElement, String typeName, String memberName) { return memberName; } private void appendAnnotationValue(IAnnotation annotation, Object value, int valueKind, long flags, StringBuilder builder) throws JavaModelException { // Note: To be bug-compatible with Javadoc from Java 5/6/7, we currently don't escape HTML tags in String-valued annotations. if (value instanceof Object[]) { builder.append('{'); Object[] values = (Object[])value; for (int j = 0; j < values.length; j++) { if (j > 0) builder.append(JavaElementLabels.COMMA_STRING); value = values[j]; appendAnnotationValue(annotation, value, valueKind, flags, builder); } builder.append('}'); } else { switch (valueKind) { case IMemberValuePair.K_CLASS: appendTypeSignatureLabel(annotation, Signature.createTypeSignature((String)value, false), flags, builder); builder.append(".class"); //$NON-NLS-1$ break; case IMemberValuePair.K_QUALIFIED_NAME: String name = (String)value; int lastDot = name.lastIndexOf('.'); if (lastDot != -1) { String type = name.substring(0, lastDot); String field = name.substring(lastDot + 1); appendTypeSignatureLabel(annotation, Signature.createTypeSignature(type, false), flags, builder); builder.append('.'); builder.append(getMemberName(annotation, type, field)); break; } // case IMemberValuePair.K_SIMPLE_NAME: // can't implement, since parent type is not known //$FALL-THROUGH$ case IMemberValuePair.K_ANNOTATION: appendAnnotationLabel((IAnnotation)value, flags, builder); break; case IMemberValuePair.K_STRING: builder.append(ASTNodes.getEscapedStringLiteral((String)value)); break; case IMemberValuePair.K_CHAR: builder.append(ASTNodes.getEscapedCharacterLiteral(((Character)value).charValue())); break; default: builder.append(String.valueOf(value)); break; } } } /** * Returns the string for rendering the {@link org.eclipse.jdt.core.IJavaElement#getElementName() element name} of * the given element. * <p> * <strong>Note:</strong> This class only calls this helper for those elements where ( * {@link org.eclipse.che.jdt.javadoc.JavaElementLinks}) has the need to render the name differently. * </p> * * @param element * the element to render * @return the string for rendering the element name */ protected String getElementName(IJavaElement element) { return element.getElementName(); } private void appendTypeParameterWithBounds(ITypeParameter typeParameter, long flags, StringBuilder builder) throws JavaModelException { builder.append(getElementName(typeParameter)); if (typeParameter.exists()) { String[] bounds = typeParameter.getBoundsSignatures(); if (bounds.length > 0 && !(bounds.length == 1 && "Ljava.lang.Object;".equals(bounds[0]))) { //$NON-NLS-1$ builder.append(" extends "); //$NON-NLS-1$ for (int j = 0; j < bounds.length; j++) { if (j > 0) { builder.append(" & "); //$NON-NLS-1$ } appendTypeSignatureLabel(typeParameter, bounds[j], flags, builder); } } } } /** * Appends labels for type parameters from type binding array. * * @param typeParameters * the type parameters * @param flags * flags with render options * @throws org.eclipse.jdt.core.JavaModelException * ... */ private void appendTypeParametersLabels(ITypeParameter[] typeParameters, long flags, StringBuilder builder) throws JavaModelException { if (typeParameters.length > 0) { builder.append(getLT()); for (int i = 0; i < typeParameters.length; i++) { if (i > 0) { builder.append(JavaElementLabels.COMMA_STRING); } appendTypeParameterWithBounds(typeParameters[i], flags, builder); } builder.append(getGT()); } } /** * Appends labels for type parameters from a signature. * * @param typeParamSigs * the type parameter signature */ private void appendTypeParameterSignaturesLabel(String[] typeParamSigs, StringBuilder builder) { if (typeParamSigs.length > 0) { builder.append(getLT()); for (int i = 0; i < typeParamSigs.length; i++) { if (i > 0) { builder.append(JavaElementLabels.COMMA_STRING); } builder.append(Signature.getTypeVariable(typeParamSigs[i])); } builder.append(getGT()); } } private void appendTypeArgumentSignaturesLabel(IJavaElement enclosingElement, String[] typeArgsSig, long flags, StringBuilder builder) { if (typeArgsSig.length > 0) { builder.append(getLT()); for (int i = 0; i < typeArgsSig.length; i++) { if (i > 0) { builder.append(JavaElementLabels.COMMA_STRING); } appendTypeSignatureLabel(enclosingElement, typeArgsSig[i], flags, builder); } builder.append(getGT()); } } protected void appendTypeSignatureLabel(IJavaElement enclosingElement, String typeSig, long flags, StringBuilder builder) { int sigKind = Signature.getTypeSignatureKind(typeSig); switch (sigKind) { case Signature.BASE_TYPE_SIGNATURE: builder.append(Signature.toString(typeSig)); break; case Signature.ARRAY_TYPE_SIGNATURE: appendTypeSignatureLabel(enclosingElement, Signature.getElementType(typeSig), flags, builder); for (int dim = Signature.getArrayCount(typeSig); dim > 0; dim--) { builder.append('[').append(']'); } break; case Signature.CLASS_TYPE_SIGNATURE: String baseType = getSimpleTypeName(enclosingElement, typeSig); builder.append(baseType); String[] typeArguments = Signature.getTypeArguments(typeSig); appendTypeArgumentSignaturesLabel(enclosingElement, typeArguments, flags, builder); break; case Signature.TYPE_VARIABLE_SIGNATURE: builder.append(getSimpleTypeName(enclosingElement, typeSig)); break; case Signature.WILDCARD_TYPE_SIGNATURE: char ch = typeSig.charAt(0); if (ch == Signature.C_STAR) { //workaround for bug 85713 builder.append('?'); } else { if (ch == Signature.C_EXTENDS) { builder.append("? extends "); //$NON-NLS-1$ appendTypeSignatureLabel(enclosingElement, typeSig.substring(1), flags, builder); } else if (ch == Signature.C_SUPER) { builder.append("? super "); //$NON-NLS-1$ appendTypeSignatureLabel(enclosingElement, typeSig.substring(1), flags, builder); } } break; case Signature.CAPTURE_TYPE_SIGNATURE: appendTypeSignatureLabel(enclosingElement, typeSig.substring(1), flags, builder); break; case Signature.INTERSECTION_TYPE_SIGNATURE: String[] typeBounds = Signature.getIntersectionTypeBounds(typeSig); appendTypeBoundsSignaturesLabel(enclosingElement, typeBounds, flags, builder); break; default: // unknown } } private void appendTypeBoundsSignaturesLabel(IJavaElement enclosingElement, String[] typeArgsSig, long flags, StringBuilder builder) { for (int i = 0; i < typeArgsSig.length; i++) { if (i > 0) { builder.append(" | "); //$NON-NLS-1$ } appendTypeSignatureLabel(enclosingElement, typeArgsSig[i], flags, builder); } } /** * Returns the simple name of the given type signature. * * @param enclosingElement * the enclosing element in which to resolve the signature * @param typeSig * a {@link org.eclipse.jdt.core.Signature#CLASS_TYPE_SIGNATURE} or {@link org.eclipse.jdt.core * .Signature#TYPE_VARIABLE_SIGNATURE} * @return the simple name of the given type signature */ protected String getSimpleTypeName(IJavaElement enclosingElement, String typeSig) { return Signature.toString(Signature.getTypeErasure(typeSig)); } /** * Returns the string for rendering the '<code><</code>' character. * * @return the string for rendering '<code><</code>' */ protected String getLT() { return "<"; //$NON-NLS-1$ } /** * Returns the string for rendering the '<code>></code>' character. * * @return the string for rendering '<code>></code>' */ protected String getGT() { return ">"; //$NON-NLS-1$ } private String getJavaType(IType type) throws JavaModelException { if (type.isAnnotation()) { return "@interface"; } if (type.isClass()) { return "class"; } if (type.isInterface()) { return "interface"; } if (type.isEnum()) { return "enum"; } return "can't determine type"; } private String getModifiers(int flags, int typeFlags) { StringBuilder modifiers = new StringBuilder(); //package private modifier has no string representation if (Flags.isPublic(flags)) { modifiers.append("public "); } if (Flags.isProtected(flags)) { modifiers.append("protected "); } if (Flags.isPrivate(flags)) { modifiers.append("private "); } if (Flags.isStatic(flags)) { modifiers.append("static "); } if (Flags.isAbstract(flags) && !Flags.isInterface(typeFlags)) { modifiers.append("abstract "); } if (Flags.isFinal(flags)) { modifiers.append("final "); } if (Flags.isNative(flags)) { modifiers.append("native "); } if (Flags.isSynchronized(flags)) { modifiers.append("synchronized "); } if (Flags.isVolatile(flags)) { modifiers.append("volatile "); } int len = modifiers.length(); if (len == 0) return ""; modifiers.setLength(len - 1); return modifiers.toString(); } }