/******************************************************************************* * 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; } }