| /******************************************************************************* |
| * Copyright (c) 2000, 2011 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.ui.text.correction; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.DataOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IncrementalProjectBuilder; |
| |
| import org.eclipse.core.filebuffers.FileBuffers; |
| import org.eclipse.core.filebuffers.ITextFileBuffer; |
| import org.eclipse.core.filebuffers.ITextFileBufferManager; |
| import org.eclipse.core.filebuffers.LocationKind; |
| |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.operation.IRunnableWithProgress; |
| |
| import org.eclipse.ui.PlatformUI; |
| |
| import org.eclipse.jdt.core.Flags; |
| import org.eclipse.jdt.core.ICompilationUnit; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IRegion; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.ToolFactory; |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; |
| import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; |
| import org.eclipse.jdt.core.dom.ITypeBinding; |
| import org.eclipse.jdt.core.dom.ParameterizedType; |
| import org.eclipse.jdt.core.dom.VariableDeclarationFragment; |
| import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; |
| import org.eclipse.jdt.core.util.IClassFileReader; |
| import org.eclipse.jdt.core.util.IFieldInfo; |
| import org.eclipse.jdt.core.util.IInnerClassesAttribute; |
| import org.eclipse.jdt.core.util.IInnerClassesAttributeEntry; |
| import org.eclipse.jdt.core.util.IMethodInfo; |
| |
| import org.eclipse.jdt.internal.corext.fix.AbstractSerialVersionOperation; |
| import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel; |
| import org.eclipse.jdt.internal.corext.util.Messages; |
| |
| import org.eclipse.jdt.ui.JavaUI; |
| |
| import org.eclipse.jdt.internal.ui.JavaPlugin; |
| |
| /** |
| * Proposal for a hashed serial version id. |
| * |
| * @since 3.1 |
| */ |
| public final class SerialVersionHashOperation extends AbstractSerialVersionOperation { |
| |
| private static final String STATIC_CLASS_INITIALIZER= "<clinit>"; //$NON-NLS-1$ |
| |
| public static Long calculateSerialVersionId(ITypeBinding typeBinding, final IProgressMonitor monitor) throws CoreException, IOException { |
| try { |
| IFile classfileResource= getClassfile(typeBinding); |
| if (classfileResource == null) |
| return null; |
| |
| InputStream contents= classfileResource.getContents(); |
| try { |
| IClassFileReader cfReader= ToolFactory.createDefaultClassFileReader(contents, IClassFileReader.ALL); |
| if (cfReader != null) { |
| return calculateSerialVersionId(cfReader); |
| } |
| } finally { |
| contents.close(); |
| } |
| return null; |
| } finally { |
| if (monitor != null) |
| monitor.done(); |
| } |
| } |
| |
| private static String getClassName(char[] name) { |
| return new String(name).replace('/', '.'); |
| } |
| |
| private static Long calculateSerialVersionId(IClassFileReader cfReader) throws IOException { |
| // implementing algorithm specified on http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/class.html#4100 |
| |
| ByteArrayOutputStream os= new ByteArrayOutputStream(); |
| DataOutputStream doos= new DataOutputStream(os); |
| doos.writeUTF(getClassName(cfReader.getClassName())); // class name |
| int mod= getClassModifiers(cfReader); |
| // System.out.println(Integer.toHexString(mod) + ' ' + Flags.toString(mod)); |
| |
| int classModifiers= mod & (Flags.AccPublic | Flags.AccFinal | Flags.AccInterface | Flags.AccAbstract); |
| |
| doos.writeInt(classModifiers); // class modifiers |
| char[][] interfaces= getSortedInterfacesNames(cfReader); |
| for (int i= 0; i < interfaces.length; i++) { |
| doos.writeUTF(getClassName(interfaces[i])); |
| } |
| IFieldInfo[] sortedFields= getSortedFields(cfReader); |
| for (int i= 0; i < sortedFields.length; i++) { |
| IFieldInfo curr= sortedFields[i]; |
| int flags= curr.getAccessFlags(); |
| if (!Flags.isPrivate(flags) || (!Flags.isStatic(flags) && !Flags.isTransient(flags))) { |
| doos.writeUTF(new String(curr.getName())); |
| doos.writeInt(flags & (Flags.AccPublic | Flags.AccPrivate | Flags.AccProtected | Flags.AccStatic | Flags.AccFinal | Flags.AccVolatile | Flags.AccTransient)); // field modifiers |
| doos.writeUTF(new String(curr.getDescriptor())); |
| } |
| } |
| if (hasStaticClassInitializer(cfReader)) { |
| doos.writeUTF(STATIC_CLASS_INITIALIZER); |
| doos.writeInt(Flags.AccStatic); |
| doos.writeUTF("()V"); //$NON-NLS-1$ |
| } |
| IMethodInfo[] sortedMethods= getSortedMethods(cfReader); |
| for (int i= 0; i < sortedMethods.length; i++) { |
| IMethodInfo curr= sortedMethods[i]; |
| int flags= curr.getAccessFlags(); |
| if (!Flags.isPrivate(flags) && !curr.isClinit()) { |
| doos.writeUTF(new String(curr.getName())); |
| doos.writeInt(flags & (Flags.AccPublic | Flags.AccPrivate | Flags.AccProtected | Flags.AccStatic | Flags.AccFinal | Flags.AccSynchronized | Flags.AccNative | Flags.AccAbstract | Flags.AccStrictfp)); // method modifiers |
| doos.writeUTF(getClassName(curr.getDescriptor())); |
| } |
| } |
| doos.flush(); |
| return computeHash(os.toByteArray()); |
| } |
| |
| private static int getClassModifiers(IClassFileReader cfReader) { |
| IInnerClassesAttribute innerClassesAttribute= cfReader.getInnerClassesAttribute(); |
| if (innerClassesAttribute != null) { |
| IInnerClassesAttributeEntry[] entries = innerClassesAttribute.getInnerClassAttributesEntries(); |
| for (int i= 0; i < entries.length; i++) { |
| IInnerClassesAttributeEntry entry = entries[i]; |
| char[] innerClassName = entry.getInnerClassName(); |
| if (innerClassName != null) { |
| if (CharOperation.equals(cfReader.getClassName(), innerClassName)) { |
| return entry.getAccessFlags(); |
| } |
| } |
| } |
| } |
| return cfReader.getAccessFlags(); |
| } |
| |
| // private static void print(byte[] bytes) { |
| // StringBuffer buf= new StringBuffer(); |
| // for (int i= 0; i < bytes.length; i++) { |
| // char c= (char) bytes[i]; |
| // if (!Character.isISOControl(c)) { |
| // buf.append(c).append(' '); |
| // } else { |
| // buf.append(Integer.toHexString(c)).append(' '); |
| // } |
| // } |
| // System.out.println(buf.toString()); |
| // System.out.println(); |
| // } |
| |
| |
| private static Long computeHash(byte[] bytes) { |
| try { |
| byte[] sha= MessageDigest.getInstance("SHA-1").digest(bytes); //$NON-NLS-1$ |
| if (sha.length >= 8) { |
| long hash= 0; |
| for (int i= 7; i >= 0; i--) { |
| hash= (hash << 8) | (sha[i] & 0xFF); |
| } |
| return new Long(hash); |
| } |
| } catch (NoSuchAlgorithmException e) { |
| JavaPlugin.log(e); |
| } |
| return null; |
| } |
| |
| private static char[][] getSortedInterfacesNames(IClassFileReader cfReader) { |
| char[][] interfaceNames= cfReader.getInterfaceNames(); |
| Arrays.sort(interfaceNames, new Comparator<char[]>() { |
| public int compare(char[] o1, char[] o2) { |
| return CharOperation.compareTo(o1, o2); |
| } |
| }); |
| return interfaceNames; |
| } |
| |
| private static IFieldInfo[] getSortedFields(IClassFileReader cfReader) { |
| IFieldInfo[] allFields= cfReader.getFieldInfos(); |
| Arrays.sort(allFields, new Comparator<IFieldInfo>() { |
| public int compare(IFieldInfo o1, IFieldInfo o2) { |
| return CharOperation.compareTo(o1.getName(), o2.getName()); |
| } |
| }); |
| return allFields; |
| } |
| |
| private static boolean hasStaticClassInitializer(IClassFileReader cfReader) { |
| IMethodInfo[] methodInfos= cfReader.getMethodInfos(); |
| for (int i= 0; i < methodInfos.length; i++) { |
| if (methodInfos[i].isClinit()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static IMethodInfo[] getSortedMethods(IClassFileReader cfReader) { |
| IMethodInfo[] allMethods= cfReader.getMethodInfos(); |
| Arrays.sort(allMethods, new Comparator<IMethodInfo>() { |
| public int compare(IMethodInfo mi1, IMethodInfo mi2) { |
| if (mi1.isConstructor() != mi2.isConstructor()) { |
| return mi1.isConstructor() ? -1 : 1; |
| } else if (mi1.isConstructor()) { |
| return 0; |
| } |
| int res= CharOperation.compareTo(mi1.getName(), mi2.getName()); |
| if (res != 0) { |
| return res; |
| } |
| return CharOperation.compareTo(mi1.getDescriptor(), mi2.getDescriptor()); |
| } |
| }); |
| return allMethods; |
| } |
| |
| private static IFile getClassfile(ITypeBinding typeBinding) throws CoreException { |
| // bug 191943 |
| IType type= (IType) typeBinding.getJavaElement(); |
| if (type == null || type.getCompilationUnit() == null) { |
| return null; |
| } |
| |
| IRegion region= JavaCore.newRegion(); |
| region.add(type.getCompilationUnit()); |
| |
| String name= typeBinding.getBinaryName(); |
| if (name != null) { |
| int packStart= name.lastIndexOf('.'); |
| if (packStart != -1) { |
| name= name.substring(packStart + 1); |
| } |
| } else { |
| throw new CoreException(new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, CorrectionMessages.SerialVersionHashOperation_error_classnotfound)); |
| } |
| |
| name += ".class"; //$NON-NLS-1$ |
| |
| IResource[] classFiles= JavaCore.getGeneratedResources(region, false); |
| for (int i= 0; i < classFiles.length; i++) { |
| IResource resource= classFiles[i]; |
| if (resource.getType() == IResource.FILE && resource.getName().equals(name)) { |
| return (IFile) resource; |
| } |
| } |
| throw new CoreException(new Status(IStatus.ERROR, JavaUI.ID_PLUGIN, CorrectionMessages.SerialVersionHashOperation_error_classnotfound)); |
| } |
| |
| /** |
| * Displays an appropriate error message for a specific problem. |
| * |
| * @param message |
| * The message to display |
| */ |
| private static void displayErrorMessage(final String message) { |
| final Display display= PlatformUI.getWorkbench().getDisplay(); |
| if (display != null && !display.isDisposed()) { |
| display.asyncExec(new Runnable() { |
| |
| public final void run() { |
| if (!display.isDisposed()) { |
| final Shell shell= display.getActiveShell(); |
| if (shell != null && !shell.isDisposed()) |
| MessageDialog.openError(shell, CorrectionMessages.SerialVersionHashOperation_dialog_error_caption, Messages.format(CorrectionMessages.SerialVersionHashOperation_dialog_error_message, message)); |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Displays an appropriate error message for a specific problem. |
| * |
| * @param throwable |
| * the throwable object to display |
| */ |
| private static void displayErrorMessage(final Throwable throwable) { |
| displayErrorMessage(throwable.getLocalizedMessage()); |
| } |
| |
| /** |
| * Displays a dialog with a question as message. |
| * |
| * @param title |
| * The title to display |
| * @param message |
| * The message to display |
| * @return returns the result of the dialog |
| */ |
| private static boolean displayYesNoMessage(final String title, final String message) { |
| final boolean[] result= { true}; |
| final Display display= PlatformUI.getWorkbench().getDisplay(); |
| if (display != null && !display.isDisposed()) { |
| display.syncExec(new Runnable() { |
| |
| public final void run() { |
| if (!display.isDisposed()) { |
| final Shell shell= display.getActiveShell(); |
| if (shell != null && !shell.isDisposed()) |
| result[0]= MessageDialog.openQuestion(shell, title, message); |
| } |
| } |
| }); |
| } |
| return result[0]; |
| } |
| |
| private final ICompilationUnit fCompilationUnit; |
| |
| public SerialVersionHashOperation(ICompilationUnit unit, ASTNode[] nodes) { |
| super(unit, nodes); |
| fCompilationUnit= unit; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| protected boolean addInitializer(final VariableDeclarationFragment fragment, final ASTNode declarationNode) { |
| Assert.isNotNull(fragment); |
| try { |
| PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() { |
| |
| public final void run(final IProgressMonitor monitor) throws InterruptedException { |
| Assert.isNotNull(monitor); |
| String id= computeId(declarationNode, monitor); |
| fragment.setInitializer(fragment.getAST().newNumberLiteral(id)); |
| } |
| }); |
| } catch (InvocationTargetException exception) { |
| JavaPlugin.log(exception); |
| } catch (InterruptedException exception) { |
| // Do nothing |
| } |
| return true; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| protected void addLinkedPositions(ASTRewrite rewrite, VariableDeclarationFragment fragment, LinkedProposalModel positionGroups) { |
| //Do nothing |
| } |
| |
| private String computeId(final ASTNode declarationNode, final IProgressMonitor monitor) throws InterruptedException { |
| Assert.isNotNull(monitor); |
| long serialVersionID= SERIAL_VALUE; |
| try { |
| monitor.beginTask(CorrectionMessages.SerialVersionHashOperation_computing_id, 200); |
| final IJavaProject project= fCompilationUnit.getJavaProject(); |
| final IPath path= fCompilationUnit.getResource().getFullPath(); |
| ITextFileBufferManager bufferManager= FileBuffers.getTextFileBufferManager(); |
| try { |
| bufferManager.connect(path, LocationKind.IFILE, new SubProgressMonitor(monitor, 10)); |
| if (monitor.isCanceled()) |
| throw new InterruptedException(); |
| |
| final ITextFileBuffer buffer= bufferManager.getTextFileBuffer(path, LocationKind.IFILE); |
| if (buffer.isDirty() && buffer.isStateValidated() && buffer.isCommitable() && displayYesNoMessage(CorrectionMessages.SerialVersionHashOperation_save_caption, CorrectionMessages.SerialVersionHashOperation_save_message)) |
| buffer.commit(new SubProgressMonitor(monitor, 20), true); |
| else |
| monitor.worked(20); |
| |
| if (monitor.isCanceled()) |
| throw new InterruptedException(); |
| } finally { |
| bufferManager.disconnect(path, LocationKind.IFILE, new SubProgressMonitor(monitor, 10)); |
| } |
| project.getProject().build(IncrementalProjectBuilder.INCREMENTAL_BUILD, new SubProgressMonitor(monitor, 60)); |
| if (monitor.isCanceled()) |
| throw new InterruptedException(); |
| |
| ITypeBinding typeBinding= getTypeBinding(declarationNode); |
| if (typeBinding != null) { |
| Long id= calculateSerialVersionId(typeBinding, new SubProgressMonitor(monitor, 100)); |
| if (id != null) |
| serialVersionID= id.longValue(); |
| } |
| } catch (CoreException exception) { |
| displayErrorMessage(exception); |
| } catch (IOException exception) { |
| displayErrorMessage(exception); |
| } finally { |
| monitor.done(); |
| } |
| return serialVersionID + LONG_SUFFIX; |
| } |
| |
| private static ITypeBinding getTypeBinding(final ASTNode parent) { |
| if (parent instanceof AbstractTypeDeclaration) { |
| final AbstractTypeDeclaration declaration= (AbstractTypeDeclaration) parent; |
| return declaration.resolveBinding(); |
| } else if (parent instanceof AnonymousClassDeclaration) { |
| final AnonymousClassDeclaration declaration= (AnonymousClassDeclaration) parent; |
| return declaration.resolveBinding(); |
| } else if (parent instanceof ParameterizedType) { |
| final ParameterizedType type= (ParameterizedType) parent; |
| return type.resolveBinding(); |
| } |
| return null; |
| } |
| } |